您现在的位置是:网站首页> PLC自动化
机器视觉相关技术收集
- PLC自动化
- 2026-09-13
- 558人已阅读
机器视觉相关技术收集
S7-200 SMART(含 G2)**独立软件:STEP 7-MicroWIN SMART**,单独安装,和博途互不替代
**STEP 7-MicroWIN SMART 完全免费,不需要任何授权、不需要密钥,个人、公司商用都可以直接安装使用**
***只有形成回路才有电流***
可以把plc的公共端和输入输出端口看作是用一根随时通断的导线连接!PNP等型传感器或输出端口的信号都是集电极输出
西门子的数字量输入公共端M、M1、M2,输出公共端L、L1、L2。三菱输入S/S,输出COM
***OB在拖拽调用FB时自动生成的DB会自动映射保存FB中的静态变量,为好维护可定义一个全局DB所有模块可访问***
***PLC的输入输出端口需要格外供电,有源输出本身带电,无源输出只是通断***
***继电器输出都只是通断,并不供电,需外部供电。晶体管(接直流-输出区分PNP还是NPN型》又分)与晶闸管(接交流,COM一般接零线,端口通过负载接火线)有源输出带电***
晶体管输出带不动大功率设备时可使用-》中间继电器-》设备
传感器NPN = 漏型(Sink,灌入电流,COM口接+24V,IO口常态0V);传感器PNP = 源型(Source,流出电流,COM口接0V,IO口常态+24V),是否有电流通过IO作为是否有信号
不管是NPN还是PNP,只要传感器感应到物体(动作了),PLC程序里读到的就是“1”
无论NPN还是PNP传感器说白了就是通断信号,输入端口是外部传感器控制通断,输出端口是PLC控制通断
西门子全系普通晶体管出厂固定为 PNP‑源型输出,硬件不能切换成 NPN(漏型)
通俗解释(200‑SMART / S7‑1200 / S7‑1500)
1. 晶体管输出公共端子 1M 接 0V、1L + 接 DC24V 正极
2. Q 点导通之后,输出端子直接送出 +24V 高电平(标准 PNP 源型)
西门子直流输入是双向光耦,可以自由切换 NPN / PNP,只靠公共端子 1M 接线决定
OB主程序,FB功能块【可用静态变量】,FC函数块,DB存储块
***SCL代码内部写 VAR_TEMP / VAR_INPUT / VAR_OUTPUT / VAR_IN_OUT / VAR_STAT … END_VAR,1200 全部不支持,所有局部变量强制在块上方接口表格定义,Constant是常量都是用加#访问,用拖拽FC、FB到OB实现调用***
临时变量、输入变量、输出变量、常量直接在OB、FB、FC中的接口定义,PLC的变量是为了能方便访问I Q M存储区,DB是静态存储类似硬盘存储



