在工业视觉圈子里,Halcon 和 YOLO 的关系一直有点微妙。前者是商业机器视觉的老牌劲旅,算子稳定、标定精准、亚像素测量是看家本领;后者是深度学习目标检测的当红方案,迭代快、生态活跃、社区资源丰富。很多做产线检测的朋友都遇到过同一个问题:Halcon 自带的深度学习模块能跑目标检测,但训练效率和数据标注体验跟 YOLO 生态比差了一截;而 YOLO 推理快、精度高,可一旦要跟工业相机、PLC、运动控制卡对接,又得自己写一堆胶水代码。于是"Halcon 里怎么用 YOLO 做目标检测"就成了一个高频问题。这篇内容就把我实际项目里跑通的几条路线拆开讲清楚,包括数据怎么准备、模型怎么选、Halcon 侧怎么调用、精度怎么对齐,以及那些文档里不会写的坑。
1. 先搞清楚 Halcon 和 YOLO 各自在目标检测里扮演什么角色
1.1 Halcon 的深度学习目标检测能力边界
Halcon 从 18.11 版本开始正式引入深度学习模块,到 20.11 之后目标检测相关的算子已经比较完整。它的核心思路是基于预训练的 backbone 做迁移学习,支持分类、目标检测、语义分割、异常检测几大类任务。目标检测这块,Halcon 提供的是类似 SSD 和 YOLO 风格的 anchor-based 检测头,训练和推理都在 Halcon 自己的运行时里完成。
它的优势很明显:跟 Halcon 的标定、测量、形态学算子无缝衔接,检测框出来之后可以直接接measure_pos、fit_circle这类算子做尺寸测量,整条链路不需要跨语言。但短板也突出——标注工具只能用 Halcon 自带的deep_learning_tool或者 MVTec 的标注软件,数据增强策略相对固定,训练超参可调空间小,遇到复杂场景(比如密集小目标、严重遮挡)时调优手段有限。
1.2 YOLO 系列在工业场景的真实表现
YOLO 从 v5 开始进入工程化爆发期,到 v8、v11 以及现在社区讨论的 v26 方向,核心演进逻辑一直是"精度和速度的帕累托前沿"。工业场景里用得最多的是 YOLOv5、YOLOv8 和 YOLOv11,原因不是它们最新,而是生态最成熟:标注工具(LabelImg、Labelme、X-AnyLabeling)、数据增强(Albumentations)、训练框架(Ultralytics)、部署工具链(ONNX、TensorRT、OpenVINO)全都齐备。
在产线检测里,YOLO 的典型优势是:小目标检测经过多尺度特征融合后召回率明显高于传统方法;训练时可以通过 mosaic、mixup 等增强策略显著提升泛化;推理侧 INT8 量化后单帧能压到几毫秒。但它的坐标输出是像素级的,要做亚像素测量还得回到 Halcon 或者 OpenCV 做二次处理。
1.3 两者结合的三条现实路线
把 Halcon 和 YOLO 捏到一起,实际项目里我走过三条路,各有适用场景:
| 路线 | 核心思路 | 适用场景 | 维护成本 |
|---|---|---|---|
| 路线A:YOLO训练 + Halcon推理 | 用 YOLO 生态训练,导出 ONNX,Halcon 用read_dl_model加载 | 已有 Halcon 授权,不想引入 Python 运行时 | 中 |
| 路线B:YOLO训练 + YOLO推理 + Halcon后处理 | YOLO 独立部署,检测结果通过文件/网络传给 Halcon | 产线已有 YOLO 服务,Halcon 只做测量 | 低 |
| 路线C:Halcon训练 + Halcon推理 | 全程用 Halcon 深度学习模块 | 数据敏感、不允许外部框架、样本量小 | 低但精度受限 |
路线 A 是问得最多的,也是坑最集中的。下面重点拆这条。
2. 用 YOLO 训练出能喂给 Halcon 的模型
2.1 数据集准备:从标注到格式转换
YOLO 训练需要的数据格式是每张图对应一个 txt,每行class_id x_center y_center width height,全部归一化到 0-1。工业场景里标注工具我推荐 X-AnyLabeling,它支持 SAM 辅助标注,对缺陷、零件这类边界模糊的目标效率比纯手工高很多。
标注完导出 YOLO 格式后,目录结构应该是这样:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml内容:
path: ./dataset train: images/train val: images/val nc: 3 names: ['scratch', 'dent', 'stain']这里有个容易忽略的点:工业图像的类别不平衡往往很严重。比如划痕样本可能只有几十张,而正常样本几千张。直接训练会导致模型对少数类召回极低。我的做法是在data.yaml里不处理,而是在训练时用copy_paste增强配合类别权重,或者干脆对少数类做离线过采样。
2.2 模型选型:v5、v8 还是更新的版本
工业项目里我不建议盲目追新。YOLOv5 的优点是代码结构清晰、ONNX 导出稳定、社区 issue 多;YOLOv8 的优点是 anchor-free 头、训练更稳、Ultralytics 维护活跃;YOLOv11 在精度上又有提升,但导出到 ONNX 后某些算子对 Halcon 的兼容性需要验证。
我的经验是:如果最终要在 Halcon 里推理,优先选 YOLOv5 或 YOLOv8,因为它们的输出层结构简单,Halcon 的read_dl_model对这类标准卷积+检测头的支持最成熟。YOLOv11 及更新版本如果用了动态头或者特殊激活函数,Halcon 加载时可能报算子不支持。
模型尺寸选择上,工业场景通常s或m就够了。n虽然快,但小目标召回经常不够;l和x在产线节拍要求下往往推理时间超标。我一般先用yolov8s跑 baseline,看 mAP 和单帧耗时,再决定要不要升到m。
2.3 训练参数里最影响 Halcon 侧效果的几个
训练时大部分参数跟常规 YOLO 项目一样,但有几个直接影响后续 Halcon 推理效果:
- 输入尺寸:YOLO 默认 640,但 Halcon 加载模型时会按模型元数据里的输入尺寸走。如果产线图像分辨率很高(比如 5000x4000),直接 resize 到 640 会丢小目标。我的做法是训练时用
imgsz=1024或1280,导出 ONNX 时保持这个尺寸,Halcon 侧也按这个尺寸预处理。 - NMS 参数:YOLO 训练时的 NMS 是内置的,但导出 ONNX 时可以选择是否包含 NMS。如果 Halcon 侧要做自己的 NMS,导出时就去掉;如果想让 Halcon 直接用,就保留。我倾向于去掉,因为 Halcon 的
nms相关算子可以更灵活地调 IoU 阈值。 - 置信度阈值:训练时不用设,但导出后要在 Halcon 侧设。这个值直接决定漏检和误检的平衡,后面会细说。
训练命令示例:
yolo detect train model=yolov8s.pt data=data.yaml epochs=200 imgsz=1024 batch=8 device=0训练完成后,验证集上的 mAP@0.5 至少要到 0.85 以上再考虑部署,工业场景对漏检容忍度极低。
2.4 导出 ONNX 时的关键设置
导出这一步是路线 A 成败的关键。Ultralytics 的导出命令:
yolo export model=best.pt format=onnx imgsz=1024 opset=12 simplify=True几个要点:
opset建议 11 或 12,太高了 Halcon 可能不支持,太低了某些算子表达不了。simplify=True会做图优化,去掉冗余节点,对 Halcon 加载友好。- 如果模型里有
SiLU激活,Halcon 较新版本支持,老版本可能报错,需要替换成ReLU重新训练或导出。 - 导出后一定要用
onnxruntime跑一遍,确认输出 shape 和数值正常,再拿去 Halcon 加载。
3. Halcon 侧加载 YOLO 模型并做推理
3.1 Halcon 深度学习模型的加载流程
Halcon 加载 ONNX 模型用read_dl_model,但并不是所有 ONNX 都能直接读。Halcon 对 ONNX 的支持有自己的算子白名单,遇到不支持的层会直接报错。加载流程大致是:
read_dl_model ('best.onnx', DLModelHandle) get_dl_model_param (DLModelHandle, 'input_dimensions', InputDimensions)如果报错,先用get_dl_model_param查支持的参数列表,或者用 Halcon 自带的dl_model_inference示例验证。常见的不支持算子包括自定义的Focus层、某些版本的Concat轴设置、动态 reshape 等。
一个实用的排查方法:用 Netron 打开 ONNX,看最后几层。如果输出是[1, 25200, 85]这种 YOLOv5 风格的,Halcon 处理起来比较直接;如果是[1, 84, 8400]这种 YOLOv8 风格的,需要做转置和解析。
3.2 预处理:让 Halcon 的输入和 YOLO 训练时一致
YOLO 训练时的预处理是:letterbox resize(保持长宽比,灰边填充)、归一化到 0-1、RGB 通道顺序。Halcon 侧必须完全复现这套流程,否则精度会掉。
Halcon 里做 letterbox 的步骤:
* 读取图像 read_image (Image, 'test.png') get_image_size (Image, Width, Height) * 计算缩放比例 Scale := min(1024.0 / Width, 1024.0 / Height) NewWidth := round(Width * Scale) NewHeight := round(Height * Scale) * 缩放 zoom_image_size (Image, ImageZoomed, NewWidth, NewHeight, 'bilinear') * 创建灰边画布并粘贴 gen_image_const (ImageCanvas, 'byte', 1024, 1024) paint_region (ImageCanvas, ImageCanvas, ImageCanvas, 128, 'fill') * 这里需要计算粘贴位置,略去具体坐标计算归一化和通道转换用convert_image_type和trans_from_rgb配合。注意 Halcon 默认图像是 BGR 还是 RGB 取决于相机,YOLO 训练用的是 RGB,所以如果相机出的是 BGR,必须转。
3.3 推理与输出解析
Halcon 推理用apply_dl_model:
apply_dl_model (DLModelHandle, ImagePreprocessed, [], DLResult)输出是一个 tuple,具体结构取决于模型。对于 YOLOv8 导出的 ONNX,输出通常是[1, 84, 8400],84 = 4 个框坐标 + 80 个类别分数(COCO)或者 4 + nc(自定义)。Halcon 拿到的结果需要自己解析:
- 前 4 行是
cx, cy, w, h,需要转换成row1, col1, row2, col2。 - 后面是类别分数,取最大值对应的类别。
- 然后做置信度过滤和 NMS。
Halcon 有nms相关算子,但更灵活的做法是自己写循环做 IoU 计算,因为工业场景经常需要按类别设不同阈值。
3.4 后处理:从检测框到测量结果
检测框出来之后,才是 Halcon 真正发挥价值的地方。比如检测到一个零件,接下来要做圆度测量:
* 假设检测框是 Row1, Col1, Row2, Col2 gen_rectangle1 (ROI, Row1, Col1, Row2, Col2) reduce_domain (Image, ROI, ImageROI) * 边缘提取 edges_sub_pix (ImageROI, Edges, 'canny', 1, 20, 40) * 圆拟合 fit_circle_contour_xld (Edges, 'algebraic', -1, 0, 0, 3, 2, Row, Col, Radius, ...)这条链路是 Halcon 的强项,也是为什么很多项目宁愿绕一圈也要把 YOLO 塞进 Halcon 的原因。
4. 精度对齐与性能调优的实战细节
4.1 为什么 Halcon 推理结果和 YOLO 原版对不上
这是路线 A 最常见的坑。同一张图,YOLO 原版推理和 Halcon 推理结果差几个像素甚至漏检,原因通常有这几个:
- 预处理不一致:letterbox 的填充值、插值方式、归一化系数任何一个不同都会导致偏移。YOLO 默认填充 114/255,归一化是除以 255,Halcon 侧必须一致。
- 通道顺序:BGR vs RGB,这个错了精度会崩。
- 输出解析:YOLOv8 的输出需要转置,如果直接按 YOLOv5 的方式解析,框全错。
- NMS 阈值:YOLO 默认 IoU 0.45,Halcon 侧如果设 0.5,重叠目标会被多检。
我的做法是先用一张图,在 Python 里跑 YOLO 拿到框坐标,再在 Halcon 里跑,逐像素对比。差超过 2 个像素就说明预处理有问题。
4.2 置信度阈值和 NMS 阈值的调法
工业场景调这两个值有明确的方法论:
- 置信度阈值:先设 0.25 跑一批测试图,统计漏检和误检。如果漏检多,降到 0.15;如果误检多,升到 0.4。不要一次调太多,每次 0.05 步进。
- NMS IoU 阈值:密集目标(比如一堆小零件)用 0.3-0.4,稀疏目标用 0.5-0.6。如果发现同一个目标出两个框,降 IoU;如果相邻目标被合并,升 IoU。
这两个值没有万能解,必须按产线实际图像调。我一般会留一个配置文件,不同产品切换时加载不同阈值。
4.3 推理速度优化:从 200ms 压到 30ms
Halcon 侧推理速度受几个因素影响:
- 输入尺寸:1024 比 640 慢一倍以上。如果小目标不多,降到 640 能省很多时间。
- 批处理:Halcon 支持 batch 推理,但产线通常单帧流,batch 意义不大。
- CPU/GPU:Halcon 深度学习可以用 GPU,但需要 CUDA 版本匹配。如果产线工控机没独显,CPU 推理 1024 尺寸可能要 200ms 以上,这时候要么降尺寸,要么换轻量模型。
- 模型量化:Halcon 支持 INT8 量化,但需要校准集。量化后速度能提升 2-3 倍,精度掉 1-2 个点。对节拍要求高的场景值得做。
实测数据(i7-12700 + RTX 3060):
| 模型 | 输入尺寸 | 设备 | 单帧耗时 |
|---|---|---|---|
| yolov8s | 640 | GPU | 8ms |
| yolov8s | 1024 | GPU | 18ms |
| yolov8s | 1024 | CPU | 210ms |
| yolov8m | 1024 | GPU | 35ms |
4.4 模型更新时的版本管理
产线模型不是训一次就完事。每次新增缺陷样本、调整阈值、换产品型号,都可能要重新训练。我的做法是:
- 模型文件按
产品_日期_版本命名,比如scratch_20240601_v3.onnx。 - Halcon 程序里模型路径做成配置项,不硬编码。
- 每次更新模型,先用历史测试集跑一遍回归,确认旧类别不掉点。
- 保留最近三个版本的模型文件,出问题能快速回滚。
5. 那些文档里不会写的坑
5.1 Halcon 版本对 ONNX 算子支持的差异
Halcon 20.11 和 23.11 对 ONNX 的支持差别很大。20.11 对SiLU、Hardswish这些激活函数支持不完整,加载 YOLOv8 模型经常报错。23.11 之后支持好很多,但仍有部分算子限制。如果项目允许,尽量用 23.11 或更新版本。如果只能用老版本,训练时就把激活函数换成ReLU,或者导出时做算子替换。
5.2 图像通道和位深的隐形陷阱
工业相机出的图可能是 8 位灰度、16 位灰度、24 位彩色。YOLO 训练用的是 8 位 RGB。如果 Halcon 侧直接拿 16 位图去推理,数值范围不对,结果全乱。必须先用convert_image_type转成 byte,再转 RGB。灰度图要复制成三通道,否则 Halcon 可能报维度不匹配。
5.3 多类别时的类别索引对齐
YOLO 训练时names列表的顺序就是类别索引。Halcon 侧解析输出时,类别索引必须跟训练时一致。我见过有人训练时names: ['ok', 'ng'],Halcon 侧按['ng', 'ok']解析,结果所有 OK 品被判成 NG。这种错误排查起来很费时间,建议在配置文件里把类别映射写死,两边共用。
5.4 内存泄漏与长时间运行稳定性
Halcon 的apply_dl_model在循环里调用时,如果不释放中间结果,内存会持续增长。产线 24 小时运行,几小时后就可能 OOM。解决办法是每次循环结束clear_obj清理图像对象,模型句柄只在程序启动时加载一次,不要反复read_dl_model。
5.5 模型文件路径中的中文和空格
Halcon 对路径中的中文和空格支持不好,read_dl_model遇到中文路径可能直接失败。模型文件放在纯英文、无空格的路径下,比如D:/models/scratch_v3.onnx。这个坑很隐蔽,因为报错信息不会提示路径问题。
6. 什么情况下该放弃 Halcon 内推理,改用混合架构
路线 A 虽然能把 YOLO 塞进 Halcon,但维护成本不低。如果项目满足以下条件,我会建议改用路线 B:
- 产线已经有独立的推理服务(比如用 TensorRT 部署的 YOLO),Halcon 只做测量。
- 模型更新频繁,每次都要重新导出 ONNX 再验证 Halcon 兼容性,太耗时。
- 团队里没人熟悉 Halcon 深度学习模块的调试。
路线 B 的架构是:YOLO 服务通过 TCP 或共享内存把检测框传给 Halcon 程序,Halcon 只负责图像采集、测量、结果输出。这样两边解耦,YOLO 侧可以用最新的部署工具链,Halcon 侧专注它擅长的测量。通信协议用简单的 JSON 或者自定义二进制都行,延迟通常在 1-2ms,对产线节拍影响可以忽略。
我自己在最近一个 3C 零件检测项目里用的就是路线 B。YOLO 用 TensorRT INT8 部署,单帧 6ms;Halcon 收到框之后做圆度和平面度测量,整个节拍控制在 50ms 以内。模型迭代时只动 YOLO 侧,Halcon 程序完全不用改,维护效率比路线 A 高很多。
如果非要在 Halcon 里跑 YOLO,那就把预处理和后处理的代码封装成独立函数,模型路径、阈值、类别映射全部配置化。这样至少换模型的时候不用改主逻辑。另外,Halcon 的深度学习调试工具比较弱,遇到精度问题很难定位,建议在 Python 侧先把模型调好,确认无误再移植到 Halcon,不要两边同时调。