基于Tesseract与EmguCV(C#)的文本位置提取及标注等需求咨询
嘿,我之前在项目里刚好实现过类似的需求,用EmguCV + Tesseract完全能搞定!下面给你一套具体的分步实现方案,代码都给你写好啦:
1. 先搞定环境依赖
首先得把需要的NuGet包装上:
Emgu.CV(选最新稳定版,比如4.8.x或者5.x都可以)Emgu.CV.runtime.windows(必须装,不然运行会报错)Tesseract(NuGet上的官方包,版本选5.x,和EmguCV兼容就行)
另外还要下载Tesseract的语言训练数据,比如英文的eng.traineddata,放在项目的tessdata文件夹里,记得设置文件属性为“复制到输出目录”。
2. 核心代码实现
2.1 加载图像并初始化Tesseract引擎
先把图像加载进来,转成灰度图(Tesseract对灰度图识别效果更好),然后初始化Tesseract引擎,设置合适的页面分割模式和字符白名单:
using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Tesseract; // 加载目标图像 var image = new Image<Bgr, byte>("your_image_path.jpg"); var grayImage = image.Convert<Gray, byte>(); // 初始化Tesseract引擎 using var engine = new TesseractEngine("./tessdata", "eng", EngineMode.Default); // 可选:只识别字母,过滤其他字符 engine.SetVariable("tessedit_char_whitelist", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); // 自动分割页面,适合大多数通用场景 engine.DefaultPageSegMode = PageSegMode.Auto;
2.2 获取字符和单词的位置并绘制矩形
用ResultIterator遍历每个单词和字符,再调用EmguCV的矩形绘制API,给字母画蓝色框,单词画红色框:
// 把EmguCV图像转成Bitmap给Tesseract处理 using var page = engine.Process(grayImage.ToBitmap()); using var iterator = page.GetIterator(); // 遍历所有单词,绘制红色矩形 iterator.Begin(); do { if (iterator.TryGetBoundingBox(PageIteratorLevel.Word, out var wordRect)) { // 转换Tesseract的Rect格式到EmguCV的Rectangle var emguWordRect = new Rectangle(wordRect.X1, wordRect.Y1, wordRect.Width, wordRect.Height); // 红色框,线宽2 CvInvoke.Rectangle(image, emguWordRect, new Bgr(0, 0, 255).MCvScalar, 2); } // 遍历当前单词内的每个字符,绘制蓝色矩形 if (iterator.TryGetBoundingBox(PageIteratorLevel.Symbol, out _)) { do { if (iterator.TryGetBoundingBox(PageIteratorLevel.Symbol, out var charRect)) { var emguCharRect = new Rectangle(charRect.X1, charRect.Y1, charRect.Width, charRect.Height); // 蓝色框,线宽1 CvInvoke.Rectangle(image, emguCharRect, new Bgr(255, 0, 0).MCvScalar, 1); } } while (iterator.Next(PageIteratorLevel.Symbol)); } } while (iterator.Next(PageIteratorLevel.Word));
2.3 获取单词的行列位置
通过判断单词的Y坐标范围区分行,同一行内按X坐标排序得到列顺序,这样就能给每个单词标记行列号,方便后续添加3D对象:
// 定义辅助类存储单词的完整信息 public class WordInfo { public string Text { get; set; } public Rectangle Rect { get; set; } public int LineNumber { get; set; } public int ColumnNumber { get; set; } } // 收集所有单词的基础信息 var wordInfos = new List<WordInfo>(); iterator.Begin(); int currentLine = 0; int prevY = -1; // 两行之间的Y差阈值,根据图像字体大小调整 int lineThreshold = 10; do { if (iterator.TryGetBoundingBox(PageIteratorLevel.Word, out var wordRect) && iterator.GetText(PageIteratorLevel.Word) is string wordText) { // 判断是否进入新行 if (prevY == -1 || Math.Abs(wordRect.Y1 - prevY) > lineThreshold) { currentLine++; prevY = wordRect.Y1; } wordInfos.Add(new WordInfo { Text = wordText, Rect = new Rectangle(wordRect.X1, wordRect.Y1, wordRect.Width, wordRect.Height), LineNumber = currentLine }); } } while (iterator.Next(PageIteratorLevel.Word)); // 按行分组,每行内按X坐标排序,设置列号 var groupedByLine = wordInfos.GroupBy(w => w.LineNumber).OrderBy(g => g.Key); foreach (var lineGroup in groupedByLine) { var sortedWords = lineGroup.OrderBy(w => w.Rect.X).ToList(); for (int i = 0; i < sortedWords.Count; i++) { sortedWords[i].ColumnNumber = i + 1; // 列号从1开始计数 } } // 现在每个单词都有明确的行列位置了,可用于3D对象定位 foreach (var wordInfo in wordInfos) { Console.WriteLine($"单词: {wordInfo.Text}, 行: {wordInfo.LineNumber}, 列: {wordInfo.ColumnNumber}, 位置: {wordInfo.Rect}"); // 这里可以根据行列号和2D坐标,转换为3D空间坐标来添加3D对象 }
2.4 显示或保存结果图像
最后把处理好的图像展示出来或者保存:
// 显示结果图像 CvInvoke.Imshow("字符&单词框选结果", image); CvInvoke.WaitKey(0); CvInvoke.DestroyAllWindows(); // 保存结果到本地 image.Save("result.jpg");
一些实用提示
- 如果图像有噪声或模糊,建议先做预处理:比如阈值化
CvInvoke.Threshold(grayImage, grayImage, 127, 255, ThresholdType.Binary);,或者膨胀腐蚀操作 - 若图像是单行/单列文本,可以修改
PageSegMode为SingleLine/SingleColumn,提升识别准确率 lineThreshold的数值要根据实际字体大小调整,字体越大阈值需要设得越高
内容的提问来源于stack exchange,提问作者soroush Saidy
相关产品推荐
相关产品推荐