***避免写驱动:采用串口、网口、蓝牙***
Halcon技术收集
用 gen_rectangle1 (ROI1, 120,180,380,450) 标记器件,难道没有软件直接标注生成模板文件 shm 吗?
Halcon 完整棋盘格相机标定 + 图像去畸变 + 九点 N 点标定的完整 HDevelop 代码。
C# Halcon SDK:棋盘格相机标定代码(求解相机内参 + 畸变系数,输出 camera_param.dat)
海康 VisionMaster
同样的功能用 VisionMaster 实现过程详细介绍并给出实现代码
OpenCV
同样功能用 OpenCV 如何实现,详细介绍操作步骤并给出实现代码
综合
开发板
集成商,1 台开发电脑 + 交付 3 台机械臂视觉工作站
- Halcon Steady:1 个 Dev (2.5w)+3 个 Runtime (1.5w×3)=**合计 7 万**
- VisionMaster:开发许可免费 + 3 个 Runtime (0.5w×3)=**合计 1.5 万**
- OpenCV:授权 0 元,人力成本高。
# 重要提醒(很多集成商踩坑)
1. 淘宝上几百、一两千的 Halcon 狗,**都是盗版**。MVTec 可以审计设备,商用量产抓到会有法律和索赔风险,项目交付客户不建议用。
2. 软授权 CmActLicense 绑定主板 / 网卡,更换硬件需要向代理商申请迁移,有迁移次数限制;USB 狗不怕换电脑,但现场 USB 容易松动、丢失。
3. 报价受项目数量、年度框架合同、是否采购技术支持服务影响,**一定要找大恒图像授权代理商拿正式报价**。
# 选型建议
- 项目设备台数少、客户预算充足、追求稳定、不想自己啃大量算法 bug → Halcon
- 大量设备交付、预算紧张,只做 2D 定位抓取 → VisionMaster 国产视觉平台
- 自研设备,有专职算法团队,想彻底砍掉每台设备授权费 → OpenCV(人力成本高)
# 机器视觉引导机械臂(2D)完整小白教程
> 目标:固定相机(眼在外 Eye-to-Hand),2D 工业相机,Halcon + C#/Python,获取工件 XY + 旋转角度,输出给机械臂。
> 前提:**工件 Z 高度固定不变!** 这是 2D 视觉的硬性前提。
> 软件:Halcon 20.05(免费学习版),小白最友好;相机可以先用电脑摄像头模拟,不用真实工业相机。
> 整体流程分 4 大块:
> 1. 采集图像,制作模板
> 2. 九点手眼标定(像素坐标 → 机器人世界坐标)
> 3. 在线模板匹配,得到图像上工件位置 + 角度
> 4. 通过标定矩阵转换,输出机器人 X,Y,Rz 给机械臂 / PLC
> ⚠️ 小白重点区分:
> 图像坐标:像素(row, column, phi)
> 机器人坐标:世界坐标(X,Y,Rz),必须手眼标定才能转换!
-
# 一、完整实现步骤(小白版,一步一步)
## 步骤 1:搭建硬件(模拟方案,不用工业相机也能先跑通算法)
1. 相机固定在机架上方,**位置不动**(眼在外)
2. 机械臂放在相机视野下方
3. 工件放在同一个水平面(Z 固定)
4. 相机连接电脑,Halcon 可以读取相机图像(USB 摄像头即可测试)
> 真实项目换成工业相机:海康、大华、Basler,用 GigE/USB3,Halcon 有对应的相机 SDK。
## 步骤 2:采集标准图片,训练形状模板(核心,用来识别工件 + 角度)
1. 将工件放到视野中心,摆正,拍一张清晰无反光图片
2. Halcon 中截取工件区域,创建形状模板
3. 记录模板 ID,保存模板文件,后续在线匹配调用
> 形状模板优势:抗光照变化,直接返回中心像素坐标 + 旋转角度 Phi。
## 步骤 3:九点手眼标定(最重要!很多小白卡在这里)
目的:建立【图像像素平面】到【机器人底座 XY 平面】的映射关系。
操作流程:
1. 拿一个标定标记(小圆点 / 小方块)装在机械臂夹爪上
2. 在相机视野内移动机械臂,依次走到 9 个点位,均匀分布在整个视野
3. 机械臂每走到一个点,记录 2 组数据:
- ① 图像:Halcon 找到标记的像素坐标 (Row, Col)
- ② 机器人:机器人控制器读出当前世界坐标 (Rx, Ry)
4. 9 组坐标代入 Halcon 算子 `vector_to_hom_mat2d`,生成**单应矩阵 HomMat2D**
> HomMat2D:就是转换矩阵,后面所有识别到的工件像素点,乘这个矩阵就变成机器人 XY。
> 角度:图像角度 Phi,经过简单符号修正,得到机器人 Rz。
## 步骤 4:在线实时识别
循环执行:
1. 相机拍照
2. Halcon 模板匹配,查找工件,得到:图像中心 Row, Col,图像旋转 Phi
3. 用 HomMat2D 矩阵,把 (Row,Col) 转换为机器人世界坐标 (X,Y)
4. 修正角度正负,得到机器人 Rz
5. 将 X,Y,Rz 通过 TCP/Modbus 发送给机器人控制器或 PLC
6. 机械臂收到位姿,移动抓取工件
## 步骤 5:现场验证调参
1. 多次放工件,对比视觉输出坐标和机器人实际抓取位置,修正角度方向
2. 调整匹配阈值,过滤误匹配
3. 限制匹配搜索角度范围,提升速度
# 二、代码实现(Halcon HDevelop 代码,小白首选,复制直接跑)
> 这套代码:① 创建模板 ② 九点标定 ③ 在线匹配,获取像素 XY + 角度,再转机器人坐标
> 先用电脑摄像头模拟相机。
* ====================== 【第一部分:创建形状模板,只需要运行一次】 ======================
* 采集标准工件图像,你也可以直接read_image读取本地图片
read_image (Image, 'part_template.png')
* 裁剪ROI,框选工件区域
gen_rectangle1 (ROI_0, 120, 180, 380, 450)
reduce_domain (Image, ROI_0, ImageReduced)
* 创建形状模板,参数小白不用深究
create_shape_model (ImageReduced, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID)
* 保存模板,后续程序直接加载不用重新训练
write_shape_model (ModelID, 'part_model.shm')
* ======================【第二部分:九点标定,只运行一次,得到HomMat2D】======================
* 9组【图像像素点 Row, Col】
RowPoints := [200,200,200,320,320,320,450,450,450]
ColPoints := [200,320,450,200,320,450,200,320,450]
* 9组【机械臂世界坐标 X,Y】,机械臂移动到标记点读出
RobotX := [10,10,10,20,20,20,30,30,30]
RobotY := [10,20,30,10,20,30,10,20,30]
* 构造齐次矩阵:像素坐标 → 机器人世界坐标
vector_to_hom_mat2d (RowPoints, ColPoints, RobotX, RobotY, HomMat2D)
* 保存标定矩阵,后面在线程序加载
write_matrix (HomMat2D, 'hom_mat2d.dat', 'real', '')
* ======================【第三部分:在线实时识别主程序,循环运行】======================
* 加载模板和标定矩阵,程序启动执行一次
read_shape_model ('part_model.shm', ModelID)
read_matrix ('hom_mat2d.dat', 'real', HomMat2D)
* 循环拍照识别
while (true)
* 相机采集图像;如果是工业相机用open_framegrabber
read_image (Image, 'current_image.png')
* 形状模板匹配,查找工件
find_shape_model (Image, ModelID, rad(-180), rad(360), 0.5, 1, 0.5, 'least_squares', 0, 0.7, Row, Column, Angle, Score)
* 判断是否找到工件,Score>0.5代表匹配成功
if(|Score|>0)
* 图像像素中心 Row, Column;图像角度 Angle(弧度)
* 像素点转换到机器人XY世界坐标
projective_trans_point_2d (HomMat2D, Row, Column, RobotX, RobotY)
* 图像角度转机器人Rz(弧度转角度,注意正负号!现场根据机器人坐标系调整±)
RobotRz := deg(Angle)
* 打印输出机器人目标位姿 X Y Rz
disp_message (dev_window, '目标位姿:X='+RobotX+' Y='+RobotY+' Rz='+RobotRz, 'window', 12, 12, 'black', 'true')
* ==========【这里增加通讯代码:TCP发送X,Y,Rz给机器人/PLC】==========
* 例如socket发送字符串:"X=12.3,Y=20.1,Rz=30.5"
endif
endwhile
```
## 小白必读代码说明
1. `create_shape_model`:创建模板,`rad(-180),rad(360)` 代表工件旋转范围 ±180°
2. `find_shape_model`:搜索模板,返回 `Row(像素行), Column(像素列), Angle(弧度), Score匹配分数`
3. `vector_to_hom_mat2d`:九点标定核心算子,生成平面单应矩阵
4. `projective_trans_point_2d`:像素坐标转机器人世界 XY
5. `deg(Angle)`:弧度转角度;**角度正负是最容易踩坑点**,现场测试工件顺时针旋转,看输出 Rz 是正还是负,需要加负号修正 `RobotRz := -deg(Angle)`
# 三、通讯输出给机械臂(工程常用)
视觉算出 X,Y,Rz 之后,需要发给机器人:
1. **TCP Socket(最推荐)**:视觉作为客户端 / 服务端,发送字符串 `X:125.3,Y:241.2,Rz:15.6`;机器人端写 socket 接收程序(ABB/KUKA/ 埃斯顿都支持)
2. **Modbus TCP**:把 X,Y,Rz 放大成整数放到寄存器,适合 PLC 中转,缺点是浮点数处理麻烦。
> 小白入门优先 TCP Socket,简单直观。
## Python 简易 TCP 发送示例(对接机器人)
import socket
def send_pose_to_robot(x,y,rz):
client = socket.socket()
client.connect(("192.168.1.10", 8080)) #机器人IP和端口
msg = f"X={x:.2f},Y={y:.2f},Rz={rz:.2f}"
client.send(msg.encode("utf-8"))
client.close()
#调用
send_pose_to_robot(125.24, 240.56, 15.3)
# 四、小白高频坑点(一定要看)
1. ❗工件 Z 高度不能变。Z 只要变化几毫米,XY 坐标就会偏移,抓取不准。
2. ❗图像坐标系和机器人坐标系 Y 轴方向相反。图像 Y 向下,机器人 Y 向上,角度经常正负颠倒,现场测试修正。
3. ❗模板匹配阈值:Score 分数,阈值太高识别不到;太低容易误识别。一般 0.5~0.7。
4. ❗九点标定的 9 个点,必须均匀铺满相机整个视野,不要只集中在中心区域,否则边缘误差巨大。
5. ❗纯圆形工件没有特征:模板匹配无法得到旋转角度,必须加缺口 / 键槽。
# 五、学习路线建议(小白循序渐进)
1. 先用 Halcon+HDevelop,本地图片跑通模板匹配,能读出像素坐标和角度。
2. 电脑 USB 摄像头接入 Halcon,实时采集图像匹配。
3. 做九点标定,跑通像素坐标转世界坐标。
4. 加 TCP 通讯,输出位姿。
5. 再上真实工业相机 + 机械臂联调。
如果你想,我可以:
方案 A:改成 **眼在手上(Eye-in-Hand)** 的标定代码和步骤;
方案 B:给你一套 C# 调用 Halcon 的 Winform 完整工程代码;
方案 C:或者简化成 OpenCV 版本(免费,不用 Halcon 授权)。
# 一句话结论
**Halcon 的 `create_shape_model`(形状模板,工业 2D 定位最常用)默认只需要 1 张标准图片来建模板**,但这不代表只用一张图就一定能应对现场所有工况。
> 区分:**训练模板 = 1 张基准图;模板鲁棒性增强 = 多张图做预处理 / 训练辅助**
## 1. 基础形状模板(create_shape_model)—— 仅需 1 张图
原理:从这一张基准图里提取**工件边缘轮廓**,保存轮廓几何信息,不是保存整张图片灰度。
- 操作:拍一张光照均匀、无反光、工件完整无遮挡、姿态摆正的标准件图片,框 ROI,直接建模板。
- 优点:简单快速,就是我们前面机械臂 XY+Rz 定位用的方案。
- 短板:只学习**这一张图的轮廓**;如果现场光照变化、轻微形变、毛刺,容易匹配分数下降甚至匹配失败。
> ⚠️ 关键点:模板本身**不学习光照变化**,形状模板只关心轮廓几何,灰度明暗不敏感(可以开`use_polarity=false`关闭极性,明暗反转也能匹配)。
## 2. 什么时候需要多张图片?
### 场景 A:需要抗光照、明暗变化(灰度模板 / 基于图像的模板)
`create_template`(灰度模板,工业现在很少用),这种对灰度敏感。光照一变直接失效。
> 灰度模板不推荐用于机械臂定位项目。
### 场景 B:**多模板方案(最常用工程手段)**
工件存在**多种形态 / 多种型号 / 不同姿态**:
例:来料有 2 种外形、工件有 2 种稳定摆放形态 → 拍多张图,**建立多个独立模板**。匹配的时候循环搜索所有模板,匹配到哪个就是哪个。
> 不是把多张图合并成**一个模板**,而是多个模板文件。
### 场景 C:训练「变形模型」(create_deformable_model,可形变模板)
✅ **这个是真正支持多张样本训练的模板!**
适合:工件存在轻微弹性形变、注塑件微小变形、轻微轮廓差异。
create_deformable_model
- 训练阶段:传入**多张不同样本图片**,让模型学习工件允许的形变范围。
- 代价:计算更慢、参数复杂,机械臂普通刚性零件定位基本不用。
- 适用:软胶件、薄件,外形每次有轻微变形。
### 场景 D:图像增强预处理(多张图做背景平均)
不是用来建模板,是**预处理降噪**:
拍多张空背景图做平均背景,后续实时图像减去背景,消除杂光噪声。模板本身仍然只用 1 张工件图。
## 3. 小白容易踩的误区
1. ❌ 不是多拍几张工件照片喂进去,就能让形状模板自动学会抗反光。
形状模板只提取轮廓;反光只要**不把工件边缘吃掉**就没问题。反光把边缘盖住,哪怕 100 张训练图也救不了,优先改光源。
2. ❌ 一张模板不能识别**外形不一样的工件**。
外形差异大,必须多模板。
3. ✅ 一张形状模板,可以识别工件**旋转、平移、一定缩放**(缩放需要开启模型缩放参数)。
> `create_shape_model`可以设置最小 / 最大缩放,允许工件图像上大小轻微变化(比如镜头轻微离焦)。
## 4. 结合你的【2D 视觉引导机械臂】场景建议
你的需求:固定工件外形,抓取定位,获取 XY+Rz
👉 **首选:单张图片创建 shape_model**
- 拍照要求:光源打好,工件轮廓清晰,边缘连续,无大面积反光遮挡轮廓,工件摆正。
- 调参:设置角度搜索范围,按需开启缩放。
- 如果来料有 2 种工件:建 2 个模板,循环匹配。
## 补充对比表

## 极简示例代码(单张图建形状模板,前面代码精简版)
read_image (Image, 'standard_part.png')
gen_rectangle1 (ROI, 100,100,400,400)
reduce_domain (Image, ROI, ImageROI)
* 创建形状模板,允许旋转±180度
create_shape_model (ImageROI, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID)
write_shape_model(ModelID,'part.shm')
# 方案说明
Halcon `create_shape_model` 单个模板**仍然只能 1 张图生成**。
想要支持多种工件形态,做法是:**建立多个独立模板(多个 shm 文件)**,在线循环依次搜索所有模板,一旦匹配成功就输出对应的坐标 + 角度。
适用场景:工件有**多种稳定外形 / 多种摆放形态**(不是同一工件形变;形变要用 deformable_model,后面附带)。
> 不是把多张图片合并成**一个模板**,而是 N 个模板,遍历搜索。
## 完整 HDevelop 代码:多模板匹配(3 个模板举例)
* ======================【第一部分:一次性创建多个模板,只跑一次】======================
* 模板1
read_image (Img1, 'part_01.png')
gen_rectangle1 (ROI1, 120,180,380,450)
reduce_domain (Img1, ROI1, ImgROI1)
create_shape_model (ImgROI1, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID1)
write_shape_model (ModelID1, 'part_01.shm')
* 模板2
read_image (Img2, 'part_02.png')
gen_rectangle1 (ROI2, 120,180,380,450)
reduce_domain (Img2, ROI2, ImgROI2)
create_shape_model (ImgROI2, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID2)
write_shape_model (ModelID2, 'part_02.shm')
* 模板3
read_image (Img3, 'part_03.png')
gen_rectangle1 (ROI3, 120,180,380,450)
reduce_domain (Img3, ROI3, ImgROI3)
create_shape_model (ImgROI3, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID3)
write_shape_model (ModelID3, 'part_03.shm')
* ======================【第二部分:加载标定矩阵(九点标定结果,像素→机器人XY)】======================
* 这里直接加载之前九点标定得到的HomMat2D,只初始化一次
read_matrix ('hom_mat2d.dat', 'real', HomMat2D)
* 定义模板数组,方便循环
ModelFiles := ['part_01.shm','part_02.shm','part_03.shm']
ModelCount := |ModelFiles|
ModelIDs := []
for Index := 0 to ModelCount -1 by 1
read_shape_model (ModelFiles[Index], TempID)
ModelIDs := [ModelIDs, TempID]
endfor
* ======================【第三部分:在线循环识别主程序】======================
while(true)
* 拍照,工业相机换成open_framegrabber采集
read_image (Image, 'current_img.png')
Found := 0
FinalRow := 0.0
FinalCol := 0.0
FinalAngle := 0.0
FinalScore := 0.0
FinalModelIndex := -1
* 依次遍历所有模板进行匹配
for m := 0 to ModelCount -1 by 1
find_shape_model (Image, ModelIDs[m], rad(-180), rad(360), 0.5, 1, 0.5, 'least_squares', 0, 0.7, Row, Column, Angle, Score)
if(|Score|>0)
* 找到目标,取匹配分数最高的一组
if(Score[0] > FinalScore)
FinalScore := Score[0]
FinalRow := Row[0]
FinalCol := Column[0]
FinalAngle := Angle[0]
FinalModelIndex := m
Found := 1
endif
endif
endfor
if(Found ==1)
* 像素坐标转机器人世界XY
projective_trans_point_2d (HomMat2D, FinalRow, FinalCol, RobotX, RobotY)
RobotRz := deg(FinalAngle)
* 注意:现场如果角度反向,改成 RobotRz := -deg(FinalAngle)
disp_message (dev_window, '找到模板'+FinalModelIndex+' X='+RobotX+' Y='+RobotY+' Rz='+RobotRz+' Score='+FinalScore, 'window', 12,12,'black','true')
* 这里增加TCP发送X,Y,Rz给机械臂
else
disp_message (dev_window, '未匹配到工件','window',12,12,'red','true')
endif
endwhile
## 如果你想要【单模板支持多张样本训练:可形变模板 deformable_model】
> 适用:**同一种零件,每次有轻微形变**(不是多种不同零件),可以一次性传入多张样本图片训练**同一个模型**。
* 可形变模板:输入多张样本训练同一个模型
* 读取多张样本图片
read_image (Img1, 'sample1.png')
read_image (Img2, 'sample2.png')
read_image (Img3, 'sample3.png')
* 放入图像数组
ImageArray := [Img1,Img2,Img3]
* 创建可形变模板,多张样本训练一个模型
create_deformable_model (ImageArray, 'auto', rad(-180), rad(360), 'auto', 'auto', 'auto', 'use_polarity', 30, 10, ModelID)
write_deformable_model(ModelID,'deform_part.shm')
* 在线匹配
read_deformable_model('deform_part.shm',ModelID)
find_deformable_model (Image, ModelID, rad(-180), rad(360), 0.5, 1, 0.5, 'least_squares', 0, 0.7, Row, Column, Angle, Score)
⚠️ 注意:`create_deformable_model` 速度比普通 shape_model 慢很多,**刚性零件机械臂定位一般不推荐**。
# 工程重点提醒
1. **多 shape_model(上面第一个代码):多种零件 / 多种稳定姿态,项目最常用**。缺点:循环搜索模板会略微增加耗时;可以限制每个模板角度范围来提速。
2. **deformable_model:同一个零件轻微变形,多张样本训练 1 个模板**。代价:算力高、节拍变慢。
3. 匹配阈值:`0.5` 是最低分数,根据现场反光调整;分数越高匹配越严格。
4. 多个模板尽量**不要外形高度相似**,否则会出现 A 零件匹配到 B 模板,需要加二次校验(轮廓面积 / 长宽过滤)。
## 扩展:增加轮廓校验,防止不同模板误匹配(可选片段)
* 拿到匹配结果后,截取区域,提取轮廓面积做校验
gen_rectangle2 (ROI_check, FinalRow, FinalCol, FinalAngle, 80,80)
reduce_domain (Image, ROI_check, ImgCheck)
edges_sub_pix (ImgCheck, Edges, 'canny', 1, 20,40)
select_contours_xld (Edges, ContoursSel, 'contour_length', 0, 150,0,0)
* 判断轮廓长度,过滤错误匹配
用 gen_rectangle1 (ROI1, 120,180,380,450) 标记器件,难道没有软件直接标注生成模板文件 shm 吗?
# 有的!Halcon 自带【匹配助手 Matching Assistant】,**鼠标直接在图片上拖拽框选器件,图形界面调参,一键导出 shm 模板文件**,完全不用手动写死 `gen_rectangle1 (ROI1, 120,180,380,450)` 这种固定数字代码!

## 一、匹配助手怎么操作(小白图形化建模板)
1. HDevelop 顶部菜单:**助手 → 打开新的 Matching**,弹出可视化匹配工具
2. 【创建】标签页:加载图片(本地图片 / 相机实时采集拍照)
3. ROI 工具:选矩形 / 圆形 / 多边形,**鼠标直接框选工件**,右键确认选区(不用手动填行列数字)
4. 选择匹配类型:**基于形状的匹配(就是 shape_model,机械臂定位首选)**
5. 设置参数:角度范围、金字塔层数、对比度;界面里可以预览提取出来的工件轮廓
6. 点【检测】标签,载入测试图片,**直接在界面测试匹配效果**,实时看匹配分数、坐标、角度
7. 关键一步:
- 方式 A:**直接保存模板文件 → 输出 .shm**,后面在你的主程序用 `read_shape_model` 加载使用
- 方式 B:**生成代码**,助手自动生成全套 HDevelop 代码(包含 draw_rectangle1、reduce_domain、create_shape_model、write_shape_model),自动把鼠标框选出来的坐标填到代码里
> 我前面给你的代码里写死 `gen_rectangle1 (ROI1, 120,180,380,450)`,**只是演示用**,工程里不会手动写这串数字,都是用两种方案:
> 1)Matching 助手鼠标框选,自动生成代码
> 2)代码里用 `draw_rectangle1` 运行时鼠标交互式框选
## 二、交互式鼠标框选代码(不用固定数字,直接鼠标拖框,推荐)
read_image (Image, 'part_template.png')
* 鼠标在图像窗口画矩形ROI,右键确认,自动返回Row1,Col1,Row2,Col2
draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
gen_rectangle1 (ROI_0, Row1, Column1, Row2, Column2)
reduce_domain (Image, ROI_0, ImageReduced)
* 创建形状模板
create_shape_model (ImageReduced, 'auto', rad(-180), rad(360), 'auto', 'auto', 'use_polarity', 30, 10, ModelID)
write_shape_model (ModelID, 'part.shm')
```
运行后图片窗口出现十字光标,**直接鼠标拉框圈零件**,右键确认,自动生成 ROI,不需要手动填 120,180 这些像素值。
## 三、其他视觉软件,也是这种拖拽建模板
- VisionMaster(国产)、VisionPro:全部都是可视化拖拽框选工件,界面一键生成模板文件,几乎不用手写代码,集成商现场调试最喜欢用
> 适合项目快速落地,但是底层原理和 Halcon shape_model 完全一致
## 四、回到你之前【多模板方案】实操流程(推荐现场这么做)
1. 打开 Matching 助手,载入零件图片 A,鼠标框选 → 测试匹配 → 保存 `part01.shm`
2. 再载入零件图片 B,鼠标框选 → 测试匹配 → 保存 `part02.shm`
3. 再载入零件图片 C,鼠标框选 → 测试匹配 → 保存 `part03.shm`
4. 在线主程序循环加载这 3 个 shm 文件,依次匹配,就是我们前面多模板代码
## 五、什么时候不推荐只用助手?
- 后期打包成 C#/C++ 上位机,**程序运行阶段动态创建模板**,这种场景才需要写算子代码;
- 现场调试、制作模板、验证匹配效果,**优先 Matching 助手,效率极高**。
# 九点标定(N 点标定,2D 平面手眼标定,Eye-to-Hand 眼在外,你的场景)
> ✅ **一句话定义:九点标定 = 建立【图像像素坐标 (Row,Col)】 ↔【机械臂基坐标系 XY (mm)】之间平面映射关系**
> 前提:**所有工件、标定点必须严格在同一个 Z 高度平面!** 一旦 Z 高度变化,标定失效。
> 它不是传统相机内外参标定,**不单独标定镜头畸变**,直接拟合 2D 变换矩阵 `HomMat2D`(3×3 单应矩阵)。
> 理论最少**3 个不共线点**就可以算出仿射矩阵;工程上用 9 个点,**超定方程组最小二乘拟合,降低单点噪声带来误差,全视野精度更均匀**
## 一、原理
我们有两套坐标系:
1. **图像坐标系(像素)**:Row = 图像行(向下增大),Column = 图像列(向右增大),单位:像素
2. **机械臂基坐标系(机器人世界)**:X,Y,单位 mm;由机器人控制器读出
九点标定的目标:求 3×3 单应矩阵 HomMat2D

