☰
C#+Halcon上下双相机对位贴合:标定、MARK点校正与补偿算法实战
2026/10/2 14:43:11 网站建设 项目流程

简介:这份资源面向自动化视觉与SMT贴片机方向的C#开发者及机器视觉工程师,聚焦模板识别、相机标定与对位贴合等核心环节,提供一套可参考的工程级源程序。包内共101个文件,以cs源码、exe可执行程序、dll动态库、config配置、manifest与application清单、pdb调试符号及cache缓存等为主,另有sln解决方案、csproj工程文件与少量txt说明,压缩包约10.45MB,结构贴近实际Visual Studio项目。内容覆盖Halcon模板匹配、MARK点4点校正与2点补偿、贴合补偿算法,以及上下双相机对位贴合流程,可帮助读者理解相机标定、定位校正与偏差修正的代码实现思路。目前已有876人学习下载,适合需要参考完整视觉对位方案、排查标定与贴合精度问题的中高级开发者。

1. 从一台 SMT 贴片机的上下双相机说起:这套 C# + Halcon 方案到底解决什么问题

SMT 贴片机在贴装 QFN、BGA、连接器这类元件时,最怕的不是贴不上,而是贴偏。元件从吸嘴取料那一刻起,就带着来料偏差、吸嘴同心度误差、飞行过程中的微小位移;到了 PCB 上方,板子本身还有 MARK 点定位误差、涨缩变形。如果只靠机械定位,0402 电阻或许还能凑合,0.4mm pitch 的 IC 基本就是批量翻车。这套「C# + Halcon 模板识别 + 上下双相机对位贴合」的方案,核心就是用视觉把偏差量测出来,再用贴合补偿算法把偏差补回去。

它适合谁?做 SMT 设备上位机、半导体固晶机、贴合类自动化设备的 C# 工程师,尤其是已经会用 Halcon 做单相机定位、但没系统做过「上相机看元件 + 下相机看 PCB + 双相机坐标统一 + 补偿下发」这条完整链路的人。整套逻辑拆开看是四块:相机标定把像素换算成毫米,MARK 点 4 点校正建立 PCB 坐标系,2 点补偿修正旋转与平移残差,贴合补偿算法算出最终贴装坐标。下面按落地顺序一层层讲透。

2. 相机标定与上下双相机坐标系统一:像素怎么变成毫米

2.1 为什么标定是整套方案的地基

Halcon 里做模板识别,find_shape_model返回的是像素坐标和角度,但贴片机执行机构要的是毫米和度。这中间隔着一个标定矩阵。标定不准,后面 MARK 点校正、补偿算法全是白算——这就是典型的「地基歪了楼越高越危险」。常见做法是先用标定板(圆点阵列或棋盘格)标出相机的内参和畸变,再标出像素当量(mm/pixel)。对于贴片机这种工作距离固定、视野固定的场景,其实不需要每次都做完整内参标定,用九点标定求一个仿射矩阵就够了,速度快、现场可复现。

九点标定的本质是求一个 2x3 的仿射变换矩阵,把像素坐标 (u,v) 映射到机械坐标 (x,y)。Halcon 提供vector_to_hom_mat2d,输入九组像素点和对应的机械点,直接解出矩阵。这里有个血泪经验:九点一定要覆盖整个视野,别图省事只在中心打九个密集点,边缘的畸变残差会全部漏掉,贴到板边就偏。

// C# 调用 Halcon 做九点标定,求像素到机械的仿射矩阵 HTuple pixelRows = new HTuple(new double[] { 100, 300, 500, 100, 300, 500, 100, 300, 500 }); HTuple pixelCols = new HTuple(new double[] { 100, 100, 100, 300, 300, 300, 500, 500, 500 }); // 对应的机械坐标,单位 mm,由运动平台实际走到每个点后记录 HTuple mechX = new HTuple(new double[] { 10.0, 30.0, 50.0, 10.0, 30.0, 50.0, 10.0, 30.0, 50.0 }); HTuple mechY = new HTuple(new double[] { 10.0, 10.0, 10.0, 30.0, 30.0, 30.0, 50.0, 50.0, 50.0 }); HTuple homMat2D; HOperatorSet.VectorToHomMat2d(pixelRows, pixelCols, mechX, mechY, out homMat2D); // 后续把任意像素点转机械坐标 HTuple mechPointX, mechPointY; HOperatorSet.AffineTransPoint2d(homMat2D, new HTuple(320.0), new HTuple(240.0), out mechPointX, out mechPointY);

