
做机器视觉的同行应该都遇到过这样的需求流水线上药片乱序摆放视觉系统定位后引导机械臂抓取。听起来像是个标准得不能再标准的定位项目但真正落地时会发现药片这种高反光、低纹理、尺寸又小的目标对匹配算法、光路设计、坐标标定的综合要求远高于一般工件。我这套基于Halcon相关性匹配的药片抓取方案已经在某药厂的包装线上稳定跑了大半年今天把整体思路、关键代码和现场踩过的坑都整理出来给正准备做类似项目的朋友一个参考。这个项目适合谁看如果你是刚接触Halcon的视觉工程师可以从这里理解相关性匹配和形状匹配的区别以及全套标定流程如果是正在调试药片、胶囊、纽扣这类小圆件抓取的同行那更有实际参考价值——文里的问题排查部分基本都来自真实产线不是实验室环境能预见的。1. 项目需求与方案选型思路1.1 现场工况与核心需求先说下现场情况。项目来自于某药厂的瓶装药片包装线上游是振动盘理料药片从料斗落下后散落在一条白色PVC输送带上输送带以约0.3m/s的速度运行。视觉系统安装在皮带上方检测到药片后将坐标发送给一台四轴机械臂机械臂末端装的是海绵吸盘把药片吸起来放到装盒工位的托盘里。产线的节拍要求是每分钟不少于60片也就是视觉检测到发出坐标的整个周期必须控制在1秒以内实际调试中我们做到了约0.6秒一个循环。核心需求拆开其实就三条一是定位要准药片直径约10mm吸盘直径只有6mm抓取时偏心不能超过1mm否则吸不起来或吸偏二是检测要稳药片在皮带上可能有正反两面正面有刻痕、反面是光面光照一变就可能误检漏检三是通讯要快视觉系统与机器人之间通过TCP/IP socket通信从触发到结果返回延迟尽量小。1.2 为什么选相关性匹配而不是形状匹配这是项目选型时讨论最多的一个问题。一开始团队里有人方案是用Halcon的shape_model形状匹配理由是药片是圆形几何特征简单形状匹配速度快、精度高。但真正拿样品测试后发现一个致命问题皮带背景上的异物、药片之间形成的圆弧边缘、甚至输送带边缘的轮廓都会被误检成圆形目标因为形状匹配只看轮廓而圆形轮廓在工业现场太常见了。而药片表面有刻痕、字母压纹、光亮面和哑光面这些灰度纹理信息在形状匹配里完全用不上白瞎了。另一方面如果用传统的灰度模板匹配gray model它对光照变化太敏感皮带速度稍微波动导致亮度变化分数就直线下降。最终我们选了相关性匹配NCCNormalized Cross Correlation它在Halcon里的实现是create_ncc_model和find_ncc_model。相关性匹配的核心优势在于它对全局光照变化有归一化处理——光照整体变亮或变暗匹配分数基本不受影响同时它用的是模板区域的全部灰度信息能把药片表面的刻字、压纹这些细节用上对圆形背景干扰有天然的区分能力。打个比方形状匹配就像根据背影认人只要身形轮廓相似就会认错相关性匹配则是看完整的人脸、体态、衣着颜色来认人信息量不是一个级别。代价是计算量更大但在小尺寸模板和合理的金字塔层数下速度完全够用。1.3 系统硬件组成与布局硬件配置是整个方案的基础如果这里选错了后面算法调得再好也白搭。我们的硬件清单如下相机500万像素面阵相机分辨率2448×2048全局快门GigE接口镜头16mm定焦工业镜头C接口手动光圈光源白色同轴光源加条形光源组合同轴光负责照亮药片表面的刻痕细节条形光补充整体亮度控制器普通工控机i5处理器8GB内存Windows系统执行机构四轴机械臂加海绵吸盘通过TCP/IP与工控机通信关键的是相机安装高度和视场计算。药片直径10mm吸盘抓取允许偏心1mm按这个算视觉定位精度至少要0.3mm。我们视场定为100mm×80mm也就是皮带上一个检测区域的宽度用500万像素相机的话X方向像素精度约为100/24480.04mm/pixelY方向约80/20480.039mm/pixel远优于要求。工作距离约300mm16mm镜头的视场角和这个距离匹配得正好。光源的选择花了点时间。药片正面是高光塑料覆膜直接打条形光会有明显反光点导致药片中心区域过曝刻痕细节丢失。后来加了同轴光源光线垂直打到药片表面再垂直反射回来反光被压住刻痕也清晰了。如果你的现场也是类似高反光小目标可以优先考虑同轴光。2. 相关性匹配原理与模板制作2.1 相关性匹配到底在算什么先把这个算法的本质讲明白你才能正确地调参。相关性匹配的核心是计算模板图像和搜索图像之间的归一化互相关系数公式简化后可以理解为把模板的灰度矩阵和搜索图中对应位置的灰度矩阵各自减去均值、除以标准差之后做点乘得到一个介于-1到1之间的值。1表示完全匹配0表示毫不相关-1表示反相。这个归一化过程是关键。它让匹配对“整体亮度偏移”和“整体对比度变化”不敏感——就好比你晚上在昏暗灯光下看一个人虽然整体亮度低了但你依然能认出他的五官比例。这正是药片场景需要的皮带速度波动、环境光飘移、相机增益变化都会带来整体亮度的改变但归一化处理后匹配分数依然稳定。不过相关性匹配有个天然短板它对局部遮挡和旋转很敏感。药片在皮带上虽然大多是平放的但姿态是任意的——每片可能是正面朝上也可能是反面朝上还可能有0到360度的偏转。如果药片之间挨得太近部分区域被旁边的药片挡住匹配分数就会明显下降。所以后面我们在Halcon里同时开了角度范围搜索并配合MinScore的合理设置来解决粘连药片的问题。2.2 Halcon中创建模板的核心算子Halcon里相关性匹配的算子不多核心就两个create_ncc_model负责创建模板find_ncc_model负责在图像中查找。先看创建模板这段* 读取一张药片正面的样本图 read_image (Image, tablet_sample.png) * 手动框选药片区域尽量只包含药片本身不要带入背景 draw_region (TabletRegion, Image) reduce_domain (Image, TabletRegion, TabletImage) * 创建NCC模板 create_ncc_model (TabletImage, auto, rad(-30), rad(60), auto, use_polarity, ModelID)这里几个参数要逐一说明。NumLevels是金字塔层数auto表示让Halcon根据模板尺寸自动决定默认情况下模板越大、层数可以越多我们的药片模板大约是60×60像素Halcon给出的层数是4层。金字塔的用途就是先用低分辨率粗搜、再用高分辨率精搜速度快很多。AngleStart和AngleExtent是角度搜索范围。因为我们现场药片在皮带上姿态任意所以必须覆盖全角度。这里设了从-30度到30度rad(-30)到rad(60)跨度是90度配合find_ncc_model时的角度搜索。如果你确认药片角度变化不大可以缩小这个范围速度会更快。Metric参数选了use_polarity意思是药片相对背景是亮的还是暗的在模板里固定下来了。药片是白色偏灰皮带是白色但药片边缘有阴影所以这个模式下分数最稳定。2.3 模板参数灯箱实测模板做好之后不要急着写进代码先在离线环境里做一轮验证。我们当时的做法是拍了200张不同位置、不同角度的药片图片用find_ncc_model跑一遍统计匹配分数分布。结果发现一个规律药片正面的匹配分数普遍在0.85以上反面光面、几乎没有纹理的分数在0.65到0.8之间皮带有污渍的地方偶尔会出现0.55的伪匹配。于是我们定下MinScore0.7这样既能保证正反两面都能识别又不会把背景污渍当药片。另外一个细节模板区域的形状。很多人习惯直接在create_ncc_model里传整个药片图像的矩形区域但这样会把背景皮带也包含进模板。背景一旦进入模板匹配时就会拿“药片背景”的组合去和目标图中的每个位置比对皮带颜色稍有差异就会拉低分数。我们用reduce_domain把模板限定在药片轮廓内部匹配分数立刻提升了大约0.05到0.1。这是非常实用的一个经验——制作相关性模板时ROI一定要紧贴目标边缘宁可小一点也不要把背景包进来。还有个坑是关于“角度步长”的。create_ncc_model里如果用autoHalcon会根据模板尺寸自动决定角度步长但有时会偏大导致匹配时角度误差累积到定位精度上。我的习惯是手动指定当模板尺寸在50像素以上时角度步长用0.5度50像素以下用1度。这个值直接影响搜索速度和精度需要根据现场实际抓取效果微调。3. 抓取引导从像素坐标到机器人坐标3.1 相机标定与镜头畸变校正匹配算法给出的是药片在图像中的行列坐标但机器人需要的是自己坐标系下的X、Y、Z值。这中间隔着一层镜头畸变和一层坐标系变换任何一个环节没做对抓取都会偏。首先是镜头畸变校正。500万像素相机配合16mm镜头在视场边缘会有明显的桶形畸变视场边缘处位置误差可能达到1到2毫米——这已经超过了吸盘允许的偏心范围。我们用Halcon的标定板30mm间距的陶瓷标定板拍了十几个姿态运行find_caltab和camera_calibration求出相机内参得到畸变系数K1、K2然后用change_radial_distortion_image把图像校正为无畸变图像。这一步必须在图像预处理阶段完成而不是在坐标输出阶段因为匹配是在校正后的图像上做定位精度才能保证。这里有个容易忽略的点标定板的目标间距要和你实际视场匹配。视场是100mm×80mm标定板至少要有覆盖视场60%以上的区域否则边缘畸变拟合不准。我们最开始用了一个20mm间距的小标定板结果边缘区域定位误差始终在2mm下不来换成30mm间距、覆盖全视场的大标定板后误差直接降到0.3mm以内。3.2 手眼标定与抓取点生成畸变校正只是第一步接下来还要把相机坐标系和机器人坐标系对齐。现场相机的安装方式是“眼在外”——相机固定在皮带上方的支架上机械臂在相机视野范围内工作。这种情况下通常用hand_eye_calibration或更简单的仿射标定vector_to_hom_mat2d就能解决。我们用的是Halcon的vector_to_hom_mat2d做平面仿射变换标定因为药片抓取是在同一平面上不需要完整的3D手眼标定。具体的做法是在皮带平面上放一个带圆孔的标定板机器人末端装一根探针依次移动到标定板上9个已知位置并记录机器人坐标同时相机拍摄每个位置提取圆孔中心作为像素坐标。然后* 像素坐标和机器人坐标一一对应 * PixelRows 和 PixelCols 是9个圆孔中心的像素坐标 * RobotX 和 RobotY 是机器人记录到的对应坐标 vector_to_hom_mat2d (PixelRows, PixelCols, RobotX, RobotY, HomMat2D) * 之后的每个匹配结果都用这个矩阵转换 affine_trans_point_2d (HomMat2D, Row, Col, RobotX, RobotY)仿射标定适用于纯平移加旋转的平面映射精度完全够用。要注意的是9个采样点要尽可能铺满整个相机视场不要集中在中心区域。因为仿射变换是线性拟合边缘区域样本不足会导致拟合外推误差抓取时在视野边缘会明显偏。至于Z轴高度因为皮带高度是固定的我们直接把机械臂的吸取高度写成一个定值配合吸盘的弹性缓冲药片厚度差异±0.5mm不会造成问题。如果你现场的药片厚度波动大建议加一个激光位移传感器反馈Z轴而不是靠视觉算。4. 程序流程与关键代码解析4.1 主流程设计整个视觉程序的流程设计很直接但每一步都有讲究相机硬触发或软触发采集图像图像畸变校正change_radial_distortion_image在ROI区域内执行find_ncc_model查找药片对找到的目标做亚像素精度提取SubPixel: least_squares用仿射矩阵转换到机器人坐标通过socket发送的机器人坐标和角度、时间戳机器人到位后控制相机触发下一帧触发方式用的是硬触发——皮带上有一个光电传感器药片经过时发一个信号给相机这样保证拍照时药片在视野内的位置基本固定不会因为软件触发的延迟导致位置漂移。如果现场没有传感器用软触发也可以但需要在程序中测量皮带速度并进行位置补偿复杂度和稳定性都会差很多。4.2 关键代码段剖析在线查找部分的代码是核心逐一解释* 读取当前帧图像 grab_image (Image, AcqHandle) * 畸变校正校正后的图像存在ImageRectified中 change_radial_distortion_image (Image, ImageRectified, invalid, 0, CameraParam) * 只搜索预先定义的区域减少计算量 reduce_domain (ImageRectified, SearchRegion, ImageSearch) * 执行相关性匹配 find_ncc_model (ImageSearch, ModelID, rad(-30), rad(60), 0.7, 0, 0.5, least_squares, 4, Row, Column, Angle, Score)find_ncc_model参数逐个看AngleStart和AngleExtent同样设为-30度到60度覆盖全角度MinScore为0.7这是前面离线测试得到的阈值NumMatches设为0表示返回所有超过MinScore的目标但因为我们一次只需要抓一片实际运行中设的是1MaxOverlap设为0.5表示两个匹配结果的重叠面积不超过50%这个参数在药片粘连时特别有用——两个部分重叠的药片可能都被匹配到但吸盘一次只能抓一个这时让重叠度大的候选结果保留分数最高的那个。SubPixel用least_squares这一步很关键。相关性匹配默认的定位精度是像素级的对抓取来说分辨率0.04mm/pixel其实已经不错但药片边缘有阴影、刻痕图形的灰度渐变亚像素拟合能进一步把精度推到0.1像素级别实测定位误差从0.35mm降到了0.2mm以内。输出坐标时还有一个细节机械臂末端吸盘抓的是药片中心而匹配得到的Row、Column是模板中心也就是药片中心的位置这两者是一致的不需要额外偏移。但如果你的模板当时框选的ROI不在目标中心就需要记录模板ROI相对中心的偏移量输出坐标时加上去。5. 现场问题与排查技巧实录5.1 常见问题速查表半年调试下来整理了一个问题排查速查表新项目遇到类似问题时可以直接对照现象可能原因排查方法解决方案匹配分数整体偏低所有药片都低于阈值光照强度明显变化药片表面反光位置改变查看实时图像和模板图像的灰度分布差异重新采集模板检查光源是否衰减或位置偏移药片粘连时漏检MinScore设置过高部分被遮挡的药片分数低于阈值查看漏检药片处的实际Score值适当降低MinScore到0.6~0.65增加重叠区域后处理皮带上有污渍被误检为药片污渍灰度特征与药片局部相似查看匹配结果中Score分布缩小搜索区域在ROI设置时避开常见污渍区域增加形状筛选抓取点系统性偏移固定方向固定大小标定板固定不牢或标定时机器人坐标记录有误重新做仿射标定对比新旧矩阵标定前后各抓一次标定板中心比较偏移量高速运行时出现拖影导致定位不准曝光时间过长运动模糊查看图像中目标边缘是否有拖影缩短曝光时间到1ms以内增加频闪光源辅助偶发性匹配分数突然降低环境光变化或皮带震动引起焦距轻微漂移长期记录Score曲线观察是否有周期性波动固定光源加固相机支架定期重新采集模板5.2 两个印象深刻的案例第一个案例是关于“正反面差异”的大坑。项目初期只采集了药片正面的模板上线后发现反面药片的漏检率高达30%。反面药片表面没有刻痕灰度纹理非常少相关性匹配分数只有0.55到0.65低于我们设置的0.7阈值。解决方案是制作两个模板——一个正面、一个反面分别匹配只要任何一个匹配成功就认为是有效药片。这样处理之后漏检率降到0.1%以下。但注意如果正反面抓取后处理工艺不同你需要在程序中判断到底是哪个模板匹配成功了比较两个Score或记录模板ID然后给机器人发不同的抓取策略。第二个案例是关于皮带速度变化导致的角度误判。药片在皮带上有时会因为震动发生轻微旋转我们的角度搜索范围设置为全角度理论上没问题但由于皮带是运动的药片在曝光时间内可能有微小位移导致匹配到的角度值在0度和359度之间跳变。机械臂拿到角度后如果旋转方向判断错误会把吸盘转到相反的方向。排查了很久后来发现是曝光时间设置在3ms、皮带速度0.3m/s时药片位移了0.9mm——相当于视场内移动了22个像素亚像素角度拟合就不稳定了。最终通过缩短曝光时间到0.5ms并提高光源亮度问题彻底解决。这里提示一下运动场景下相机曝光时间一定要压短不要只看匹配分数稳定就觉得没问题。5.3 节拍优化的两个技巧关于节拍还有两个小技巧值得写一下。第一个是金字塔层数的设置。find_ncc_model的最后一个参数NumLevels设为4意味着先在第4层最低分辨率搜索然后逐层细化到第1层。如果你确认现场药片没有遮挡、灰度变化不大可以试着把层数提高到5或6搜索速度会明显提升但代价是对轻微变形更敏感。我们实测在层数6的情况下单次匹配时间从45ms降到22ms但漏检率上升了0.3%最后还是回到4层。第二个技巧是把匹配的搜索区域SearchRegion缩得很小。因为皮带上的药片在相邻两帧之间不会移动太远我们根据上一帧的位置设置一个直径80像素的圆形ROI只在该区域附近搜索。这样不仅减少了计算量还避免了远处误匹配的可能。对应地第一次找不到时再扩大到全视场搜索作为兜底方案。6. 总结性的一点个人体会这个项目做下来我的最大感受是视觉定位项目里算法选型只占三成剩下七成都在“环境适配”上。相关性匹配和形状匹配的选择本质上是“目标特征的信息量”和“干扰特征的相似度”之间的权衡——如果你也碰到类似药片这种低纹理、高反光、正反面差异大的小目标NCC确实比shape model更合适但前提是你愿意花时间把每个参数摸透把光源调好把标定做扎实。最后再分享一个细节模板不是采集一次就能用一辈子的。药片批次不同刻痕深度和颜色可能有细微差异光源用久了亮度也会衰减。我们的做法是每两周用标准样块重新采集一次模板并记录模板的平均灰度值如果灰度值变化超过10%就自动报警提醒。这个小机制上线之后匹配分数长期稳定在0.8以上几乎没再出现因为模板老化导致的批量漏检。希望对你有用祝调试顺利。