Halcon字符分割失败?用find_text一键搞定
在视觉项目中,字符分割往往是 OCR 流程里最脆弱的环节。最近我就遇到了一个典型案例:Halcon 原脚本的分割功能能正常工作,移植到 .NET 程序后却找不到有效区域,导致后续识别完全无法进行。最终通过逐层定位问题、对比 Halcon 脚本、并引入更稳健的 find_text 算子,彻底解决了困扰。本文把整个过程和思考记录下来,希望能帮助遇到类似问题的开发者。
问题初现:筛选后的矩形区域数量为0
程序的原始逻辑是这样的:
- 用户手动绘制一个矩形 ROI 框住目标文本。
- 对图像进行反转、裁取 ROI,然后 Otsu 二值化。
- 连通域分割 → 矩形结构开运算 → 二次连通域 → 形状转换(最小外接矩形)。
- 用
SelectShape按宽度 [35,60]、高度 [60,100] 筛选字符区域。
就是最后这一步,每次执行后得到的 selectedRegions 对象数量都是 0。
逐层排查:哪一步开始丢失了区域?
为了定位,在每一步处理后都加入了 CountObj 并弹窗显示区域数量。结果显示:
- 二值化后:存在大量区域(数量 > 0)
- 连通域后:区域数量正常
- 开运算后:区域数量略微减少,但仍有多个
- 形状转换矩形后:区域数量依然 > 0
- 筛选后:突然变成 0
问题锁定在 SelectShape 这里。显然,没有任何一个矩形的尺寸落入了设定的宽高范围。于是我在 SelectShape 前打印了所有矩形的实际尺寸,得到的数据让人大跌眼镜:
区域0: 宽=141.0, 高=8.0
区域1: 宽=126.0, 高=16.0
区域2: 宽=152.0, 高=9.0
区域3: 宽=126.0, 高=25.0
区域4: 宽=8.0, 高=0.0
区域5: 宽=43.0, 高=0.0
高度普遍极低(甚至为零),宽度却异常的大(120~150像素)。这些哪里是字符,分明是一条条细长的噪声或背景粘连。说明前期的二值化+开运算并没有把真正的字符分离出来,而是生成了一堆横向的碎片。
对比 Halcon 原始脚本
同样的参数,在 Halcon 中运行却是成功的。
read_image (Image, 'C:/Users/chao.tang/Desktop/OCR/1.bmp')
* rgb1_to_gray(Image, Image)
* 旋转图像
rotate_image(Image, Image, 180, 'constant')
* 1. 仿射变换
get_image_size(Image,Width, Height)
gen_rectangle1 (ROI_0, 724.733, 1143.99, 850.022, 1515.02)
text_line_orientation(ROI_0, Image, 75, -0.4, 0.4, OrientationAngle)
vector_angle_to_rigid(Width/2, Height/2, OrientationAngle, Width/2, Height/2, 0, HomMat2D)
affine_trans_image(Image, ImageAffineTrans, HomMat2D, 'bilinear', 'false')
* 2. 分割区域
* 图像反转
invert_image(ImageAffineTrans,ImageAffineTrans)
reduce_domain(ImageAffineTrans,ROI_0,ImageReduced)
binary_threshold(ImageReduced, Region, 'max_separability', 'dark', UsedThreshold)
connection(Region, ConnectedRegions)
opening_rectangle1(ConnectedRegions, RegionOpening, 4, 4)
connection(ConnectedRegions,ConnectedRegions)
* 将区域转成矩形
shape_trans(ConnectedRegions, RegionTrans, 'rectangle1')
count_obj(RegionTrans, Number)
select_shape (RegionTrans, SelectedRegions, ['width','height'], 'and', [35,60], [60,100])
count_obj(SelectedRegions, Number)
*分割区域
* partition_rectangle(SelectedRegions, Partitioned, 70, 90)
* 3. 读取omc文件并应用
read_ocr_class_mlp('Industrial_0-9A-Z_NoRej.omc', OCRHandle)
* 字符排序
sort_region(SelectedRegions, SortedRegions, 'character', 'true', 'row')
do_ocr_multi_class_mlp(SortedRegions, ImageAffineTrans, OCRHandle, Class, Confidence)
换个思路:用文本模型取代手动分割
手动分割虽然灵活,但参数(阈值、结构元尺寸、宽高范围)对环境极度敏感。一旦图像稍有变化,就得重新调整。既然我们已经知道字符的外观特征(宽约51,高约98,笔画宽约9.7像素),为什么不直接用 Halcon 自带的 find_text 算子来完成定位?它内部已经集成了可靠的文本分割算法,只需设定参数就能自动找出字符区域,省去繁琐的手动流程。
于是我顺着这条思路重构了分割代码,核心步骤如下:
- 创建文本模型:使用
create_text_model_reader并传入字符形态参数(极性、宽高、笔画宽度等)。 - 图像预处理:仅保留 180° 旋转(如有必要,也可是恒等变换裁剪)。
- 使用
find_text定位字符:直接返回所有字符连通域,无需二值化、开运算、形状转换。 - 反转图像用于 OCR:因为分类器是在暗背景亮字符上训练的,而原图是亮背景暗字符。
- 排序并识别:与原来一致。
C# 代码实现
下面是整合后的 btnOCR_Click 核心逻辑(文本模型全局初始化一次即可):
// 初始化文本模型(在窗体加载或单独按钮中调用一次)
private void InitializeTextModel()
{
HOperatorSet.CreateTextModelReader("manual", new HTuple(), out textModelHandle);
HOperatorSet.SetTextModelParam(textModelHandle, "manual_polarity", "light_on_dark"); // 暗底亮字
HOperatorSet.SetTextModelParam(textModelHandle, "manual_char_width", 51); // 字符宽度
HOperatorSet.SetTextModelParam(textModelHandle, "manual_char_height", 98); // 字符高度
HOperatorSet.SetTextModelParam(textModelHandle, "manual_stroke_width", 9.7); // 字符笔画宽度
HOperatorSet.SetTextModelParam(textModelHandle, "manual_return_punctuation", "false"); // 不返回标点符号
HOperatorSet.SetTextModelParam(textModelHandle, "manual_return_separators", "false"); // 不返回分隔符
HOperatorSet.SetTextModelParam(textModelHandle, "manual_uppercase_only", "true"); // 仅识别大写字母
HOperatorSet.SetTextModelParam(textModelHandle, "manual_fragment_size_min", 47); // 最小字符碎片尺寸
HOperatorSet.SetTextModelParam(textModelHandle, "manual_eliminate_border_blobs", "true"); // 消除边界上的小块
HOperatorSet.SetTextModelParam(textModelHandle, "manual_base_line_tolerance", 0.2); // 基线容差,剔除错位字符
HOperatorSet.SetTextModelParam(textModelHandle, "manual_max_line_num", 1); // 识别行数限制为1行
}
OCR识别
if (ho_Image == null || ocrHandle == null || ocrHandle.Length == 0 || drawing_objects.Count == 0)
{
MessageBox.Show("请先加载图像、OCR模型并绘制ROI!");
return;
}
// 1. 旋转 180° 在外面先旋转,再进行OCR识别
//HOperatorSet.RotateImage(ho_Image, out ho_Image, 180, "constant");
// 2. 获取用户绘制的 ROI
HObject roi = GetDrawingObjectRegion(drawing_objects.Last());
if (roi == null) return;
//HOperatorSet.GetImageSize(ho_Image, out HTuple width, out HTuple height);
//HOperatorSet.TransposeRegion(roi, out roi,
// width / 2, height / 2);
//_mainWindow.ClearWindow();
//_mainWindow.DispObj(ho_Image);
//_mainWindow.DispObj(roi);
// 3. 提取单通道(若为彩色图像)
HOperatorSet.CountChannels(ho_Image, out HTuple channels);
HObject imageMono;
if (channels.I == 3)
HOperatorSet.AccessChannel(ho_Image, out imageMono, 1);
else
imageMono = ho_Image.CopyObj(1, -1);
// 4. 缩小定义域到 ROI
HOperatorSet.ReduceDomain(imageMono, roi, out HObject imageReduced);
// 5. 边界扩展(模拟 Halcon 的恒等变换裁剪步骤)
// 膨胀 ROI 区域 48 像素,得到矩形边界,然后 crop_part
HOperatorSet.GetDomain(imageReduced, out HObject domain);
HOperatorSet.DilationCircle(domain, out HObject domainExpanded, 48);
HOperatorSet.SmallestRectangle1(domainExpanded, out HTuple r1, out HTuple c1, out HTuple r2, out HTuple c2);
HOperatorSet.CropPart(imageReduced, out HObject imageCropped, r1, c1, c2 - c1 + 1, r2 - r1 + 1);
// 6. find_text 分割
HOperatorSet.FindText(imageCropped, textModelHandle, out HTuple resultHandle);
HOperatorSet.GetTextObject(out HObject symbols, resultHandle, "manual_all_lines");
// 7. 反转图像(暗底亮字,用于 OCR)
HOperatorSet.InvertImage(imageCropped, out HObject imageInverted);
// 8. 字符排序
HOperatorSet.SortRegion(symbols, out HObject sortedRegions, "character", "true", "row");
// 9. OCR 识别
HOperatorSet.DoOcrMultiClassMlp(sortedRegions, imageInverted, ocrHandle,
out HTuple classResult, out HTuple confidence);
// 11. 拼接字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < classResult.Length; i++)
sb.Append(classResult[i].S);
string text = sb.ToString();
if (confidence.Length > 0)
{
double avgConf = confidence.TupleMean().D;
lblResult.Text = $"结果: {text}\n平均置信度: {avgConf:F4}";
MessageBox.Show($"识别结果: {text}\n平均置信度: {avgConf:F4}");
}
else
{
lblResult.Text = $"结果: ";
MessageBox.Show($"识别识失败!");
}
效果与总结
新方案运行后,无需繁琐的尺寸筛选,find_text 准确返回了所有字符区域,OCR 识别率完全达到 Halcon 脚本的水平。此次调坑有几点经验值得记下:
- “0区域”的根源往往是预处理不到位。
- 逐层输出中间结果是最快的定位手段。如果一开始就在二值化后查看区域形状,可能更早发现问题。
- Halcon 的
find_text是处理规律文本的利器。当字符形态规律、符合预设模型时,用它替代手动分割可以大幅提升稳定性,减少调参时间。 - C# 移植 Halcon 脚本时,务必逐行核对预处理步骤,尤其是图像变换顺序、ROI 坐标是否随图像变换而同步更新。
希望这次记录能为同样在 Halcon 二次开发中摸爬滚打的伙伴提供一点参考。调试视觉算法,耐心和对比验证永远是最可靠的方法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)