这段代码的关键在VectorToHomMat2d的参数顺序:前两组是像素的行列,后两组是机械的 XY,顺序不能错,错了矩阵就是转置的,贴出来会镜像。AffineTransPoint2d是单点转换,批量转换用AffineTransPoint2d循环或AffineTransContourXld。参数上,九点的机械坐标必须用激光干涉仪或光栅尺读出的真实值,不能用手轮刻度估,估出来的当量误差会直接放大到贴装精度上。

2.2 上下双相机怎么统一到一个坐标系

上相机朝下看吸嘴上的元件,下相机朝上看 PCB 上的 MARK 点,两个相机物理位置不同,必须统一到同一个机械坐标系。常见做法是:先各自做九点标定,得到两个仿射矩阵;再找一个公共基准——通常用吸嘴吸一个标准件,让上相机和下相机分别看同一个特征点,记录两次机械坐标,求出两个相机坐标系之间的平移和旋转关系。

这里有个容易忽略的点:上下相机的光轴不平行会引入角度偏差。如果两个相机安装时没有严格平行,公共基准求出来的旋转量会随高度变化,贴不同厚度元件时补偿量就不一样。我一般会在标定时用两个不同高度的标准件各测一次,如果两次求出的旋转角差超过 0.05 度,就说明相机安装有倾斜,得先调机械再标。

// 求上下相机坐标系之间的变换:上相机坐标 -> 下相机坐标 // 用同一物理点在两个相机下的机械坐标各测 3 组以上 HTuple upCamX = new HTuple(new double[] { 20.1, 40.2, 60.3 }); HTuple upCamY = new HTuple(new double[] { 15.0, 15.1, 15.2 }); HTuple downCamX = new HTuple(new double[] { 20.0, 40.0, 60.0 }); HTuple downCamY = new HTuple(new double[] { 15.0, 15.0, 15.0 }); HTuple camToCamMat; HOperatorSet.VectorToHomMat2d(upCamX, upCamY, downCamX, downCamY, out camToCamMat); // camToCamMat 即上相机机械坐标到下相机机械坐标的映射

参数说明:VectorToHomMat2d至少需要 3 组不共线的点,实际建议 5 组以上取平均,减少单点噪声。求出的camToCamMat包含旋转和平移,后续上相机测到的元件偏差要先经这个矩阵转到下相机坐标系,再和 PCB 的 MARK 点坐标做运算。这一步顺序搞反,补偿方向就全反了。

3. MARK 点 4 点校正与 2 点补偿:PCB 坐标系怎么建才稳

3.1 4 点校正为什么比 2 点更抗涨缩

PCB 在回流焊前后会有涨缩,尤其是大板。用 2 个 MARK 点只能求出平移和旋转,遇到板子 X 方向和 Y 方向涨缩不一致时,贴到板子另一头就偏了。4 点校正的做法是在板子四角各取一个 MARK 点,用 Halcon 的find_shape_model或find_marks_and_pose找到四个点,然后拟合出一个带缩放因子的仿射变换,甚至可以用透视变换vector_to_proj_hom_mat2d来吸收梯形变形。

Halcon 里做 MARK 点识别,模板要选对比度高、形状独特的区域,别选圆形焊盘——圆形在find_shape_model里旋转不变性差,容易和过孔混淆。常见做法是用十字标记或 L 形标记,find_shape_model的AngleStart和AngleExtent设成 ±5 度就够,贴片机 PCB 定位不会有大角度旋转,设太大反而增加误匹配。

