PaddleOCR 截图自动文字识别
春节假期在家无聊,撸了三个小工具:PC截图+编辑/PC录屏(用于meeting录屏)/PC截屏文字识别。因为感觉这三个小工具是工作中常常需要用到的,github上也有很多开源的,不过总有点或多或少的小问题,不利于自己的使用。脚本的编写尽量减少对三方库的使用。
已全部完成,这是其中的一个,后续将三个集成在在一个工具中。
import tkinter as tk
from tkinter import ttk, messagebox, font, filedialog
from PIL import Image, ImageTk, ImageGrab
import sys
import tempfile
import threading
from pathlib import Path
import ctypes
import logging.handlers
from datetime import datetime# 最小化控制台窗口
def minimize_console():ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 6)minimize_console() # 调用最小化函数# 获取脚本所在目录路径
def get_script_directory():return Path(__file__).parent# 配置日志文件路径和日志级别
log_file_path = get_script_directory() / 'ocr_errors.log'
logging.basicConfig(filename=log_file_path,level=logging.DEBUG,format='%(asctime)s - %(levelname)s - %(message)s'
)
# 添加日志轮转
handler = logging.handlers.RotatingFileHandler(log_file_path, maxBytes=1024*1024*5, backupCount=3)
logger = logging.getLogger()
logger.addHandler(handler)# 保存临时图片到磁盘
def save_temp_image(image, suffix='.png'):with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:image.save(temp_file.name)return Path(temp_file.name)class OCRApp:def __init__(self):try:self.root = tk.Tk()self.root.withdraw()# 禁用最大化按钮self.root.resizable(False, True)self.screenshot = Noneself.ocr_model = None # 延迟初始化self.recognized_text = ""self.main_frame = Noneself.load_win = None # 初始化 load_win 为 None# 启动后台线程加载OCR模型以优化性能,使run脚本后能马上进入截图状态threading.Thread(target=self.load_ocr_model, daemon=True).start()# 立即开始截图选择self.start_selection()except Exception as e:self.show_crash_message(f"程序启动失败: {str(e)}")sys.exit(1)def load_ocr_model(self):from paddleocr import PaddleOCRtry:self.ocr_model = PaddleOCR(use_angle_cls=True, show_log=False, lang='ch')except Exception as e:logger.error(f"OCR模型加载失败: {str(e)}")# 开始截图选择区域def start_selection(self):self.selection_win = tk.Toplevel()self.selection_win.attributes("-fullscreen", True)self.selection_win.attributes("-alpha", 0.3)# 绑定整个窗口的 ESC 键事件self.selection_win.bind("<Escape>", self.on_escape)self.canvas = tk.Canvas(self.selection_win,cursor="cross",bg="gray30",highlightthickness=0)self.canvas.pack(fill=tk.BOTH, expand=True)self.start_x = self.start_y = 0self.rect_id = Noneself.crosshair_ids = []self.canvas.bind("<Button-1>", self.on_mouse_down)self.canvas.bind("<B1-Motion>", self.on_mouse_drag)self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)self.canvas.bind("<Motion>", self.on_mouse_move)self.escape_label = tk.Label(self.selection_win,text="按ESC键退出截图",fg="yellow",bg="gray20",font=("Helvetica", 12, "bold"))self.escape_label.place(x=10, y=10)self.update_crosshair(0, 0)# 鼠标按下事件处理def on_mouse_down(self, event):self.start_x = event.xself.start_y = event.yself.clear_crosshair()if self.rect_id:self.canvas.delete(self.rect_id)self.rect_id = None# 鼠标拖动事件处理def on_mouse_drag(self, event):current_x = event.xcurrent_y = event.yif self.rect_id:self.canvas.coords(self.rect_id, self.start_x, self.start_y, current_x, current_y)else:self.rect_id = self.canvas.create_rectangle(self.start_x, self.start_y,current_x, current_y,outline="blue", width=2, fill="gray75", tags="rect")# 鼠标释放事件处理def on_mouse_up(self, event):try:x1 = min(self.start_x, event.x)y1 = min(self.start_y, event.y)x2 = max(self.start_x, event.x)y2 = max(self.start_y, event.y)if (x2 - x1) < 10 or (y2 - y1) < 10:raise ValueError("选区过小,请选择更大的区域")if (x2 - x1) > self.canvas.winfo_width() or (y2 - y1) > self.canvas.winfo_height():raise ValueError("选区过大,请选择更小的区域")self.screenshot = ImageGrab.grab(bbox=(x1, y1, x2, y2))self.selection_win.destroy()self.initialize_ocr_and_process()except Exception as e:logger.error(f"截图错误: {str(e)}")messagebox.showerror("截图错误", str(e))self.restart_selection()# 初始化OCR引擎并处理截图def initialize_ocr_and_process(self):try:if self.ocr_model is None:self.load_win = self.show_loading("OCR模型正在加载中,请稍后...")self.root.after(100, self.check_ocr_model) # 每100毫秒检查一次else:self.process_ocr()self.setup_main_ui()self.root.deiconify()except Exception as e:logger.error(f"OCR初始化失败: {str(e)}")if self.load_win:self.load_win.destroy()self.handle_ocr_init_error(str(e))def check_ocr_model(self):if self.ocr_model is None:self.root.after(100, self.check_ocr_model) # 每100毫秒检查一次else:if self.load_win:self.load_win.destroy()self.process_ocr()self.setup_main_ui()self.root.deiconify()# 执行OCR处理def process_ocr(self):try:temp_image_path = save_temp_image(self.screenshot)result = self.ocr_model.ocr(str(temp_image_path), cls=True)temp_image_path.unlink() # 确保临时文件被删除# 后处理识别结果,合并同一行的文字merged_text = self.merge_lines(result[0])self.recognized_text = merged_textexcept Exception as e:logger.error(f"OCR处理失败: {str(e)}")messagebox.showerror("识别错误", f"OCR处理失败: {str(e)}")self.restart_selection()# 合并同一行的文字def merge_lines(self, ocr_result):merged_text = []current_line = []current_y1 = Nonecurrent_y2 = Noneline_threshold = 5 # 设置行间距阈值,可以根据需要调整for line in ocr_result:# 提取坐标点x1, y1 = line[0][0] # 第一个坐标点x2, y2 = line[0][2] # 第三个坐标点text = line[1][0] # 提取文本if current_y1 is None or current_y2 is None:current_y1 = y1current_y2 = y2current_line.append(text)elif abs(y1 - current_y1) <= line_threshold and abs(y2 - current_y2) <= line_threshold:current_line.append(text)else:merged_text.append(" ".join(current_line))current_line = [text]current_y1 = y1current_y2 = y2if current_line:merged_text.append(" ".join(current_line))return "\n".join(merged_text)# 设置主界面UIdef setup_main_ui(self):if self.main_frame is None:self.main_frame = ttk.Frame(self.root, padding=20)self.main_frame.grid(row=0, column=0, sticky="nsew")self.root.grid_rowconfigure(0, weight=1)self.root.grid_columnconfigure(0, weight=1)# 使用 PanedWindow 来分割图片框和文本框self.paned_window = ttk.PanedWindow(self.main_frame, orient=tk.VERTICAL)self.paned_window.grid(row=0, column=0, sticky="nsew")# 创建一个 Frame 来包含图片和滚动条self.image_frame = ttk.Frame(self.paned_window)self.image_frame.pack(fill=tk.BOTH, expand=True)# 使用 Canvas 来显示图片并添加滚动条self.image_canvas = tk.Canvas(self.image_frame, highlightbackground=self.root.cget("bg"), highlightthickness=0)self.image_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)self.image_scrollbar = ttk.Scrollbar(self.image_frame, orient=tk.VERTICAL, command=self.image_canvas.yview)self.image_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.image_canvas.config(yscrollcommand=self.image_scrollbar.set)self.image_canvas.bind("<Configure>", self.on_canvas_configure)self.image_container = ttk.Frame(self.image_canvas)self.image_container_id = self.image_canvas.create_window((0, 0), window=self.image_container, anchor="nw")self.img_label = ttk.Label(self.image_container)self.img_label.pack(fill=tk.BOTH, expand=True)# 定义字体custom_font = font.Font(family="Microsoft YaHei", size=9)self.text_area = tk.Text(self.paned_window,wrap=tk.WORD,font=custom_font, # 设置字体height=15 # 初始高度设置为15行)self.text_area.pack(fill=tk.BOTH, expand=True)self.paned_window.add(self.image_frame)self.paned_window.add(self.text_area)btn_frame = ttk.Frame(self.main_frame)btn_frame.grid(row=1, column=0, sticky="ew", pady=10)# 确保按钮行不会被压缩self.main_frame.grid_rowconfigure(0, weight=1)self.main_frame.grid_rowconfigure(1, weight=0)ttk.Button(btn_frame,text="重新选择",command=self.restart_selection).pack(side=tk.LEFT, padx=5)ttk.Button(btn_frame,text="复制文本",command=self.copy_result).pack(side=tk.LEFT, padx=5)ttk.Button(btn_frame,text="保存图片",command=self.save_image).pack(side=tk.LEFT, padx=5)ttk.Button(btn_frame,text="退出",command=self.safe_exit).pack(side=tk.RIGHT, padx=5)# 设置窗口标题self.root.title("文字识别@PDM3")self.update_image_display()self.text_area.delete(1.0, tk.END)self.text_area.insert(tk.END, self.recognized_text.strip())self.update_text_area_height() # 更新文本框高度# 设置窗口总是最顶层self.root.attributes('-topmost', True)# 更新图片显示def update_image_display(self):if self.screenshot:photo = ImageTk.PhotoImage(self.screenshot)self.img_label.config(image=photo)self.img_label.image = photo# 获取图片的实际大小img_width, img_height = self.screenshot.size# 获取屏幕高度screen_height = self.root.winfo_screenheight()# 计算图片框的最大高度max_image_height = screen_height // 2# 设置 Canvas 的滚动区域self.image_canvas.config(scrollregion=(0, 0, img_width, img_height))# 调整 image_canvas 的高度if img_height > max_image_height:self.image_canvas.config(height=max_image_height)else:self.image_canvas.config(height=img_height)# 配置 Canvas 大小def on_canvas_configure(self, event):# 更新 Canvas 的滚动区域self.image_canvas.config(scrollregion=self.image_canvas.bbox("all"))# 显示加载中的窗口def show_loading(self, message):load_win = tk.Toplevel()load_win.title("请稍候")frame = ttk.Frame(load_win, padding=20)frame.pack()ttk.Label(frame, text=message).pack(pady=10)progress = ttk.Progressbar(frame, mode='indeterminate')progress.pack(pady=5)progress.start()return load_win# 处理OCR初始化错误def handle_ocr_init_error(self, error_msg):choice = messagebox.askretrycancel("OCR初始化失败",f"{error_msg}\n\n是否重试?",icon='error')if choice:threading.Thread(target=self.initialize_ocr_and_process).start()else:self.safe_exit()# 重新开始截图选择def restart_selection(self):if self.root.winfo_exists():self.root.withdraw()self.screenshot = Noneself.recognized_text = ""self.clear_ui()self.start_selection()# 清理UI界面def clear_ui(self):if hasattr(self, 'img_label'):self.img_label.config(image='')self.img_label.image = Noneif hasattr(self, 'text_area'):self.text_area.delete(1.0, tk.END)# 复制识别结果到剪贴板def copy_result(self):self.root.clipboard_clear()self.root.clipboard_append(self.recognized_text)messagebox.showinfo("成功", "已复制到剪贴板")# 安全退出程序def safe_exit(self):if self.root.winfo_exists():self.root.destroy()sys.exit(0)# 显示程序崩溃错误信息def show_crash_message(self, message):crash_win = tk.Tk()crash_win.withdraw()messagebox.showerror("致命错误", message)crash_win.destroy()# 按下ESC键时退出程序def on_escape(self, event):self.selection_win.destroy()self.safe_exit()# 鼠标移动事件处理def on_mouse_move(self, event):current_x = event.xcurrent_y = event.yself.update_crosshair(current_x, current_y)# 更新十字线位置def update_crosshair(self, x, y):self.clear_crosshair()self.crosshair_ids.append(self.canvas.create_line(0, y, self.canvas.winfo_width(), y,tags="crosshair", fill="yellow", width=2))self.crosshair_ids.append(self.canvas.create_line(x, 0, x, self.canvas.winfo_height(),tags="crosshair", fill="yellow", width=2))# 清除十字线def clear_crosshair(self):for crosshair_id in self.crosshair_ids:self.canvas.delete(crosshair_id)self.crosshair_ids = []# 保存图片def save_image(self):if self.screenshot:# 获取用户桌面路径desktop_path = Path.home() / 'Desktop'# 生成当前日期和时间的字符串current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")default_filename = f"screenshot_{current_datetime}.png"file_path = filedialog.asksaveasfilename(initialdir=desktop_path, # 设置初始目录为用户桌面initialfile=default_filename, # 设置默认文件名defaultextension=".png",filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg"), ("All files", "*.*")])if file_path:self.screenshot.save(file_path)messagebox.showinfo("保存成功", f"图片已保存到 {file_path}")# 更新文本框高度def update_text_area_height(self):# 计算当前文本行数line_count = int(self.text_area.index('end-1c').split('.')[0])if line_count > 15:self.text_area.config(height=15) # 如果行数超过15行,固定高度为15行else:self.text_area.config(height=line_count) # 否则根据内容调整高度# 运行主循环def run(self):self.root.mainloop()if __name__ == "__main__":app = OCRApp()app.run()
相关文章:
PaddleOCR 截图自动文字识别
春节假期在家无聊,撸了三个小工具:PC截图编辑/PC录屏(用于meeting录屏)/PC截屏文字识别。因为感觉这三个小工具是工作中常常需要用到的,github上也有很多开源的,不过总有点或多或少的小问题,不利于自己的使用。脚本的编…...

