C#+Halcon SMT贴片机视觉对位:从模板匹配到相机标定与补偿算法
简介面向SMT贴片机视觉系统开发者的工程资源整合了C#与Halcon在模板识别、相机标定及对位贴合中的完整实践。核心内容包含MARK点四点校正与两点补偿、贴合补偿算法、上下双相机协同定位逻辑覆盖从模板匹配、坐标标定到最终贴合补偿的视觉定位链路适合需要开发或优化贴片机视觉控制程序的工程师参考。压缩包总计101个文件体积10.45MB主要文件类型包括C#源码.cs、工程配置.sln/.csproj/.config、可执行程序.exe、Halcon相关DLL与PDB调试文件、部署清单.application/.manifest以及资源文件.resources/.ico等工程结构完整可编译调试并直接查看上下相机对位流程。已有881人学习适合作为SMT视觉项目的入门范例或二次开发蓝本。借助该工程能快速掌握双相机对位、MARK点校正与补偿算法的实际落地方式同时可复用其中的标定工具、贴合流程和控制逻辑有效缩短项目开发周期。1. C#Halcon的SMT贴片机视觉对位先搞定坐标再说抄源码做SMT贴片机或电子件贴合机的人迟早会撞上这个标题。我最早接触这类“C#Halcon模板识别SMT贴片机源程序”的时候以为难点在模板识别把Halcon的匹配参数调了一周结果贴装精度还是忽好忽坏。后来才明白这套东西真正值钱的不是“识别”而是把上下两个相机、吸嘴中心、旋转中心、运动平台这四套坐标用4点校正、2点补偿和贴合补偿算法串成一个闭环。标题里的“源程序”不是为了让你直接编译运行而是给你一条已经跑通的视觉定位链路你抄的是坐标变换的骨架不是某一行识别代码。适合谁看正在做贴装头对位、双相机贴合、或者想把Halcon模板匹配接到运动控制里的工程师。这篇就按我实际调机的顺序从MARK点模板开始把标定、校正、补偿一层层拆开讲坑也一并告诉你。2. Halcon模板识别与MARK点定位从模板制作到亚像素坐标输出2.1 为什么贴片机选Halcon模板识别而不自己写二值化SMT贴片机现场的MARK点并不好认。铜箔反光、油墨颜色深浅、板面上有走线和过孔固定阈值的二值化很容易翻车。Halcon的形状模板Shape Model不是按灰度找而是按轮廓梯度找它天然对亮度变化不敏感。这是它适合贴片机MARK点定位的第一点。第二点是亚像素输出。MARK点中心不能只给到整数像素否则在0.1mm级贴装精度下哪怕0.3mm视场里1个像素也可能对应几微米到几十微米。Halcon的FindScaledShapeModel返回的Row/Column是亚像素浮点数再配合标定参数能直接给运动控制用。第三点是带缩放和旋转。SMT元件贴装高度、板子厚度不同相机到MARK的实际距离会有微小变化MARK在图像里的尺度会变。用带缩放的形式模型ScaledShapeModel能容忍0.8到1.2倍的尺度变化。我一般会把角度范围放开到±22.5度因为PCB上料和固定夹具的方向偏差通常不会超过这个范围。2.2 模板创建与查找的C#最小实现这一节给出能直接放进C#工程的核心段。前提是工程里已经引用了HalconDotNet并且相机图像已经拿到了HObject对象。HObject hoImage null, hoModel null; HOperatorSet.GenEmptyObj(out hoImage); HOperatorSet.GenEmptyObj(out hoModel); // 读一张只包含MARK点的图像灰度图即可 HOperatorSet.ReadImage(out hoImage, mark.bmp); // 创建带缩放的形状模板 HOperatorSet.CreateScaledShapeModel( hoImage, auto, // 金字塔层数自动 -0.39, 0.79, auto, // 角度范围约±22.5度 0.9, 1.1, auto, // 缩放范围0.9~1.1 auto, // 优化方式 use_polarity, // 考虑正负极性 auto, // 对比度自动 out hoModel); HTuple hvRow, hvCol, hvAngle, hvScale, hvScore; HOperatorSet.FindScaledShapeModel( hoImage, hoModel, -0.39, 0.79, // 搜索角度范围与模板一致 0.9, 1.1, // 搜索缩放范围 0.5, // 最小分数 0, // 不限制匹配个数 0.5, // 最大重叠度 least_squares, // 亚像素精度方式 0, // 金字塔层数0表示用模板里的层数 0.8, // 贪婪度 out hvRow, out hvCol, out hvAngle, out hvScale, out hvScore); // 输出亚像素MARK中心 double markRow hvRow[0].D; double markCol hvCol[0].D;逻辑不复杂但有两个地方容易写错。第一CreateScaledShapeModel里的角度范围单位是弧度-0.39到0.79正好是逆时针22.5度到顺时针45度范围内实际项目里角度偏差通常不会超过±22.5度所以这个范围够用。第二FindScaledShapeModel里minScore不要一味追求高飞板子脏污和墨迹会把分数拉低我一般放在0.5到0.7之间如果频繁出现误匹配优先检查模板区域是否包含了噪声边缘。参数说明缩放范围0.9到1.1是给板厚变化留的余量greediness设0.8表示搜索时可以跳过部分候选路径来提速如果发现漏匹配就降到0.5或0。亚像素用“least_squares”输出位置会比“none”稳定很多虽然会多一点耗时但对于贴片机这种固定视觉周期来说多出的0.5ms完全可以接受。2.3 MARK点识别的三个必调参数现场调试顺序我建议先调金字塔层数再调最小分数最后动角度范围。参数常用范围调试方向金字塔层数auto 或 3~5匹配慢就调大匹配错就调小最小分数0.5~0.8误匹配多就抬高漏匹配就降低角度范围±0.39 rad 左右来料角度稳定就缩小不稳定就扩大还有一个容易被忽略的参数“maxOverlap”。如果MARK周围有同类圆形图案比如多个定位孔离得很近需要把maxOverlap调低让两个重叠候选不会被同时返回。否则你取的是第0个匹配可能不是你要的那个MARK。模板制作本身也有玄学。我一般不用整张图做模板而是先在HDevelop里框选MARK圆环区域然后用reduce_domain裁出来最后对模板图像做一次二值化或阈值分割让轮廓更干净。模板区域选得越大匹配越稳但也会把周围无关特征学进去。所以好的模板是“只包含MARK本身轮廓尽量不包含板面线路”。区分好这几点MARK定位基本不会成为整个项目里的瓶颈。3. 相机标定与像素坐标统一把上下两个相机拉到同一套物理坐标3.1 标定板与标定流程有了MARK的像素坐标还不够贴片机要的是物理坐标。只要镜头存在畸变或者相机和运动轴之间有角度安装误差直接用像素比例去算物理坐标在视野边缘能偏出好几个微米甚至几十微米。这就是为什么标题里同时出现相机标定。我自己常用的方案是圆形阵列标定板。流程分三步把标定板放在吸嘴或贴装平台上调整光源保证板上的圆点都能看清。连续采集10到15张图像每张之间改变标定板的位移和旋转尽量让圆点覆盖整个视野。用Halcon的标定助手或直接调用算子先找圆点再做单目标定得到内参和畸变系数。标定板不必是什么高精度进口货但要保证板面平整。如果标定板本身有翘曲标定出来的畸变参数会把翘曲也当成镜头畸变算进去后期怎么调都别扭。我遇到过一次标定板边角翘起视野中间准四边越偏越大重新换了一块平整板子就好了。所以标定板选型的优先级是平整度大于精度。3.2 用Halcon做单目标定的C#关键调用贴片机标定一般不是每次开机都做所以代码里我习惯把相机参数和位姿保存成文件运行时直接读。下面这段是标定执行阶段的C#写法HTuple hvCalibDataID, hvFinalCamPar new HTuple(); // 读取标定板描述文件比如caltab_30mm.descr HOperatorSet.ReadCalibData(out hvCalibDataID, caltab_30mm.descr); // 对采集到的每一张标定图像执行查找标定板 // calibImageIndex 从0开始 for (int idx 0; idx 12; idx) { HOperatorSet.FindCalibObject(hvCalibDataID, 0, 0, idx, new HTuple(), new HTuple()); } // 执行标定输出最终相机参数 HOperatorSet.CalibrateCameras(hvCalibDataID, out hvFinalCamPar); // 保存参数后续直接读取 HOperatorSet.WriteCamPar(hvFinalCamPar, cam_param.dat);说明几点。ReadCalibData第二个参数是标定板描述文件路径里面描述了圆点阵列的物理间距和排列编程序的人必须保证这个文件的尺寸和实际标定板一致否则算出来的焦距全是错的。FindCalibObject的第三个参数是calibration object index一般是0第四个参数是image index我这里是循环代入对应实际拍的每一张图。CalibrateCamPar只能做单目标定如果要同时标定多个相机需要用CalibrateMultiCamera。标定完成后相机参数数组里包含焦距、径向畸变系数、切向畸变系数、像元尺寸和图像中心坐标。真正给视觉定位用的是这个参数数组配合位姿。3.3 像素到物理坐标一行换算避开手工比例尺标定之后像素坐标转物理坐标不要自己去算“每像素多少毫米”应该直接用Halcon的映射算子HTuple hvWorldX, hvWorldY; // hvFinalCamPar 是标定结果参数 // hvPose 是标定板位姿一般由FindCalibObject获得 HOperatorSet.ImagePointsToWorldPlane( hvFinalCamPar, hvPose, 1, // 1表示标定板所在平面 hvRow, hvCol, // 亚像素MARK坐标 1, // 坐标数量 out hvWorldX, out hvWorldY);这样输出的hvWorldX和hvWorldY就是标定板坐标系下的物理坐标单位由标定板描述文件决定通常是毫米。注意这里的前提是MARK和标定板在同一物理平面上。对于贴片机来说上相机照PCB上的MARKPCB面就是标定平面下相机照吸嘴上的元件元件高度和标定板高度不同会在精度上有微弱差异。所以我一般会把标定板的厚度垫到与待测物一致或者用“高度补偿”的方式对物理坐标做Z修正否则只能保证50微米量级的重复精度达不到高精度贴装要求。4. 4点校正、2点补偿与贴合补偿算法实现4.1 4点校正的仿射矩阵怎么算MARK点4点校正解决的是“平台理论坐标”与“视觉实测坐标”之间的系统性偏差。常见做法是让运动平台带着PCB走到某个标准位置分别读取PCB上四个MARK的理论平台坐标再通过相机识别出这四个MARK的实际坐标。理论坐标是“该到的地方”实测坐标是“我看到的地方”两者之间因为机械装配误差、轴正交度误差、视觉坐标和平台坐标的角度差会有一个线性映射。这个映射至少需要3个点确定仿射变换用4个点做最小二乘能顺带把XY方向的比例差异和剪切项也吸收掉。Halcon的VectorToHomMat2d可以直接算// 理论平台坐标单位mm HTuple hvXBase new HTuple(new double[] { 0.0, 100.0, 100.0, 0.0 }); HTuple hvYBase new HTuple(new double[] { 0.0, 0.0, 100.0, 100.0 }); // 实测视觉坐标单位mm必须经过相机标定 HTuple hvXMeas new HTuple(new double[] { 0.05, 100.05, 99.90, -0.10 }); HTuple hvYMeas new HTuple(new double[] { -0.08, -0.04, 100.10, 100.06 }); HTuple hvHomMat2D; HOperatorSet.VectorToHomMat2d(hvXBase, hvYBase, hvmXMeas, hvmYMeas, out hvHomMat2D); // 以后任何一个视觉像素坐标换算成物理坐标后都用这个矩阵转成平台坐标 HTuple qx, qy; HOperatorSet.AffineTransPoint2d(hvHomMat2D, 50.0, 50.0, out qx, out qy);代码里我故意把实测坐标写得和理论坐标有一点点差值模拟真实机械偏差。VectorToHomMat2d要求至少3对点4对点是最稳的。求出来的矩阵包含旋转、平移、缩放和剪切。注意这里输入的一定是先经过相机标定转为物理坐标的数值不能直接喂像素坐标。如果项目里只关心纯平移和旋转可以用VectorToRigid但贴片机平台一般存在X轴和Y轴的垂直度误差所以我还是推荐直接用单应矩阵。4.2 2点补偿小角度偏差的旋转补偿2点补偿通常用在只有两个MARK点或者不想做完整4点校正的场景。它的数学很简单用两个MARK的理论角度减去实测角度得到角度偏差再把该偏差补偿到旋转轴上。double angleBase Math.Atan2(baseY2 - baseY1, baseX2 - baseX1) * 180 / Math.PI; double angleMeas Math.Atan2(measY2 - measY1, measX2 - measX1) * 180 / Math.PI; double compAngle angleBase - angleMeas; // 把角度回绕到-180~180 while (compAngle 180) compAngle - 360; while (compAngle -180) compAngle 360;这个角度就是旋转轴需要补转的量。实际贴装时先做4点校正得到XY位置映射再做2点补偿修正角度顺序不能反。如果先转角度再做XY校正XY校正矩阵里的旋转分量就会重复叠加。标题里“4点校正2点补偿”的先后顺序我理解就是先用4点把静态线性映射校准再用2点把来料角度偏差补掉各干各的活。2点补偿的局限性也很明显只能校准平移和旋转不能校准XY比例差异和剪切变形。如果板子本身涨缩比如PCB受潮或拼板张力不一致两个点不够必须用4点甚至更多点去做非线性校正。4.3 贴合补偿算法上下双相机对位贴合的坐标变换这部分是标题里最值钱的一段。上下双相机对位贴合我常见的设备结构是上相机固定在Z轴或贴装头上跟随贴装头一起运动负责向下寻找PCB上的MARK或焊盘目标下相机固定在设备底板上镜头朝上负责拍吸嘴上的元件。贴装头吸取元件后先经过下相机让系统知道元件在吸嘴上的位置和角度偏了多少然后上相机识别PCB上目标位置最后运动控制系统根据两边数据算出最终贴装坐标。完整坐标变换我习惯拆成四个量下相机给出的“元件实际中心”相对于“吸嘴理论中心”的偏移记为(dCompX, dCompY)。这是吸取偏差。下相机给出的“元件实际角度”相对于“吸嘴角度”的偏差记为compAngle。这是吸取角度偏差。上相机识别出的PCB目标点在平台坐标系里的位置。由于上相机和运动轴的关系需要通过4点校正标定所以这里要把像素坐标先转物理坐标再用4点校正矩阵转成平台坐标。旋转中心补偿。如果设备上旋转轴不在吸嘴正中心每次转角度都会牵引圆心变化。这时要把旋转中心的偏移量也加进补偿里。核心代码段如下// 1. 上相机拍到的目标点经过标定转物理坐标 HOperatorSet.ImagePointsToWorldPlane(camParam, poseTarget, 1, targetRow, targetCol, 1, out wTargetX, out wTargetY); // 2. 4点校正把物理坐标转成平台坐标 HOperatorSet.AffineTransPoint2d(hvHomMatBoard, wTargetX, wTargetY, out platformX, out platformY); // 3. 下相机给出的元件吸取偏移 double deltaX compCenterX - nozzleCenterX; // 注意方向符号 double deltaY compCenterY - nozzleCenterY; // 4. 旋转角度补偿后的附加位移 double theta compAngle * Math.PI / 180.0; double rotateCompX rotationCenterOffsetX * Math.Cos(theta) - rotationCenterOffsetY * Math.Sin(theta); double rotateCompY rotationCenterOffsetX * Math.Sin(theta) rotationCenterOffsetY * Math.Cos(theta); // 5. 最终贴装位置 double placeX platformX deltaX rotateCompX; double placeY platformY deltaY rotateCompY; double placeAngle targetAngle compAngle;这里最容易被坑的是符号方向。下相机的图像坐标系和平台坐标系的镜像关系决定了deltaX和deltaY到底该加还是该减。我的习惯是在写程序之前先用一个已知偏移的元件做一次“空跑实验”贴装头吸一个元件故意不纠正任何偏差直接贴到目标点看最终贴装结果往哪个方向偏再把符号改过来。这事靠看代码是看不出来的因为每台设备的相机方向、轴方向都不一样硬背别人的符号一定会翻车。贴合补偿算法不是单一公式它是一整套“透视投影线性校正旋转补偿”的串联。调机时如果发现某些角度正常某些角度偏基本就是旋转中心补偿没做或没做对。旋转中心偏移量不是固定不变的它随吸嘴更换、Z轴高度变化而变化所以严谨的做法是每次换吸嘴后重新测一次旋转中心。5. 上下双相机对位贴合常见问题排查与避坑5.1 现象MARK坐标天天漂模板找得到但位置偏半格这个坑我踩过很长时间。模板分数很稳定每次都能找到但MARK中心算出来会有一个像素左右的波动。后来发现是相机自动曝光没关现场照明跳变时曝光时间自动补偿导致图像灰度变化虽然模板匹配能找对但亚像素拟合的位置却跟着灰度偏移了。解决把相机曝光、增益全部固定成手动模式光源用恒流驱动并且把相机触发和光源频闪同步到同一个外部触发信号上。如果波动还比较大就在软件层面对同一个MARK连续取三帧做平均。5.2 现象标定之后中间准、边缘偏越靠视野边缘越离谱这是典型的标定图像覆盖范围不足。很多人标定时只把标定板放在视野中间拍几张标定算法对边缘畸变的约束就不够边缘误差自然大。另外标定板的平整度也会直接影响这个现象板子边缘翘起来标出来的畸变参数是假的。解决标定板放在各个位置拍照让圆点覆盖到视野四角和中心同时检查标定板本身有没有鼓包如果板子变形换新板子重新采集。还有镜头畸变系数选型工业相机配小畸变镜头不代表零畸变标定过程一定不能省。5.3 现象上下相机换算出来的坐标同一个目标点总是差一个固定角度这说明上下相机的图像坐标系之间有旋转偏差不是XY偏移。单纯依赖4点校正无法消除固定角度偏差因为4点校正作用于单个相机。上相机和下相机各拍同一个物体识别出来的角度不一样两者之间有一个由安装姿态决定的固定旋转角。解决做一个上下相机的“手眼标定”或“对位标定”。具体做法是放一个特征明显的平板上相机和下相机分别拍同一个MARK记录角度差把这个角度差作为系统常量写入。换相机或动过相机支架后重测。5.4 现象补偿后贴装位置来回摆像过冲一样振荡顺环补偿看起来逻辑没错但机械和视觉有响应延迟。如果每次视觉算出来的偏差直接全部发给运动控制运动轴会在这个闭环里产生振荡尤其在小惯量高速贴装时特别明显。解决对补偿量做低通滤波最简单的就是用一阶惯性比如newDelta 0.3 * currentDelta 0.7 * lastDelta或者限制单步最大补偿增量比如每帧最多补偿0.05mm。调参时先让补偿量变小从稳定跑到轻微欠补偿再逐步加大比例。5.5 现象模板分数很高但MARK中心对不上焊盘这个坑容易发生在用“十字形”MARK或非对称MARK上。Halcon返回的是模板原点的位置不是MARK几何中心。如果模板制作时选的参考点不对分数再高也白搭。解决制作模板前先用area_center或moments_region_2nd计算MARK区域的中心把这个中心作为模板坐标系原点或者直接把模板Region中心设为参考点。做完后一定要在HDevelop里叠加显示模板轮廓和识别结果确认返回点落在MARK中心上。6. 进阶把这套补偿精度验证成一页报告的小脚本项目交付时不能只说“能贴”。我习惯写一个验证脚本让设备自动重复识别同一个MARK 20次统计坐标重复性再把结果按3σ输出成报告。这样给到机械和工艺同事大家能在同一个数据上对话。Listdouble rows new Listdouble(); Listdouble cols new Listdouble(); for (int i 0; i 20; i) { // 触发相机这里省略相机取流细节 HOperatorSet.FindScaledShapeModel(hoImage, hoModel, -0.39, 0.79, 0.9, 1.1, 0.5, 0, 0, least_squares, 0, 0.8, out hvRow, out hvCol, out hvAngle, out hvScale, out hvScore); rows.Add(hvRow[0].D); cols.Add(hvCol[0].D); } double avgRow rows.Average(); double avgCol cols.Average(); double sigma Math.Sqrt(rows.Sum(r (r - avgRow) * (r - avgRow)) / (rows.Count - 1)); Console.WriteLine($Row 3σ {3 * sigma:F4} px); // 结合标定结果的物理比例把像素3σ转成毫米除了统计重复性还要做“全行程校验”在平台上分散取9个点每个点用4点校正后的坐标去跑测量实际平台坐标和目标坐标的残差这个残差能直接反映出4点校正在整个工作区域内的有效性。我现在的习惯是每次改完视觉参数或换完相机先跑一遍20次重复定位再跑一遍9点残差两次都合格才允许进入贴装测试。这套习惯帮我挡掉过好几次“看似正常、批量偏位”的尴尬现场也省了很多和机械扯皮的功夫。写这套C#Halcon的对位程序重要的不是把别人的源码拷过来而是自己把坐标变换的每一条链路写在纸上再按上面的验证方法逐步确认。记住所有补偿算法在最终设备上都要过一遍“符号反转”这一关不要盲目相信教程里的正负号。希望帮到你。本文还有配套的精品资源点击获取