// Halcon 识别 4 个 MARK 点并建立 PCB 坐标系 HObject ho_Image, ho_Region; HTuple hv_ModelID; // 读取预先训练好的 MARK 点模板 HOperatorSet.ReadShapeModel("mark_model.shm", out hv_ModelID); // 在整幅图中找所有 MARK 点,Score 阈值设 0.7 过滤误匹配 HTuple hv_Row, hv_Column, hv_Angle, hv_Score; HOperatorSet.FindShapeModel(ho_Image, hv_ModelID, -0.09, 0.18, 0.7, 1, 0.5, "least_squares", 0, 0.9, out hv_Row, out hv_Column, out hv_Angle, out hv_Score); // 假设已按顺序取到 4 个点,用它们建立到标准 PCB 坐标的投影变换 HTuple pcbRow = new HTuple(new double[] { 100, 100, 900, 900 }); HTuple pcbCol = new HTuple(new double[] { 100, 900, 100, 900 }); HTuple stdX = new HTuple(new double[] { 0, 50, 0, 50 }); HTuple stdY = new HTuple(new double[] { 0, 0, 40, 40 }); HTuple projMat; HOperatorSet.VectorToProjHomMat2d(pcbRow, pcbCol, stdX, stdY, out projMat);

参数说明:FindShapeModel的MinScore设 0.7 是经验值,光照稳定可以降到 0.6,光照波动大就提到 0.8。NumMatches设 1 表示每个模板只找一个,但 4 个 MARK 点如果模板相同,需要设成 4 或者用不同模板分别找。Greediness设 0.9 是速度优先,精度要求高可以降到 0.7。VectorToProjHomMat2d求出的投影矩阵能吸收梯形变形,比仿射矩阵更适合大板。

3.2 2 点补偿补的是什么

4 点校正建立了 PCB 的整体坐标系,但实际贴装时还有残差:吸嘴取料时的同心度偏差、元件在吸嘴上的微小转动、飞行过程中的偏移。这些残差用 2 点补偿来修。做法是在 PCB 上选两个已知坐标的基准点,贴装头带着元件分别移到这两个点上方,用上相机看元件、下相机看基准点,算出实际偏差量,然后把这个偏差量按线性关系补偿到所有贴装点。

2 点补偿的数学本质是求一个平移加旋转的修正量。假设两个基准点的理论坐标是 P1、P2,实测偏差是 d1、d2,那么任意贴装点 P 的补偿量可以用线性插值算出来。这里的关键是两点要尽量拉开距离,距离越远,旋转修正的灵敏度越高。如果两点只隔 10mm,旋转 0.1 度产生的偏差只有 0.017mm,根本测不出来。

// 2 点补偿:根据两个基准点的实测偏差,计算任意点的补偿量 // 理论坐标 double x1 = 0, y1 = 0, x2 = 50, y2 = 0; // 实测偏差(实测坐标 - 理论坐标) double dx1 = 0.02, dy1 = -0.01, dx2 = 0.05, dy2 = 0.03; // 求补偿的平移和旋转 double dxAvg = (dx1 + dx2) / 2.0; double dyAvg = (dy1 + dy2) / 2.0; double dTheta = Math.Atan2(dy2 - dy1, (x2 + dx2) - (x1 + dx1)) - Math.Atan2(y2 - y1, x2 - x1); // 对任意贴装点 (px, py) 计算补偿后坐标 double px = 25, py = 20; double cx = px + dxAvg - dTheta * (py - y1); double cy = py + dyAvg + dTheta * (px - x1);

参数说明:dTheta是旋转修正量,单位弧度,计算时要用实测坐标而不是理论坐标,否则旋转量会算偏。补偿公式里-dTheta * (py - y1)和+dTheta * (px - x1)是旋转矩阵的小角度近似,角度小于 1 度时误差可忽略,大于 1 度建议用完整旋转矩阵。实际工程中,2 点补偿通常只补平移和旋转,缩放交给 4 点校正,两者分工明确。

4. 贴合补偿算法与上下双相机对位流程:从识别到下发

4.1 贴合补偿算法的完整计算链

贴合补偿算法不是单一公式,而是一条计算链:上相机测元件偏差 → 转到下相机坐标系 → 叠加 PCB MARK 点偏差 → 叠加 2 点补偿残差 → 输出最终贴装坐标。这条链上每一步的坐标系必须统一,否则补偿量会互相抵消或叠加错误。我一般会在代码里把每一步的中间结果都打日志,出问题时能快速定位是哪一步算错了。

上相机测元件偏差,用find_shape_model找到元件上的特征,和标准位置比,得到 dx、dy、dtheta。这里要注意元件的旋转中心:如果元件不是绕几何中心旋转,dtheta 引起的偏移量要用旋转中心到特征点的距离来算,不能直接用元件中心。常见做法是在模板训练时就把旋转中心定义在吸嘴中心,这样 dtheta 直接就是元件相对吸嘴的转角。