【Blazor学习笔记】.NET Blazor学习笔记
我是大标题 我学习Blazor的顺序是基于Blazor University,然后实际内容不完全基于它,因为它的例子还是基于.NET Core 3.1做的,距离现在很遥远了。 截至本文撰写的时间,2025年,最新的.NET是.NET9了都,可能1…...

UE求职Demo开发日志#21 背包-仓库-装备栏移动物品
1 创建一个枚举记录来源位置 UENUM(BlueprintType) enum class EMyItemLocation : uint8 {None0,Bag UMETA(DisplayName "Bag"),Armed UMETA(DisplayName "Armed"),WareHouse UMETA(DisplayName "WareHouse"), }; 2 创建一个BagPad和WarePa…...

力扣988. 从叶结点开始的最小字符串
Problem: 988. 从叶结点开始的最小字符串 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 在先序遍历的过程中,用一个变量path拼接记录下其组成的字符串,当遇到根节点时再将其反转并比较大小(字典顺序大小&…...

《PYTHON语言程序设计》(2018版)1.7近似π。利用步幅来进行修改
利用循环的步幅来计算派 利用正常的办法, pi 4 *(1-(1/3)(1/5)-(1/7)(1/9)-(1/11)(1/13)-(1/15)) print(pi)利用这段代码得出结果 我们如何利用循环来进行呢 一、思路 首先我们来利用excel表格来计算一下结果 我做了一个设想,让相加的部分先进行相加,然后再进行减法呢?? 结…...

低通滤波算法的数学原理和C语言实现
目录 概述 1 原理介绍 1. 1 基本概念 1.2 一阶RC低通滤波器模型 2 C语言完整实现 2.1 滤波器结构体定义 2.2 初始化函数 2.3 滤波计算函数 3 应用示例 3.1 噪声信号滤波 3.2 输出效果对比 3.3 关键参数选择指南 4 性能优化技巧 4.1 定点数优化 4.2 抗溢出处理 …...

【BUUCTF杂项题】荷兰宽带数据泄露、九连环
一.荷兰宽带数据泄露 打开发现是一个.bin为后缀的二进制文件,因为提示宽带数据泄露,考虑是宽带路由器方向的隐写 补充:大多数现代路由器都可以让您备份一个文件路由器的配置文件,软件RouterPassView可以读取这个路由配置文件。 用…...

安全策略实验报告
1.实验拓扑图 2.实验需求 vlan2属于办公区,vlan3生产区 办公区pc在工作日时间可以正常访问OAserver,i其他时间不允许 办公区pc可以在任意时间访问Web server 生产区pc可以在任意时间访问OA server但不能访问web server 特例:生产区pc可以…...

Haproxy+keepalived高可用集群,haproxy宕机的解决方案
Haproxykeepalived高可用集群,允许keepalived宕机,允许后端真实服务器宕机,但是不允许haproxy宕机, 所以下面就是解决方案 keepalived配置高可用检测脚本 ,master和backup都要添加 配置脚本 # vim /etc/keepalived…...

亚博microros小车-原生ubuntu支持系列:20 ROS Robot APP建图
依赖工程 新建工程laserscan_to_point_publisher src/laserscan_to_point_publisher/laserscan_to_point_publisher/目录下新建文件laserscan_to_point_publish.py #!/usr/bin/env python3import rclpy from rclpy.node import Node from geometry_msgs.msg import PoseStam…...
Dockerfile构建容器镜像
Dockerfile 是一种文本格式的配置文件,用于自动化构建 Docker 镜像。它包含了一系列指令(命令),每个指令定义了容器镜像构建过程中的一步操作。通过Dockerfile,我们可以指定基础镜像、安装依赖、配置环境变量、复制文件…...
python 在包含类似字符\x16、\x12、\x某某的数组中将以\x开头的字符找出来的方法
话不多说直接看例子: import re# 原始列表 data [\x16, \x17, s, \x16, hello, \x1A]# 正则表达式匹配以 \x 开头的字符串 pattern r^\\x# 找出以 \x 开头的字符 result [item for item in data if isinstance(item, str) and re.match(pattern, repr(item)[1:-…...
Spring Bean 的生命周期介绍
Spring Bean 的生命周期涉及多个阶段,从实例化到销毁,在开发中我们可以通过各种接口和注解介入这些阶段来定制化自己的功能。以下是详细的生命周期流程: 1. Bean 的实例化(Instantiation) 方式:通过构造函…...
调用腾讯云批量文本翻译API翻译srt字幕
上一篇文章介绍了调用百度翻译API翻译日文srt字幕的方法。百度翻译API是get方式调用,参数都放在ur中,每次调用翻译文本长度除了接口限制外,还有url长度限制,而日文字符通过ur转码后会占9个字符长度,其实从这个角度来讲…...

车载软件架构 --- 软件定义汽车面向服务架构的应用迁移
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活…...

Baklib引领内容中台与人工智能技术的创新融合之路
内容概要 在数字化转型的浪潮中,各行业正在面临前所未有的挑战与机遇。内容中台作为一种新的概念,逐渐进入了企业的视野,它不仅是一个技术平台,更是提供了整合和管理内容的新思路。从根本上,内容中台旨在提升企业对信…...
想品客老师的第十一天:模块化开发
模块化概念 模块化开发可以提高代码的可维护性、可读性和复用性,同时降低开发和调试的复杂性,把业务根据功能分开写,解决变量命名的冲突,可以开放部分接口给类(例如调用模块里的一个函数)也更适合团队协作…...

接入DeepSeek大模型
接入DeepSeek 下载并安装Ollamachatbox 软件配置大模型 下载并安装Ollama 下载并安装Ollama, 使用参数ollama -v查看是否安装成功。 输入命令ollama list, 可以看到已经存在4个目录了。 输入命令ollama pull deepseek-r1:1.5b, 下载deepse…...

基于遗传算法的256QAM星座图的最优概率整形matlab仿真,对比优化前后整形星座图和误码率
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): GA优化曲线: 优化前后星座图对比 优化前后误码率对比 仿真操作步骤…...
JavaScript系列(57)--工程化实践详解
JavaScript工程化实践详解 🏗️ 今天,让我们深入探讨JavaScript的工程化实践。良好的工程化实践对于构建可维护、高质量的JavaScript项目至关重要。 工程化基础概念 🌟 💡 小知识:JavaScript工程化是指在JavaScript开…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...