写这篇东西的起因很简单:最近好几个读者私信问我“Halcon里能不能跑YOLO”、“Halcon自带的目标检测够不够用”、“找圆测量这些活和YOLO到底什么关系”。这些问题背后其实是同一个困惑——Halcon这个老牌机器视觉软件,和当下最火的YOLO目标检测,到底怎么结合才最顺手。
我做视觉项目有些年头了,从早期的纯Halcon脚本到后来在Halcon工程里接深度学习模型,中间踩了不少坑,也整理出一套比较实用的流程。这篇文章就把我在实际项目里用Halcon做目标检测、接YOLO模型的全过程拆开讲清楚,包括方案选型、模型导出、预处理、后处理、训练侧避坑,以及找圆、抓边、字符识别、试卷切割这类具体场景怎么落地。不管你是刚接触Halcon的新手,还是已经在用传统视觉算法、想往深度学习方向过渡的工程师,这篇都能给你一份能直接参考的思路。
1. 先想清楚:Halcon做目标检测,为什么绕不开YOLO
很多从传统视觉转过来的朋友,第一反应是“Halcon自己不是有深度学习模块吗,为什么还要折腾YOLO”。这个想法没错,但实际项目里往往不是二选一,而是各干各的活。
1.1 Halcon自带深度学习到底够不够用
Halcon从17版开始推深度学习的几个预训练模型和推理工具,像是apply_dl_model、read_dl_model这些算子,配合标注工具MVTec Deep Learning Tool,确实能跑分类、检测、分割。对于简单场景——比如固定光照下的零件有无判断、单一类别的位置定位——它够用,而且好处是跟Halcon本身的数据类型无缝衔接,不需要自己写额外代码。
但它的问题也很明显。第一,Halcon官方预训练模型基本以分类和检测为主,模型结构相对固定,自定义能力弱。第二,训练流程封闭,想改网络结构、想换更强的backbone、想用最新的训练技巧,基本无从下手。第三,生态跟不上。你自己在外面用YOLOv5、YOLOv8训好的模型,Halcon原生读不进去,除非转成它能认的格式——这就绕了一大圈。
所以我的观点很明确:Halcon适合做后处理、标定、传统几何测量和现场部署,YOLO这类外部模型负责前端的目标识别与区域定位。两者配合,比强行用Halcon自带DL模块做复杂检测要舒服得多。
1.2 Halcon与YOLO的组合方式怎么选
实际工程里,Halcon和YOLO的组合方式我试过三种,各有适用场景。
第一种是YOLO跑在别处,只把结果传给Halcon。比如YOLO在Python里推理完,把检测框坐标通过文件、Socket或共享内存传给Halcon,Halcon拿到框再做ROI处理和测量。这种方式解耦最彻底,适合检测模型需要频繁更新、Halcon工程相对稳定的场景。
第二种是在Halcon进程内调用ONNX Runtime或OpenCV DNN,加载导出的YOLO模型直接推理。这就省了跨进程通信,延时低,适合现场实时性要求高的设备。Halcon的read_dict、get_dict_tuple这些算子可以解析结果,整条链路都在一个程序里。
第三种是只用Halcon的深度学习推理接口,强行把YOLO权重转换后塞进去。我试过,非常折腾,而且转换工具长期没人维护,YOLOv8之后的版本基本没法转。除非是早期YOLOv3这种结构简单的模型,否则不推荐。
从近两年的项目看,最稳的是第一种和第二种结合:Halcon负责采集图像和几何量测,YOLO负责定位和分类,中间用ONNX Runtime做桥梁。
1.3 适用场景切分
给场景做个简单切分,能帮大家少走弯路。
如果是纯几何检测,比如圆环定位、边缘抓取、曲线长度测量,这类用Halcon传统算子就够了。find_circle、edges_sub_pix、fit_line_contour_xld这些算子成熟稳定,精度高,实时性也好,没必要上YOLO。
如果场景里需要“先识别再测量”,比如先找到产品上的铭牌区域,再测量铭牌上的字符间距;或者先定位到工件,再测量工件的圆孔位置——这时候YOLO负责粗定位,Halcon负责精测量,是黄金搭档。
如果场景本身就是要做目标分类和区域检测,比如检测传送带上的缺陷类别、识别图像里的多类目标——那核心工作量就在YOLO训练侧,Halcon更多是做图像前处理、结果显示和与上位机通信。
2. 核心链路:从YOLO模型到Halcon推理
聊完方案,说点能直接落地的。下面这条链路是我项目里验证过很多次的,从YOLO训练完到Halcon里跑起来,一共四步。
2.1 YOLO模型导出的关键一步:ONNX
在Halcon侧做YOLO推理,最常用的方式是接ONNX Runtime。所以训练完YOLO后,第一件事是把.pt权重导出成.onnx格式。以YOLOv8为例,导出的命令很简单:
yolo export model=yolov8n.pt format=onnx opset=12导出时注意几个参数。opset建议设在12到14之间,ONNX Runtime兼容性最好;simplify建议打开,能去掉一些冗余运算,减小模型体积。导出完成后最好用onnxruntime先验证一遍输出维度,确认输入输出节点名。
很多朋友会在这卡住:Halcon里读ONNX模型读不了。因为Halcon原生不支持ONNX格式,我们需要的是在Halcon代码里调用ONNX Runtime的动态库,而不是让Halcon去“读”ONNX文件。这跟Halcon读自带DL模型完全是两个概念。我常用的做法是Qt或C++写一个推理封装,里面初始化Ort::Session,加载模型文件,然后在Halcon的C++接口里直接调用这个封装。
如果你不想用C++,还有个偏招:用Halcon的execute_operator配合外部程序,或者直接把推理写在Python里,Halcon通过system调用Python脚本。但这种方式在工业现场部署不太靠谱,每次调用都起一个Python进程,速度太慢。
2.2 YOLO输出预处理:一半的坑都在这里
YOLO模型的输入是固定尺寸的RGB图像,归一化到0到1之间。而Halcon读进来的图像通常是单通道灰度图或者三通道的byte类型图像,通道顺序是R/G/B但也可能是B/G/R,尺寸更不可能刚好是模型要求的640×640或者1280×1280。
所以推理之前必须做三件事:通道转换、尺寸resize、归一化。
通道转换用Halcon的trans_to_rgb或者直接compose3把三个通道合成RGB图。注意YOLO训练时用的是OpenCV读图,通道顺序是BGR,如果你把Halcon的RGB图直接塞进模型,检测结果会明显变差。保险的方法是先decompose3拆通道,再按BGR顺序compose3回来。这个细节我吃过亏——第一次接YOLOv5时发现所有类别都测不准,排查半天就是通道顺序反了。
尺寸resize不是简单拉伸。YOLO原版训练用了letterbox,即在保持宽高比的前提下,把长边缩放,短边填充灰边。如果推理时直接用zoom_image_size拉伸到640×640,目标会被拉变形,检测精度下降。Halcon里没有现成的letterbox算子,需要自己写:
* 假设输入图像为Image,目标尺寸640x640 get_image_size(Image, Width, Height) Scale := 640.0 / max(Width, Height) NewWidth := round(Width * Scale) NewHeight := round(Height * Scale) zoom_image_size(Image, ImageZoomed, NewWidth, NewHeight, 'constant') * 再创建一个640x640的画布,把缩放图贴到中间 gen_image_const(Canvas, 'byte', 640, 640) * 计算粘贴偏移 OffsetX := (640 - NewWidth) / 2 OffsetY := (640 - NewHeight) / 2 * 用reduce_domain和compose相关操作完成复制归一化比较简单,convert_image_type(ImageZoomed, 'real')之后乘上1.0 / 255.0就行。有些同学会把归一化和通道顺序搞混,记住一句话:归一化是数值缩放,不影响通道顺序;通道顺序错了,模型看到的颜色就是错的。
2.3 后处理:从输出张量到检测框
ONNX导出的YOLO模型,输出是一个三维张量,形状大概是[1, 84, 8400]。其中84表示4个坐标信息加80个类别概率,8400是不同尺度下anchor的数量。要从这堆数字里解析出检测框,需要做置信度过滤和NMS。
这一步我建议用C++写,不要用Halcon脚本硬算。Halcon做矩阵运算和循环效率不行,8400个候选框在脚本层过滤一遍,耗时可能翻好几倍。正确做法是:ONNX Runtime在C++层完成推理,拿到输出后直接处理。
NMS方面有现成的cv::dnn::NMSBoxes可用,C++里的OpenCV会自带DNN模块,很方便。如果没有OpenCV,手写一个最简单的NMS也就几十行代码,按置信度从高到低排序,然后逐个比较IoU,大于阈值的框直接丢弃。
后处理完拿到的是归一化坐标,需要在Halcon里还原成像素坐标。注意前面letterbox填充的边距要在这里减掉。具体做法是:先根据原图宽高和缩放比例反算,再把画布偏移减掉。我见过有人忘了减偏移,所有检测框整体往右下角偏了一大截,找了好久才发现是letterbox的边距没处理。
得到检测框后,可以用Halcon的gen_rectangle1生成矩形Region,再做后续的reduce_domain、裁剪、OCR或者测量。这个流程跑通之后,YOLO在Halcon里就算真正落地了。
3. 几个能直接参考的落地场景
链路通了,得看具体怎么用。下面挑几个高频场景聊,都是我在实际项目里做过的。
3.1 经典几何检测场景:Halcon找圆和抓边
靠近Halcon的老本行,那就离不开找圆和抓边。这些操作和YOLO怎么配合呢?举个例子:检测一个圆形工件的圆心坐标和半径,但工件的位置是随机摆放的,图像里还有多个干扰圆。
传统做法是全局find_circle或者create_shape_model做模板匹配,一旦背景复杂,误检率就上去了。换成“YOLO定位+Halcon测量”之后,YOLO先输出圆的大致Region,Halcon再在Region内部做亚像素找圆:
* 假设YOLO已经返回了一个检测框区域DetectionRegion reduce_domain(Image, DetectionRegion, ImageReduced) threshold(ImageReduced, Region, 0, 128) connection(Region, ConnectedRegions) select_shape_max(ConnectedRegions, SelectedRegions) * 生成亚像素轮廓 gen_contour_region_xld(SelectedRegions, Contours, 'border') fit_circle_contour_xld(Contours, 'algebraic', -1, 0, 0, 3, 2, Row, Column, Radius, StartPhi, EndPhi, PointOrder)这样得到圆心和半径都是在局部坐标系里做的,精度比全局找圆稳定得多。而且测量范围被限制在YOLO给出的框里,那些干扰圆自然就被排除了。
抓边拟合直线也是同一个思路。传统的edges_sub_pix是全图提取边缘,很容易把背景纹理当成目标边缘。先让YOLO框出目标区域,再reduce_domain到区域内提取边缘,最后fit_line_contour_xld拟合直线,整个过程的鲁棒性会提升一大截。
曲线长度测量也类似。YOLO定位到目标轮廓的大致范围后,Halcon在局部提取轮廓,然后用get_contour_xld或者length_xld计算长度。这套组合拳我用了很多年,基本能应付大多数“先定位后测量”的场景。
3.2 YOLO字符识别与试卷题目自动切割
热词里有人提到了“基于YOLO的试卷题目自动切割”和“YOLO字符识别”,这两个其实是同一类问题——检测文本区域,再做后续识别或切分。
我做过一个试卷扫描整理的活儿,流程是:先用YOLO检测每道题目的题号区域,输出每个题号的检测框,Halcon根据检测框对整张试卷图做切割,把每道题保存成独立的图片。这比传统投影法切割靠谱得多,因为手写试卷的排版不规整,投影法经常把两题切开或者并到一起。
字符识别方面,纯Halcon自带OCR对印刷体还行,但遇到手写体识别率就很惨。我当时是YOLO把每个字符或词条的位置框出来,然后裁剪成单字符图片,送进训练好的识别模型。这个思路本质上是“检测+识别”两步走,YOLO负责检测,识别环节可以用Halcon的OCR,也可以用外部的分类模型。
3.3 图像写入文字与测量标注的小技巧
很多现场项目需要在检测结果图上写字,或者在Halcon里把测量结果显示出来。这个用Halcon的set_tposition和write_string就能完成:
set_tposition(WindowHandle, 50, 50) write_string(WindowHandle, 'Detected: ' + ClassName)有一点值得提:如果是在保存的图片上写文字而不是窗口上显示,需要把窗口句柄换成gen_image_to_window相关流程,或者直接把文字做成Region再叠加到图上。我见过不少同事一直在窗口上写,结果保存图片时文字不见了,就是因为没搞清楚write_string是写进窗口Buffer的。
4. YOLO训练侧绕不开的坑与经验
落地时很多问题其实出在训练环节,而不是Halcon调用环节。不少同学拿着别人的模型直接接进Halcon,效果不好就以为是集成出了问题,其实模型本身就没训好。
4.1 数据集准备与标注
数据集的多样性比数量更重要。比如做鸟类目标检测,光下载一个鸟类数据集是不够的,现场背景、遮挡程度、光照条件都不同。我的建议是至少一半数据来自现场实拍,而不是只用公开数据集。标框时要统一标准——是靠紧目标还是留边距,遮住的算不算,这些细节都要写进标注规范。
热词里还有“中餐数据集”这种偏门的,说明大家确实在尝试各种垂直场景。垂直场景数据集少不要紧,可以用公开模型做预训练,再拿少量实际数据微调。比如用一个通用的检测模型作为起点,在几百张中餐图片上微调,效果往往比从零训练好得多。
4.2 损失函数、预训练模型与训练稳定性
YOLO的损失函数在v5之后变成了组合Loss,包含分类损失、置信度损失和边框损失,边框部分用CIoU或者SIoU。很多人问“损失函数怎么调”,我的经验是优先调置信度损失的权重,因为它直接影响误检率。如果预测框很准但多了很多误检,那置信度损失的权重可以适当加大。
预训练模型下载也是个高频问题。YOLOv8官方仓库会发布在不同数据集上预训练的权重,比如基于ImageNet预训练的backbone;如果是从头训练,backbone加载预训练权重能显著加速收敛。我的习惯是优先用官方发布的COCO预训练权重来做迁移学习,而不是自己从头跑。
训练过程中常见的一个异常是“BN崩溃”,表现为训练loss突然暴涨或者变成NaN。这主要是因为batch size太小,或者学习率初始值太大。特别是用预训练模型微调时,如果整个backbone一起训,学习率设在0.01以上风险很高。我一般微调时学习率从0.0001开始,并且固定backbone前几层的参数,等loss稳定后再逐步解冻。
混淆矩阵方面,YOLO训练完都会输出,但很多人不太看。热词里提到“yolo混淆矩阵总合不唯一”,那是因为混淆矩阵是按类别独立计算IoU匹配的,有的目标同时匹配多个类别,再加上背景类别,各列之和当然不等于总数。不用太纠结这个,重点看每类的Recall和Precision就够了。
4.3 小目标检测与移动目标检测
小目标检测是另一个持久战。热词里的“移动小目标检测”和“三维目标检测”都跟这个有关。做过小目标检测的朋友都懂,YOLO在小目标上表现一直不如大中目标。我的经验是:不要一味加大输入分辨率,而是改造anchor或者用多尺度特征融合。Halcon这边配合的话,可以把原图先切块再做检测,每一块单独推理,最后合并结果。虽然速度慢一点,但小目标的召回率能明显提升。
移动目标检测要关注推理速度和帧间一致性,单帧检测是不够的。我在工程上用YOLO做移动目标定位时,会加上简单的追踪逻辑——上一帧的检测框与当前帧做IoU匹配,匹配上的框继承ID,没匹配上的作为新目标。Halcon虽然没有内置追踪器,但基于Region的匹配操作就能实现一个简单版本。
4.4 YOLO实例分割与Transformer变体
热词里提到“yolo实例分割”和“yolo和transformer结合”,这些都是YOLO家族的分支。YOLOv8-seg在检测的基础上多了一个mask分支,可以直接输出目标轮廓。如果项目里需要的不只是框而是轮廓区域,用实例分割模型在Halcon里拿轮廓做测量会比“检测框+图像分割”更准确。
Transformer结合YOLO的思路目前更多应用在backbone替换上,比如用Swin Transformer替换CSPDarknet。效果好但在工业相机上额外消耗大,我一般只在GPU设备上启用,普通的CPU现场还是老老实实用经典YOLO。
多模态目标检测和开放词汇目标检测这些新方向,暂时还进不了工业现场:一个是推理速度不够,一个是依赖外部大模型的embedding。如果只是做实验可以参考,但落地上建议保持观望。
5. Halcon+YOLO落地时的高频问题与排查记录
这节整理一下我实际遇到比较多的问题,也算是个速查表。
5.1 环境、授权与版本兼容问题
第一个绕不开的是Halcon授权。Halcon商用需要license,很多公司用试用版做开发,但试用的输出会有水印或者限制分辨率。如果项目要落地,license该买还得买。另一个是版本问题,不同版本的Halcon对深度学习的支持差别很大,18版之后相关算子才比较齐全,23版对ONNX Runtime集成更友好。我建议新项目直接上Halcon 23及以上,旧项目做深度学习相关功能也尽快升级。
“qt怎么调用halcon”也经常被问到。Halcon官方提供了HalconCpp库,在Qt项目里只要在.pro文件里加上INCLUDEPATH和LIB路径,然后#include "halconcpp/HalconCpp.h"就可以用了。注意在Release和Debug模式下要分别链接对应版本的库,否则一堆LNK2019错误等着你。
5.2 检测速度和精度失衡
现场最常见的反馈是“YOLO检测太慢”或者“一卡一卡的”。先梳理一下瓶颈在哪里:是推理本身慢,还是图像采集慢,还是Halcon后处理慢?如果推理慢,优先尝试更小的模型,比如从yolov8m换到yolov8n;如果图像采集慢,看看相机是触发模式还是连续模式;如果后处理慢,把后处理从Halcon脚本搬到C++层。我见过最夸张的一次,客户说“检测很慢”,结果发现是Halcon里的dev_display不停刷新窗口导致的显示开销,去掉显示后速度直接翻倍。
精度方面,如果检测框明显偏移,优先检查预处理。通道顺序搞反、letterbox偏移没还原、归一化忘记做,三个里至少占一个。如果检测不稳定,比如同一张图两次检测结果不一样,检查输入图像是否真的相同——有些相机硬件增益自动变化,看起来一样的图,实际像素值差了几个灰度级。
5.3 如何判断一个物体是否完整
“Halcon怎么检测一个物体是否完整”这个搜索词很有代表性。我的做法是:让YOLO先检测出物体的整体区域和每个子部件的区域,然后在Halcon里计算子部件区域与整体区域的包含关系。比如检测手机是否缺少摄像头,YOLO输出手机框和摄像头框,Halcon判断摄像头框是否落在手机框内,并且面积占比是否在合理区间。
这个方法的关键在于子部件的面积阈值。面积太小,一个噪点也会误判;面积太大,真正的缺失检测不出来。我一般选目标框面积的10%到15%作为下限,具体值要对着现场数据调。
汉化算子手册的问题也提一下:Halcon官方只有英文手册,中文社区有翻译版本,但更新速度跟不上新版本。我的建议是直接看英文原版,遇到不认识的算子,用系统自带的help窗口查一下,比依赖翻译版靠谱得多。
个人体会
折腾这么多年Halcon和YOLO,最大的体会是:不要把Halcon当深度学习框架用,也不要把YOLO当万能测量工具。Halcon最擅长的是在确定“看哪里”之后做精确的几何分析,YOLO最擅长的是在不确定“看哪里”之前先框出候选区域。两者天然互补,硬要合二为一反而两边都不讨好。
如果你正在做类似的项目,我的建议很直接:先花两天时间把本节2.2里的预处理流程在Halcon里跑一遍,确认输出的检测框和坐标还原都没有问题,再去动训练和调优。因为所有后续工作都建立在“模型推理结果能正确传回Halcon”这条链路上,这条链路不通,模型训得再好也白搭。
最后再分享一个细节:写Halcon调用外部模型时,记得把图像转换和结果解析封装成独立的函数,不要散落在主程序里。因为现场调试时你一定会反复改预处理参数和后处理逻辑,封装好能让自己少掉一半头发。