// 贴合补偿完整计算:上相机元件偏差 + PCB MARK 偏差 + 2点补偿 // 上相机测得的元件偏差(已转到下相机坐标系) double compDx = 0.03, compDy = -0.02, compDtheta = 0.001; // PCB MARK 点算出的板子偏差 double pcbDx = 0.01, pcbDy = 0.015, pcbDtheta = 0.0005; // 2 点补偿残差 double residualDx = 0.005, residualDy = -0.003; // 最终补偿量 = 元件偏差 + 板子偏差 + 残差 double finalDx = compDx + pcbDx + residualDx; double finalDy = compDy + pcbDy + residualDy; double finalDtheta = compDtheta + pcbDtheta; // 贴装点理论坐标 double targetX = 100.0, targetY = 80.0; // 最终下发坐标 double pasteX = targetX - finalDx; double pasteY = targetY - finalDy; double pasteTheta = finalDtheta;

参数说明:补偿量的符号很关键。如果定义偏差是「实测 - 理论」,那么下发坐标应该是「理论 - 偏差」,因为要把元件往偏差的反方向推。这个符号搞反是新手最常见的翻车点,贴出来会偏两倍偏差量。finalDtheta直接作为贴装角度增量下发,但要注意旋转方向:Halcon 的角度是逆时针为正,机械旋转如果是顺时针为正,要取反。

4.2 上下双相机对位的执行时序

上下双相机对位不是同时拍照,而是有严格时序:吸嘴取料 → 上相机拍照测元件 → 运动到 PCB 上方 → 下相机拍照测 MARK 点 → 计算补偿 → 贴装。这个时序里,上相机拍照时吸嘴要旋转到贴装角度,这样测出的元件角度才是最终贴装姿态下的角度。如果上相机拍照时吸嘴没转到位,测出的 dtheta 还要叠加吸嘴旋转量,容易出错。

下相机拍照测 MARK 点时,要注意相机的曝光和光源。PCB 上的 MARK 点通常是铜箔或镀金,反光特性差异大。常见做法是用同轴光或环形光,曝光时间设到 MARK 点清晰、背景不过曝。如果 MARK 点识别不稳定,先调光源和曝光,别急着改算法参数。我见过太多人一上来就调MinScore,结果把误匹配调进来了。

// 上下双相机对位执行时序伪代码 // 1. 吸嘴取料后旋转到贴装角度 nozzle.RotateTo(pasteTheta); // 2. 上相机拍照,测元件偏差 HObject upImage = upCamera.Capture(); double dx, dy, dtheta; MeasureComponent(upImage, out dx, out dy, out dtheta); // 3. 运动到 PCB 上方,下相机拍照测 MARK 点 HObject downImage = downCamera.Capture(); double pcbDx, pcbDy, pcbDtheta; MeasureMarkPoints(downImage, out pcbDx, out pcbDy, out pcbDtheta); // 4. 计算最终补偿并下发 double finalX = targetX - (dx + pcbDx + residualDx); double finalY = targetY - (dy + pcbDy + residualDy); motion.Paste(finalX, finalY, pasteTheta + dtheta + pcbDtheta);

参数说明:nozzle.RotateTo要在上相机拍照前完成,且要等旋转稳定后再拍照,否则会有运动模糊。MeasureComponent里用find_shape_model时,AngleStart和AngleExtent要覆盖吸嘴旋转后的角度范围。motion.Paste的坐标是机械坐标,不是像素坐标,中间所有转换都要用标定矩阵。实际工程中,这一步的耗时主要在运动和数据传输,视觉算法本身在 Halcon 里通常只要几毫秒。

5. 避坑与排查:这套方案最容易翻车的 5 个地方

5.1 标定矩阵用错坐标系导致补偿方向反

现象:贴装后元件偏差不但没减小,反而变成两倍,方向还反了。原因:标定矩阵的输入输出坐标系定义混乱,比如把像素行列当成 XY,或者把机械坐标的 XY 和图像坐标的 XY 搞反。解决:在标定代码里加断言,用已知点验证矩阵,比如把标定用的九点重新代入矩阵,看输出是否和机械坐标一致,误差超过 0.01mm 就说明矩阵有问题。

