简介:面向Python学习者与高校学生,这份基于OpenCV和Tesseract-OCR的图像文字识别项目,提供完整可运行的源码与设计报告,可直接用于毕业设计、期末大作业或课程设计。资源共14个文件,压缩包大小约96.84MB,包含Python源码(py)、设计报告(pdf)、操作演示视频(mp4)、Tesseract中文语言包(traineddata)、示例图片(jpg)以及Windows安装程序(exe),覆盖环境配置、代码阅读到结果测试的完整链路。目前已有82人学习下载,适合需要快速搭建OCR功能、借鉴项目结构或进行二次开发的学习者。项目代码带有详细注释,新手也能理解;配套的设计报告与流程图(vsdx)可帮助梳理设计思路,演示视频则展示实际运行效果,配合中文识别包与安装程序,部署后即可体验图像文字识别全过程,节省大量调试时间。
1. 从一张带文字的图片到可编辑文本:这套代码到底解决了什么问题
图像文字识别,英文叫 OCR,落地场景远比想象中具体:票据录入、车牌抓拍、截图转文档、扫描件归档。你手头可能正有一批图片需要批量提取文字,也可能是在做毕设或课程设计,需要一个能跑通、能写进报告、能现场演示的完整方案。基于 Python + OpenCV + Tesseract-OCR 这套组合,正是目前本地离线 OCR 里最成熟、最容易调试、也最能讲清楚原理的路线——OpenCV 负责把图像处理成机器更容易读的状态,Tesseract 负责真正把字形映射成文字,Python 当胶水把整个流程串起来。
这套方案的直接价值在于:不依赖云服务、不烧 API 额度、不把图片传给别人,数据留在本地;同时源码结构直观,预处理每一步都可以可视化验证,非常适合学习也好出报告。接下来我按自己做这类项目时的完整链路来讲:环境怎么搭、预处理参数怎么调、Tesseract 怎么让它认字更准、以及最折磨人的那些坑。
2. 环境搭建和选型理由:为什么是 OpenCV + Tesseract 而不是直接调云 API
2.1 三个组件各管哪一段:预处理、识别、胶水层
先理清楚职责划分,这决定了你后面调试时往哪个方向使劲。OpenCV(Python 里 import 的是 cv2,安装包叫 opencv-python)负责的是“让图片变得更适合识别”:把彩色图转成灰度图、去掉噪点、把背景和文字分离开、纠正倾斜、放大缩小。这些步骤解决的核心问题是——Tesseract 对干净的黑白文字图识别率很高,但对真实世界拍出来的照片、扫描件、带阴影和反光的图,直接送进去效果很差。
Tesseract-OCR 是 Google 开源的 OCR 引擎,它做的事情是“把图像里的文字形状翻译成字符串”。引擎内部有完整的字符识别模型,支持上百种语言,中文需要单独的语言包。Python 调用它有两种方式:一是直接用 subprocess 调 tesseract.exe 命令行,二是用 pytesseract 这个封装库,它内部也是调命令行,但返回的是 Python 字符串,方便直接处理。
Python 在整个流程里做的事情包括:读取图片文件、调用 OpenCV 的各个处理函数、把处理结果传给 pytesseract、对识别出的文本做正则清洗和格式化。换句话说,Python 是流程编排者,不参与真正的字符识别。理解这一点很重要——很多人遇到识别不准,第一反应是换 OCR 引擎,但实际上问题往往出在预处理环节,OpenCV 没把图伺候到位。
2.2 Windows 和 Linux 下的安装步骤与版本注意
环境安装是整个项目里最容易卡住新手的一步,因为 OpenCV 和 Tesseract 是两套独立的安装体系。先说 Python 侧。建议直接用 pip 安装 opencv-python 和 pytesseract:
pip install opencv-python pip install pytesseract两个包都装完后,验证 OpenCV 是否能正常导入:
python -c "import cv2; print(cv2.__version__)"能输出版本号说明 OpenCV 安装成功。这里有个常见情况:如果你用的是 conda 环境,opencv-python和系统里已有的opencv可能出现冲突,表现为 import cv2 时直接报错。遇到这种情况,建议在干净的虚拟环境里重装 opencv-python,不要和 conda 自带的 opencv 混用。
Tesseract 本体不能通过 pip 安装,它是独立的 C++ 程序。Windows 下需要去 GitHub 的 tesseract-ocr 发布页下载对应系统的安装包,安装时勾选需要的语言包。Linux 下用 apt 安装:
sudo apt update sudo apt install tesseract-ocr sudo apt install tesseract-ocr-chi-sim第一条命令装的是英文识别引擎,第二条装简体中文语言包。如果缺了 chi-sim,后面识别中文会直接输出乱码。装完后在终端验证:
tesseract --version能显示版本信息就说明引擎本体没问题。还要确认语言包是否就位:
tesseract --list-langs输出里应该有 eng 和 chi_sim 两项。注意语言包名字是chi_sim,而在 pytesseract 里调用时写lang="chi_sim",带下划线。
2.3 路径陷阱:pytesseract 找不到 tesseract.exe 怎么办
这是 Windows 下几乎人人都会踩的一个坑。安装完 Tesseract 后,pytesseract 默认去系统 PATH 里找 tesseract.exe,但 Windows 安装包经常不会自动把安装目录加进 PATH。结果就是运行代码时报错:
pytesseract.pytesseract.TesseractNotFoundError: tesseract is not installed or it's not in your PATH解决方法是写代码时显式指定路径。常见的安装位置是C:\Program Files\Tesseract-OCR\tesseract.exe:
import pytesseract # 仅 Windows 需要显式指定,Linux 一般安装后就在 PATH 里 pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"提示:如果你的 Tesseract 安装在别的位置,可以在系统设置里搜“tesseract.exe”确认实际路径,再替换上面这行。Linux 系统一般不需要这步,但如果你用的是自定义编译的 Tesseract,同样可以用这个方式指向你的二进制文件。
这个指定路径的代码务必放在任何调用 pytesseract 的函数之前。另外,macOS 上用 Homebrew 安装的路径通常是/usr/local/bin/tesseract,也同样需要确认。
3. 图像预处理:识别率七成靠这里,二值化和降噪怎么做
3.1 读取图片后的第一个动作:灰度化和缩放
Tesseract 内部做字符切分时依赖的是文字的轮廓和连通域,彩色信息对它不仅没有帮助,反而会因为 RGB 三个通道各自有噪声而干扰判断。所以预处理第一步永远是灰度化。
import cv2 # 读取图片,cv2.IMREAD_GRAYSCALE 直接读成灰度图 img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE) # 如果图片高度过大,按比例缩小;如果字太小,按比例放大 # 这里把高度统一缩放到 2000 像素左右 h, w = img.shape[:2] target_h = 2000 if h > target_h: scale = target_h / h img = cv2.resize(img, (int(w * scale), target_h), interpolation=cv2.INTER_AREA) elif h < 800: scale = 800 / h img = cv2.resize(img, (int(w * scale), 800), interpolation=cv2.INTER_CUBIC)这段代码的逻辑是:图太大时缩小(高度超过 2000 像素),图太小时放大(高度不足 800 像素)。为什么要控制在 800 到 2000 这个区间?因为 Tesseract 对字号有一定的适应范围,字太小了笔画连在一起切不开,字太大了反而会因为像素点边缘锯齿干扰识别。缩小时用INTER_AREA,它对像素做区域平均,能有效抑制高频噪声;放大时用INTER_CUBIC,双三次插值,边缘过渡更平滑。
注意:直接读取彩色图再转灰度,和用
IMREAD_GRAYSCALE读图,结果略有差异。前者用的是标准 RGB 转灰度公式,后者是 OpenCV 内部按 BGR 通道加权。对识别来说差异很小,但如果你在做预处理实验,尽量统一用一种方式,别混用。
3.2 二值化:固定阈值还是大津法
灰度图里文字和背景的亮度差已经拉开了,但还不够——Tesseract 喜欢的是“非黑即白”的图:文字是纯黑(像素值 0),背景是纯白(像素值 255)。二值化就是按一个阈值,把 0~255 的灰度值压成 0 和 255 两个值。
# 大津法自动计算阈值 _, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)大津法(OTSU)的思路是:遍历所有可能的阈值,找到一个值让前景和背景的类内方差最小、类间方差最大。这套方法对“背景均匀、文字对比度清晰”的图非常有效,你不用手动试阈值,自适应能力强。
但如果图片光照不均匀——比如扫描件半边亮半边暗,或者拍照时边缘有阴影——全局阈值就扛不住了。这时改用自适应阈值:
# 自适应阈值,blockSize 是邻域大小,C 是常量修正值 adaptive = cv2.adaptiveThreshold( img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 11 )blockSize=31表示每个像素的阈值由它周围 31x31 邻域的加权均值决定,C=11表示在均值基础上减 11。这样每个局部区域都有自己的阈值,能应对光照从左到右渐变的情况。但自适应阈值的副作用是容易把纸张纹理误判成前景,产生大量椒盐噪点。所以我的一般做法是:先试大津法,如果识别结果里杂点太多,再换自适应,同时配合下一步的降噪。
二值化之后,你要做的第一件事是“目测确认”——用cv2.imwrite("debug_binary.png", binary)把结果存下来看一眼。这一步省不了,因为代码跑通不代表预处理做对了,图上看文字是否黑白分明、背景是否干净,直接决定了后面 OCR 能不能用。
3.3 降噪和文字增强:去点、去线、形态学闭运算
二值化后的图常见两类问题:一是背景残留黑色杂点,二是文字笔画断裂——尤其是打印体文字笔画薄的地方被阈值吃掉,导致一个字裂成好几块,Tesseract 切分时就认不出来。分别对应两个处理手段。
# 第一步:中值滤波去孤立噪点,kernel 取 3x3,太大会模糊文字 denoised = cv2.medianBlur(binary, 3) # 第二步:形态学闭运算,把断裂的笔画连起来 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) closed = cv2.morphologyEx(denoised, cv2.MORPH_CLOSE, kernel)中值滤波的原理是把每个像素的值替换成邻域内的中位数,孤立噪点在这种操作下会被邻域的正常值淹没。ksize=3是安全的起点,如果图里噪点很多可以试 5,但 5 以上容易把细笔画也当噪点抹掉。
闭运算是“先膨胀后腐蚀”。膨胀让白色区域向外扩张一圈,原本断裂的笔画被连上;腐蚀再把扩张出去的边界收回来,恢复原始尺寸。它专门治“文字缺胳膊少腿”的毛病。kernel 尺寸决定每次膨胀腐蚀的幅度,3x3 对常见文档图的断裂修复效果最均衡。
如果你处理的图里还有干扰线条——比如表格线、下划线、图片边框——这些线会严重影响 Tesseract 的字符切分。常见做法是用霍夫变换检测直线,然后画成背景色抹掉。
import numpy as np # dst 是上一步得到的二值图 # 检测直线并抹除 edges = cv2.Canny(closed, 50, 150, apertureSize=3) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=200, minLineLength=200, maxLineGap=30) if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] # 只抹水平线和垂直线,避免误杀文字里的斜线 angle_abs = abs(np.arctan2(y2-y1, x2-x1) * 180 / np.pi) if angle_abs < 5 or angle_abs > 85: cv2.line(closed, (x1, y1), (x2, y2), 255, 5)minLineLength=200过滤掉短线段(避免把文字笔画里的短横当成线),maxLineGap=30允许检测到断开的线时把间隙连起来。抹除时用255(白色背景色)填充,线宽取 5 像素保证盖住整条线。
3.4 角度校正:霍夫变换检测倾斜
拍照或者扫描的时候,图片经常会有几度的倾斜。Tesseract 对倾斜的容忍度很低,稍微歪一点,水平方向的字符切分就对不齐,识别率断崖式下跌。最常见的纠偏方案是:检测图中最长的直线族的倾斜角,然后按角度反向旋转。
def get_skew_angle(image): # 输入灰度图或二值图 edges = cv2.Canny(image, 50, 150, apertureSize=3) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=300, minLineLength=image.shape[1] * 0.3) angles = [] if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi if angle != 90: # 排除垂直线的影响 angles.append(angle) if not angles: return 0 # 返回角度众数,最普遍的那个倾斜角 return np.median(angles) skew = get_skew_angle(binary) if abs(skew) > 0.5: h, w = binary.shape[:2] center = (w // 2, h // 2) matrix = cv2.getRotationMatrix2D(center, skew, 1.0) binary = cv2.warpAffine(binary, matrix, (w, h), flags=cv2.INTER_CUBIC, borderValue=255)minLineLength设为图片宽度的 30%,作用是只统计足够长的线——因为文档里的文字行水平排列,文字本身会制造大量短横线,threshold 设高、line 设长能过滤掉这些干扰。得到一组角度后用中位数而不是均值来代表整体倾斜,因为中位数对离群值不敏感,某条歪得离谱的线不会把结果带偏。
这整条预处理链路——灰度、缩放、二值化、降噪、去线、纠偏——每一步都有可视化检查点。我的习惯是把每步的中间结果都imwrite存下来,拼在一起看一遍,哪个环节把文字弄花了立刻能定位到。把预处理做好了,Tesseract 的工作量至少减一半。
4. 调用 Tesseract-OCR:语言包、psm 模式和识别参数一个都不能少
4.1 pytesseract 的最小调用代码
预处理做完,终于到调用 OCR 引擎的环节。pytesseract 的接口非常简洁,但参数里藏着大量影响识别质量的开关。
import pytesseract from PIL import Image import cv2 # OpenCV 的 BGR 或灰度图要先转成 PIL 的 Image 对象 # 因为 pytesseract 内部调用 tesseract 命令行,而 tesseract 的文件接口和 PIL 兼容度最好 text = pytesseract.image_to_string( Image.fromarray(binary), # binary 是前面预处理好的二值图 lang="chi_sim+eng", # 中英文混合识别 config="--psm 6" # 按统一文本块识别 ) print(text)这里有个隐藏的坑:pytesseract 接收的是 PIL Image 对象,而不是 OpenCV 的 numpy 数组。虽然新版 pytesseract 也支持直接传 numpy 数组,但为了稳妥,我总是显式用Image.fromarray()转换一次。如果你直接把 OpenCV 读出来的 BGR 图传进去,识别出的文字颜色反相,结果里全是乱码。
lang="chi_sim+eng"表示中文和英文同时启用,中间用加号连接。--psm 6是页面分割模式(Page Segmentation Mode),它告诉 Tesseract 图片里的文字是如何排版的——这是影响识别率最关键的参数之一,值得单独展开讲。
4.2 psm 模式选择:6、7、3、13 各自用在哪
Tesseract 的--psm参数从 0 到 13 一共 14 个模式,但日常用途里常用的就 4 个。选错模式的典型症状是:识别结果顺序打乱、多出奇奇怪怪的换行、或者干脆什么都识别不出来。
下面是我在不同场景下的选择经验:
| psm 值 | 含义 | 适用场景 | 备注 |
|---|---|---|---|
| 6 | 把整张图当成一个统一的文本块 | 文档、扫描件、书页 | 最常用,段落顺序正确 |
| 7 | 单行文字 | 截图里的标题、按钮文字 | 一行字单独识别时最稳 |
| 3 | 完全自动分行分块 | 排版不规则的图、带分栏的报纸 | 速度最慢,但适应性最好 |
| 13 | 原始行,不额外切分 | 表格里每一格单独识别 | 配合逐格裁剪食用 |
代码里怎么切 psm?在 config 参数里直接写:
# 单行文字识别,适合验证某一行是否切干净了 text = pytesseract.image_to_string( Image.fromarray(binary), lang="chi_sim+eng", config="--psm 7" )我的调试习惯是:一张图先跑 psm 3 和 psm 6 各一次,对比哪个结果更贴近真实排版。如果内容是一整段连续的正文,psm 6 通常赢;如果有分栏、混合排版,psm 3 更稳。单行文本用 psm 7 能明显减少误切分。
4.3 白名单和字符黑名单:限制识别范围
当你知道目标文本的字符集范围时,用白名单是提升识别率最有效的方法。比如识别银行卡号,只可能出现数字和空格;识别车牌,可能只要汉字+字母+数字。指定白名单后,Tesseract 会过滤掉一切不在列表里的候选字符,很大程度上消除了“0 认成 O”、“1 认成 l”这类错误。
# 只识别数字和横杠,适合身份证号、银行卡号场景 config_digits = "--psm 7 -c tessedit_char_whitelist=0123456789-" text = pytesseract.image_to_string( Image.fromarray(binary), lang="eng", config=config_digits )注意设置白名单时语言包建议只用eng——因为中文字符数量巨大,白名单过滤对中文并不高效,而且 chi_sim 语言包混用时白名单的表现不稳定。数字场景单独跑 eng 就够了。
反过来,黑名单用于排除特定字符,比如去掉引号、括号这类噪声符号:
config_no_quote = '--psm 6 -c tessedit_char_blacklist="\'"'4.4 识别结果的后处理:正则清洗
Tesseract 输出的原始字符串往往带有多余空格、歪斜的换行和误识别的字符。不要指望 OCR 一遍输出就完全干净,后处理是流程里必不可少的一环。
import re raw = pytesseract.image_to_string(Image.fromarray(binary), lang="chi_sim+eng", config="--psm 6") # 清除行首行尾的空白字符 cleaned = raw.strip() # 把连续两个以上的换行压成一个,避免空行刷屏 cleaned = re.sub(r'\n\s*\n', '\n', cleaned) # 中英文混排时,英文单词之间多余的空格会影响可读性 # 但中文之间误插入的空格要删掉,替换中文字符间的空格 cleaned = re.sub(r'(?<=[\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff])', '', cleaned)如果你做的是结构化识别,比如识别固定模板的表格,后处理还可以更进一步:按行拆分、按冒号分割 key-value、用正则提取邮箱和电话号码等。这些规则要根据实际数据写,没有万能模板,但大原则是一致的——OCR 输出是“按照最像的字符猜出来的文本”,永远要当成带噪声的数据来处理,而不是可信的最终结果。
5. 避坑:中文识别乱码、环境报错、阈值翻车——整理自真实复现经验
5.1 现象:中文全变乱码,英文却正常
原因分析:Tesseract 默认只装英文语言包,没有 chi_sim 语言包。直接把中文图交给它,引擎只能拿英文字形库去匹配,匹配不上就输出乱码或空字符串。
解决办法:确认语言包已安装——Linux 上执行sudo apt install tesseract-ocr-chi-sim,Windows 上在安装 Tesseract 时勾选 Chinese Simplified,或者在 GitHub tessdata 仓库手动下载chi_sim.traineddata放进 Tesseract 安装目录的tessdata文件夹。装好后在代码里调用时记得写lang="chi_sim",并先跑一下tesseract --list-langs确认列表中出现了 chi_sim。
5.2 现象:直接报 TesseractNotFoundError,代码一步都跑不动
原因分析:pytesseract 是封装库,它本身不含 OCR 引擎,需要去系统里找 tesseract 可执行文件。Windows 安装包没把目录加进 PATH 时,pytesseract 找不到二进制文件,直接在 API 层报错。
解决办法:在代码头部显式指定路径,pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"。如果你不知道自己装在哪,就在文件资源管理器里搜tesseract.exe。Linux 下如果用的是自定义编译安装,同样用这个方式指向tesseract的实际位置。这是一个“配好一次、以后再也不用管”的步骤。
5.3 现象:彩色背景图上识别率极低,甚至一个字都认不出
原因分析:直接把彩色原图送进 Tesseract,背景颜色、阴影、渐变都会参与字符切分和匹配,相当于把“干净的白底黑字”变成了“花底花字”,引擎的字符模型被背景噪声带偏。
解决办法:严格遵守预处理链路——灰度化 → 大津法二值化(光照不匀时换自适应阈值)→ 中值滤波。如果图片是深色背景浅色文字,注意二值化时会让文字变白背景变黑,Tesseract 默认按黑字白底识别,需要做一次反色,cv2.bitwise_not(binary)把黑白互换再送识别。
5.4 现象:识别出来的英文单词之间全是多余空格,句读全乱
原因分析:Tesseract 在识别时对字符间距的判断过于保守,把本来连着的字母之间也插入了空格。中英文混排时尤其明显,中文识别结果里经常冒出莫名其妙的西文空格。
解决办法:先用正则做后处理,把中文之间的空格删掉,把连续的换行压缩掉,把英文单词之间多余空格合并。还能从源头缓解——config 里加-c preserve_interword_spaces=1,这个参数让引擎尽量保留单词间真实距离,而不是靠概率硬猜。但根治方案还是后处理清洗,因为 OCR 的字符间距判断本质上就是概率性的。
5.5 现象:截图里的小字号文字识别结果缺胳膊少腿
原因分析:小字号文字的笔画在像素网格上占不满一个字符框,切分时容易被当成噪声吞掉,或者笔画断裂导致字符匹配失败。Tesseract 的字号适应范围大约是 10~20 像素高的文字,低于这个范围识别率急剧下降。
解决办法:在预处理阶段把图片放大 2~3 倍再送识别,用cv2.resize时选择INTER_CUBIC插值,放大后笔画轮廓更完整。我一般会把目标高度定在 1000 到 2000 像素之间。放大这个操作看似简单,实际对识别率的提升往往比调一堆 OCR 参数都明显。另外,宁大勿小——放大后如果字还是花,可以继续加,但不要缩小。
5.6 现象:识别速度奇慢,一张图要十几秒
原因分析:psm 3 模式全自动分行分块时计算量最大,加上高分辨率大图,引擎要对整张图做多轮候选搜索。另一个隐藏原因是图片没缩小时像素量太大,Tesseract 的字符切分复杂度近似 O(像素数)。
解决办法:控制输入图尺寸——高度超过 2000 像素就缩小;不要用 psm 3 处理规则排版,优先 psm 6。如果只是识别单行文本,用 psm 7 速度最快。调试阶段先用低分辨率图跑通流程,全部确认后再上原图跑正式识别。
6. 验证识别的笨办法:一个 30 张图的标注集,算出真实准确率
很多时候代码跑通、识别结果“看着还行”,但别人一问“你的准确率是多少”就答不上来。这里说一个我自己一直用的、不太花哨但很实用的验证方法:准备一个标注测试集,批量跑一遍,逐行比对预测文本和真实文本,字符级准确率一算就有。
先建一个结构简单的标注文件,每张图一行,用 Tab 分隔文件名和真实文本:
img001.png 联系人:张伟 电话:13812345678 img002.png 订单编号:A2024109然后写批量验证脚本:
import csv import cv2 import numpy as np import pytesseract from PIL import Image def preprocess(path): img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) _, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return binary def predict(binary): text = pytesseract.image_to_string( Image.fromarray(binary), lang="chi_sim+eng", config="--psm 6" ) return text.replace(" ", "").replace("\n", "") # 去掉空白后干净对比 # 加载标注 samples = [] with open("labels.tsv", "r", encoding="utf-8") as f: reader = csv.reader(f, delimiter="\t") for row in reader: if len(row) == 2: samples.append((row[0], row[1])) total_chars = 0 correct_chars = 0 per_sample_errors = [] for filename, truth in samples: binary = preprocess(filename) pred = predict(binary) # 字符级比对:统计每个位置是否一致 correct = sum(1 for a, b in zip(truth, pred) if a == b) total_chars += len(truth) correct_chars += correct err = len(truth) - correct per_sample_errors.append((filename, err, len(truth), pred)) print(f"{filename}: 错误 {err}/{len(truth)} 字,预测={pred}") print(f"字符级准确率: {correct_chars / total_chars * 100:.2f}%")这个脚本的逻辑很直白:逐字符比对真实文本和预测文本,相同字符数除以总字符数得到准确率。比对时我去掉了所有空格和换行,避免排版差异干扰“字符认没认对”的判断。比对脚本简陋,但它的价值在于把“感觉识别得还行”变成数字——一旦变成数字,你就能拿它做 A/B 对比,比如切换 psm 模式、调整二值化阈值、改变放大倍数,每组改动跑一遍测试集,准确率涨了就是有效优化,跌了就是方向不对。
我的习惯是保持这个 30 张左右的测试集固定不变,所有参数调优都拿同一组图来验证。30 张图虽然不算多,对常见场景已经足够暴露问题——如果 OCR 在某个特定排版上会系统性翻车,在这个量级里通常就会现形。固定测试集还有个好处:它能拦住很多“玄学调参”的冲动,让你只保留那些在数据上有效的改动。这个验证脚本我现在做任何 OCR 项目都会第一时间写好,不是最后补的。希望帮到你。
本文还有配套的精品资源,点击获取