矩阵包含:平移、旋转、缩放、镜头轻微梯形透视畸变、坐标轴方向翻转。
Halcon 算子:`vector_to_hom_mat2d` 输入 9 组(Row, Column)和对应的 9 组机器人 (X,Y),最小二乘求解 H 矩阵
标定完成后,任意识别到的工件像素点,调用`projective_trans_point_2d`,代入 H 矩阵,直接算出机械臂 XY;图像角度 Phi 只需要修正正负号,得到机器人 Rz。
> ⚠️重点区分:
> - 九点标定:**2D 平面映射,Eye-to-Hand 眼在外最常用**,只处理固定高度平面抓取,就是我们做视觉引导机械臂 XY+Rz 这套方案。
> - AX=XB 手眼标定:**眼在手上 Eye-in-Hand**,相机装机械臂末端,求解相机相对于法兰的空间位姿,那是另一套标定,不要混淆。
## 二、硬件准备
1. 相机固定(眼在外,相机不动),镜头调好焦距,光源稳定。
2. **标定标记**:在机械臂末端法兰上固定一个高精度小圆点 / 小方块标记(推荐实心黑圆点,亚像素识别),这个标记就是机械臂 TCP。
> ❗不要用棋盘格标定板放地上!九点标定是**机械臂带着标记,依次移动到视野内 9 个位置**,每移动到一个位置拍照,记录像素坐标 + 机器人坐标。
3. 9 个点位排布:**3 行 ×3 列,均匀铺满相机整个视野,不要集中在画面中心,不要共线**,Z 字形顺序移动。