5.2 MARK 点识别不稳定导致偶发贴偏

现象:大部分板子贴得准,偶尔一两块偏,且偏移量随机。原因:MARK 点识别受光照、板面污染影响,MinScore设太低导致误匹配到过孔或焊盘。解决:先固定光源和曝光,把MinScore提到 0.8 以上,同时用find_shape_model的NumMatches限制匹配数量,再对匹配结果做几何校验——4 个 MARK 点的相对距离应该和标准值一致,偏差超过 0.1mm 就判为识别失败,报警而不是硬贴。

5.3 上下相机坐标系转换漏了旋转量

现象:元件在视野中心时贴得准,在视野边缘就偏,且偏移方向随位置变化。原因:上下相机坐标系转换只做了平移,没做旋转,或者旋转量算错。解决:用至少 5 组公共基准点求VectorToHomMat2d,检查求出的矩阵旋转部分是否接近单位矩阵,如果旋转量超过 0.1 度,说明相机安装有倾斜,先调机械。转换时用完整矩阵,别只取平移量。

5.4 2 点补偿的两点距离太近

现象:旋转偏差补不回来,元件角度总是差一点。原因:两个补偿基准点距离太近,旋转修正的灵敏度不够,测量噪声淹没了真实旋转量。解决:两点距离至少拉到视野的 1/2 以上,最好在板子对角。如果板子太小,改用 4 点补偿,用最小二乘拟合旋转量,比 2 点更稳。

5.5 Halcon 模板训练时旋转中心设错

现象:元件旋转后,贴装位置绕着一个错误的中心偏。原因:模板训练时旋转中心默认在模板区域中心,但实际元件旋转中心在吸嘴中心,两者不重合。解决:训练模板时用set_shape_model_origin把原点设到吸嘴中心对应的图像位置。如果不知道吸嘴中心在图像里的位置,先用一个标准件吸在吸嘴上,拍照后手动标定原点。

6. 把补偿量做成可追溯的日志:一个让我少加很多班的小技巧

这套方案跑通之后,真正让我省心的不是算法多精妙,而是把每一步的补偿量都写进了日志。贴片机是 24 小时跑的,出问题往往在半夜,现场操作员说不清现象,我第二天到现场只能靠日志复盘。我的做法是每贴一块板,就把上相机偏差、MARK 点偏差、2 点残差、最终下发坐标全部写进 CSV,字段包括时间戳、板序号、元件位号、各项偏差量、补偿后坐标。

// 补偿量追溯日志,每贴一个元件写一行 string logPath = @"D:\smt_log\compensation.csv"; using (StreamWriter sw = new StreamWriter(logPath, true, Encoding.UTF8)) { // 表头只在文件新建时写一次,实际工程中先判断文件是否存在 sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}," + $"{boardIndex},{componentRef}," + $"{compDx:F4},{compDy:F4},{compDtheta:F5}," + $"{pcbDx:F4},{pcbDy:F4},{pcbDtheta:F5}," + $"{residualDx:F4},{residualDy:F4}," + $"{pasteX:F4},{pasteY:F4},{pasteTheta:F5}"); }

这段代码本身很简单,价值在字段设计。compDx和pcbDx分开记,出问题时能一眼看出是元件识别偏了还是板子定位偏了。residualDx单独记,如果它持续变大,说明 2 点补偿的基准点该重新标了。pasteX和pasteY是最终下发值,和实际贴装结果对比,能算出这套补偿算法的真实残差。

有了日志之后,我还加了一个简单的统计:每 100 块板算一次各项偏差的均值和标准差。均值反映系统性偏差,比如标定不准;标准差反映随机误差,比如识别不稳定。如果某个元件的compDtheta标准差突然变大,多半是吸嘴磨损或者元件来料一致性变差,提前换吸嘴比批量贴废再返工划算得多。

这个习惯说起来不起眼,但它把「玄学调机」变成了「看数据说话」。贴片机这行,经验很重要,但经验如果不落成数据,换个人、换台机器就得重新踩一遍坑。我现在做新项目,第一版代码可以糙,但日志字段一定先设计好,后面省下的排查时间远超写日志的那点功夫。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询