
1. 为什么要用Halcon做颜色识别1.1 颜色识别这件事难在哪颜色识别是机器视觉里最基础也最容易“看着简单、做起来翻车”的需求之一。不管是检测产品表面的色差、分拣不同颜色的零件还是判断指示灯是否点亮落到产线上都会变成同一个问题怎么让算法稳定地区分出目标颜色而不是被环境光照、反光、颜色相近等因素带偏。很多新手做颜色识别的第一步就是把图片转成RGB三个通道然后对着每个通道调阈值。这种方式在实验室拍几张干净图片确实能跑通一到现场就露馅了。原因在于RGB三个通道高度相关只要光照稍微一变三个数值一起跳动你辛苦调好的阈值瞬间失守。另外RGB对“颜色本身”的表达不够直观你很难凭直觉说“这个红色对应R200、G30、B35”但你可以很自然地说“这个红色的色调在偏红区域”。Halcon作为工业级视觉算法库在颜色识别这件事上提供了非常成熟的算子链路。从颜色空间转换、多通道阈值分割到形态学处理、连通域分析、特征筛选每一步都有对应的标准算子而且HDevelop编辑器里可以实时调试中间结果对工程落地极其友好。这篇东西我打算把整个颜色识别项目的设计思路、算子选型、实操步骤和踩坑经验完整拆开讲一遍不管是刚接触Halcon的新手还是已经在用它做项目、想进一步优化识别效果的工程师都能拿走直接参考。1.2 Halcon和OpenCV、VisionMaster这些框架怎么选做视觉项目选型时大家最常纠结的就是到底用Halcon、OpenCV还是VisionMaster。我的经验是这样的如果你是做纯算法验证、快速原型、或者不想为License付费的小项目OpenCV确实够用而且零成本如果是在工业现场做视觉系统交付要求开发效率高、调试工具顺手、算子丰富稳定Halcon是绕不开的选择。至于VisionMaster这类图形化流程软件胜在快速搭建标准流程但灵活性远不如Halcon。颜色识别这个细分场景里Halcon相对OpenCV有几个实打实的优势对比维度HalconOpenCV颜色空间转换trans_from_rgb算子支持HSV、HLS、IHLS等多种空间参数可直接指定cvtColor函数需要自己记住转换代码如COLOR_BGR2HSV调试体验HDevelop里可以实时看每一行算子的图像输出哪一步出了问题一目了然需要自己写代码输出中间结果或者用cv2.imshow逐个展示工业图形化针对表面检测、颜色分拣等场景有大量预置算子和参数模板需要自己组合各种函数跨平台部署支持Windows/Linux可导出C/C/C#等接口跨平台能力同样不错但Debug起来没那么直观另外Halcon的HDevelop环境里可以直接拖入图像、调整ROI、查看区域叠加这在现场调试时能节省大量时间。尤其颜色识别项目里最耗时间的部分是调阈值和验证鲁棒性Halcon的实时可视化能力几乎是不可替代的。这也是为什么很多设备厂商在做带有颜色检测功能的视觉软件时底层都会选择Halcon的原因。2. 颜色识别的核心思想先转HSV再分割2.1 为什么RGB直出方案在工业场景里不靠谱想理解颜色识别的方案设计必须先建立一个底层认知RGB是面向显示设备的颜色模型不是面向人类感知的模型。RGB的值受亮度影响极大同一个物体在亮光下和暗光下RGB三个分量的绝对值差异非常明显但人眼看上去它还是同一个颜色。这就意味着如果用RGB做阈值分割你必须同时应付三个通道的联动变化阈值空间是三维的而且这个三维空间不是规则的、适合用简单阈值框选的形状。这也是为什么在Halcon做颜色识别时业界通用的做法是先做颜色空间转换。RGB空间里颜色不是一个独立的观测量而是三个分量的组合亮度信息耦合在其中。一旦光源老化、环境光波动、或者物体表面有轻微的角度变化RGB分量就会整体漂移导致阈值失效。而HSV这类颜色空间把色调、饱和度、亮度分开其中色调通道H基本不受光照强度影响饱和度S描述颜色的纯正程度亮度V单独承载光照信息这样我们就能丢掉不稳定的V通道主要靠H和S来完成颜色区分。用生活化的说法来类比RGB就像用“红漆、绿漆、蓝漆各自兑了多少”来描述一种颜色光照一变相当于三种漆的浓度同时变了HSV则是直接说“这颜色是偏红还是偏蓝、颜色纯不纯、亮不亮”其中“偏红还是偏蓝”这个属性在光照变化时基本不会变。所以做颜色识别把RGB当成坐标系去画阈值是绕远路换成HSV才是走直线。2.2 Halcon里颜色空间转换的算子基础Halcon里做颜色空间转换核心算子是trans_from_rgb。它的基本用法是这样的* 将RGB图像转换为HSV颜色空间 trans_from_rgb (ImageR, ImageG, ImageB, ImageH, ImageS, ImageV, hsv)这里需要注意输入是三个单通道图像而不是一个多通道图像。所以实际用的时候要么提前用decompose3把RGB三通道拆开要么用一张三通道图像配合通道选择的方式处理。转换完成后输出的ImageH、ImageS、ImageV分别是色调图、饱和度图、亮度图全部都是单通道图像后续可以直接对这它们做阈值分割。Halcon支持的转换类型除了hsv还有hls、ihls等。它们之间的区别在于色调角度定义和亮度分量的计算方式但对大多数工业颜色识别场景hsv和ihls是最常用的两个。我自己的习惯是优先用hsv因为色调范围是0到360度和颜色本身的直观感知对应得比较好调参的时候脑子里的换算负担小。如果你用OpenCV做过类似项目要注意OpenCV里HSV的H范围是0到180为了塞进8位单通道而Halcon的H范围是0到360同一个红色在两边的阈值写起来完全不同迁移代码时最容易在这里出错。在HDevelop里调试时转完HSV之后建议先用dev_display分别显示一下H、S、V三个通道的灰度图观察目标和背景在三个通道上的灰度差异。这一步看起来不起眼但能帮你快速判断当前颜色在哪个通道上最容易分割从而决定阈值策略。2.3 不同颜色在HSV空间里的典型范围做颜色识别离不开一个经验表就是常见颜色在HSV空间中的大致范围。这里我以Halcon的0到360色调范围为准给一份常用参考颜色H范围度S范围V范围8位图备注红色0~30 或 330~36080~25546~255红色在色环上是断开区间要分两段取H然后再并集绿色90~15080~25546~255具体范围随色相变化较大蓝色200~27080~25546~255工业蓝色通常比较纯S值较高黄色20~5080~255100~255黄色V值普遍偏高白色0~3600~30200~255白色主要靠高V低S区分黑色0~3600~2550~46黑色主要靠低V区分这份表只是参考起点不是拿来就能直接套用的万能公式。不同光源、不同相机、不同材质表面的同一个颜色HSV值都会有偏移。真正干活的时候需要用HDevelop的灰度直方图工具对着实际现场图像采样从目标区域里读出真实的H、S、V分布再反过来设置阈值。我见过太多人把网上找的HSV范围表直接填进代码然后抱怨算法不稳定——其实算法没问题是你没有针对现场图做标定。另外要特别提醒不要孤立地用H通道去做分割。H通道在饱和度很低的时候会变得非常不稳定纯白、纯灰区域的H通道值会乱跳呈现出类似噪声的分布。所以标准做法是H、S、V三通道做“与”逻辑用H定位颜色、用S排除灰白区域、用V排除过暗和过亮区域三者取交集后才是可靠的目标区域。3. 手把手实操一个完整的Halcon颜色识别案例3.1 场景设定与图像采集我用一个实际做过的项目来演示整个流程某产线上的圆形塑料片有三种颜色——红、绿、蓝需要视觉系统识别出每个圆片的颜色并输出它们的中心坐标和面积。这个项目用到的Halcon版本是17.12以上都行硬件只是普通的面阵工业相机加一个条形白光光源。第一步肯定是要有一张高质量的输入图。颜色识别对图像质量要求极高不要指望后处理算法能拯救一张严重偏色或过曝的原图。采集时注意几点曝光时间不要过长避免高光溢出导致颜色信息丢失光圈不要开太大边缘和颜色细节会变糊光源色温要稳定最好选用显色指数高的白光。如果你是离线做验证没有现场相机直接用手机拍一张平铺的彩色物体照片也可以但后续调参时要知道现场图的颜色分布会和手机照片有差异。读取图像和初始化窗口的代码如下* 读取彩色图像 read_image (Image, color_parts.png) * 初始化显示窗口 dev_open_window (0, 0, 640, 480, black, WindowHandle) dev_display (Image)3.2 转HSV并观察通道分布拿到图像后先转HSV然后分离三个通道逐个查看目标区域在三个通道上的表现。这里我先用decompose3把RGB拆开再用trans_from_rgb转HSV* 拆开RGB三通道 decompose3 (Image, ImageR, ImageG, ImageB) * 转换到HSV颜色空间 trans_from_rgb (ImageR, ImageG, ImageB, ImageH, ImageS, ImageV, hsv)转完之后的调试手法非常关键。在HDevelop里你可以分别执行下面的代码来查看H通道图像dev_display (ImageH)这时你会在窗口里看到一张灰度图。注意观察红、绿、蓝三个颜色的区域在灰度图上的亮度差异越大说明H通道对当前颜色区分度越好。同理可以继续查看S通道和V通道。以我这个项目经验来说红色和蓝色在H通道上区分非常明显红色大概对应较低灰度值接近0度的色调在8位显示里会被归一化到0附近蓝色对应较高的灰度值。绿色则在两者之间。这个灰度观察是后续设置阈值的直接依据。3.3 三通道阈值与交集运算接下来的核心步骤是分别对三个通道做阈值提取然后把结果做交集得到最终的颜色区域。以红色为例红色的H范围是0到30再加上330到360Halcon里色调0度对应红色S要大于一定值排除灰白V要大于一定值排除黑色区域。* 红色H通道提取0~30和330~360两段 threshold (ImageH, RegionHRed1, 0, 30) threshold (ImageH, RegionHRed2, 330, 360) union1 (RegionHRed1, RegionHRed2, RegionHRed) * S通道排除低饱和区域 threshold (ImageS, RegionS, 80, 255) * V通道排除过暗和过亮区域 threshold (ImageV, RegionV, 46, 255) * 三通道求交集 intersection (RegionHRed, RegionS, Region1) intersection (Region1, RegionV, RegionRed)执行完之后RegionRed就是图像中所有符合“红色”特征的点集。这个区域里可能包含一些噪点、杂散点不要急着用先做形态学处理再分析连通域。这里细说一下为什么三通道都要做阈值。如果你只做H通道阈值你会发现图像里的白色标签、黑色边框在某些H值下也会被捡进来因为低饱和度的区域H通道是乱跳的。S通道就是把这部分“灰白”干扰丢掉的关卡。而V通道的作用是防止过暗阴影区域被误判同时避免过曝的镜面高光被框进来。三个条件单独看都不完美合在一起才靠谱。3.4 形态学处理与连通域分析阈值出来的区域通常有大量细小颗粒和毛刺直接用会严重影响后面的特征计算。所以做一次开运算去掉细小噪点再做一次闭运算填补内部空洞是比较标准的处理顺序* 开运算去噪点 opening_circle (RegionRed, RegionOpened, 3.5) * 闭运算填补空洞 closing_circle (RegionOpened, RegionClosed, 7.5) * 连通域分析把每个独立目标分开 connection (RegionClosed, ConnectedRegions)结构元素的尺寸取决于目标大小和图像分辨率。我这里待检测的圆片直径大概有120个像素所以开运算半径3.5个像素对目标大小来说无伤大雅但能清掉大部分零星噪点。闭运算半径7.5个像素可以填补圆片内部因为反光产生的细小空洞让区域更完整。如果目标区域边缘还是不干净可以考虑增加一步中值滤波median_image (Image, ImageMedian, circle, 3, mirrored)这步可以作为预处理在转HSV之前做能有效抑制图像噪声让后续阈值分割更稳定。3.5 特征筛选与结果输出连通域分析之后得到的ConnectedRegions里有真实目标也可能有没被形态学处理干净的杂质区域或者残留在图像边缘的干扰块。这时候用select_shape按特征筛选颜色识别项目里最常用的筛选特征就是面积和圆度* 按面积筛选排除小杂质 select_shape (ConnectedRegions, SelectedRegions, area, and, 500, 99999) * 按圆度筛选排除非圆形的干扰区域 select_shape (SelectedRegions, SelectedRegions, circularity, and, 0.5, 1.0)面积阈值500需要根据你图像中的目标尺寸换算。测量方式很简单用Halcon的灰度直方图工具框选一个完整的圆片区域直接能看到该区域面积是多少像素。然后取目标面积的50%到正无穷作为范围即可。圆度特征范围是0到1越接近1越圆。一个完整的圆片圆度通常能在0.85以上但如果目标本身不是圆形这个特征要换成别的比如矩形度或长宽比。最后把这些区域叠加在原图上显示并获取每个区域的基础特征* 在原图上叠加显示红色区域 dev_clear_window () dev_display (Image) dev_set_color (red) dev_set_draw (margin) dev_display (SelectedRegions) * 获取每个区域的面积和中心坐标 area_center (SelectedRegions, Area, Row, Column) * 在窗口上输出每个目标的面积和中心坐标 for I : 0 to |Area| - 1 by 1 disp_message (WindowHandle, Red: Area Area[I] , Center( Row[I] , Column[I] ), window, Row[I] - 30, Column[I], black, true) endfor到这里一个红色识别分支就完整跑通了。绿色和蓝色分支的逻辑完全一致只需要把H通道的阈值范围改成对应颜色的区间即可。整个过程并不复杂真正考验人的是阈值怎么定、噪点怎么排、目标特征怎么筛。为了让你能快速完整复现我把红色识别的完整代码放在下面。绿色和蓝色识别只需要替换H通道阈值范围然后照搬后面的处理链路* 完整红色识别流程 read_image (Image, color_parts.png) decompose3 (Image, ImageR, ImageG, ImageB) trans_from_rgb (ImageR, ImageG, ImageB, ImageH, ImageS, ImageV, hsv) threshold (ImageH, RegionHRed1, 0, 30) threshold (ImageH, RegionHRed2, 330, 360) union1 (RegionHRed1, RegionHRed2, RegionHRed) threshold (ImageS, RegionS, 80, 255) threshold (ImageV, RegionV, 46, 255) intersection (RegionHRed, RegionS, Region1) intersection (Region1, RegionV, RegionRed) opening_circle (RegionRed, RegionOpened, 3.5) closing_circle (RegionOpened, RegionClosed, 7.5) connection (RegionClosed, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, area, and, 500, 99999) select_shape (SelectedRegions, SelectedRegions, circularity, and, 0.5, 1.0) dev_display (Image) dev_set_color (red) dev_set_draw (margin) dev_display (SelectedRegions) area_center (SelectedRegions, Area, Row, Column)4. 常见问题与排查技巧实录4.1 光照变化导致识别时好时坏这是颜色识别项目里最典型的问题。上午调试好一切正常到了下午阳光照进车间或者旁边的设备灯光发生变化识别率就崩了。原因很简单光照变化导致V通道整体漂移而S通道和H通道虽然稳定度相对高但也不是完全免疫的。排查思路分三步走。第一步检查图像是不是过暗或过曝直接在HDevelop里看直方图如果V通道整体偏向0或者255优先调节相机的曝光时间和光源亮度不要靠算法硬扛。第二步把所有依赖V通道的阈值改成宽范围比如V的下限甚至可以直接设成30上限保留255让算法不过度依赖亮度信息。第三步是考虑加物理遮光把环境光对检测区域的影响降到最低。我在现场解决问题的习惯是不急着改代码先打开HDevelop实时取图分别在正常照明和人为改变亮度的情况下抓几张图对比H、S、V三个通道的直方图分布。如果只有V通道明显变化那阈值策略改成“放宽V、收紧H和S”即可。如果H和S也跟着漂移那就该考虑增加光源稳定性或者做白平衡校准。4.2 目标颜色和背景颜色太接近有时候待检测物体的颜色和输送带、工装板的颜色非常接近比如深蓝色零件放在黑色吸塑盘里或者浅绿色标签贴在灰绿色纸箱上。这种情况下单纯靠颜色阈值很难干净地区分。对策有两条路。第一条是调整成像条件换背景色差大的工装或者使用特定波长的光源来拉开色差。比如红色物体用红色光源照射时背景和物体的亮度差异可能并没有变化但H、S通道的区分度会变好。第二条是引入更多维度的特征不要只看颜色。圆片项目里我用圆度做筛选就是因为“同样的蓝色”既可能出现在目标圆片上也可能出现在背景的蓝色文字上但背景蓝色块的形状不是圆的一筛就没了。另一个常见思路是配合位置信息做约束。比如目标只在某个ROI区域内出现那就先裁剪ROI再做颜色识别直接从源头排除背景干扰。Halcon里用reduce_domain和rectangle1_domain就能很方便地实现* 设定ROI矩形区域 gen_rectangle1 (ROI, 100, 100, 500, 700) reduce_domain (Image, ROI, ImageROI) * 后续处理基于ImageROI进行4.3 HSV阈值怎么调才高效颜色识别项目一半的时间都耗在调HSV阈值上。新手最容易犯的错是把阈值当成一次性的魔法数字调完就再也不管了。正确的做法是把阈值设置当成一个标定过程用真实现场图像反复修正。我的调参习惯是先框选出目标区域用HDevelop的“直方图”工具分别查看H、S、V三个通道的像素分布记录目标区域在每个通道上的大致分布区间比如“H在10到25之间S在150到220之间V在180到240之间”。然后把阈值初始设为这个区间的80%跑一遍流程把误检和漏检的图像找出来再看误检区域的HSV值分布如果是重叠区就调整H/S/V的优先级如果是差异区就直接改阈值。还有一个小技巧如果一套阈值在白天稳定、晚上就翻车可以考虑写一个标定程序每隔一段时间自动采集一张现场图从背景区域采样计算V通道的均值再动态调整V阈值。Halcon的灰度特征算子比如intensity、min_max_gray都可以用来做这种自适应标定效果远比固定阈值好。4.4 反光和镜面反射干扰金属件、塑料薄膜、光滑表面的反光是颜色识别的另一大杀手。反光区域在图像里呈现为白色或接近白色的高亮斑点这些区域的颜色信息已经完全丢失HSV分割时要么被当成白色目标要么因为V值过高而被正确排除但边缘处会产生一圈“伪彩色”区域导致目标边界不稳定。物理方案最直接有效在光源前加偏振片相机镜头前也加偏振片调整偏振角度可以大幅削减镜面反射。没有偏振条件的话就降低曝光强度或者改变光源入射角度让高光不直接反射进镜头。算法层面能做的有限但可以通过形态学操作补救。反光造成的区域缺陷通常是目标内部出现空洞用closing可以填掉反光边缘产生的毛刺则用opening去掉。如果反光严重到目标颜色区域破碎成多个小块可以尝试把面积筛选阈值调低然后用dilation把相近的碎片连起来再重新做连通域分析。但归根结底颜色识别的上限取决于图像质量物理光学解决不了的问题算法再好也难救。5. 个人项目心得做了这么多颜色识别项目后我最深的体会是Halcon的颜色识别算子本身并不复杂真正决定项目成败的往往在调参之外。光源选型、成像角度、颜色空间选择、阈值逻辑设计每一步都需要针对实际场景仔细验证。另外有一点想特别提醒不要在开发环境里用几张漂亮图片验证过就认为万事大吉一定要到现场、在实际光照条件下、用真实的待检对象跑一轮稳定性测试。尤其是颜色识别这个功能环境光的影响太大了实验室里没问题的阈值到产线上很可能完全失效。我的习惯是在现场至少准备50张不同条件下的样图交替测试阈值稳定性再做最终交付。如果你后续想进一步提升颜色识别能力可以考虑把单纯的颜色阈值升级为颜色分类器。Halcon的class_mlp或class_svm可以基于HSV直方图特征训练颜色分类模型对颜色相近、光照复杂场景的适应能力比固定阈值强得多。也可以把颜色特征和形状特征、位置特征融合进同一套流程构建更鲁棒的识别逻辑。这个方向后续我可以再单独写一篇详解。