
最近在看一份英文技术文档,PDF格式的,三百多页。不是不能读英文,是读起来慢。碰到长句子,脑子里要过一遍才能理解意思。
我用过几种办法。
浏览器插件?PDF在本地看的,用不了。
翻译软件?把整段复制粘贴进去,看完译文再回来找读到哪了,来回切窗口,效率很低。
截图翻译?截图、打开翻译APP、粘贴、等结果、切回去。看一页要操作五六次。
后来有一次,我玩一个没有汉化的游戏,菜单和对话全是英文。实在懒得一个个查字典,突然想:能不能做个工具,自动截屏、识别文字、翻译、然后显示在旁边?
花了两个晚上写出来了。虽然简陋,但真的好用。
三步:
截屏:用pyautogui 截取屏幕的指定区域
文字识别:用 Tesseract OCR(或 PaddleOCR)从截图里提取文字
翻译:调用翻译API把文字翻译成中文,显示在浮窗里
支持两种模式:
选区模式:你框选一个区域,工具只翻译这个区域的内容
全屏模式:翻译整个屏幕
翻译结果会显示在一个置顶的半透明窗口里,不遮挡原内容。
| 功能 | 选择 | 理由 |
|---|---|---|
| 截屏 | pyautogui | 跨平台,API简单 |
| OCR | pytesseract | 免费,离线运行,支持多语言 |
| 翻译 | Google翻译(免费接口) | 不用注册API key |
| GUI | tkinter | Python自带,够用了 |
为什么用pytesseract而不是PaddleOCR?因为PaddleOCR安装包太大(依赖PaddlePaddle深度学习框架),对新手不友好。pytesseract只需要装一个Tesseract引擎,轻量得多。
如果你的场景对中文识别要求很高(比如翻译的是扫描件、手写体),可以后期换成PaddleOCR,代码改动不大。
第一步:安装Tesseract OCR引擎
Windows用户:去 https://github.com/UB-Mannheim/tesseract/wiki 下载安装包。安装时记得勾选中文语言包(chi_sim)。安装完后记住安装路径,后面要用。
Mac用户:
brew install tesseract tesseract-langLinux用户:
sudo apt install tesseract-ocr tesseract-ocr-chi-sim第二步:安装Python依赖
pip install pyautogui pytesseract Pillow googletrans==4.0.0-rc1googletrans 是Google翻译的非官方Python接口,免费,不用注册API key。4.0.0-rc1是目前稳定的版本。
"""实时屏幕翻译工具截屏 → OCR识别 → 翻译 → 浮窗显示依赖安装:pip install pyautogui pytesseract Pillow googletrans==4.0.0-rc1注意:需要先安装 Tesseract OCR 引擎"""import pyautoguiimport pytesseractfrom PIL import Image, ImageTkimport tkinter as tkfrom tkinter import ttkimport threadingimport time# ============ 配置 ============# Tesseract 安装路径(Windows用户改成你自己的路径)TESSERACT_PATH = r"C:\Program Files\Tesseract-OCR\tesseract.exe"pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH# OCR识别语言:eng 英文,chi_sim 简体中文OCR_LANG = "eng"# 翻译目标语言TARGET_LANG = "zh-cn"# 截屏刷新间隔(秒)REFRESH_INTERVAL = 3# 浮窗透明度(0.0全透明 ~ 1.0不透明)WINDOW_ALPHA = 0.85# ============ OCR 识别 ============def extract_text_from_image(image):"""从PIL Image对象中提取文字"""# 转成灰度图,提高OCR识别率gray = image.convert("L")# 二值化处理,让文字更清晰threshold = 150binary = gray.point(lambda x: 255if x> threshold else 0)# 使用 Tesseract 识别text = pytesseract.image_to_string(binary, lang=OCR_LANG)# 清理多余的空行lines = [line.strip() for line in text.splitlines() if line.strip()]return"\n".join(lines)# ============ 翻译 ============def translate_text(text, target_lang=TARGET_LANG):"""调用Google翻译"""if not text:return""try:from googletrans import Translatortranslator = Translator()result = translator.translate(text, dest=target_lang)return result.textexcept Exception as e:return f"[翻译失败: {e}]"# ============ 截屏 ============def capture_region(region=None):""" 截取屏幕指定区域 region: (left, top, width, height) 或 None(全屏) """screenshot = pyautogui.screenshot(region=region)return screenshot# ============ 选区功能 ============def select_region():""" 让用户用鼠标框选一个区域 返回 (x, y, width, height) """root = tk.Tk()root.attributes("-fullscreen", True)root.attributes("-alpha", 0.3)root.configure(bg="black")root.title("用鼠标框选要翻译的区域")start_x = [0]start_y = [0]rect_id = [None]canvas = tk.Canvas(root, cursor="cross", bg="black", highlightthickness=0)canvas.pack(fill=tk.BOTH, expand=True)selected = [None]def on_press(event):start_x[0] = event.xstart_y[0] = event.ydef on_drag(event):if rect_id[0]:canvas.delete(rect_id[0])rect_id[0] = canvas.create_rectangle(start_x[0], start_y[0], event.x, event.y,outline="red", width=2, fill="" )def on_release(event):x1 = min(start_x[0], event.x)y1 = min(start_y[0], event.y)x2 = max(start_x[0], event.x)y2 = max(start_y[0], event.y)selected[0] = (x1, y1, x2-x1, y2-y1)root.destroy()canvas.bind("<ButtonPress-1>", on_press)canvas.bind("<B1-Motion>", on_drag)canvas.bind("<ButtonRelease-1>", on_release)# 提示文字canvas.create_text(root.winfo_screenwidth() //2,50,text="用鼠标框选要翻译的区域,松开鼠标确认",fill="white", font=("Arial", 20), )root.mainloop()return selected[0]# ============ 翻译浮窗 ============class TranslationOverlay:"""置顶的翻译结果浮窗"""def __init__(self):self.root = tk.Tk()self.root.title("屏幕翻译")self.root.geometry("500x400+50+50")self.root.attributes("-topmost", True) # 始终置顶self.root.attributes("-alpha", WINDOW_ALPHA) # 半透明self.root.configure(bg="#1e1e1e")# 原文区域self.source_label = tk.Label(self.root,text="原文(等待截屏...)",bg="#1e1e1e", fg="#888888",font=("Consolas", 10),anchor="nw", justify="left", wraplength=460, )self.source_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0))# 分割线sep = ttk.Separator(self.root, orient="horizontal")sep.pack(fill=tk.X, padx=10, pady=5)# 译文区域self.target_label = tk.Label(self.root,text="译文将显示在这里",bg="#1e1e1e", fg="#d4d4d4",font=("Microsoft YaHei", 12),anchor="nw", justify="left", wraplength=460, )self.target_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))# 状态栏self.status_var = tk.StringVar(value="就绪")self.status_bar = tk.Label(self.root,textvariable=self.status_var,bg="#2d2d2d", fg="#888888",font=("Consolas", 9),anchor="w", padx=10, )self.status_bar.pack(fill=tk.X, side=tk.BOTTOM)# 控制按钮区btn_frame = tk.Frame(self.root, bg="#2d2d2d")btn_frame.pack(fill=tk.X, side=tk.BOTTOM, padx=10, pady=5)self.is_running = Falseself.toggle_btn = tk.Button(btn_frame, text="开始监控", command=self.toggle_monitor,bg="#0078d4", fg="white", relief="flat", padx=10, )self.toggle_btn.pack(side=tk.LEFT, padx=5)self.mode_btn = tk.Button(btn_frame, text="选区模式", command=self.select_mode,bg="#3c3c3c", fg="white", relief="flat", padx=10, )self.mode_btn.pack(side=tk.LEFT, padx=5)self.refresh_btn = tk.Button(btn_frame, text="立即翻译", command=self.translate_once,bg="#3c3c3c", fg="white", relief="flat", padx=10, )self.refresh_btn.pack(side=tk.LEFT, padx=5)self.region = None# 选区,None表示全屏self.monitor_thread = Nonedef toggle_monitor(self):"""切换自动监控状态"""self.is_running = notself.is_runningif self.is_running:self.toggle_btn.config(text="停止监控", bg="#d83b01")self.status_var.set("监控中...")self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)self.monitor_thread.start()else:self.toggle_btn.config(text="开始监控", bg="#0078d4")self.status_var.set("已停止")def _monitor_loop(self):"""自动监控循环:定时截屏、识别、翻译"""while self.is_running:self.translate_once()time.sleep(REFRESH_INTERVAL)def translate_once(self):"""执行一次截屏→识别→翻译"""self.status_var.set("正在截屏...")self.root.update()# 截屏try:image = capture_region(self.region)except Exception as e:self.status_var.set(f"截屏失败: {e}")return# OCR识别self.status_var.set("正在识别文字...")self.root.update()source_text = extract_text_from_image(image)if not source_text:self.source_label.config(text="[未识别到文字]")self.target_label.config(text="")self.status_var.set("未识别到文字")return# 显示原文(只显示前200字符,避免太长)display_text = source_text[:200]if len(source_text) >200:display_text += "..."self.source_label.config(text=display_text)# 翻译self.status_var.set("正在翻译...")self.root.update()translated = translate_text(source_text)self.target_label.config(text=translated)word_count = len(source_text.split())self.status_var.set(f"完成 · 识别 {word_count} 个词")def select_mode(self):"""切换到选区模式"""# 先隐藏浮窗,避免截屏时把自己截进去self.root.withdraw()time.sleep(0.3)region = select_region()if region:self.region = regionself.mode_btn.config(text=f"选区: {region[2]}x{region[3]}")self.status_var.set(f"选区模式: x={region[0]}, y={region[1]}, w={region[2]}, h={region[3]}")else:self.region = Noneself.mode_btn.config(text="全屏模式")self.status_var.set("全屏模式")# 重新显示浮窗self.root.deiconify()def run(self):"""启动主界面"""self.root.mainloop()# ============ 命令行模式(不需要GUI的轻量方案) ============def cli_translate(region=None):"""命令行模式:截屏一次,识别翻译,打印结果"""print("正在截屏...")image = capture_region(region)print("正在识别文字...")text = extract_text_from_image(image)if not text:print("未识别到文字")returnprint("\n--- 识别结果 ---")print(text)print()print("--- 翻译结果 ---")translated = translate_text(text)print(translated)# ============ 主程序 ============def main():import sysif "--cli" in sys.argv:# 命令行模式cli_translate()else:# GUI浮窗模式app = TranslationOverlay()app.run()if __name__ == "__main__":main()
GUI模式:
python screen_translator.py会弹出一个深色主题的小窗口,上面是原文,下面是译文。
操作流程:
点“选区模式”,用鼠标在屏幕上框选你要翻译的区域(比如PDF文档的文字部分)
点“开始监控”,工具会每隔3秒自动截屏、识别、翻译
译文实时显示在浮窗里
浮窗半透明,可以拖到屏幕角落,不遮挡你正在看的内容。
命令行模式:
如果你不想用GUI,可以加 --cli 参数:
python screen_translator.py --cli会截取当前全屏,识别文字后翻译,直接在终端打印结果。适合一次性翻译。
假设屏幕上有一段英文技术文档:
The garbage collector in Python uses a combination of reference counting and a cyclic garbage collector to manage memory. Reference counting is the primary mechanism, where each object maintains a count of references pointing to it.
工具识别并翻译后,浮窗显示:
Python中的垃圾回收器使用引用计数和循环垃圾回收器的组合来管理内存。引用计数是主要机制,每个对象维护一个指向它的引用计数。
识别准确率取决于屏幕清晰度和字体。标准印刷体(比如PDF、网页、IDE里的代码)识别率很高,基本在95%以上。手写体或低分辨率的截图会差一些。
调整二值化阈值。代码里 threshold = 150,如果你的截图背景偏暗,调低一点(比如120)。背景偏亮就调高(比如180)。
放大截图。 如果原文字体很小,OCR识别率会下降。可以在识别前先放大图片:
# 在 extract_text_from_image 函数里加一行width, height = image.sizeimage = image.resize((width*2, height*2), Image.LANCZOS)
放大两倍后,小字也能认出来了。
选区要精确。 框选区域的时候,尽量只框文字部分,别把图标、边框、空白区域框进去。干扰越少,识别越准。
PaddleOCR对中文的识别效果更好,尤其是一些不太规范的场景。改动不大:
from paddleocr import PaddleOCR# 初始化(首次会自动下载模型)ocr = PaddleOCR(use_angle_cls=True, lang="en")def extract_text_paddle(image):"""用PaddleOCR识别"""import numpyasnpimg_array = np.array(image)result = ocr.ocr(img_array, cls=True)texts = []if result and result[0]:for line in result[0]:texts.append(line[1][0]) # line[1][0] 是识别文字return "\n".join(texts)
安装PaddleOCR:
pip install paddlepaddle paddleocr安装包大概2GB左右,需要等一会儿。但识别效果确实好,尤其是中英混排的场景。
代码里用的是 googletrans,免费的非官方接口。优点是不用注册,缺点是不够稳定,偶尔会被限流。
如果你的使用频率高,可以考虑换成付费翻译API。几个选择:
百度翻译API:每月200万字符免费额度,注册简单
腾讯翻译君:每月500万字符免费,需要腾讯云账号
DeepL API:翻译质量公认好,每月50万字符免费
换API只需要改 translate_text 函数。比如用百度翻译:
import hashlibimport requestsdef translate_baidu(text, app_id="你的APPID", secret_key="你的密钥"):"""百度翻译API"""salt = "12345"sign = hashlib.md5(f"{app_id}{text}{salt}{secret_key}".encode()).hexdigest()url = "https://fanyi-api.baidu.com/api/trans/vip/translate"params = {"q": text,"from": "en","to": "zh","appid": app_id,"salt": salt,"sign": sign, }resp = requests.get(url, params=params)result = resp.json()return"\n".join(item["dst"] foriteminresult.get("trans_result", []))
不能翻译视频里的字幕。 视频字幕变化太快,3秒刷一次跟不上。你可以把 REFRESH_INTERVAL 调成1秒,但CPU占用会上去。
选区模式在有些系统上有兼容问题。macOS的权限管控比较严,需要在系统设置里给终端授权“屏幕录制”权限。Linux下如果用了Wayland,pyautogui 可能截不了屏,需要用 gnome-screenshot 或 grim 替代。
离线翻译。 目前用的是在线翻译API,需要联网。如果想完全离线,可以用 argostranslate 这个库,能在本地跑翻译模型,但翻译质量比在线的差一截。
如果你经常需要阅读英文内容,建议动手试一下。代码跑通之后,按自己的习惯调一调界面和参数,会变成你日常离不开的小工具。
有什么想改进的地方,欢迎留言。
https://ima.qq.com/wiki/?shareId=f2628818f0874da17b71ffa0e5e8408114e7dbad46f1745bbd1cc1365277631c
