简介:面向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上的MARK,PCB面就是标定平面;下相机照吸嘴上的元件,元件高度和标定板高度不同,会在精度上有微弱差异。所以我一般会把标定板的厚度垫到与待测物一致,或者用“高度补偿”的方式对物理坐标做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σ输出成报告。这样给到机械和工艺同事,大家能在同一个数据上对话。
List<double> rows = new List<double>(); List<double> cols = new List<double>(); 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的对位程序,重要的不是把别人的源码拷过来,而是自己把坐标变换的每一条链路写在纸上,再按上面的验证方法逐步确认。记住,所有补偿算法在最终设备上都要过一遍“符号反转”这一关,不要盲目相信教程里的正负号。希望帮到你。
本文还有配套的精品资源,点击获取