1. 项目缘起与整体思路拆解
做图像处理的朋友多半都绕不开 EMGU.CV 这个库,它是 OpenCV 在 .NET 生态里最成熟的封装之一,C# 开发者用它做图像滤波、特征检测、模板匹配都很顺手。但真到了“文档 OCR”这个场景,很多人会发现事情没那么简单——把一张纸质文档、扫描件或者手机拍的照片,变成一段可编辑、可检索的文字,中间要跨过的坑远比想象中多。我这次做的就是一个基于 EMGU.CV 的文档 OCR 小工具,目标很明确:输入一张文档图片,输出结构化的文本内容,尽量保留原有的段落和行序。
先说清楚这个项目解决什么问题。日常工作中经常遇到扫描版合同、纸质表格、书籍拍照、发票截图这类“图片里的文字”,手动敲一遍费时费力,直接丢给 OCR 引擎又经常因为图像质量差、倾斜、噪点、光照不均而识别率暴跌。所以核心思路是:先用 EMGU.CV 做图像预处理,把图片“洗干净、摆正、切清楚”,再交给 OCR 引擎去识别。预处理做得好,识别准确率能从六成提到九成以上,这是整个项目最值钱的部分。
适合谁来参考?如果你是有一定 C# 基础、用过 EMGU.CV 做过基础图像操作,现在想把它用到实际文档处理场景的开发者,这篇内容会非常对口。如果你只是听说过 OCR 但没动过手,也能从里面看到一条完整的落地路径。我不会只贴几行调用代码就完事,而是把每一步“为什么这么做”“参数怎么定”“哪里容易翻车”都讲透。
整体方案我拆成四个阶段:图像读取与灰度化、几何校正与去噪、版面分析与区域切分、OCR 识别与结果后处理。这四个阶段里,EMGU.CV 主要负责前三步,OCR 引擎负责最后一步。之所以把重头放在前三步,是因为实测下来,识别引擎本身的能力已经足够强,真正拖后腿的永远是输入图像的质量。你可以把 OCR 引擎想象成一个视力很好但很挑剔的读者,图越干净、字越端正,它读得越准;图一歪一糊,它就瞎猜。
提示:不要一上来就调 OCR 接口。先花时间把预处理链路搭稳,后面调识别参数的时间会省下一大半。
2. 图像预处理的核心细节与实操要点
2.1 灰度化与通道选择的门道
第一步永远是灰度化,但灰度化不是简单地把彩色图转成黑白就完事。EMGU.CV 里用CvInvoke.CvtColor配合ColorConversion.Bgr2Gray就能完成,但关键在于什么时候不该直接灰度化。比如文档上有红色印章、蓝色手写批注,直接灰度化会让这些内容和其他文字混在一起,后续二值化时容易被当成噪点抹掉。我的做法是:先判断图像是否包含明显的高饱和度彩色区域,如果有,就先分离通道,单独处理彩色信息,再合并回灰度图。
具体操作上,我会先把图像转到 HSV 空间,用CvInvoke.InRange把高饱和度区域抠出来,看看占比。如果占比低于 5%,说明基本是黑白文档,直接灰度化没问题;如果超过 15%,就要考虑保留彩色通道信息。这个阈值不是拍脑袋定的,是我在几十张不同类型的文档图上试出来的经验值。低于 5% 时彩色内容太少,单独处理反而增加复杂度;高于 15% 时彩色内容已经影响主体识别,必须区别对待。
灰度化之后还有一个容易被忽略的点:对比度拉伸。很多扫描件整体偏灰,最暗的地方不够黑,最亮的地方不够白,直方图挤在中间。这时候用CvInvoke.EqualizeHist做直方图均衡化,能把对比度拉回来。但均衡化对文档图有个副作用——它会把背景噪点也一起放大。所以我的顺序是:先做轻度高斯模糊压噪,再均衡化,最后二值化。这个顺序换一下,效果差很多。
2.2 几何校正:把歪掉的文档摆正
文档拍歪是 OCR 的头号杀手。倾斜 5 度,识别率可能掉两成;倾斜 15 度,基本就废了。几何校正的核心是检测文档的四个角点,然后做透视变换。EMGU.CV 里做这件事有两条路:一是用CvInvoke.FindContours找最大轮廓,再逼近四边形;二是用霍夫变换检测直线,算倾斜角。我两种都试过,最后选了轮廓法为主、霍夫法为辅的组合策略。
轮廓法的逻辑是:文档通常是图像里最大的那个亮色矩形区域。先二值化,再找轮廓,按面积排序取最大的几个,用CvInvoke.ApproxPolyDP逼近多边形,如果逼近结果是四个点,就认为是文档边界。这里有个坑——ApproxPolyDP的精度参数epsilon设大了,四边形会变成三角形;设小了,会变成五边形六边形。我的经验值是取轮廓周长的 2% 到 5% 之间,具体看图像分辨率。高分辨率图取小一点,低分辨率图取大一点。
找到四个角点后,用CvInvoke.GetPerspectiveTransform算变换矩阵,再用CvInvoke.WarpPerspective做透视校正。这里的关键是目标矩形的宽高怎么定。我的做法是取校正后文档的宽为四边中最长边的长度,高按原始宽高比推算。这样能保证文字不被拉伸变形。如果文档本身有标准尺寸(比如 A4),也可以直接映射到标准比例,但通用场景下还是按实际比例来更稳。
注意:透视变换后图像边缘会出现黑边,后续二值化时这些黑边会被当成内容。记得用
CvInvoke.CopyMakeBorder或者直接裁剪掉边缘几个像素。
2.3 二值化:全局还是局部,这是个问题
二值化是文档 OCR 里最考验经验的一步。全局阈值(CvInvoke.Threshold配ThresholdType.Binary)速度快,但对光照不均的图基本没用——左边亮右边暗,一个阈值切下去,暗的地方全黑,亮的地方全白。所以实际项目里我几乎都用自适应阈值(CvInvoke.AdaptiveThreshold)。
自适应阈值的两个核心参数是blockSize和C。blockSize是计算局部阈值的邻域大小,必须是奇数。这个值怎么定?我的经验是取图像宽度的 1/20 到 1/50 之间,然后强制转成奇数。比如一张 2000 像素宽的图,blockSize取 41 到 101 之间比较合适。太小了会把文字笔画内部也切成黑白相间,太大了又退化成全局阈值。C是从局部均值里减去的常数,用来微调阈值。文档图一般取 5 到 15 之间,噪点多就取大一点。
还有一个进阶技巧:先做背景估计再相减。用一个大核的形态学闭运算估计背景光照,然后用原图减去背景,再做二值化。这个方法对手机拍的文档特别有效,因为手机拍摄经常有阴影和光照渐变。EMGU.CV 里用CvInvoke.MorphologyEx配MorphOp.Close加大结构元素就能做背景估计。结构元素大小取图像宽度的 1/10 左右,效果比较稳。
2.4 去噪与形态学清理
二值化之后,图像上通常还有三类噪点:孤立小黑点、孤立小白点、文字笔画断裂。处理这三类问题要用不同的形态学操作。孤立小黑点用开运算(先腐蚀后膨胀)去掉,孤立小白点用闭运算(先膨胀后腐蚀)填掉,笔画断裂用闭运算连接。但形态学的结构元素大小很关键——太大了会把细小文字直接抹掉,太小了又没效果。
我的做法是分两步:先用 3x3 的小核做一次开运算去小黑点,再用 3x3 的小核做一次闭运算填小白点和连笔画。如果文字本身比较粗(比如标题),可以适当加大到 5x5。但正文部分千万别用大核,否则笔画细的字直接消失。这里有个判断技巧:看二值化后文字笔画的平均宽度。用距离变换或者简单的行列投影就能估出来,结构元素大小取笔画宽度的 1.5 倍左右最合适。
另外,文档边缘的噪点往往比中间多,因为扫描或拍摄时边缘容易有阴影。我通常会在二值化前先把图像四周各裁掉 2% 到 3% 的边,能省掉不少后续清理工作。这个比例也是试出来的,裁太多会丢内容,裁太少没效果。
3. 版面分析与区域切分的完整实现
3.1 行投影与列投影的配合使用
版面分析的目标是把整页文档切成一个个文本行或文本块,方便 OCR 引擎逐块识别。最经典的方法是投影法:把二值图按行求和,得到水平投影曲线,文字行对应的位置投影值高,行间空白对应的位置投影值低。找投影曲线的波谷,就能切出行边界。同理,按列求和得到垂直投影,能切出列边界。
EMGU.CV 里没有直接的投影函数,但可以用CvInvoke.Reduce把图像降到一维,或者直接遍历像素统计。我一般用CvInvoke.Reduce配ReduceDimension.SingleRow或SingleCol,效率比手动遍历高不少。得到投影数组后,用简单的阈值判断就能找到行边界:投影值低于某个阈值的连续区域就是行间空白。阈值取投影最大值的 5% 到 10% 之间比较合适。
但投影法有个前提——文档必须是水平校正过的。如果还有残余倾斜,投影曲线会糊成一片,切不出清晰的行边界。所以投影法一定要放在几何校正之后。另外,如果文档有多栏排版,直接做水平投影会把不同栏的文字混在一起。这时候要先做垂直投影切出栏,再对每栏单独做水平投影切行。判断是否多栏的方法:看垂直投影曲线是否有明显的、周期性的波谷。如果有,就是多栏。
3.2 连通域分析处理复杂版面
投影法对规整的文档很有效,但遇到表格、图片混排、不规则排版就力不从心了。这时候要用连通域分析。EMGU.CV 里用CvInvoke.ConnectedComponentsWithStats能拿到每个连通域的位置、大小、面积。文字通常是小而密集的连通域,图片和表格线是大而稀疏的连通域。按面积和宽高比过滤,就能把文字区域和干扰区域分开。
我的过滤规则是这样的:面积小于 10 像素的连通域直接丢掉,肯定是噪点;面积大于图像面积 1/10 的连通域标记为图片或表格,单独处理;宽高比超过 20:1 的连通域标记为表格线或下划线,也单独处理。剩下的就是文字连通域。然后把相邻的文字连通域按行聚类——纵坐标重叠超过 50% 的归为同一行,这样就能得到文本行。
这个方法比投影法灵活,但计算量大一些。实测下来,一张 2000x3000 的图,连通域分析大概要 200 到 500 毫秒,投影法只要几十毫秒。所以我的策略是:先判断文档类型,规整文档用投影法,复杂文档用连通域法。判断依据是投影曲线的“干净程度”——如果波谷清晰、周期规律,就用投影法;如果曲线毛糙、没有明显周期,就上连通域法。
3.3 文本行切分的边界处理
切分文本行时,最容易出问题的是行边界切得太紧或太松。切太紧,文字上下被削掉,OCR 认不全;切太松,相邻行混在一起,OCR 串行。我的做法是在投影法找到的行边界基础上,上下各留 10% 到 15% 的余量。这个余量按行高算,行高就是相邻两个行边界之间的距离。留余量的目的是给 OCR 引擎一点上下文,它识别时能利用上下文的字形信息,准确率会高一些。
还有一个细节:标点符号和上下标。中文文档里,逗号句号通常贴着前一个字,但引号、括号可能跨行。英文文档里,上标下标会超出正常行高。如果严格按投影切,这些内容容易被切掉。我的处理办法是:切完行之后,检查每行的上下边缘是否有“孤立的小连通域”,如果有,把它归到最近的行里。这个逻辑用连通域分析很容易实现,就是看连通域的中心纵坐标离哪一行最近。
提示:切分后的每一行最好单独保存成小图再送 OCR,而不是整页送。整页送的话,OCR 引擎自己也要做版面分析,多一层不确定性。自己切好送进去,可控性高很多。
3.4 表格与图片区域的特殊处理
表格是文档 OCR 里最麻烦的东西。表格线会被 OCR 当成文字识别成乱码,表格里的文字又需要按单元格组织。我的做法是:先检测表格线,把表格区域抠出来,单独处理。检测表格线用霍夫变换或者形态学操作——用长条形的结构元素做开运算,水平线用 1xN 的核,垂直线用 Nx1 的核,就能把表格线提取出来。
提取到表格线后,用CvInvoke.BitwiseNot把表格线从原图里去掉,剩下的就是单元格里的文字。然后按表格线的位置切分单元格,每个单元格单独送 OCR。这样识别出来的文字天然就是按单元格组织的,后续还原成表格结构很方便。图片区域则直接跳过,或者标记出来让用户知道这里有图,不送 OCR。
这里有个坑:表格线去掉后,文字可能被切碎。因为表格线和文字可能有重叠,去掉线的时候会把文字笔画也去掉一部分。我的解决办法是:去掉表格线之前,先把表格线区域稍微膨胀一下,确保线被完整去掉;去掉之后,再用闭运算把文字笔画连回来。这个顺序不能反,反了就没效果。
4. OCR 识别与结果后处理的实操细节
4.1 识别引擎的调用与参数调优
预处理做完,终于到 OCR 环节了。我用的是常见的开源 OCR 引擎,通过 C# 封装调用。调用本身不复杂,关键是参数怎么调。最重要的两个参数是识别语言和页面分割模式。语言不用多说,中文文档选中文模型,中英混排选中文+英文组合模型。页面分割模式决定了引擎怎么理解输入图像——是当成单行、单字、还是整页。
因为我前面已经自己切好了文本行,所以页面分割模式要选单行模式。这样引擎就不会再自己做版面分析,直接认这一行的字,速度快、准确率高。如果偷懒整页送进去,就要选自动分割模式,但引擎的分割逻辑不一定符合你的预期,尤其是复杂版面。实测下来,自己切行+单行模式的组合,比整页+自动分割的准确率高 10% 到 20%。
还有一个参数是字符白名单。如果文档内容已知(比如只有数字和字母的发票),可以设置白名单限制识别范围,能显著降低误识别。但白名单不能设太死,否则遇到白名单外的字符会直接丢。我的做法是:先不设白名单跑一遍,看看识别结果里有哪些异常字符,再针对性地加白名单。比如发票场景,白名单设成数字、小数点、货币符号、常见单位字就够了。
4.2 识别结果的后处理与纠错
OCR 出来的原始结果不能直接用,必须做后处理。后处理分三层:字符级纠错、词级纠错、行级重组。字符级纠错主要处理形近字混淆,比如“0”和“O”、“1”和“l”、“rn”和“m”。这些混淆在特定上下文里可以自动纠正——如果前后都是数字,那“O”大概率是“0”;如果前后都是字母,那“0”大概率是“O”。
词级纠错要用词典。维护一个常见词的词典,识别结果里的词如果在词典里找不到,就找编辑距离最近的候选词替换。但这一步要谨慎,专有名词、人名、专业术语很容易被“纠错”成常用词,反而错了。我的做法是:只对长度大于 3 的词做纠错,长度小于等于 3 的词不动,因为短词本身歧义就大,纠错风险高。
行级重组是把切分时打散的行重新拼回段落。判断依据是行间距和行首缩进。行间距明显大于平均行距的,认为是段落分隔;行首有明显缩进的,认为是新段落开始。这两个规则结合使用,能还原出大致的段落结构。但不要指望完全还原原始排版,OCR 的后处理能做到“可读”就不错了,“完美还原”是不现实的。
4.3 置信度过滤与人工复核接口
OCR 引擎通常会返回每个识别结果的置信度。置信度低的区域,要么是图像质量差,要么是内容本身模糊。我的做法是:置信度低于阈值的行,标记出来,在结果里用特殊符号包裹,提示用户这里可能需要人工复核。阈值取多少?我试下来 60% 到 70% 之间比较合适。低于 60% 的基本是乱码,高于 70% 的通常可信。但不同引擎的置信度标定不一样,要针对具体引擎调。
标记出来的低置信度区域,最好能提供一个可视化复核界面——左边显示原图对应区域,右边显示识别结果,用户可以直接修改。这个功能对实际使用体验提升很大,因为 OCR 永远不可能 100% 准确,关键是让用户能快速找到并修正错误。如果只是输出一堆文本让用户自己找错,效率太低了。
注意:置信度不是万能的。有些引擎对某些字体天生置信度低,但识别结果其实是对的;有些引擎置信度虚高,结果却是错的。所以置信度只能作为参考,不能作为唯一判断依据。
4.4 批量处理与性能优化
实际使用中,往往不是处理一张图,而是几十上百张。批量处理时,性能瓶颈通常在两个地方:图像预处理和OCR 调用。预处理是 CPU 密集型的,OCR 调用如果是本地引擎也是 CPU 密集型,如果是远程接口则是网络密集型的。我的优化策略是:预处理用多线程并行,OCR 调用用队列串行(因为本地引擎通常不支持并发调用,远程接口则有速率限制)。
EMGU.CV 的图像操作本身是线程安全的,只要每个线程操作自己的图像对象就行。我用Parallel.ForEach把预处理并行化,实测在 8 核机器上能提速 4 到 5 倍。OCR 调用则用一个生产者-消费者队列,预处理完的图放进队列,OCR 线程从队列取图识别。这样预处理和识别能重叠进行,整体吞吐量提升明显。
内存管理也要注意。EMGU.CV 的Mat和Image对象实现了IDisposable,用完必须释放,否则处理大批量图时内存会爆。我习惯用using块包住所有图像对象,确保及时释放。另外,处理大图时可以先降采样到合适的分辨率再处理,OCR 对分辨率的要求其实不高,200 到 300 DPI 就够了,太高的分辨率反而慢且没必要。
5. 常见问题与排查技巧实录
5.1 识别率突然下降的排查思路
识别率突然下降,通常不是 OCR 引擎的问题,而是预处理链路某个环节出了变化。我的排查顺序是:先看输入图像有没有变化(分辨率、色彩模式、压缩质量),再看预处理参数有没有被改动,最后才怀疑 OCR 引擎。实际遇到最多的情况是输入图像的分辨率变了,导致自适应阈值的blockSize不再合适。
排查方法很简单:把预处理每一步的中间结果都保存下来,肉眼检查。灰度图、二值图、校正后的图、切分后的行图,一张张看。哪一步的效果明显不对,问题就在那一步。这个方法虽然笨,但最有效。我习惯在开发阶段把中间结果都输出到临时目录,出问题时直接翻看,比调试代码快得多。
还有一个隐蔽的坑:图像方向。手机拍的文档可能带有 EXIF 方向信息,EMGU.CV 读取时不一定自动应用。如果读进来是旋转 90 度的图,后续所有处理都会错。解决办法是读取时检查 EXIF 方向标签,手动旋转校正。这个坑我踩过好几次,后来养成了读图后先检查方向、再检查尺寸的习惯。
5.2 二值化后文字断裂或粘连的处理
文字断裂和粘连是二值化的两个极端。断裂是阈值太高,笔画细的地方被切断了;粘连是阈值太低,笔画粗的地方和背景糊在一起。判断方法:看二值图里文字的笔画是否连续、是否和相邻文字粘连。断裂的话,降低阈值或者增大自适应阈值的C值;粘连的话,提高阈值或者减小C值。
但有时候调阈值解决不了问题,因为图像本身光照太不均。这时候要回到背景估计那一步,用形态学方法先把光照校正了再二值化。我的经验是:如果一张图调了三次阈值还不行,就别调了,回去做光照校正。光照校正是治本,调阈值是治标。光照校正做完,阈值往往用默认值就能有不错的效果。
还有一个技巧:对二值图做一次小核的闭运算,能连接细小的断裂。但核不能大,3x3 就够了。大核会把相邻文字连在一起,反而制造粘连。闭运算之后如果还有粘连,可以用分水岭算法或者基于距离变换的分割来切开,但这两个方法计算量大,只在必要时用。
5.3 表格识别乱码的解决路径
表格识别乱码的根源是表格线被当成文字。解决路径有三条:一是去掉表格线再识别,二是不去线但告诉 OCR 引擎忽略线,三是把表格区域整体跳过。我推荐第一条,因为可控性最高。去掉表格线的方法前面说过,用长条形结构元素做开运算提取线,再相减。
但去掉线之后,单元格里的文字可能被切碎。这时候要用闭运算把文字连回来。闭运算的核大小要略大于表格线宽度,这样才能把线去掉后留下的缝隙填上。表格线宽度通常 1 到 3 像素,核取 3x3 或 5x5 就够。如果表格线特别粗,核也要相应加大。
还有一种情况是无线表格,就是靠空白分隔单元格的表格。这种表格没有线可去,只能靠连通域分析或者投影法切分单元格。无线表格的切分难度更大,因为空白区域的边界不明确。我的做法是:先做行投影切出行,再对每行做列投影切出单元格。列投影的波谷就是单元格边界。这个方法对规整的无线表格有效,对不规整的就没辙了。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 识别率整体偏低 | 图像质量差或预处理不当 | 检查中间结果图 | 加强去噪和对比度拉伸 |
| 文字断裂 | 二值化阈值过高 | 看二值图笔画连续性 | 降低阈值或增大 C 值 |
| 文字粘连 | 二值化阈值过低 | 看二值图文字间距 | 提高阈值或减小 C 值 |
| 行切分错乱 | 残余倾斜或多栏混排 | 检查投影曲线 | 重新校正或分栏处理 |
| 表格乱码 | 表格线被当文字 | 看识别结果是否含线条字符 | 去表格线后重新识别 |
| 内存暴涨 | 图像对象未释放 | 监控内存占用 | 用 using 块及时释放 |
| 处理速度慢 | 分辨率过高或未并行 | 看单张耗时 | 降采样或并行处理 |
| 方向错误 | EXIF 方向未应用 | 检查图像方向 | 读取时校正方向 |
这张表是我在实际项目中遇到问题后一条条记下来的,基本覆盖了八成以上的常见故障。遇到新问题,先查表,表里没有的再从头排查。排查时记住一个原则:从输入往输出查,不要从输出往输入猜。先确认输入图像没问题,再确认预处理每一步没问题,最后才怀疑 OCR 引擎。
6. 实操心得与后续扩展方向
6.1 参数调优的经验法则
调参这件事,说到底是用数据说话,不要凭感觉。我建了一个小测试集,包含 20 到 30 张不同类型的文档图,每张都人工标注了正确答案。每次改参数,就在测试集上跑一遍,算准确率。准确率涨了就保留,跌了就回退。这样调出来的参数才靠谱,而不是“我觉得这样应该行”。
具体到参数上,我的经验法则是:自适应阈值的 blockSize 取图像宽度的 1/30 左右,C 取 10 左右;形态学核大小取笔画宽度的 1.5 倍;行切分余量取行高的 10% 到 15%。这些值不是绝对的,但作为起点很合适。在这个基础上微调,比从零开始试要快得多。
还有一个原则:参数要能自适应图像尺寸。不要写死像素值,要按图像宽高的比例来算。因为输入图像的分辨率是不确定的,写死的参数换一张图就失效了。我所有的参数都是相对值,按图像尺寸动态计算,这样鲁棒性好很多。
6.2 踩过的坑与避坑建议
第一个坑是过度预处理。一开始我总想把图处理得“完美”,去噪去了好几轮,形态学操作做了一堆,结果文字笔画被磨得又细又断,识别率反而降了。后来明白一个道理:预处理的目标是“够用”,不是“完美”。OCR 引擎对轻微噪点是有容忍度的,过度处理反而破坏文字特征。现在我的预处理链路很克制,能一步解决的不做两步。
第二个坑是忽略图像方向。前面提过,手机拍的图带 EXIF 方向,不校正的话整个流程都错。这个坑让我浪费了大半天时间,后来在读取图像后加了一步方向检查,问题就没了。建议所有做图像处理的朋友,读图后第一件事就是检查方向和尺寸,养成习惯。
第三个坑是OCR 引擎的并发限制。本地引擎通常不支持多线程并发调用,我一开始用多线程同时调,结果引擎内部状态混乱,识别结果错乱。后来改成单线程队列,问题解决。如果确实需要并发,要么用多个引擎实例,要么用支持并发的远程接口。但远程接口有网络延迟和速率限制,要综合考虑。
6.3 后续可以扩展的方向
这个工具目前只做了基础的单页文档 OCR,后续可以扩展的方向不少。第一个方向是版面还原,不仅识别文字,还还原原始版面——标题、正文、表格、图片的位置关系都保留,输出成结构化格式。这个需要更强的版面分析能力,可能要引入深度学习模型。
第二个方向是手写体识别。印刷体 OCR 已经很成熟了,但手写体还是难点。如果文档里有手写批注,目前的引擎识别率很低。可以针对手写体单独训练模型,或者调用专门的手写识别接口。
第三个方向是实时识别。目前是拍完照再处理,如果做成实时预览——摄像头对着文档,屏幕上直接叠加识别结果,体验会好很多。这个对性能要求高,预处理和识别都要在几十毫秒内完成,需要大量优化。
第四个方向是多语言支持。目前主要针对中文和英文,如果要做日文、韩文、阿拉伯文,需要换对应的语言模型,预处理链路也要针对不同文字特点调整。比如阿拉伯文是从右往左写的,行切分和阅读顺序都要反过来。
我个人在实际操作中的体会是,文档 OCR 这个领域,预处理的重要性怎么强调都不过分。同样的 OCR 引擎,预处理做得好坏,识别率能差一倍。而预处理里,几何校正和光照校正是最值钱的两步,这两步做好了,后面的路就顺了。另外,不要追求一步到位,先跑通一个最简版本,再逐步加功能、调参数,这样迭代效率最高。