简介:本资源面向工业机器人与机器视觉方向的C#开发者及自动化工程师,提供一套完整的9点标定与串口通讯上位机方案。包内包含Halcon标定程序、C#联合Halcon的源码工程,以及向机器人发送数据的串口通讯模块,同时集成用户权限登录与皮肤美化界面,适合需要快速搭建视觉标定与设备联调原型的初中级开发者参考。资源共577个文件,以385个ico图标、73个ssk皮肤资源、29个cs源码文件为主,另含tif、bmp、png等标定与测试图片,以及exe、dll、hdev、sln、csproj等可执行与工程文件,压缩包约50.25MB,目录结构完整,便于直接编译运行与二次开发。目前已有137人学习下载。通过该资源可掌握9点标定流程、Halcon与C#混合编程接口、串口数据收发及界面美化等实用技能,并获取可复用的工程模板与排错思路。
1. 9点标定 + Halcon + C#:一套能落地的工业机器人视觉定位方案
产线上机械手抓件偏了 2 毫米,整批料就报废——这是很多做 3C、五金、注塑上下料的工程师都遇到过的场景。9点标定就是解决这件事的核心手段:让相机看到的像素坐标,和机器人手里的毫米坐标对上号。而Halcon负责把标定板圆心、Mark 点、边缘这些特征稳定提取出来,C#负责把算法、串口通讯、界面串成一个操作工能用的上位机。这套组合在工业机器人视觉引导里非常常见,尤其是搭配RS485 串口通讯控制机械手或 PLC 的场合。
这篇文章不讲空理论,我按自己做过几套上下料设备的经验,把 9 点标定的数学原理、Halcon 算子怎么选、C# 怎么封装、串口怎么和机器人握手、界面怎么做得不像半成品,一步步拆开。适合两类人:刚接触机器视觉、想跑通第一套标定程序的新手;以及做过单点标定、但被旋转偏差和重复精度坑过的熟手。看完你应该能自己搭出一套可复现的 9 点标定上位机。
2. 9点标定的数学底子:为什么仿射变换能扛住旋转和缩放
2.1 从像素到毫米,中间到底发生了什么
相机拍到的图是像素坐标系,原点在左上角,X 向右、Y 向下,单位是 pixel。机器人走的是世界坐标系,原点在基座或工件台,单位是 mm,方向由机械结构决定。两者之间差了一个二维仿射变换:
[x_mm] [a b c] [x_px] [y_mm] = [d e f] [y_px] [ 1 ]其中 a、b、d、e 管旋转和缩放,c、f 管平移。6 个未知数,理论上 3 个不共线的点就能解出来。那为什么工业上普遍用 9 点而不是 3 点?因为 3 点对像素噪声零容忍,一个圆心提取偏 0.5 像素,整个变换就歪了。9 点用最小二乘拟合,把随机误差摊平,重复精度能稳一个数量级。
9 点的排布也有讲究。常见做法是 3×3 网格,间距取视野的 1/3 到 1/2,覆盖相机工作区域。点太挤,标定出来的变换在边缘外推时误差爆炸;点太散,超出视野又提不到。我一般让 9 个点均匀铺满视野的 70% 左右,留边给镜头畸变。
2.2 Halcon 里做标定的两条路线:vector_to_hom_mat2d 与手眼标定
Halcon 提供两种思路。第一种是纯 2D 仿射,用vector_to_hom_mat2d,输入 9 组像素坐标和对应的机器人坐标,直接吐出 HomMat2D。这条路简单、快,适合相机固定、工件在同一平面上的场景,也就是常说的 Eye-to-Hand 平面抓取。
第二种是完整手眼标定,用calibrate_hand_eye,需要相机内参、标定板位姿、机器人末端位姿,能解出相机和机器人基座的三维关系。这条路精度更高,但流程重,要采集十几组不同姿态。对于 2D 上下料,绝大多数情况第一条路就够了,别一上来就上三维标定,纯属给自己找麻烦。
核心算子调用长这样:
* Halcon 伪代码,展示 9 点标定的核心流程 * 1. 提取 9 个标定点在图像中的像素坐标 find_calib_object (Image, CalibDataID, ...) get_calib_data_observ_points (CalibDataID, 0, 0, 'all', Row, Column, ...) * 2. 机器人端记录的 9 个物理坐标(mm) RobotX := [100.0, 150.0, 200.0, 100.0, 150.0, 200.0, 100.0, 150.0, 200.0] RobotY := [100.0, 100.0, 100.0, 150.0, 150.0, 150.0, 200.0, 200.0, 200.0] * 3. 求解仿射变换矩阵 vector_to_hom_mat2d (Column, Row, RobotX, RobotY, HomMat2D) * 4. 用矩阵把任意像素点转成机器人坐标 affine_trans_point_2d (HomMat2D, PixelCol, PixelRow, WorldX, WorldY)逻辑说明:vector_to_hom_mat2d的输入顺序是「像素 X、像素 Y、物理 X、物理 Y」,注意 Halcon 里图像坐标是 Row(行,对应 Y)在前、Column(列,对应 X)在后,很多人第一次写反,标定出来直接镜像。affine_trans_point_2d是正向变换,把像素点映射到物理坐标,实际引导时就用它算抓取点。
参数说明:vector_to_hom_mat2d没有可调参数,它内部就是最小二乘。真正影响精度的是输入点的质量——9 个点的像素坐标必须来自同一张图或同一批稳定光照下的图,物理坐标必须是机器人实际走到该点时的编码器读数,不能用手册上的理论值。
2.3 标定精度的自检:残差和回代验证
标定完不能直接用,得先验证。两个指标:一是残差,把 9 个像素点用求出的矩阵反算回物理坐标,和机器人记录值比,最大偏差应小于 0.1 mm;二是回代,让机器人走到一个没参与标定的第 10 点,相机算出的坐标和机器人实际坐标比,偏差在 0.2 mm 内算合格。
* 残差自检 affine_trans_point_2d (HomMat2D, Column, Row, EstX, EstY) ResidualX := RobotX - EstX ResidualY := RobotY - EstY MaxErr := max([abs(ResidualX), abs(ResidualY)]) * MaxErr 超过 0.1 就说明标定点质量有问题,重拍如果残差大,先查是不是某个点提偏了,把该点剔除重算,或者检查机器人记录的坐标是不是有编码器滞后。这一步是血泪经验,跳过自检直接上产线,翻车概率极高。
3. C# 上位机怎么把 Halcon 和串口通讯缝在一起
3.1 Halcon 在 C# 里的调用方式与 license 处理
Halcon 给 .NET 提供的是halcondotnet.dll,在项目里引用后,命名空间是HalconDotNet。核心类有HImage(图像)、HRegion(区域)、HTuple(通用数组)、HObject(基类)。C# 里调 Halcon 算子,基本就是把 Halcon 脚本翻译成方法调用。
using HalconDotNet; // 读取图像并做阈值分割,提取标定点 HImage image = new HImage("calib_09.png"); HRegion threshold = image.Threshold(100, 255); HRegion connected = threshold.Connection(); HTuple area, row, col; connected.AreaCenter(out area, out row, out col); // row、col 就是各连通域的质心,可用来做标定点逻辑说明:Threshold做灰度阈值,Connection把区域拆成独立连通域,AreaCenter一次拿到面积和质心。质心就是标定点的像素坐标。参数 100 和 255 是灰度上下限,实际要根据打光调,背光下 Mark 点通常取 0~80 或 200~255。
关于halcon license,这是新手最容易卡的地方。Halcon 运行时需要 license 文件,开发时用试用 license,部署到工控机要换成正式 license。license 和网卡 MAC 或加密狗绑定,换机器要重新申请。常见报错「HALCON error #2106: License file not found」就是路径没配对,把 license 放到程序目录或设置HALCONLICENSEFILE环境变量即可。别想着绕过,工业软件这块没有后悔药。
3.2 串口通讯:RS485 下和机器人握手的帧格式
工业机器人控制柜大多支持RS485 串口通讯,走 Modbus RTU 或自定义 ASCII 协议。C# 里用System.IO.Ports.SerialPort类。关键参数:波特率 9600 或 115200、8 数据位、1 停止位、无校验(8N1)最常见,具体看机器人手册。
using System.IO.Ports; SerialPort port = new SerialPort("COM3", 115200, Parity.None, 8, StopBits.One); port.ReadTimeout = 500; port.WriteTimeout = 500; port.Open(); // 发送抓取坐标,自定义 ASCII 帧:$MOVE,X,Y,ANGLE# string frame = $"$MOVE,{x:F2},{y:F2},{angle:F2}#"; byte[] data = System.Text.Encoding.ASCII.GetBytes(frame); port.Write(data, 0, data.Length); // 读取机器人应答 string resp = port.ReadLine(); if (!resp.StartsWith("$OK")) { // 机器人拒绝执行,记录日志并报警 }逻辑说明:帧头$、帧尾#是自定义的,用来做粘包拆分。机器人收到后回$OK#表示执行,回$NG#表示坐标超限或未使能。ReadLine依赖换行符,如果机器人不发换行,就得自己按帧尾#拼包。
参数说明:波特率必须两端一致,不一致会收到乱码。ReadTimeout设 500 ms,超时就当通讯失败,别无限等。RS485 是半双工,收发要分时,如果机器人响应慢,发送后要留 20~50 ms 再读,否则读到的是自己发的回显。
3.3 界面美化:让操作工一眼看懂状态
工业上位机的界面不需要花哨,但要清晰。我一般分三块:左上图像显示区(Halcon 的HWindowControl)、右上参数区(标定文件路径、串口选择、波特率)、下方日志和状态灯。状态灯用Panel改BackColor,绿色运行、黄色等待、红色报警,比文字直观。
// 状态灯切换 private void SetStatus(Color color, string text) { panelStatus.BackColor = color; lblStatus.Text = text; // 跨线程更新要用 Invoke if (panelStatus.InvokeRequired) panelStatus.Invoke(new Action(() => SetStatus(color, text))); }逻辑说明:串口接收在独立线程,更新 UI 必须Invoke回主线程,否则报「线程间操作无效」。这是 C# 上位机的经典坑,新手十有八九踩过。
参数说明:HWindowControl显示图像时,用HImage.DispObj(hWindow),缩放用hWindow.SetPart。别在 UI 线程里跑 Halcon 的重算子,会卡界面,放到Task.Run里。
4. 从零跑通一套 9 点标定程序的完整步骤
4.1 标定前的硬件与坐标系准备
动手前先把三件事定死。第一,相机固定牢,标定过程中不能动,动了就得重标。第二,机器人末端装一个尖头标定针,针尖就是 TCP,标定时让针尖去对 Mark 点。第三,确定机器人坐标系方向,是 X 向前 Y 向左,还是别的,这决定物理坐标的正负号。
标定板可以用 Halcon 自带的圆点标定板,也可以用打印的棋盘格。圆点板对圆心提取更稳,推荐。把标定板放在工作台,让 9 个点落在相机视野内,光照均匀,别有过曝和阴影。
4.2 采集 9 组像素坐标与机器人坐标
流程是:机器人带标定针走到第 1 个 Mark 点正上方,记录机器人坐标 (X1, Y1);相机拍图,Halcon 提取该点像素坐标 (u1, v1);重复 9 次。注意每次机器人到位后要等稳定再拍,机械振动会让圆心飘。
// 采集一组标定数据 HTuple rows = new HTuple(), cols = new HTuple(); for (int i = 0; i < 9; i++) { // 机器人移动到第 i 点(通过串口发指令) SendMove(robotPoints[i].X, robotPoints[i].Y); WaitStable(300); // 等 300ms 稳定 // 拍照并提取圆心 HImage img = GrabImage(); HRegion circle = ExtractCircle(img); HTuple r, c; circle.AreaCenter(out _, out r, out c); rows = rows.TupleConcat(r); cols = cols.TupleConcat(c); }逻辑说明:TupleConcat把每次的圆心坐标拼成数组,最后一次性喂给vector_to_hom_mat2d。WaitStable是等机械稳定的延时,300 ms 是经验值,高速机可以短些,重载机要长些。
参数说明:ExtractCircle内部可以用edges_sub_pix提亚像素边缘再fit_circle_contour_xld拟合圆,亚像素能把圆心精度提到 0.1 像素级。别用Threshold直接算质心,精度差一个档次。
4.3 求解矩阵、保存标定文件与加载
9 组数据齐了,调vector_to_hom_mat2d求矩阵,然后序列化保存。Halcon 的HTuple可以WriteTuple存成文件,下次启动ReadTuple读回来。
HTuple homMat = new HTuple(); HOperatorSet.VectorToHomMat2d(cols, rows, robotX, robotY, out homMat); // 保存 homMat.WriteTuple("calib_9point.tup"); // 加载 HTuple loaded = new HTuple(); loaded.ReadTuple("calib_9point.tup"); // 用 loaded 做坐标转换 HTuple worldX, worldY; HOperatorSet.AffineTransPoint2d(loaded, pixelCol, pixelRow, out worldX, out worldY);逻辑说明:WriteTuple存的是矩阵 6 个元素,文件很小。加载后直接用于生产。建议标定文件带时间戳备份,换产品换工装时能快速切回。
参数说明:VectorToHomMat2d的输入顺序再强调一次——像素列、像素行、物理 X、物理 Y。顺序错了矩阵就是错的,而且不一定报错,只是结果偏,最难查。
5. 避坑与排查:9 点标定和串口通讯最容易翻车的地方
5.1 标定残差大,但每个点看起来都对
现象:9 个点单独看圆心都提得很准,但残差超过 0.5 mm。原因通常是机器人记录的物理坐标有系统性偏差,比如编码器滞后、针尖没对准 TCP、或者机器人走的是关节插补导致到位点有偏差。解决:改用直线插补,到位后加延时再读编码器,标定针重新对 TCP。
5.2 串口能发不能收,或收到乱码
现象:C# 发指令机器人动了,但读回来是乱码或超时。原因有三:波特率不一致、RS485 收发切换没留时间、帧尾没对上。解决:先用串口助手单独测通,确认波特率和帧格式;C# 里发送后Thread.Sleep(30)再读;如果机器人不发换行,改用按#拼包而不是ReadLine。
5.3 Halcon 报 license 错误,程序起不来
现象:开发机好好的,部署到工控机报 #2106 或 #2107。原因是 license 和机器绑定,换机没换 license。解决:把正式 license 放到程序目录,或设HALCONLICENSEFILE环境变量指向 license 路径。别用试用 license 上产线,会过期。
5.4 界面卡死,图像不刷新
现象:点开始后界面无响应,图像区空白。原因是 Halcon 重算子在 UI 线程跑,或者串口ReadLine阻塞主线程。解决:采集和算法放Task.Run,串口接收用DataReceived事件或独立线程,UI 更新统一Invoke。
5.5 标定通过但抓取偏,边缘点误差大
现象:中心区域抓得准,视野边缘偏 1 mm 以上。原因是镜头畸变没校正,或者 9 点覆盖范围太小,外推不可靠。解决:用change_radial_distortion_cam_par做畸变校正,或者把 9 点铺满整个工作区域,别只集中在中心。
6. 进阶技巧:把标定精度再压一半的几个习惯
想让这套方案从「能用」到「好用」,有几个我踩坑后养成的习惯。第一,标定不是一次性的,每天开班用第 10 点回代验证一次,偏差超 0.2 mm 就重标,别等出废品才发现。第二,9 点采集时每个点拍 3 张取圆心均值,能把随机噪声压到 0.05 像素,代价只是多花两秒。
第三,串口通讯加心跳。上位机每 500 ms 发一次$PING#,机器人回$PONG#,连续 3 次没回就报警停机。产线上机器人急停、断线是常事,没心跳的后果就是上位机还在发坐标,机器人已经不动了,料堆一地。
第四,标定文件带元数据。别只存 6 个矩阵元素,把标定时间、机器人型号、相机型号、镜头焦距一起存进去。换线时一眼就知道这份标定能不能用,省得翻记录。
// 带元数据的标定文件 string meta = $"time={DateTime.Now:yyyyMMdd_HHmmss};robot=ABB_IRB1200;cam=Basler_acA1300"; File.WriteAllText("calib_9point.meta", meta); homMat.WriteTuple("calib_9point.tup");最后说个验证方法:拿一个标准件,让机器人抓 20 次,记录每次实际落点和理论落点的偏差,算均值和标准差。均值反映系统偏差(标定问题),标准差反映随机误差(机械和视觉噪声)。均值大就重标,标准差大就查机械和光照。这套方法比看单次结果靠谱得多。
我自己做第一套 9 点标定时,图省事没做回代验证,结果产线跑了两天才发现边缘偏,返工重标花了半天。从那以后,回代验证和心跳检测成了我每套程序的标配。希望帮到你。
本文还有配套的精品资源,点击获取