Halcon深度学习分割实战:从标注到部署的完整流程
搞机器视觉的应该都有这种经历一个缺陷检测或者区域定位的需求传统算法调了半天阈值换几个光源还行现场光线一变就崩。我前段时间接的模拟项目X就是这样某零件表面需要按像素精确分割出两个材质区域传统方法怎么调都差一口气。最后逼着自己把Halcon的深度学习分割完整走了一遍从标注、训练到部署踩了不少坑也有些经验可以分享。这篇就把整个案例的实操过程梳理出来给同样被传统视觉玩法卡住的人一个参考。先说清楚它是什么Halcon里的深度学习分割本质上就是一个逐像素分类器。和普通目标检测画框不同它会输出一张和输入图同尺寸的置信度图每个像素被归到对应的类别里。它解决的是“这堆像素里哪些是A、哪些是B、哪些是背景”的问题。这套东西适合谁来用如果你正在做产品外观缺陷分割、工件区域划分、或者任何需要精细边界的视觉定位而且手头已经有Halcon授权那值得花一周时间试一次。它能扛住光照波动、纹理干扰还有那些“说不清哪里有缺陷但人眼一看就知道”的模糊场景。不需要你在Linux里配环境也不需要学会写PyTorch工作流全在HDevelop里面这点对从传统视觉转过来的人特别友好。下面就是这次案例的全部内容我会按项目推进顺序讲把选型理由、数据准备、训练参数和部署踩坑各拆开尽量说人话想复现的可以直接跟着操作走。1. 为什么这个案例要硬啃深度学习分割——项目背景与方案取舍1.1 需求场景传统算法解决不了的分割任务这个模拟项目X的真实需求是这样的某机加工零件上有两个相邻材质区材质A表面有细微纹理材质B表面相对光滑两种材质在可见光下呈现的灰度范围高度重叠。产线需要用一个固定工位的相机拍下零件然后在图像里把材质A连通域完整提取出来用于后续的打磨轨迹规划。听起来像是个普通的区域分割问题对吧按传统思路先试全局阈值分割发现不行再试自动阈值加动态阈值光照稍微变一点就分错接着上Canny边缘检测加形态学闭合结果纹理干扰严重边界处尼龙毛刺一样的小边缘太多连完形之后区域内部全是洞。折腾了两天换光源角度、试偏振片效果始终不稳定遇到表面有一点点油污的情况就直接崩。这个场景就是典型的“灰度特征不具备可分性”。材质A和材质B的灰度直方图扣在一起很难用一个固定阈值切开。算法工程师在这个阶段通常会有两个选择一是继续加特征工程比如提取纹理特征、频域特征再分类二是上深度学习让网络自己去学像素级别的决策边界。1.2 为什么不选自建深度学习框架而要锁死Halcon在选型的时候我也认真考虑过用开源框架做语义分割。技术上行得通但落到这个项目上有几个绕不开的现实问题标注数据流转成本高开源框架需要自己把标注格式转成特定格式Halcon的标注工具做完直接生成数据集对象全流程一个软件闭环。训练和部署环境割裂用PyTorch或TensorFlow训练出来的模型部署时要么转ONNX再调推理引擎要么用框架自带的serving工具和现场的HDevelop脚本是两个体系。后续维护门槛项目验收之后还得有人维护现场工程师基本都熟悉Halcon让他们去改一个Python推理服务维护成本完全不同。Halcon的深度学习模块和传统视觉算子无缝集成分割出来的像素置信度图可以直接转Region然后继续用connection、select_shape这些经典算子做后处理。这个衔接过程非常流畅整个项目不需要写一行C或Python全部用HDevelop脚本就能跑通。对于一些生产环境有实时性要求但不苛刻的场景这套方案足够实用。1.3 深度学习分割在这个项目里到底改变了什么如果只说“用深度学习效果好”那说服力不够。实际操作时最大的感受是它把“找特征”的工作从算法工程师手里转移到了网络训练过程里。传统方案我需要手工设计“灰度变化率”“局部方差”这些特征而分割网络只需要我喂标注好的图像它自己学出了一套鲁棒的决策依据。实测之后最有价值的一点是模型对光照变化和油污干扰变得相当鲁棒。刚开始标注的图片只有20张模型就能达到大概85%的像素准确率后来补充到80张IoU直接涨到0.92左右之后训练集再怎么加增益就开始放缓。这是典型的深度学习曲线数据量过了阈值之后边际收益递减。2. 数据底子从拍照到训练集的完整构建过程2.1 图像采集别急着标注先想清楚数量和质量图像采集是一切分割模型的基础。一个容易犯的错就是拿产线上同一时段拍的几十张图就开始标注导致训练集光照分布单一模型一换班就失效。我的处理方式是分三个阶段采集第一阶段覆盖白天、傍晚、夜间不同自然光条件下的图像以及不同零件个体之间的表面差异。第二阶段专门拍摄边界难分的情况比如轻微油污、轻微锈斑、纹理角度发生变化。第三阶段随机抽取一批“困难样本”作为补充用于专门测试模型的极限能力。数量上官方文档的建议是每个类别至少几十张训练图像这指的是最简单场景。实际项目里以分割任务为准我建议从40张起步跑通流程后看效果决定是否补充。我这次最终使用的完整数据集一共80张训练集64张、验证集8张、测试集8张验证集和测试集完全独立比例是8:1:1这个比例在样本量偏少时比较实用。有一点必须强调Halcon的训练过程对标注一致性的敏感程度比大多数人预想的要高得多。如果一个区域边缘前20张图标注得粗糙后面20张标注得精细模型会学到一种“边缘抖动幻觉”最后推理出来的边界会带上不规则的锯齿。所以开工之前一定要定好一套标注标准。2.2 标注阶段用Halcon的标注工具做逐像素区域标记Halcon的深度学习标注操作本身并不复杂重点在于HDevelop和标注工具之间的配合。打开标注工具创建一个分割任务然后在每张图上把对应的区域画出来。分割任务需要标注的类别一般就两类材质A区域和材质B区域。如果有背景不需要把所有背景像素都标注出来只标注目标区域的轮廓区域背景默认会按“不属于任何类别”处理。标注过程中我的标准操作流程是先对整个图片做一次总体观察确定A、B两类在当前图像里的边界位置。使用多边形绘制工具沿着边界依次打点尽量保证边缘贴合。对于纹理干扰严重的局部区域放大图像到200%以上再补点。每张图标注完做一次视觉检查确保没有出现区域缺漏、轮廓交叉和多边形自交。标注效率方面我前20张图平均每张耗时大概8分钟主要是手生到后面每张压缩到4分钟左右。80张图一个人用时约6小时分成两天做完每天标注2-3小时眼睛不会太疲劳效果也会稳定很多。2.3 数据集创建和预处理参数设置标注完成后进入代码环节这也是从传统视觉转过来最需要适应的一段。* 创建数据集 create_dl_dataset(DLDataset, ImageDirectory, segmentation) * 将标注结果加入数据集 add_dl_dataset(DLDataset, ImageFile, LabelDir, segmentation)预处理阶段需要重点设置几个参数图片尺寸因为零件占图幅比例不低直接全尺寸训练可以保留更多边界细节。但如果全尺寸导致显存溢出可以先把长边缩放到1024像素。这个项目的零件区域本身比较大我选择缩放边长大约是原图的70%左右实测速度提升不少精度损失很小。灰度归一化Halcon内置了灰度归一化的选项会避免模型对绝对亮度过于敏感。开启它之后模型对光照变化的容忍度明显提升这是非常关键的一个参数。指定类别数分割任务类别数设为2并在数据集参数中设置类别顺序保证后续训练时类别索引一致。预处理完成后用split_dl_dataset划分训练集和验证集再配合apply_dl_preprocessor生成预处理后的数据字典训练数据准备结束。2.4 数据增强几百张图也能用尺度扰动撑起泛化样本量不足时数据增强是泛化能力的主要来源。Halcon的分割训练流程里内置了几种增强策略包括随机旋转、左右翻转、镜像翻转、缩放和灰度扰动。训练早期我为了省事全关了增强结果模型在验证集上的表现非常拉胯边界来回抖。后来逐步开启水平翻转开启因为左右对称材质在成像上没有方向性差异。随机旋转正负10度防止模型对方向过度敏感。缩放范围0.9到1.1这个范围可以规避尺寸扰动带来的边缘错位又不至于让几何失真太严重。灰度扰动正负5%模拟光照波动。开启增强之后验证loss下降了大概一半泛化能力提升很明显。2.5 类别不平衡背景占比过大怎么处理分割任务里类别不平衡是绕不开的坑。背景区域占比可能达到50%以上网络会倾向于把不确定像素判成背景。Halcon训练流程中没有直接给每个类别加权的傻瓜参数但有一个间接思路在标注阶段就把包含目标的局部区域裁出来作为训练图像而不是把整张包括大量背景的画面都丢进去。我当时把每张零件图裁剪成包含完整目标的最小包围盒再外扩30像素作为背景缓冲这相当于人为提高了目标像素比例。训练时模型对目标的敏感性显著提升边缘处也更干净。裁剪还有一个好处是减少空白区域的干扰打正样本效率更高。3. 从预训练到微调训练过程的关键参数与判断依据3.1 用预训练模型还是从零训练Halcon的深度学习分割模块自带预训练模型第一次使用时它会下载初始权重。这里强烈建议直接用预训练权重初始化而不是随机初始化原因很简单目标检测和分割的底层特征比较通用轮廓信息和纹理信息都可以迁移到新任务。我实验过两个配置预训练初始化与随机初始化相比相同迭代次数之后像素准确率高5到6个百分点。对于一个工业项目来说这差距可能直接决定是“可用”还是“不可用”。尤其当训练集只有60张这种量级时随机初始化的效果很难令人满意。3.2 关键训练参数从batch size到学习率Halcon的train_dl_model核心参数配置如下这次项目最终采用的配置是这样参数经验值说明batch size4显存和训练稳定性折中的选择初始学习率1e-4预训练微调场景不宜太大迭代次数3000训练过程中需要同时观察loss曲线优化器Adam收敛快参数可调范围宽验证间隔100每100次迭代跑一次验证loss初始化学习率很关键。预训练模型的权重已经比较适应自然图像特征如果一开始就用1e-3以上很容易把学到的底层特征破坏掉还没进入目标分类阶段就训练崩了。从1e-4起步配合预热阶段是我试过最稳的组合。所谓预热是前几百次迭代用一个很小的学习率让网络慢慢适应新数据集之后切到正常学习率。batch size4是一个比较保险的值既不会因为太小导致loss震荡太厉害对显存的要求也不高。一张标准显卡就能轻松跑完能输出流畅的loss走向。3.3 训练过程的观察重点loss曲线和可视化结果训练期间每隔100次迭代输出一次loss信息。我习惯重点关注两个指标训练loss和验证loss。训练loss持续下降说明网络仍在学习没有停滞。验证loss和训练loss之间差距逐渐拉大就要警惕过拟合。这次项目里训练loss从最初的0.8左右一路下降到1500次迭代左右变得平缓验证loss稳定在0.2上下。此时我停了一次训练用中间结果直接跑推理发现边界已经很干净只是漏掉了一些不连续的小区域。于是我没有继续增加迭代次数而是选择增加训练数据来强化小区域特征。一个小技巧是不要只盯着loss数值训练到一半时可以随时中断打开一张验证集图像用当前模型跑一次推理看看输出的置信度图长什么样。这比纯看数字直观得多。我见过不少案例loss看起来很低但实际分割结果里目标区域是碎的。只有直接看可视化结果才能发现网络到底学到了什么。3.4 过拟合和欠拟合的判断方法过拟合在分割任务里的表现很典型训练loss猛降验证loss到了某个点就开始回升而推理时边缘会带上一圈“记忆痕迹”——对训练集里某种背景纹理响应异常。应对策略主要有三种本次项目全部用上了增加数据增强的强度尤其是灰度扰动和尺度扰动可以明显压制验证集上的抖动。简化模型容量Halcon提供了不同大小的骨干网络选项如果数据很少使用较小的backbone反而效果更好。提前停止选验证loss最低的那一点作为最终模型而不用训练最后一次迭代的权重。Halcon自动保存最优权重但最好自己额外记录一次保留最优点。欠拟合的情况相对好判断训练loss高居不下验证loss也同步高说明网络容量不够或者学习率太低。先试把迭代次数提到5000再看是否改善如果没改善再考虑换大模型。4. 推理部署模型导出、运行速度与实测效果4.1 从训练结果到可部署模型的完整链路训练完成后Halcon会把训练好的网络保存为hdl格式这个文件就是部署用的模型本体。推理代码流程很短核心也就是四个步骤* 读取模型 read_dl_model(segmentation_model.hdl, DLModelHandle) * 读取需要推理的图像 read_image(Image, test_image.png) * 前处理 preprocess_dl_model(Image, PreprocessedImage, DLModelHandle) * 推理 apply_dl_model(DLModelHandle, PreprocessedImage, DLResult)拿到DLResult之后从里面取出像素分类置信度图然后转成Region做后处理* 提取置信度最高的类别索引图 query_dl_model(DLResult, segmentation, ClassIndexImage) * 把对应类别的区域转成Halcon Region threshold(ClassIndexImage, Region, 1, 1) connection(Region, ConnectedRegions) select_shape(ConnectedRegions, SelectedRegions, area, and, 1000, 999999)后处理部分保留了很大的调整空间。比如需要边界平滑可以加一个closing_circle需要过滤掉小噪点就加一个面积阈值。深度学习模型的输出和传统分割一样都先转Region后面用什么算子都顺手。4.2 运行速度CPU和GPU的实际差异对很多生产环境来说GPU不是标配CPU推理速度直接决定方案能不能落地。我这套模型全图分辨率约1200x900实测结果大致为CPU推理单张约500毫秒普通i5级别处理器集显加GPU加速单张约80毫秒独立显卡单张约30毫秒。如果产线节拍在2秒以上纯CPU软方案完全可以跑。如果节拍要求在500毫秒以内则需要考虑一块低端独立显卡。工业现场如果想省成本可以先用CPU方案验证效果再决定加不加显卡。4.3 精度怎么评IoU之外还要看什么模型评估除了IoU之外我还额外关注三类指标边界贴合度沿着真实标注边缘的像素差。深度学习分割会在边界处出现大约1到2个像素的浮动这取决于标注质量和输入分辨率。连通域数量目标区域是否被分割成碎片。项目后期出现过大区域中包裹了小洞的情况通过后处理面积选择可以过滤大部分。关键点位稳定度实际项目中我更关心分割区域质心和面积的稳定性。连续10张图跑下来质心的偏移量如果超过0.5个像素就能认为推理结果稳定。实验数据来看模型IoU约0.92边界平均像素误差约1.3像素连通域数量和标注结果基本一致。这个精度水平对后续轨迹规划已经完全够用。4.4 失败案例什么场景还是会翻车真实项目里没有100%可靠。这次测试暴露了几个短板场景强反光区零件表面出现镜面反射时反光区域被网络误判为材质B。原因很简单反光区域的纹理特征和材质B极其相似。我在后处理里加了一个灰度上限过滤把超过一定灰度值的区域强制排除才压下去。小目标漏检有一种材质A的细小纹理条带宽度只有几个像素在缩放过程中容易丢失。加大输入分辨率之后改善明显但推理时间也随之增加。样本外形态零件规格微调后边缘出现新倒角时模型会在这个区域出现错分类。这个没法靠微调简洁解决只能补标几张图重新训练。把这些失败样本收集起来归类放回训练集重新训练一轮之后模型整体泛化能力又上了一个台阶。这也说明了持续积累现场样本的重要性模型会随数据迭代越来越稳。5. 复盘几个特别想说清楚的坑和最后一条建议5.1 标注不一致比标注错误更隐蔽标注错误一般是局部的人眼检查能发现标注不一致却会让模型学到错误的边缘分布。这次项目中一个典型问题就是第一天上下午标注时边缘粗细不同导致网络在推理时产生“无中生有”的毛刺。解决方式标注完成后做两次整体复查把边缘点密度骤降到明显稀疏的区域全部重新补点。这个动作能直接提升2%左右的IoU值得每一位做分割项目的人重视。5.2 训练集和验证集的划分要足够严谨样本量少的时候很容易手滑出现“泄露”。我最初用随机划分时验证集里混入了同一批次不同光照的图像结果验证loss虚低感觉自己已经完美收工。后来重新按采集批次划分确保同一次产的图像不在两个集合同时出现验证loss立刻涨了不少。这个教训说明评估指标要是来自一个“作弊”的验证集后续优化全部是白费功夫。5.3 分辨率缩得太狠细小结构直接消失Halcon的预处理环节会优先保证显存占用可控但缩图也要看目标尺寸。我试验过把全图缩到512x384推理速度极快但材质A的条纹状纹理几乎全糊了边缘锯齿严重。最终折中到长边约1000像素速度和精细度都还能接受。如果项目中存在细小结构我建议直接从800像素往上试不建议一上来就缩到极小。5.4 这套流程还能怎么扩展分割模型的产出是区域Halcon里区域的概念和所有传统算子都是相通的这意味着后面有非常多的玩法分割出缺陷区域后可以直接用get_region_contour提取轮廓供机器人轨迹规划分割结果的面积、圆形度等特征可以直接送进统计工具做质量分析多个相机视角的分割结果可以拼在一起做全景分析。至少对我个人来说这个案例证明了Halcon深度学习分割不是实验室玩具它完全能扛住真实产线项目。最后再分享一个我个人操作上的小习惯每次训练之前把数据集、模型参数、训练日志完整归档文件名带上日期和实验序号。这个习惯救了我好几次——当发现一个改动让效果变差时可以快速回到上一个可用的权重而不是靠记忆去还原参数。训练分割模型本质是一个实验驱动的过程能快速回退和对比才是真正的高效。