## 三、完整实操步骤(小白现场操作版)
### 步骤 1:固定硬件,锁定高度
标定平面 = 工件抓取平面。机械臂末端标记的高度,必须和后续抓取工件的 Z 高度**完全一致**。标定过程中 Z 轴全程不动。
### 步骤 2:机械臂手动移动到第 1 个标定点
1. 操控机械臂,把末端标记移动到视野左上角(第 1 点)
2. 相机拍照,Halcon 找标记圆心,得到像素坐标 Row1, Col1
3. 在机器人示教器,读出当前机器人基坐标:RobotX1, RobotY1
4. 把两组数据存入数组
> 一定要一一对应:第 1 组像素点 ↔ 第 1 组机器人坐标,顺序绝对不能乱!
### 步骤 3:依次走完 9 个点位
按 3×3 网格依次移动机械臂,重复上面操作,一共采集 9 组配对数据:
```
Row = [R1,R2,R3,R4,R5,R6,R7,R8,R9]
Col = [C1,C2,C3,C4,C5,C6,C7,C8,C9]
RobotX = [X1,X2,X3,X4,X5,X6,X7,X8,X9]
RobotY = [Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8,Y9]
```
### 步骤 4:Halcon 算子求解 HomMat2D 矩阵
vector_to_hom_mat2d (Row, Col, RobotX, RobotY, HomMat2D)
输出 3×3 矩阵,保存矩阵文件 `write_matrix(HomMat2D,'hom_mat2d.dat','real','')`
### 步骤 5:验证标定精度(非常重要,现场必做)
拿**没有参与标定的第 10 个测试点**,移动机械臂到视野任意位置:
1. 拍照识别标记像素坐标
2. 用`projective_trans_point_2d`计算视觉输出 X,Y
3. 和示教器读出真实机器人 XY 对比,看误差
> 合格标准:整个视野内误差小于 ±0.1~0.3mm(根据项目精度要求)。如果边缘误差大,说明 9 个点分布太集中,需要重新采点铺满视野。
### 步骤 6:标定完成,上线使用
后续模板匹配得到工件像素 Row、Column,直接代入矩阵,算出机器人 XY;图像角度单独处理得到 Rz。
## 四、完整 Halcon 九点标定代码(可直接运行)
*====================九点标定主程序 眼在外Eye-to-Hand====================
* 【第一步】采集9组数据,现场运行采集,下面是示例数据,现场替换成你实际采集值
* 图像像素坐标 Row(行),Column(列)
RowPoints := [200,200,200,320,320,320,450,450,450]
ColPoints := [200,320,450,200,320,450,200,320,450]
* 机械臂基坐标系XY(单位mm),示教器读出
RobotX := [10,10,10,20,20,20,30,30,30]
RobotY := [10,20,30,10,20,30,10,20,30]
*【第二步】求解2D单应矩阵
vector_to_hom_mat2d (RowPoints, ColPoints, RobotX, RobotY, HomMat2D)
*【第三步】保存标定矩阵,后面在线识别程序加载
write_matrix (HomMat2D, 'hom_mat2d.dat', 'real', '')
*【第四步:验证测试点】随便取一个像素点,转换为机器人坐标
TestRow := 320
TestCol := 320
projective_trans_point_2d (HomMat2D, TestRow, TestCol, TestX, TestY)
disp_message (dev_window, '测试点:视觉输出 X='+TestX+' Y='+TestY, 'window', 12,12,'black','true')
*【第五步】计算重投影误差,评估标定好坏
projective_trans_point_2d (HomMat2D, RowPoints, ColPoints, CalcRobotX, CalcRobotY)
ErrorX := CalcRobotX - RobotX
ErrorY := CalcRobotY - RobotY
RMSE := sqrt(mean(ErrorX*ErrorX + ErrorY*ErrorY))
disp_message (dev_window, '标定RMSE误差(mm)='+RMSE$'.3f','window',40,12,'red','true')
>
> RMSE 就是标定整体均方根误差;数值越小标定越好,一般要求 < 0.2mm。
## 五、角度怎么处理(很多小白踩坑)
九点标定只映射**XY 坐标**,**不会自动把图像角度转换为机器人 Rz**!
图像匹配出来的 Angle(弧度)是图像平面旋转角,需要你手动修正符号:
RobotRz := deg(Angle)
* 现场测试,如果旋转方向相反,改成 RobotRz := -deg(Angle)
为什么会反向:图像坐标系 Y 轴向下,机器人 Y 轴向上,方向相反,角度正负经常颠倒,**现场实测一次就能确定加不加负号**。
> 如果想要标定同时修正角度,行业会做**12 点标定**:9 个平移点 + 3 个旋转点,同时校正角度方向,高精度项目使用。
## 六、高频坑点(工程踩坑总结)
1. ❗Z 高度不一致:标定标记高度和工件抓取高度不一样 → 边缘位置偏移,是九点标定最常见失败原因。
2. ❗9 个点集中在画面中心:视野四角误差巨大,必须均匀铺满整张图片。
3. ❗点顺序错乱:像素点和机器人坐标配对顺序搞错,矩阵完全错误,抓取直接撞件。
4. ❗机械臂重复定位差:机械臂本身重复精度差,采集标定点自带噪声,标定误差大。
5. ❗光源波动:采集 9 个点时光照变化,标记圆心提取不准,带来误差。
6. ❗镜头畸变严重:广角镜头边缘畸变太大,九点标定只能拟合近似,这种场景建议做相机内参标定。
## 七、什么时候不用九点标定?
- 工件 Z 高度变化、工件倾斜:放弃 2D 九点标定,改用 3D 相机。
- 眼在手上(相机装机械臂末端法兰):九点标定可以用,但行业标准做法是 AX=XB 手眼标定。
## 八、和你前面整套项目串联
1. Matching 助手框选工件,生成 shm 模板
2. 九点标定,采集 9 组点,算出 HomMat2D 保存
3. 在线拍照 → find_shape_model 得到 Row,Col,Angle
4. projective_trans_point_2d 转成机器人 X,Y
5. 修正角度得到 Rz
6. TCP 发送 X,Y,Rz 给机械臂,执行抓取
同样功能用 OpenCV 如何实现,详细介绍操作步骤并给出实现代码
# OpenCV 实现:2D 视觉引导机械臂(眼在外 Eye-to-Hand,九点标定 + 形状模板匹配,输出 XY + 旋转角)
> 目标和 Halcon 完全一致:
> 1. 模板匹配,获取工件在图像中的中心像素坐标 + 平面旋转角度
> 2. 九点标定求解**单应矩阵 H**,像素坐标 → 机械臂基坐标系 XY(mm)
> 3. 角度单独换算,输出 `X,Y,Rz` 给到机械臂
> ⚠️重要区别:
> - OpenCV**没有内置 Halcon 那种亚像素形状模板**!
> 方案选型:OpenCV 做**模板匹配有两条路线**
> - 路线 1:`matchTemplate`灰度模板:简单,但光照一变直接崩,**工业不推荐**
> - 路线 2:轮廓匹配(`findContours` + `matchShapes` + minAreaRect):适合刚性零件,提取轮廓、最小外接矩形,获取中心 + 旋转角度,**这是工业项目用 OpenCV 做 2D 定位最常用方案**,下面我们采用这个方案。
>
>
> ✅前提:工件 Z 高度固定不变(和 Halcon 九点标定一样硬性前提)
> 开发环境:Python + OpenCV(`pip install opencv-python numpy`)
# 整体操作步骤(小白实操版)
## 阶段 1:制作模板图像(只做一次)
1. 拍一张标准工件图片,摆正、光照均匀,保存为`template.jpg`
2. 预处理:灰度、阈值,提取工件外轮廓,保存模板轮廓信息(不是保存整张图片)
> OpenCV**不能像 Halcon 一样直接生成 shm 文件**,我们提取轮廓保存,运行时比对轮廓。
## 阶段 2:九点标定(眼在外,和 Halcon 原理一模一样)
1. 机械臂末端固定标记圆点,Z 固定不动
2. 移动机械臂到 9 个位置,均匀铺满相机视野
3. 每一点:拍照 → OpenCV 识别标记圆心(像素 row,col);同时从机器人示教器读出机械臂 XY(mm)
4. 9 组配对数据输入`cv2.findHomography()`求解**单应矩阵 H**(等价 Halcon HomMat2D)
5. 保存矩阵,在线识别时加载,像素坐标乘 H,得到机器人 XY
> `cv2.findHomography`内部就是最小二乘求解单应矩阵,等效`vector_to_hom_mat2d`
## 阶段 3:在线实时识别循环
1. 相机采集实时图像
2. 图像预处理,提取轮廓
3. 轮廓匹配,筛选和模板轮廓相似的工件轮廓
4. 对匹配成功轮廓求**最小外接矩形 minAreaRect**:返回 中心像素 (x,y)、旋转角度
5. 使用单应矩阵 H,把像素中心点转为机械臂世界坐标 X,Y
6. 修正旋转角正负,得到 Rz
7. 输出 X,Y,Rz,可以 TCP 发送给机械臂 / PLC
## 阶段 4:精度验证
取第 10 个测试点,验证像素坐标转换后的 XY 和机器人真实坐标误差。
> 重点提醒:OpenCV 图像坐标系:
> - `x`:列(横向,对应 Halcon 的 Column)
> - `y`:行(纵向,对应 Halcon 的 Row)
> OpenCV `minAreaRect` 返回的角度定义 和 Halcon 的角度定义**不一样**,后面代码里做转换。
# Python 完整代码
## 1. 轮廓模板生成脚本(一次性运行,提取模板轮廓)
> 作用:读取标准工件图片,提取轮廓并保存到文件,替代 Halcon 的 shm 模板
import cv2
import numpy as np
def create_template_contour(img_path, save_path="template_contour.npy"):
# 读取模板图片
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,根据你的现场阈值自行调整
_, binary = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY_INV)
# 找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 取面积最大轮廓作为工件模板轮廓
template_contour = max(contours, key=cv2.contourArea)
# 保存轮廓
np.save(save_path, template_contour)
print("模板轮廓已保存到", save_path)
return template_contour
if __name__ == "__main__":
create_template_contour("template.jpg")
## 2. 九点标定脚本(一次性运行,计算单应矩阵 H)
import cv2
import numpy as np
def nine_point_calibration():
# ===================== 【这里替换成你现场采集的9组数据!】=====================
# 像素坐标 (col, row) → OpenCV (x,y)
pixel_points = np.array([
[200, 200],
[320, 200],
[450, 200],
[200, 320],
[320, 320],
[450, 320],
[200, 450],
[320, 450],
[450, 450]
], dtype=np.float32)
# 机械臂基坐标 X,Y 单位 mm,示教器读出
robot_points = np.array([
[10, 10],
[10, 20],
[10, 30],
[20, 10],
[20, 20],
[20, 30],
[30, 10],
[30, 20],
[30, 30]
], dtype=np.float32)
# ==========================================================================
# 求解单应矩阵,等效Halcon vector_to_hom_mat2d
H, mask = cv2.findHomography(pixel_points, robot_points, cv2.RANSAC, 5.0)
print("单应矩阵 H:")
print(H)
# 保存标定矩阵
np.save("homography_matrix.npy", H)
print("标定矩阵已保存 homography_matrix.npy")
# ============ 验证标定精度 ============
test_pixel = np.array([[320, 320]], dtype=np.float32)
test_robot = cv2.perspectiveTransform(test_pixel, H)
print(f"测试点像素(320,320) → 机械臂坐标 X={test_robot[0][0]:.2f}, Y={test_robot[0][1]:.2f}")
# 计算重投影误差
calc_robot = cv2.perspectiveTransform(pixel_points, H)
err = calc_robot - robot_points
rmse = np.sqrt(np.mean(np.sum(err**2, axis=1)))
print(f"标定RMSE误差(mm):{rmse:.3f}")
return H
if __name__ == "__main__":
nine_point_calibration()
## 3. 在线实时识别主程序(循环拍照,输出 X,Y,Rz)
import cv2
import numpy as np
import socket
# TCP发送函数,发送位姿给机械臂(和前面Halcon配套)
def send_pose(robotX, robotY, robotRz):
try:
s = socket.socket()
s.connect(("192.168.1.10",8080))
msg = f"X={robotX:.2f},Y={robotY:.2f},Rz={robotRz:.2f}"
s.send(msg.encode('utf-8'))
s.close()
except Exception as e:
print("发送失败", e)
if __name__ == "__main__":
# 加载模板轮廓 + 标定矩阵
template_contour = np.load("template_contour.npy")
H = np.load("homography_matrix.npy")
# 开启相机 0=USB摄像头;工业GigE相机需要替换为工业相机SDK
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# 模板轮廓面积,用于过滤干扰
template_area = cv2.contourArea(template_contour)
match_threshold = 0.15 # 轮廓匹配阈值,越小越严格,范围0~1
while True:
ret, frame = cap.read()
if not ret:
break
img_draw = frame.copy()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
best_score = 999
best_rect = None
for cnt in contours:
area = cv2.contourArea(cnt)
if area < template_area * 0.5 or area > template_area * 1.5:
continue
# 轮廓匹配,0=完全一致
score = cv2.matchShapes(template_contour, cnt, cv2.CONTOURS_MATCH_I1, 0.0)
if score < match_threshold and score < best_score:
best_score = score
best_rect = cv2.minAreaRect(cnt)
if best_rect is not None:
# minAreaRect 返回:(中心(x,y), (宽,高), 角度)
(px, py), (w, h), angle = best_rect
# 像素中心点转机械臂XY
pixel_pt = np.array([[px, py]], dtype=np.float32)
robot_pt = cv2.perspectiveTransform(pixel_pt, H)
rx = robot_pt[0][0]
ry = robot_pt[0][1]
# =========角度转换【重点!OpenCV角度定义坑】========
# OpenCV minAreaRect角度范围 [-90,0],和Halcon定义不一样,需要转换
rz = angle
# 现场测试,角度反向就 rz = -rz
print(f"像素点 x={px:.1f},y={py:.1f} | 机械臂 X={rx:.2f}, Y={ry:.2f}, Rz={rz:.2f}")
# 绘制框和文字
box = cv2.boxPoints(best_rect)
box = np.int64(box)
cv2.drawContours(img_draw, [box], 0, (0,255,0), 2)
cv2.putText(img_draw, f"X:{rx:.1f} Y:{ry:.1f} Rz:{rz:.1f}",
(int(px),int(py)), cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1)
# 发送给机械臂
send_pose(rx, ry, rz)
else:
cv2.putText(img_draw, "No match", (20,30), cv2.FONT_HERSHEY_SIMPLEX,0.6,(0,0,255),2)
cv2.imshow("Vision", img_draw)
key = cv2.waitKey(30)
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# 关键知识点 & 和 Halcon 对比(工程重点)
## 1. 轮廓匹配 `matchShapes` 原理
只比较轮廓几何形状,**灰度明暗不敏感**,类似 Halcon 形状模板;
缺点:
- 没有原生亚像素精度(需要自己写亚像素轮廓提取)
- 抗遮挡弱;Halcon shape_model 支持局部轮廓匹配,`matchShapes`是整体轮廓比对,工件局部遮挡直接匹配失败
- 不支持大范围旋转搜索的优化,大角度搜索性能差
> 如果工件经常局部遮挡,OpenCV 这套轮廓匹配方案就不太稳。
## 2. 单应矩阵转换函数
- Halcon:`projective_trans_point_2d(HomMat2D,Row,Col,RobotX,RobotY)`
- OpenCV:`cv2.perspectiveTransform(pixel_array, H)`
数学原理完全一致,都是单应变换。
## 3. 角度大坑,一定要现场调
OpenCV `minAreaRect` 角度规则:
> 当矩形长边在竖直方向,角度从 - 90 向 0 变化;
> 和 Halcon 形状模板输出的 Phi 定义不同,**直接拿来用旋转方向大概率不对**。
> 必须现场放工件测试,按需加负号:`rz = -angle`
## 4. 标定标记圆心提取(补充代码片段)
九点标定采集图像时,需要找到标记圆点,下面是圆点检测代码(替换上面采集时的坐标获取)
```
# 找圆形标记获取圆心
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50,param2=30,minRadius=5,maxRadius=30)
if circles is not None:
cx, cy, r = circles[0][0]
```
# 5. OpenCV 这套方案的工程短板(对比 Halcon)
1. 轮廓匹配整体比对,**工件部分遮挡容易匹配失败**;Halcon shape_model 是局部边缘搜索,抗遮挡强
2. 没有原生亚像素,定位精度上限更低;想要亚像素,需要自己实现`subPixel边缘`
3. 没有自带稳定的图像采集、硬触发接口;工业相机需要额外 SDK 对接(Basler pypylon,海康 SDK)
4. 没有官方技术支持,遇到偶发匹配 bug,需要自己啃底层算法
# 6. 选型建议
- 学习原理、原型验证、简单低精度场景:OpenCV 这个方案完全够用
- 量产 24 小时产线、工件有轻微遮挡,追求稳定:优先 Halcon 或者国产 VisionMaster
同样的功能用 VisionMaster 实现过程详细介绍并给出实现代码
# VisionMaster(VM)实现【2D 特征模板匹配 + N 点九点标定,引导机械臂输出 X,Y,Rz】完整教程
> 目标:和前面 Halcon、OpenCV 功能完全一致,**眼在外(相机固定不动),固定 Z 平面抓取**
> VM 是海康图形化拖拽式视觉软件,底层自带**高精度特征匹配(等效 Halcon shape_model)、N 点标定模块、标定转换模块**,不用手写复杂图像算法,现场调试速度远快于 OpenCV;支持 C# SDK 二次开发。
> 生成文件:**模板文件 .vmtpl、标定文件 .cal、工程方案 .vmsln**
## 一、核心原理
1. **特征匹配(快速匹配 / 高精度特征匹配)**:在图像上框选零件,提取边缘特征生成模板,运行时搜索模板,输出像素坐标(Px,Py)+ 图像旋转角度 Angle(亚像素),和 Halcon 形状模板逻辑几乎一样,抗遮挡、抗轻微光照变化。
2. **N 点标定(九点标定)**:VM 内置【N 点标定】工具,输入 9 组配对数据(图像像素点、机械臂 BASE 坐标系 XY),求解仿射 / 单应变换矩阵,保存`.cal`标定文件;
3. **标定转换模块**:输入特征匹配得到的像素 Px,Py,加载`.cal`标定文件,直接输出**机械臂物理坐标 X,Y (mm)**;角度只需要修正正负,得到 Rz。
> ✅ 重点:**VM 图形界面可以全部做完,不用写代码;上位机集成时,用 C# SDK 加载方案调用流程**。
## 二、图形界面操作详细步骤(现场调试优先用这个)
### 阶段 1:硬件准备
1. 工业相机固定,镜头对焦完成,光源稳定;**标定平面 = 工件抓取平面,全程 Z 高度固定**。
2. 机械臂末端贴高精度圆形标记(黑色实心圆点),作为 TCP 标记。
### 阶段 2:新建 VM 方案,添加图像源
1. 打开 VisionMaster → 新建通用方案;
2. 左侧工具箱拖入【图像源】,连接海康工业相机(或者本地图片用于调试);
3. 设置相机触发:软件触发 / 硬触发(产线推荐硬触发)。
### 阶段 3:制作特征模板(替代 Halcon shm 模板,鼠标拖拽框选)
1. 左侧【定位】→ 拖入 **高精度特征匹配**(推荐,亚像素,等效 Halcon shape_model),图像输入接图像源。
2. 双击打开配置窗口,【特征模板】标签页 → 创建模板。
3. 在图像窗口鼠标画 ROI 框选工件,软件自动提取工件边缘特征;可以橡皮擦删除多余干扰轮廓。
4. 设置参数:角度搜索范围 ±180°、匹配阈值(0~1,一般 0.7~0.85)、金字塔层数。
5. 测试匹配:载入多张测试图片验证,观察匹配分数;保存模板到`.vmtpl`。
>
> 多模板方案:拖多个【高精度特征匹配】模块,分别创建不同零件模板,逻辑分支判断哪一个匹配成功。
### 阶段 4:九点标定(N 点标定模块)
1. 拖入【圆查找】模块,输入图像来自图像源;用来识别机械臂末端标记圆点,输出圆心像素坐标 Px,Py。
2. 拖入【N 点标定】模块。
- 平移次数 = 9,旋转次数 = 0(纯九点平移标定,不需要旋转点)
- 图像点输入:圆查找输出的圆心 Px,Py
3. 采集 9 组配对数据(重点!顺序不能乱)
1. 示教机械臂,移动标记到视野第 1 个点位;
2. 相机拍照,圆查找得到像素 (Px1,Py1);
3. 从机械臂示教器读取 BASE 坐标系物理坐标 (X1,Y1);
4. 将这一组【图像点 + 机械臂物理点】填入 N 点标定;
5. 重复,一共采集 9 个点位,均匀铺满整个相机视野。
4. 执行 N 点标定模块,软件自动求解变换矩阵,**评估标定状态 = 0 代表正常**,查看平移平均像素误差,建议<0.5 像素;
5. 点击【保存标定文件】,输出`.cal`标定文件。
### 阶段 5:标定转换(像素坐标转机械臂 XY)
1. 拖入【标定转换】模块
- 标定文件:加载刚才保存的`.cal`
- 输入图像点:高精度特征匹配输出的像素中心点 (Px,Py)
- 输出:机械臂物理坐标 X (mm), Y (mm)
2. 角度处理:特征匹配输出图像角度 Angle,**现场测试,按需加负号得到 Rz**(和 Halcon 一样,图像坐标系 Y 向下,角度方向可能反转)。
### 阶段 6:通信输出(发给机械臂)
拖入【通信管理】,TCP Server,把 X,Y,Rz 拼接字符串发给机械臂控制器。
### 阶段 7:整体连线逻辑
图像源 → 高精度特征匹配 → 标定转换 → 输出 X,Y
高精度特征匹配输出 Angle → 脚本修正角度 Rz
通信模块打包`X=xx,Y=xx,Rz=xx`发送给机械臂
## 三、C# SDK 二次开发代码(上位机调用 VM 方案,商用项目使用)
> 环境准备:安装 VisionMaster SDK;VS 新建 C# x64 项目,引用 `VM.Core.dll`、`VM.PlatformSDKCS.dll`。
> 前提:已经在 VM 软件里做完上面流程,保存工程为 `RobotLoc.vmsln`,包含特征匹配、标定转换模块,标定文件已经嵌入方案内。
using System;
using HIKVISION.VM;
namespace VMRobotVision
{
class Program
{
//VM方案对象
private static VmSolution _vmSolution;
static void Main(string[] args)
{
//1. 初始化VM系统
VMSystem vmSys = new VMSystem();
vmSys.Initialize();
//2.加载预先在VM软件做好的方案
string slnPath = @"D:\VM_Project\RobotLoc.vmsln";
_vmSolution = new VmSolution();
bool loadOk = _vmSolution.Load(slnPath);
if (!loadOk)
{
Console.WriteLine("VM方案加载失败!");
return;
}
Console.WriteLine("方案加载成功");
//3.循环执行视觉流程
while (true)
{
Console.WriteLine("按回车触发拍照识别,q退出");
string key = Console.ReadLine();
if (key == "q") break;
//执行VM内整个流程(相机采集+特征匹配+标定转换)
bool runRet = _vmSolution.Run();
if (!runRet)
{
Console.WriteLine("流程执行失败");
continue;
}
//====读取模块输出结果====
//高精度特征匹配输出:像素坐标、角度、匹配分数
float pixelX = 0, pixelY = 0, imgAngle = 0, score = 0;
_vmSolution.GetModuleOutput("高精度特征匹配1", "中心X", ref pixelX);
_vmSolution.GetModuleOutput("高精度特征匹配1", "中心Y", ref pixelY);
_vmSolution.GetModuleOutput("高精度特征匹配1", "角度", ref imgAngle);
_vmSolution.GetModuleOutput("高精度特征匹配1", "匹配分数", ref score);
if (score < 0.7f)
{
Console.WriteLine("工件未匹配");
continue;
}
//标定转换模块输出机械臂物理XY(mm)
float robotX = 0, robotY = 0;
_vmSolution.GetModuleOutput("标定转换1", "物理X", ref robotX);
_vmSolution.GetModuleOutput("标定转换1", "物理Y", ref robotY);
//角度修正,现场实测判断是否需要负号
float robotRz = imgAngle;
//robotRz = -imgAngle;
Console.WriteLine($"匹配成功,像素({pixelX:F2},{pixelY:F2})");
Console.WriteLine($"机械臂坐标 X:{robotX:F3} mm, Y:{robotY:F3} mm, Rz:{robotRz:F2} °");
//====TCP发送坐标给机械臂,这里封装发送函数====
SendRobotTcp(robotX, robotY, robotRz);
}
//释放资源
_vmSolution.Unload();
vmSys.UnInitialize();
}
//TCP发送到机械臂
static void SendRobotTcp(float x, float y, float rz)
{
try
{
System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient();
client.Connect("192.168.1.100", 5000);
string sendStr = $"X={x:F2},Y={y:F2},Rz={rz:F2}";
byte[] buf = System.Text.Encoding.ASCII.GetBytes(sendStr);
client.GetStream().Write(buf, 0, buf.Length);
client.Close();
}
catch (Exception ex)
{
Console.WriteLine("发送失败:" + ex.Message);
}
}
}
}
```
## 四、VM 内置脚本代码(VM 软件内部脚本模块,不用开 VS,直接在流程内写)
如果你不想用 C# 上位机,直接在 VM 流程里面加【脚本模块】,读取结果、打包 TCP 发送:
using System;
using Hik.Script.Methods;
class UserScript : ScriptMethods, IProcessMethods
{
public bool Process()
{
float px, py, angle, score, rx, ry;
GetFloatValue("高精度特征匹配1.中心X", ref px);
GetFloatValue("高精度特征匹配1.中心Y", ref py);
GetFloatValue("高精度特征匹配1.角度", ref angle);
GetFloatValue("高精度特征匹配1.匹配分数", ref score);
GetFloatValue("标定转换1.物理X", ref rx);
GetFloatValue("标定转换1.物理Y", ref ry);
if(score>0.7)
{
float rz = angle;
//rz = -angle;
string sendData = $"X={rx:F2},Y={ry:F2},Rz={rz:F2}";
SetStringValue("SendText", sendData);
}
return true;
}
}
## 五、VM / Halcon / OpenCV 横向对比(这套 2D 机械臂定位场景)

## 六、高频坑点
1. **N 点标定 9 个点必须铺满视野**,集中在画面中心,四角误差会很大。
2. 标定 Z 高度必须和抓取工件高度完全一致,否则标定失效。
3. VM【高精度特征匹配】角度定义和 Halcon 略有差异,**Rz 正负号必须现场实测一次**。
4. 不要修改镜头光圈焦距,一旦改动,必须**重新九点标定**。
5. Runtime 许可:VM 每一台产线工控机需要单独 Runtime 授权,开发环境免费。
6. 多模板:VM 中放多个特征匹配模块,并行执行,用条件分支选取匹配分数最高的工件。
# 核心结论
你说得非常关键:**前面讲的 Halcon 九点标定、OpenCV findHomography、VisionMaster N 点标定,全部都是【平面单应拟合】,它是把「镜头畸变 + 透视 + 缩放 + 旋转平移」打包,用一个 3×3 矩阵整体近似拟合。**
> 当镜头畸变比较小(远心镜头 / 高质量定焦镜头、视野不大),效果尚可;
> 普通工业定焦镜头,画面**四角离中心越远,镜头径向畸变(桶形 / 枕形)带来误差会明显变大**。
> 👉 **九点标定不能消除畸变,只能 “近似补偿畸变”,只在标定采样过的 9 个点附近精度好;四角没有标定点的区域,畸变带来的误差会暴露出来。**
## 1. 区分两个概念:相机内参标定 vs N 点(九点)手眼平面标定
### ① 相机内参标定(棋盘格标定,专门解镜头畸变)
求解:
- 内参矩阵(焦距、主点)
- 畸变系数 k1,k2,k3,p1,p2(径向畸变、切向畸变)
作用:**对整张图像每个像素做畸变校正,从根源消除镜头本身的形变**,图像先矫正成理想无畸变图像,再做定位 + 坐标转换。
### ② N 点 / 九点标定(单应矩阵,你前面学的这套)
不分离镜头畸变,直接拿 9 组像素→机械臂坐标,拟合一个全局线性变换。
数学假设:图像到世界平面是**理想透视变换**;镜头存在畸变时,这个假设本身不成立。
> 本质:用最小二乘法强行用一个平面变换去拟合带畸变的非线性图像。标定点覆盖到的区域误差被压低;远离标定点区域,畸变误差会显现。
## 2. 两种方案对比(针对镜头畸变场景)
### 方案 A:只做九点标定(之前方案)
✅ 优点:操作简单,现场调试快,代码少
❌ 缺点:**畸变大的镜头,画面四角精度差;更换镜头、光圈、焦距后必须重采 9 点**
适用:小视野、低畸变镜头、对四角精度要求不高的场景。
### 方案 B:【棋盘格标定去畸变 → 九点 N 点标定】推荐高精度场景
完整流水线(解决你担心的边缘畸变问题):
1. **棋盘格标定板拍摄多张不同角度图片 → 求解相机内参 + 畸变系数**
2. **图像预处理:使用内参、畸变系数对图像做去畸变矫正(undistort)**,矫正后图片消除镜头本身的桶形 / 枕形畸变
3. 在**矫正后的图像**上,再执行九点 N 点标定(像素点已经是无畸变的理想像素),求单应矩阵
4. 在线运行:相机原图 → 去畸变 → 模板匹配获取矫正后像素坐标 → 单应矩阵转机械臂 XY
> 这个方案,**镜头畸变被提前消除,全视野(中心 + 四角)精度均匀**。
> Halcon / OpenCV / VisionMaster 都支持这套流程。
## 3. OpenCV 增加【相机内参 + 畸变校正】完整代码扩展
> 在你上一版 OpenCV 代码基础上增加畸变校正,完整流程:
> 步骤 1:拍摄棋盘格,标定得到内参、畸变系数(一次性操作)
> 步骤 2:在线采集原图,先 undistort 去除畸变,再轮廓匹配 + 九点单应变换
### 3.1 棋盘格标定脚本(一次性运行,求内参、畸变系数)
import cv2
import numpy as np
import glob
# 棋盘格内角点数量:横向9个,纵向6个,按你的标定板修改
chess_w, chess_h = 9,6
objp = np.zeros((chess_w*chess_h,3), np.float32)
objp[:,:2] = np.mgrid[0:chess_w,0:chess_h].T.reshape(-1,2)
obj_points = [] # 世界点
img_points = [] # 图像点
images = glob.glob("chess/*.jpg") # 拍20张左右不同角度棋盘格放到chess文件夹
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (chess_w,chess_h),None)
if ret == True:
obj_points.append(objp)
#亚像素细化角点
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
img_points.append(corners2)
# 标定,求解内参、畸变系数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)
print("相机内参mtx:\n",mtx)
print("畸变系数dist:\n",dist)
#保存
np.save("mtx.npy",mtx)
np.save("dist.npy",dist)
### 3.2 在线识别主程序【增加去畸变】
import cv2
import numpy as np
#加载内参畸变
mtx = np.load("mtx.npy")
dist = np.load("dist.npy")
#加载九点标定单应矩阵
H = np.load("homography_matrix.npy")
cap = cv2.VideoCapture(0)
template_contour = np.load("template_contour.npy")
template_area = cv2.contourArea(template_contour)
match_threshold = 0.15
while True:
ret, frame = cap.read()
if not ret:break
h,w = frame.shape[:2]
# ==========核心:畸变矫正 ==========
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
dst = cv2.undistort(frame, mtx, dist, None, newcameramtx)
#裁剪黑边
x,y,ww,hh = roi
dst = dst[y:y+hh, x:x+ww]
#后续轮廓匹配,全部在矫正后的dst图像上处理
gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 80,255,cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
best_rect = None
best_score = 999
for cnt in contours:
area = cv2.contourArea(cnt)
if area < template_area*0.5 or area>template_area*1.5:
continue
score = cv2.matchShapes(template_contour,cnt,cv2.CONTOURS_MATCH_I1,0)
if score < match_threshold and score < best_score:
best_score = score
best_rect = cv2.minAreaRect(cnt)
if best_rect is not None:
(px,py),(ww,hh),angle = best_rect
pixel_pt = np.array([[px,py]],dtype=np.float32)
robot_pt = cv2.perspectiveTransform(pixel_pt, H)
rx, ry = robot_pt[0][0], robot_pt[0][1]
rz = angle
print(f"X={rx:.2f},Y={ry:.2f},Rz={rz:.2f}")
box = cv2.boxPoints(best_rect)
box = np.int64(box)
cv2.drawContours(dst,[box],0,(0,255,0),2)
cv2.imshow("undistort_img",dst)
k = cv2.waitKey(30)
if k == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
## 4. Halcon 对应的畸变校正流程
1. 使用`calibrate_cameras`,棋盘格标定,得到相机内参 + 畸变系数
2. `gen_image_undistort`或者`change_radial_distortion_image`对原图矫正
3. 在矫正后的图像上,采集 9 个标定点,做`vector_to_hom_mat2d`九点标定
> 优点:Halcon 标定、亚像素角点提取更稳定;
> 注意:**九点标定的标定点图像,必须使用矫正后的图像,不能直接原图**
## 5. VisionMaster 处理镜头畸变
VM 图形界面直接自带:【相机标定】模块(棋盘格内参标定)
1. 拖入【相机标定】模块,拍摄棋盘格,计算畸变系数
2. 开启图像流的**畸变校正**,输出矫正后的图像流
3. 矫正图像送入【N 点标定】做九点标定,再做特征匹配
> 全部可视化,不用手写内参代码。
## 6. 选型建议(非常实用)
1. **远心镜头**:几乎没有畸变 → 直接九点标定足够,不需要棋盘格内参标定。
2. **普通定焦镜头,视野大,四角精度要求高**:必须做【棋盘格相机标定 + 去畸变 + N 点九点标定】,消除桶形畸变带来的四角误差。
3. **小视野普通镜头,精度要求 ±0.3~0.5mm**:单纯九点标定可以勉强够用,尽量把 9 个标定点延伸到视野四个角落,用更多标定点(比如 12/16 点)降低畸变带来的拟合误差。
## 7. 补充一个误区
很多人以为:**标更多点(16 点、25 点)就可以替代畸变校正**。
❌不行。
畸变是非线性的;单应矩阵是线性变换。哪怕用 25 点最小二乘拟合,依然只是**近似拟合非线性畸变**,不能真正消除畸变。
想要全视野均匀高精度,**必须先做相机内参标定、图像去畸变**。
Halcon 完整棋盘格相机标定 + 图像去畸变 + 九点 N 点标定的完整 HDevelop 代码。
> 整套流水线:
> 原图 → 相机标定求解内参畸变 → 图像去畸变(消除桶形 / 枕形畸变)→ 在矫正图上做九点标定求 HomMat2D → 特征模板匹配(shape_model)→ 像素转机械臂 XY,输出 X,Y,Rz
> 前提:
> 1. 眼在外 Eye-to-Hand,工件在固定 Z 平面;
> 2. 棋盘格拍摄多幅图像做相机标定;
> 3. **九点标定点,全部在【去畸变后的图像】采集**(关键点!不能用原始带畸变图片)
## 一、阶段 1:相机标定(棋盘格,一次性执行,求相机内参 + 畸变系数)
HDevelop 代码
* ====================== 【1.相机标定,棋盘格】======================
* 棋盘格参数:内角点数量 Row行,Col列,棋盘格方块尺寸mm
CalibBoardRow := 9
CalibBoardCol := 6
CellSize := 5.0
* 创建标定模型
create_calib_model ('calib_board', CalibBoardRow, CalibBoardCol, CellSize, [], [], CalibModelID)
* 读取多张棋盘格图片(建议15~20张,多角度,远近,铺满画面)
ImageFiles := ['calib/chess_01.png','calib/chess_02.png','calib/chess_03.png','calib/chess_04.png','calib/chess_05.png']
NumImg := |ImageFiles|
for Index := 0 to NumImg -1 by 1
read_image (Image, ImageFiles[Index])
* 添加图像到标定模型,自动提取棋盘格角点
add_calib_image (Image, CalibModelID, Row, Column)
endfor
* 执行相机标定,得到相机内参、畸变、外参
calibrate_cameras (CalibModelID, CameraParam, CameraPose, Errors)
* 保存相机参数(后面在线程序加载)
write_cam_par (CameraParam, 'camera_param.dat')
* 释放标定模型
clear_calib_model (CalibModelID)
* 打印相机参数,查看畸变系数
disp_message (WindowHandle, '相机标定完成', 'window', 12, 12, 'black', 'true')
> CameraParam 里面包含:焦距、主点、径向畸变 k1,k2,k3,切向畸变 p1,p2。
## 二、阶段 2:九点标定(**在去畸变后的图像采集 9 组点**,一次性执行)
> 操作:机械臂标记移动 9 个位置,**相机原图先去畸变,再提取标记圆心,得到矫正后像素坐标**,配对机械臂 BASE 坐标。
* ======================【2.九点标定(矫正后图像上采集)】======================
* 加载前面保存好的相机内参
read_cam_par ('camera_param.dat', CameraParam)
* -----------【现场采集的9组数据,替换为你现场实际值】-----------
* 【去畸变后的图像像素坐标 Row, Column】
RowPoints := [200,200,200,320,320,320,450,450,450]
ColPoints := [200,320,450,200,320,450,200,320,450]
* 机械臂基坐标系XY mm,示教器读取
RobotX := [10,10,10,20,20,20,30,30,30]
RobotY := [10,20,30,10,20,30,30,20,10]
* -------------------------------------------------------------
* 求解单应矩阵 HomMat2D,像素(矫正后) → 机械臂XY
vector_to_hom_mat2d (RowPoints, ColPoints, RobotX, RobotY, HomMat2D)
* 保存九点标定矩阵
write_matrix (HomMat2D, 'hom_mat2d.dat', 'real', '')
* 验证:重投影误差
projective_trans_point_2d (HomMat2D, RowPoints, ColPoints, CalcRobotX, CalcRobotY)
ErrorX := CalcRobotX - RobotX
ErrorY := CalcRobotY - RobotY
RMSE := sqrt(mean(ErrorX*ErrorX + ErrorY*ErrorY))
disp_message (WindowHandle, '九点标定RMSE(mm)='+RMSE$'.3f', 'window', 40, 12, 'red', 'true')
## 三、阶段 3:在线识别完整主程序(重点:原图→去畸变→模板匹配→坐标转换)
> 流程:相机拍照原图 → 图像去畸变校正 → shape_model 特征模板匹配得到矫正后 Row/Col/Angle → projective_trans_point_2d 转机械臂 XY → 角度修正得到 Rz → TCP 发送给机械臂
* ======================【3.在线识别主程序】======================
dev_close_window ()
dev_update_off ()
* 加载相机内参、九点标定矩阵、shape模板
read_cam_par ('camera_param.dat', CameraParam)
read_matrix ('hom_mat2d.dat', HomMat2D)
read_shape_model ('part_template.shm', ModelID)
* 打开窗口,读取测试图像(工业现场替换为grab_image采集相机)
read_image (ImageRaw, 'test_img.png')
get_image_size (ImageRaw, W, H)
dev_open_window (0, 0, W, H, 'black', WindowHandle)
* =========核心:对原始图像做径向畸变矫正=========
* 根据相机参数生成校正映射
gen_radial_distortion_map (Map, CameraParam, 'bilinear')
* 原始图像映射矫正,得到无畸变图像
map_image (ImageRaw, Map, ImageUndist)
* 在【去畸变后的图像】做形状模板匹配
find_shape_model (ImageUndist, ModelID, rad(-180), rad(180), 0.7, 0, 0, 'least_squares', 0, 0.7, Row, Column, Angle, Score)
if(|Row|>0)
* 取第一个匹配结果
R = Row[0]
C = Column[0]
AngRad = Angle[0]
ScoreVal = Score[0]
* 矫正后像素坐标 → 机械臂XY
projective_trans_point_2d (HomMat2D, R, C, RobotX, RobotY)
* 角度转为角度制;现场按需加负号 RobotRz := -deg(AngRad)
RobotRz := deg(AngRad)
disp_message (WindowHandle, '匹配成功 Score='+ScoreVal$'.3f', 'window', 10,10,'black','true')
disp_message (WindowHandle, 'Robot X='+RobotX$'.3f'+' Y='+RobotY$'.3f'+' Rz='+RobotRz$'.2f', 'window',30,10,'black','true')
* 绘制匹配轮廓
get_shape_model_contours (ModelContours, ModelID, 1)
vector_to_hom_mat2d (0,0, R, C, AngRad, HomMat2D, HomMat2DTrans)
affine_trans_contour_xld (ModelContours, ContoursAffin, HomMat2DTrans)
dev_display (ImageUndist)
dev_display (ContoursAffin)
else
disp_message (WindowHandle, '未匹配到工件', 'window',10,10,'red','true')
endif
* 释放资源
clear_shape_model (ModelID)
# 关键要点(必须注意,很容易踩坑)
1. **两套图像不能混淆**
- 相机标定:原始相机拍摄的带畸变图片
- 九点采集标定点:**必须使用去畸变后的图片**,提取标定点圆心;
- 在线模板匹配:同样在去畸变图上运行。
如果九点标定点用原图像素,而匹配在矫正图,坐标完全错乱。
2. `gen_radial_distortion_map` 只需要生成一次 map,可以保存映射文件,在线循环直接加载,加速运行。
write_image (Map, 'map.tif')
read_image (Map, 'map.tif')
3. 镜头光圈、焦距一旦调整,**必须重新做相机标定(内参 + 畸变)**,然后重新九点标定。
4. 远心镜头:畸变极小,可以跳过`gen_radial_distortion_map`这一步,直接原图九点标定。
# 整套流程顺序总结
1. 拍摄 15~20 张棋盘格图片,运行【相机标定代码】,输出`camera_param.dat`
2. 机械臂示教 9 个标定点,**每张图片原图做去畸变**,在矫正图识别标记圆心,收集 9 组矫正后像素 + 机械臂 XY → 运行九点标定代码,输出`hom_mat2d.dat`
3. 在矫正后的图片,使用 Matching 助手制作 shape 模板,保存`part_template.shm`
4. 在线程序:相机抓取原图 → map_image 去畸变 → find_shape_model → projective_trans_point_2d → X,Y,Rz 输出给机械臂
# 对比简化版(不带畸变校正)
- 不带畸变校正:原始图直接九点标定,**畸变是拟合在 HomMat 矩阵里面**,四角误差大;
- 带这套畸变校正:先数学消除镜头非线性畸变,九点标定只负责平面仿射 / 透视映射,**全画面精度均匀**,适合大视野、高精度场景。
功能:相机抓拍原图 → 径向畸变校正 → 形状模板匹配 → 九点单应矩阵坐标转换 → 输出机械臂 X,Y,Rz
> 配套前面 HDevelop 代码:棋盘格标定得到`camera_param.dat`、九点标定`hom_mat2d.dat`、形状模板`part_template.shm`
> 开发环境:Visual Studio,.NET Framework /.NET6+,引用 `halcondotnet.dll`、`hdevengine_dotnet.dll`
> 注意:**所有坐标逻辑和 HDevelop 完全对齐;像素是矫正后图像坐标**
## 引用命名空间
using System;
using HalconDotNet;
using System.Net.Sockets;
using System.Text;
## 完整主类代码
namespace HalconRobotVision
{
class VisionRobot
{
// Halcon句柄
private HImage _imgRaw;
private HImage _imgUndist;
private HMap _distortMap;
private HCamPar _camParam;
private HMatrix _homMat2D;
private HShapeModel _shapeModel;
// 窗口句柄(可选,用于显示图像,产机可删除)
private HWindow _win;
public VisionRobot()
{
// 初始化Halcon引擎
HOperatorSet.HalconInit();
}
/// <summary>
/// 加载标定文件、模板、畸变映射(一次性初始化,程序启动调用一次)
/// </summary>
public bool LoadResources()
{
try
{
// 1.加载相机内参(棋盘格标定输出)
_camParam = new HCamPar();
_camParam.ReadCamPar("camera_param.dat");
// 2.生成径向畸变校正映射(只生成一次)
_distortMap = new HMap();
HOperatorSet.GenRadialDistortionMap(_distortMap, _camParam, "bilinear");
//3.加载九点标定单应矩阵
_homMat2D = new HMatrix();
_homMat2D.ReadMatrix("hom_mat2d.dat");
//4.加载shape模板
_shapeModel = new HShapeModel();
_shapeModel.ReadShapeModel("part_template.shm");
// 创建显示窗口,产线工控机可以注释掉
_win = new HWindow();
return true;
}
catch (Exception ex)
{
Console.WriteLine("资源加载失败:" + ex.Message);
return false;
}
}
/// <summary>
/// 视觉主流程:输入原图,输出机械臂 X,Y,Rz
/// 返回值:是否匹配成功
/// </summary>
public bool RunVision(HImage imgRaw, out double robotX, out double robotY, out double robotRz, out double score)
{
robotX = 0;
robotY = 0;
robotRz = 0;
score = 0;
try
{
_imgRaw = imgRaw;
// ============【核心:图像去畸变校正】============
_imgUndist = new HImage();
HOperatorSet.MapImage(_imgRaw, _distortMap, _imgUndist);
// ============【形状模板匹配,在矫正后图像上】============
HTuple row, col, angle, scoreTuple;
HOperatorSet.FindShapeModel(_imgUndist, _shapeModel, -180 * Math.PI / 180, 180 * Math.PI / 180,
0.7, 0, 0, "least_squares", 0, 0.7, out row, out col, out angle, out scoreTuple);
if (row.Length == 0)
{
Console.WriteLine("未检测到工件");
return false;
}
//取第一个匹配结果
double r = row[0].D;
double c = col[0].D;
double angRad = angle[0].D;
score = scoreTuple[0].D;
// ============【矫正后像素 → 机械臂物理坐标 九点单应变换】============
HTuple rxTuple, ryTuple;
HOperatorSet.ProjectiveTransPoint2d(_homMat2D, r, c, out rxTuple, out ryTuple);
robotX = rxTuple.D;
robotY = ryTuple.D;
// 弧度转角度;现场测试,角度方向反了就 robotRz = -angRad * 180 / Math.PI;
robotRz = angRad * 180 / Math.PI;
// 可选:绘图展示匹配轮廓
ShowResult(r, c, angRad);
return true;
}
catch (Exception ex)
{
Console.WriteLine("视觉流程异常:" + ex.Message);
return false;
}
}
/// <summary>
/// 绘制匹配结果(调试用,产线可删掉)
/// </summary>
private void ShowResult(double row, double col, double angRad)
{
HXLDCont modelContour = _shapeModel.GetShapeModelContours(1);
HMatrix homTrans = new HMatrix();
HOperatorSet.VectorToHomMat2d(0, 0, row, col, angRad, homTrans);
HXLDCont affContour = new HXLDCont();
HOperatorSet.AffineTransContourXld(modelContour, affContour, homTrans);
_win.DispImage(_imgUndist);
_win.DispXld(affContour, "green");
}
/// <summary>
/// TCP发送坐标字符串给机械臂控制器
/// </summary>
public void SendRobotTcp(double x, double y, double rz, string ip, int port)
{
try
{
TcpClient client = new TcpClient();
client.Connect(ip, port);
string sendStr = $"X={x:F3},Y={y:F3},Rz={rz:F2}";
byte[] data = Encoding.ASCII.GetBytes(sendStr);
client.GetStream().Write(data, 0, data.Length);
client.Close();
Console.WriteLine("发送成功:" + sendStr);
}
catch (Exception ex)
{
Console.WriteLine("TCP发送失败:" + ex.Message);
}
}
/// <summary>
/// 释放资源,程序退出调用
/// </summary>
public void Dispose()
{
if (_imgRaw != null) _imgRaw.Dispose();
if (_imgUndist != null) _imgUndist.Dispose();
if (_distortMap != null) _distortMap.Dispose();
if (_camParam != null) _camParam.Dispose();
if (_homMat2D != null) _homMat2D.Dispose();
if (_shapeModel != null) _shapeModel.Dispose();
if (_win != null) _win.Dispose();
HOperatorSet.HalconExit();
}
}
class Program
{
static void Main(string[] args)
{
VisionRobot vision = new VisionRobot();
if (!vision.LoadResources())
{
Console.WriteLine("资源加载失败,程序退出");
vision.Dispose();
return;
}
//===== 循环视觉检测 =====
while (true)
{
Console.WriteLine("\n按回车触发拍照识别,q退出");
string input = Console.ReadLine();
if (input == "q") break;
// ========= 相机采集部分 =========
// 【重点】这里替换为工业相机GrabImage
// HImage imgRaw = new HImage();
// imgRaw.GrabImage(acqHandle);
// 调试阶段:直接读取本地图片代替相机抓拍
HImage imgRaw = new HImage();
imgRaw.ReadImage("test_img.png");
bool ok = vision.RunVision(imgRaw, out double rx, out double ry, out double rz, out double score);
if (ok)
{
Console.WriteLine($"匹配分数:{score:F3} | 机械臂 X:{rx:F3} mm, Y:{ry:F3} mm, Rz:{rz:F2} °");
//发送给机械臂IP和端口
vision.SendRobotTcp(rx, ry, rz, "192.168.1.100", 5000);
}
imgRaw.Dispose();
}
vision.Dispose();
}
}
}
## 配套说明
### 1. 文件放置位置
将下面 4 个文件放在程序`bin/Debug`目录下:
1. `camera_param.dat` 棋盘格相机标定输出(内参 + 畸变系数)
2. `hom_mat2d.dat` 九点 N 点标定单应矩阵
3. `part_template.shm` shape 形状模板(在矫正后的图像上制作!重要)
4. 测试图片 `test_img.png`
> ⚠️ **模板制作关键点**:模板必须在**去畸变后的图像**上创建。不能直接在原图用 HDevelop 做模板。
### 2. 工业相机采集替换代码
如果用 Basler / 海康工业相机,替换掉读取图片代码。
// 相机采集示例(需要先初始化图像采集句柄AcqHandle)
// HImage imgRaw = new HImage();
// imgRaw.GrabImage(AcqHandle);
### 3. 重要坑点提醒
1. **坐标原点、角度方向**:
机械臂收到 Rz 不对,修改`robotRz = -angRad * 180 / Math.PI;`
2. 一旦镜头光圈、焦距变动,**必须重新棋盘格标定 + 重新九点标定 + 重建模板**。
3. 畸变映射`GenRadialDistortionMap`比较耗时,**只在程序启动生成一次,不要放到循环内部**。
4. 资源释放:所有 HImage、HMatrix、HShapeModel,用完一定要 Dispose,不然内存持续上涨。
### 4. 运行流程回顾
相机原图 → MapImage去畸变 → FindShapeModel形状模板匹配
→ 得到矫正后像素(R,C) → ProjectiveTransPoint2d用HomMat2D转换
→ 输出机械臂X,Y(mm),角度转Rz → TCP发送给机械臂控制器
### 5. 性能优化建议(量产项目)
1. `GenRadialDistortionMap`生成的 Map 可以`WriteImage`保存为文件,启动直接 ReadImage 加载,省去每次生成 map 的时间。
2. 匹配参数:金字塔层数、最小匹配分数,根据现场工件调整。
3. 产线不需要图像显示时,直接删除 HWindow 相关代码,减少 CPU 开销。
C# Halcon SDK:棋盘格相机标定代码(求解相机内参 + 畸变系数,输出 camera_param.dat)
> 作用:采集多张棋盘格图片,自动识别内角点,执行相机标定,输出`camera_param.dat`,供上面视觉主程序加载做图像去畸变。
> 配套环境:引用 `halcondotnet.dll`
> 标定板参数示例:棋盘内角点 9 列 ×6 行,方格尺寸 5mm,根据你的标定板修改。
using System;
using System.Collections.Generic;
using HalconDotNet;
namespace HalconCameraCalibration
{
class Program
{
static void Main(string[] args)
{
HOperatorSet.HalconInit();
// ========== 【请根据你的棋盘格修改这两个参数!】==========
int chessCol = 9; // 棋盘内角点 列数
int chessRow = 6; // 棋盘内角点 行数
double cellSizeMm = 5.0; // 每个方格边长(mm)
// ======================================================
// 1. 创建标定模型
HCalibModel calibModel = new HCalibModel();
HOperatorSet.CreateCalibModel("calib_board", chessRow, chessCol, cellSizeMm,
new HTuple(), new HTuple(), calibModel);
// 2. 棋盘图片列表,准备15~20张不同角度的棋盘照片
List<string> imgPaths = new List<string>()
{
@"chess\chess_01.png",
@"chess\chess_02.png",
@"chess\chess_03.png",
@"chess\chess_04.png",
@"chess\chess_05.png",
@"chess\chess_06.png",
@"chess\chess_07.png",
@"chess\chess_08.png",
@"chess\chess_09.png",
@"chess\chess_10.png",
@"chess\chess_11.png",
@"chess\chess_12.png",
@"chess\chess_13.png",
@"chess\chess_14.png",
@"chess\chess_15.png"
};
HTuple allRow = new HTuple();
HTuple allCol = new HTuple();
HTuple imgCount = 0;
foreach (string path in imgPaths)
{
HImage img = new HImage();
img.ReadImage(path);
HTuple rowPoints, colPoints;
bool findOk = HOperatorSet.AddCalibImage(img, calibModel, out rowPoints, out colPoints);
if (findOk)
{
Console.WriteLine($"成功识别棋盘:{path}");
imgCount += 1;
}
else
{
Console.WriteLine($"【警告】无法识别棋盘角点:{path}");
}
img.Dispose();
}
Console.WriteLine($"\n一共加载 {imgCount} 张有效标定图像,开始标定计算...");
//3. 执行相机标定
HCamPar camParam = new HCamPar();
HPoseData camPose = new HPoseData();
HTuple errors;
HOperatorSet.CalibrateCameras(calibModel, camParam, camPose, out errors);
//打印标定平均误差
double avgError = errors.TupleMean().D;
Console.WriteLine($"标定平均重投影误差:{avgError:F4} 像素");
if (avgError > 0.3)
{
Console.WriteLine("⚠️ 误差偏大,建议重新拍摄棋盘图片!理想<0.1~0.2像素");
}
//4. 保存相机参数(给主视觉程序使用)
camParam.WriteCamPar("camera_param.dat");
Console.WriteLine("✅ 相机参数已保存 camera_param.dat");
//释放资源
calibModel.Dispose();
camParam.Dispose();
camPose.Dispose();
HOperatorSet.HalconExit();
Console.WriteLine("标定完成,按回车退出");
Console.ReadLine();
}
}
}
## 标定操作步骤
1. 准备**棋盘格标定板**(建议陶瓷标定板,精度高,不要打印 A4 纸)
2. 固定相机、镜头,光圈、焦距锁死!**标定之后绝对不能再调镜头**
3. 拍摄 15~20 张照片:
- 棋盘放在视野中心、左上、右上、左下、右下
- 棋盘轻微倾斜、远近变化
- 棋盘尽量铺满画面,但不要超出图像边界
4. 图片放到 `chess` 文件夹,修改代码内棋盘行列和方格尺寸
5. 运行程序,自动识别角点,执行标定,输出`camera_param.dat`
## 接下来:九点标定 C# 代码(采集矫正后像素,生成 hom_mat2d.dat)
> 这个代码,用于机械臂示教 9 个标定点,**原图先畸变矫正,提取标记圆心,配对机械臂坐标,求解单应矩阵**
using System;
using HalconDotNet;
namespace HalconNinePointCalib
{
class Program
{
static void Main(string[] args)
{
HOperatorSet.HalconInit();
//加载相机内参(上一步棋盘格标定输出)
HCamPar camParam = new HCamPar();
camParam.ReadCamPar("camera_param.dat");
//生成畸变校正映射
HMap distMap = new HMap();
HOperatorSet.GenRadialDistortionMap(distMap, camParam, "bilinear");
//========= 【现场采集的9组数据,替换为你现场真实值!】=========
// 注意:Row,Col 是【去畸变之后图像上的圆心像素坐标】!不是原图!
HTuple rowList = new HTuple(new double[] { 200, 200, 200, 320, 320, 320, 450, 450, 450 });
HTuple colList = new HTuple(new double[] { 200, 320, 450, 200, 320, 450, 200, 320, 450 });
//机械臂BASE坐标,示教器读取(mm)
HTuple robotXList = new HTuple(new double[] { 10, 10, 10, 20, 20, 20, 30, 30, 30 });
HTuple robotYList = new HTuple(new double[] { 10, 20, 30, 10, 20, 30, 10, 20, 30 });
//===========================================================
//求解单应矩阵:矫正图像像素 → 机械臂XY
HMatrix homMat2D = new HMatrix();
HOperatorSet.VectorToHomMat2d(rowList, colList, robotXList, robotYList, homMat2D);
//保存矩阵
homMat2D.WriteMatrix("hom_mat2d.dat", "real", "");
Console.WriteLine("✅ 九点标定矩阵保存成功 hom_mat2d.dat");
//重投影误差校验
HTuple calcRobotX, calcRobotY;
HOperatorSet.ProjectiveTransPoint2d(homMat2D, rowList, colList, out calcRobotX, out calcRobotY);
HTuple errX = calcRobotX - robotXList;
HTuple errY = calcRobotY - robotYList;
HTuple errSquare = errX * errX + errY * errY;
double rmse = Math.Sqrt(errSquare.TupleMean().D);
Console.WriteLine($"九点标定RMSE误差(mm):{rmse:F4}");
//资源释放
camParam.Dispose();
distMap.Dispose();
homMat2D.Dispose();
HOperatorSet.HalconExit();
Console.ReadLine();
}
}
}
## 九点标定现场采集流程(非常关键)
1. 机械臂末端贴圆形标记(实心黑圆)
2. 依次移动机械臂标记到视野 9 个点位,均匀分布到画面四角 + 中心
3. 每一个点位:
- 相机抓拍原图
- 执行畸变矫正得到矫正图
- 在矫正图上找圆,得到矫正后圆心 Row/Col
- 记录该点机械臂 BASE X,Y
4. 把 9 组【矫正后像素 + 机械臂 XY】填入代码数组,运行,得到 hom_mat2d.dat
## 整套工程文件清单汇总
bin/Debug
├─ camera_param.dat 棋盘格标定输出(内参+畸变)
├─ hom_mat2d.dat 九点N点标定单应矩阵
├─ part_template.shm shape模板(必须在矫正图像上创建)
├─ chess文件夹 棋盘标定图片
└─ test_img.png 测试工件图片
```
## 整套流水线完整顺序回顾
1. 拍摄棋盘格照片 → C# 棋盘标定程序 → `camera_param.dat`
2. 机械臂示教 9 点,采集矫正图像上标记圆心 → C# 九点标定程序 → `hom_mat2d.dat`
3. 在**畸变矫正后的图像**制作 shape 模板 → `part_template.shm`
4. 主视觉程序:Grab 原图 → 畸变校正 → FindShapeModel → ProjectiveTransPoint2d → TCP 发送 X,Y,Rz 给机械臂
## 重要注意事项
1. 所有标定、模板制作,**镜头光圈、焦距固定锁死,不能改动**,改动后全部标定作废。
2. 模板不能用原图制作!必须在 undistort 矫正后的图像上制作 shape model。
3. 九点标定点像素,**必须是矫正后的像素坐标,不能使用原始图像像素**,否则坐标转换完全错乱。
4. 标定重投影误差参考:
- 相机标定:<0.2 像素优秀,>0.3 像素需要重拍棋盘。
- 九点标定 RMSE:根据工作距离,一般控制在 0.05~0.2mm 为佳。
国产 RISC-V 架构 AI 视觉芯片,是 K210 的升级款,用来做嵌入式机器视觉
## 核心信息
1. **厂商**:嘉楠科技(勘智 Kendryte)
2. **定位**:端侧 AI 视觉芯片,自带硬件 KPU 人工智能算力,**能接摄像头做图像识别、模板匹配、目标检测、YOLO 识别,还内置 3D 结构光深度引擎,可以接深度相机**
3. 算力:等效约 6T,是老款 K210 的 13.7 倍;支持最多 3 路摄像头同时采集,最高 4K 图像输入。
4. 开发板:**CanMV K230**(庐山派),直接买到成品开发板,MicroPython 简单开发,很多自动化培训、教学项目在用。
## 和你前面的项目关系(重点)
- ✅ 可以替代 PC、OpenCV 上位机,**低成本做 2D 视觉定位,算出工件 XY + 旋转角度**
- ✅ 自带网口 / 串口,识别完坐标,通过 Modbus/TCP 发给 S7-200SMART、S7-1200 PLC,驱动机械臂抓取
- ✅ 优势:**便宜!芯片功耗很低,不需要电脑,嵌入式小型视觉方案**
## 极简卡片
- K230:国产 AI 视觉芯片,CanMV 开发板
- 用途:摄像头图像采集、AI 识别、2D 定位,输出 XYR 坐标给 PLC
- 开发:MicroPython 上手简单,自动化培训常用
- 对比:K210(老款弱一点);K230D 是精简版,芯片自带内存,板子更小
## 选型区分
- Halcon/VisionMaster:工业成熟视觉软件(电脑 / 专用控制器,贵,稳定)
- OpenCV:电脑上位机开源视觉
- K230 CanMV:嵌入式板卡,**低成本小型视觉方案,无电脑**
下一篇:PLC学习前置知识点汇集