您现在的位置是:网站首页> PLC自动化

机器视觉相关技术收集

摘要

机器视觉相关技术收集

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是静态存储类似硬盘存储


1.jpg

1.png


1.png

***避免写驱动:采用串口、网口、蓝牙***



AI视觉技术应用与开发收集

工业机器人相关信息收集

AI人工智能模型创建训练等知识积累yolo

商用权衡


Halcon技术收集

机器视觉Halcon教程

对于一个开发小白详细介绍机器视觉实现步骤并给出实现代码

Halcon 创建模版只要一张图片吗?

改成使用几张图做模板,检测匹配获得坐标的代码

用 gen_rectangle1 (ROI1, 120,180,380,450) 标记器件,难道没有软件直接标注生成模板文件 shm 吗?

详细介绍下九点标定

Halcon 完整棋盘格相机标定 + 图像去畸变 + 九点 N 点标定的完整 HDevelop 代码。

C# Halcon SDK:棋盘格相机标定代码(求解相机内参 + 畸变系数,输出 camera_param.dat)

C# + Halcon SDK 完整工程代码


海康 VisionMaster

同样的功能用 VisionMaster 实现过程详细介绍并给出实现代码


OpenCV

同样功能用 OpenCV 如何实现,详细介绍操作步骤并给出实现代码



综合

好像没考虑镜头畸变问题左右上下距离镜头远的地方


开发板

AI视觉开发板


商用权衡

集成商,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(人力成本高)



机器视觉Halcon教程


对于一个开发小白详细介绍机器视觉实现步骤并给出实现代码

# 机器视觉引导机械臂(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 创建模版只要一张图片吗?

# 一句话结论

**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 个模板,循环匹配。


## 补充对比表

QQ_1788938374763.png

## 极简示例代码(单张图建形状模板,前面代码精简版)

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.jpg

## 一、匹配助手怎么操作(小白图形化建模板)

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

QQ_1788941768098.png

矩阵包含:平移、旋转、缩放、镜头轻微梯形透视畸变、坐标轴方向翻转。

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.jpg

## 三、完整实操步骤(小白现场操作版)

### 步骤 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 机械臂定位场景)

QQ_1788944150531.png

## 六、高频坑点

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 矩阵里面**,四角误差大;

- 带这套畸变校正:先数学消除镜头非线性畸变,九点标定只负责平面仿射 / 透视映射,**全画面精度均匀**,适合大视野、高精度场景。


C# + Halcon SDK 完整工程代码

功能:相机抓拍原图 → 径向畸变校正 → 形状模板匹配 → 九点单应矩阵坐标转换 → 输出机械臂 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 为佳。


AI视觉开发板

K230(嘉楠勘智 Kendryte K230)




K230(嘉楠勘智 Kendryte K230)

国产 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:嵌入式板卡,**低成本小型视觉方案,无电脑**





Top