面型工件有序上下料(并行化)视觉参数调整指南
约 23344 字大约 78 分钟
入门引导
背景介绍
新增 "面型工件有序上下料(并行化)" 场景,作为面型有序的视觉加速方案,场景特点在于提升了视觉计算节拍,通过并行化计算减少计算耗时,直接生成抓取点。
与其他场景视觉加速模式开启的方式不同,面型有序场景是通过新建作业。
作业场景选择
当前面型有序场景下,可选作业场景有两种,对比如下表。
| 工作流 | 面型有序上下料 | 面型有序上下料(并行化) | 备注 |
|---|---|---|---|
| 精度 | 高依赖点云密度与场景点云一致性 | 较高依赖图像特征、点云密度及场景点云的一致性 | / |
| 速度 | 较快(单实例) | 快(多实例) | 仅并行化可以输出场景全部有效结果 |
| 调参 | 中等需要有匹配调参经验 | 简单较简单固定(类似通用工件) | 并行化参数后续会隐藏部分大师类 |
| 适用性 | 强适用所有面型工件 | 较弱当前版本支持来料朝向不一致的情况 | 并行化已更新多模版模式,支持多个来料方向 |
| 模版制作难度 | 一般软件支持 | 较复杂当前版本需要使用脚本 | 后续并行化将模版制作并入PickWiz中 |
| 模版特点 | 参考 | 选取相机视野相对居中的完整场景实例点云 | / |
搭建项目
(1)新建一个面型工件有序上下料(并行化)项目(项目名称、项目地址可以自定义,项目名称不能有中文)
工件类型:面型工件(不是圆、圆柱、四边形,且正反面差异较小)
(2)相机、机器人配置
(3)添加工件

- 工件信息
工件名称可自定义,工件类型默认为标准工件且不可更改,工件ID可自定义,用于机器人抓取时自动切换工件
点云文件:工件点云模板,面型工件有序上下料(并行化)场景的点云文件较为特殊,制作方法请参照2.2.1模板文件路径
精匹配点云模板:用于精匹配
相机参数:不需要
- 模型信息
视觉模型:面型工件应用的2D识别方案是基于CAD的合成数据训练(一键联通)。不同的面型工件应用的视觉模型需要经过一键联通训练得到。



网格文件:一般上传工件CAD,为了摒弃一些噪声需要标准化网格文件,也可在点云模板制作标准化网格文件
工件属性:长条型、对称型、高反型、Low solidity型
来料形式:自定义来料形式------录入来料形式;紧密贴合------每行每列工件个数范围
作业环境:录入环境文件,一键联通中数据生成的环境会被自动替换为录入的环境,提高识别效果
工件纹理:录入工件纹理,一键联通中训练模型时会使用录入的工件纹理做数据增强,提高识别效果
混合无序场景数据:开启后,使用一键联通训练模型时,会同时生成无序场景和有序场景的合成数据用于模型训练,提高识别效果
模型最大识别数:默认20,根据场景需求修改
- 抓取点:根据工件设置抓取点

绝对坐标系:以初始点为原点,初始点是工件点云和CAD自带的。
抓取点坐标系(偏移量):以当前抓取点为原点。
(4)添加末端工具、手眼标定、ROI
(5)可选的功能选项:实例优化、碰撞检测、视觉分类、点云噪点滤除、料框及工件碰撞检测
实例优化:优化模型生成的实例,对实例掩膜进行处理。
碰撞检测:碰撞检测功能用于检测末端工具与容器的碰撞,过滤可能碰撞的抓取姿态。碰撞检测使用指南
视觉分类:用于识别同一工件的不同纹理、不同朝向等特征。视觉分类使用指南
点云噪点滤除:可导入工件的点云模板,来滤除实例工件点云的噪点点云噪点滤除使用指南
料框及工件碰撞检测:检测末端工具与场景(料框,场景点云)碰撞,过滤可能碰撞的抓取姿态。料框及工件碰撞检测使用指南
视觉参数
- 2D识别:从实际场景中识别分割出实例
预处理:在进行实例分割之前,对2D图像进行处理(常用:填充深度图空洞&边缘增强&提取最上层纹理&去除roi3d外的图片背景)
实例分割:分割实例(缩放比例&置信度下限阈值&自动增强),加速可取消勾选 返回掩膜
点云生成:生成实例点云的方式,使用分割后的实例掩膜或包围盒生成实例点云/使用过滤后的实例掩膜或包围盒生成实例点云
实例过滤:对分割出的实例进行过滤
实例排序:对实例进行排序
- 3D计算:计算实例在相机坐标系下的姿态并生成抓取点
预处理:在计算抓取点之前,对3D点云进行预处理
姿态估计:计算实例在相机坐标系下的姿态(粗匹配、精匹配)并生成抓取点
- 抓取点处理:对抓取点进行过滤、调整、排序
抓取点过滤:对抓取点进行过滤
抓取点调整:对抓取点进行调整
抓取点排序:对抓取点进行排序
1. 2D识别
1.1 预处理
2D识别的预处理是在实例分割之前对2D图像进行处理

1.1.1 双边滤波

- 功能
基于双边滤波的图像平滑功能
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 最大深度差值 | 双边过滤的最大深度差值 | 0.03 | [0.01, 1] |
| 过滤核大小 | 双边过滤卷积核大小 | 7 | [1, 3000] |
1.1.2 深度转法向量图

- 功能
通过深度图计算像素法向量,并把图片转换成法向量图
1.1.3 图像增强

- 功能
常用图像增强,如色彩饱和度、对比度、亮度、锐利度
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 图像增强类型 | 对图像的某个元素进行增强 | 对比度 | 色彩饱和度、对比度、亮度、锐利度 |
| 图片增强阈值 | 对图像的某个元素增强多少 | 1.5 | [0.1, 100] |
1.1.4 直方图均衡

- 功能
提高图像的对比度
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 局部模式 | 局域或全局直方图均衡,勾选则局域直方图均衡,取消勾选则全局直方图均衡 | 勾选 | / |
| 对比度阈值 | 对比度阈值 | 3 | [1,1000] |
1.1.5 通过颜色过滤深度图

- 功能
根据颜色值过滤深度图
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 填充核大小 | 颜色填充的大小 | 3 | [1,99] |
| 根据hsv过滤深度-色域最大值 | 最大颜色值 | [180,255,255] | [[0,0,0],[255,255,255]] |
| 根据hsv过滤深度-色域最小值 | 最小颜色值 | [0,0,0] | [[0,0,0],[255,255,255]] |
| 保存颜色范围内的区域 | 勾选则保存颜色范围内的区域,不勾选则保存颜色范围外的区域 | / | / |
1.1.6 伽马图片校正

- 功能
gamma校正改变图片亮度
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| gamma补偿系数 | 该值小于1,图片变暗该值大于1,图片变量 | 1 | [0.1,100] |
| gamma校正系数 | 该值小于1,图片变暗,适用于亮度过高的图片该值大于1,图片变亮,适用于亮度过低的图片 | 2.2 | [0.1,100] |
1.1.7 填充深度图空洞

- 功能
对深度图中的空洞区域进行填充,并对填充后的深度图进行平滑处理
- 使用场景
因工件本身结构遮挡、光照不均匀等问题,深度图可能缺失工件的部分区域
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 填充核大小 | 空洞填充的大小 | 3 | [1,99] |
填充核大小只能填入奇数
- 调参
根据检测结果调整,如果填充过度,应调小参数;如果填充不足,应调大参数
- 示例
1.1.8 边缘增强

- 功能
把图像中纹理的边缘部分置为背景色或者与背景色相差较大的颜色,以凸显工件的边缘信息
- 使用场景
工件相互遮挡或重叠导致边缘不清晰
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 调参建议 |
|---|---|---|---|---|
| 法向z方向过滤阈值 | 深度图每个点对应的法向量与相机坐标系Z轴正方向的角度过滤阈值,若点的法向量与相机坐标系的Z轴正方向的角度大于此阈值,则2D图中该点对应位置的颜色将会被置为背景色或者与背景色相差较大的颜色 | 30 | [0,180] | 对于平整工件表面,该阈值可以小一些,曲面工件根据表面倾斜程度适当增大 |
| 背景色 | 背景色的RGB颜色阈值 | 128 | [0,255] | |
| 自动调节反差背景 | 勾选 自动调节反差背景后,将2D图中角度大于过滤阈值的点的颜色设置为与背景色相差较大的颜色不勾选 自动调节反差背景,将2D图中角度大于过滤阈值的点的颜色设置为背景色对应的颜色 | 不勾选 | / |
- 示例
1.1.9 提取最上层纹理

- 功能
提取最上层或最底层的工件纹理,而把其他区域置为背景色或者与背景颜色相差较大的颜色。
- 使用场景
光照条件不佳、颜色纹理相近、紧密堆叠、交错堆叠或遮挡等因素可能导致模型难以区分上层和下层工件的纹理差异,因此容易误检测。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 | 调参建议 |
|---|---|---|---|---|---|
| 距离阈值(mm) | 点与最上层平面(最底层平面)的距离低于该阈值,则被认为是最上层平面(最底层平面)内的点,应当保留,否则认为是下层(上层)的点,将下层(上层)的点的颜色置为背景色或与背景色相差较大的颜色 | 50 | [0.1, 1000] | mm | 一般调整为工件高度的1/2 |
| 聚类点云数量 | 期望参与聚类的点数量,在ROI 3D区域内采样点云的数量 | 10000 | [1,10000000] | / | 聚类点云数量越多,模型推理速度下降而精度提升;聚类点云数量越少,模型推理速度提升而精度下降 |
| 类别点最小数量 | 用于过滤类别的最小点数 | 1000 | [1, 10000000] | / | / |
| 自动计算反差背景 | 勾选 自动计算反差背景后,将2D图中最上层(最底层)外的其他区域设置为与背景色阈值相差较大的颜色不勾选 自动计算反差背景,将2D图中最上层(最底层)外的其他区域设置为背景色阈值对应的颜色 | 勾选 | / | / | / |
| 背景色阈值 | 背景色RGB颜色阈值 | 128 | [0,255] | / | / |
1.1.10 去除roi3d外的图片背景

- 功能
去除2D图像中除了ROI3D区域以外的背景
- 使用场景
图像背景噪声较多影响检测结果
- 参数说明
| 参数名 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 填充核大小 | 空洞填充的大小 | 5 | [1,99] |
| 迭代次数 | 图片膨胀的迭代次数 | 1 | [1,99] |
| 自动计算反差背景 | 勾选 自动计算反差背景后,将2D图中roi以外的区域设置为与背景色阈值相差较大的颜色不勾选 自动计算反差背景,将2D图中roi以外的区域设置为背景色阈值对应的颜色 | 勾选 | / |
| 背景色阈值 | 背景色的RGB颜色阈值 | 128 | [0,255] |
填充核大小只能填入奇数
- 调参
如果需要去除图像中更多背景噪声,应当调小填充核大小
- 示例
1.2 实例分割
1.2.1 缩放比例

- 功能
通过等比放缩原始图像后再推理以提升2D识别的准确率与召回率。
- 使用场景
检测效果不佳(如未检测到实例、漏识别、包围盒框到多个实例或框不满实例)应调整该函数。
- 参数说明
默认值:1.0
取值范围:[0.01, 3.00]
步长:0.01
调参
- 使用默认值运行,在可视化视窗查看检测结果,若出现未检测到实例、漏识别、包围盒框到多个实例或框不满实例的情况,应调整该函数。


2D识别中实例的百分数为置信度分数,数字为实例ID(实例的识别顺序)。
2D识别中实例上有颜色的阴影是掩膜,包围实例的矩形框是包围盒。
- 尝试不同的缩放比例,观察检测结果的变化,逐步确定缩放比例的范围。如果在某个缩放比例下,检测效果显著提高,则将该缩放比例作为下限;在某个缩放比例下,检测效果显著降低,则将该缩放比例作为上限。
若尝试所有缩放比例都无法获得较好检测结果,可调整ROI区域
如下图所示,缩放比例为0.33时检测效果显著提高,因此可以确定0.33为缩放比例范围的下限



缩放比例为3时检测效果依然较好,因此可以确定3为缩放比例范围的上限



- 如果实际场景对抓取精度要求不高,可以在[0.33,3]区间内选择一个检测效果较好的缩放比例;如果实际场景对抓取精度要求较高,应当进一步细化缩放比例范围,按更小步长来调整,直至找到检测效果最佳的缩放比例。
1.2.2 置信度下限阈值

- 功能
仅保留深度学习模型识别结果分数高于置信度下限阈值的识别结果
- 使用场景
检测结果框取的实例不符预期时,可调整该函数
- 参数说明
默认值:0.5
取值范围:[0.01, 1.00]
调参
- 如果模型检测出的实例较少,应当调小该阈值;取值过小,可能会影响图像识别的准确度。


- 若因为置信度下限阈值过小导致检测出错误的实例,而需要去除这些错误的实例,应当调大该阈值;取值过大,可能会导致保留的检测结果为零,没有结果输出。
1.2.3 启用自动增强

- 功能
将输入的缩放比例和旋转角度中所有的值进行组合后推理,返回组合后大于设定置信度下限阈值的所有结果,可以提升模型推理精度, 但会增加耗时。
- 使用场景
单个缩放比例无法满足实际场景需求导致检测不完整或物体摆放倾斜度较高。
- 示例
如果自动增强-缩放比例设置为 [0.8, 0.9, 1.0] ,自动增强-旋转角度设置为 [0, 90.0] ,那么将缩放比例和旋转角度中的值两两组合,模型内部会自动生成6种图片进行推理,最后将这6种推理结果统一到一起,输出大于置信度下限阈值的结果。
自动增强-缩放比例

- 功能
对原始图像进行多次缩放并进行多次推理,输出综合的推理结果
- 使用场景
单个缩放比例无法满足实际场景需求导致检测不完整
- 参数说明
默认值:[1.0]
取值范围:每个缩放比例的范围为[0.1, 3.0]
可设置多个缩放比例,每个缩放比例之间用英文逗号隔开
- 调参
填入多个 1.2.1 缩放比例 获得的检测效果较好的缩放比例
自动增强-旋转角度

- 功能
对原始图像多次旋转并进行多次推理,输出综合的推理结果
- 使用场景
物体摆放偏离坐标轴较多时使用
- 参数说明
默认值:[0.0]
取值范围:每个旋转角度的取值范围为[0, 360]
可设置多个旋转角度,每个旋转角度之间用英文逗号隔开
- 调参
根据实际场景中的物体角度调整自动增强-旋转角度,可根据麻袋的图案和袋口形状、纸箱的棱边和品牌标志判断倾斜角度
1.3 点云生成

| 实例点云生成形式 | 掩膜形式(分割后) | — | 使用分割后的实例掩膜生成点云 |
| 包围盒形式(分割后) | 包围盒缩放比例(分割后) | 使用分割后的实例包围盒生成点云 | |
| 生成点云是否需要颜色(分割后) | 生成的实例点云是否需要附着颜色 | ||
| 掩膜形式(过滤后) | — | 使用过滤后的实例掩膜生成点云 | |
| 包围盒形式(过滤后) | 包围盒缩放比例(过滤后) | 使用过滤后的实例包围盒生成点云 | |
| 生成点云是否需要颜色(过滤后) | 生成的实例点云是否需要附着颜色 |
如果不需要加速,无需使用实例过滤函数,使用掩膜形式(分割后)或包围盒形式(分割后)生成实例点云,可在 项目存储文件夹\项目名称\data\PickLight\历史数据时间戳\Builder\pose\input文件夹查看生成的实例点云;

如果需要加速,可使用实例过滤函数对实例进行过滤,使用掩膜形式(过滤后)或包围盒形式(过滤后)生成实例点云,可在 项目存储文件夹\项目名称\data\PickLight\历史数据时间戳\Builder\pose\input文件夹查看生成的实例点云

1.4 实例过滤

1.4.1 基于包围盒面积过滤

- 功能介绍
根据检测出实例的包围盒的像素面积来进行过滤。
- 使用场景
适用于实例包围盒面积相差较大的场景,通过设置包围盒面积的上限和下限来过滤图像中的噪声,提升图像识别的准确度,避免噪声给后续处理增加耗时。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 最小面积(像素) | 该参数用于设置包围盒的最小过滤面积,包围盒面积低于这个值的实例会被过滤 | 1 | [1, 10000000] | 像素点 |
| 最大面积(像素) | 该参数用于设置包围盒的最大过滤面积,包围盒面积高于这个值的实例会被过滤 | 10000000 | [2, 10000000] | 像素点 |
- 示例
按默认值运行,可在日志中查看每个实例的包围盒面积,如下图所示。


根据每个实例的包围盒面积调整 最小面积 和 最大面积,如将最小面积设置为20000,将最大面积设置为30000,即可将像素面积为小于20000或大于30000的实例过滤掉,可在日志中查看实例过滤过程。


1.4.2 基于包围盒长宽比过滤

- 功能介绍
包围盒长宽比在指定范围外的实例将被过滤掉
- 使用场景
适用于实例的包围盒长宽比相差较大的场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 最小长宽比 | 包围盒长宽比的最小值,包围盒长宽比低于该值的实例会被过滤 | 0 | [0, 10000000] |
| 最大长宽比 | 包围盒长宽比的最大值,包围盒长宽比高于该值的实例会被过滤 | 10000000 | [0, 10000000] |
| 使用X/Y轴边长作长宽比 | 默认不勾选,使用包围盒的较长边/较短边的长度比值作为长宽比,适用于包围盒的长短边长度相差大的情况; 勾选后,则使用像素坐标系下包围盒在X轴/Y轴上的边的长度比值作为长宽比,适用于大部分正常实例包围盒的长边/短边比值近似,但部分异常识别的实例包围盒在X轴上的长度/在Y轴上的长度的比值相差较大的情况。 | 不勾选 | / |
1.4.3 基于类别ID过滤实例

- 功能介绍
根据实例类别过滤
- 使用场景
适用于来料有多种类型工件的场景
- 参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
| 保留的类别ID | 保留类别ID在列表内的实例,类别ID不在列表内的实例将被过滤 | [0] |
- 示例
1.4.4 基于实例点云的边长过滤

- 功能介绍
根据实例点云的长边和短边过滤
- 使用场景
适用于实例点云在x轴或y轴的距离相差较大的场景,通过设置实例点云的距离范围来过滤图像中的噪声,提升图像识别的准确度,避免噪声给后续处理增加耗时。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 短边长度范围(mm) | 点云短边的边长范围 | [0, 10000] | [0, 10000] | mm |
| 长边长度范围(mm) | 点云长边的边长范围 | [0, 10000] | [0, 10000] | mm |
| 边缘去噪下限(%) | 提取实例点云中X/Y值(相机坐标系)的百分比下限,去除上下限外的点云,避免噪点影响长度计算 | 5 | [0, 100] | / |
| 边缘去噪上限(%) | 提取实例点云中X/Y值(相机坐标系)的百分比上限,去除上下限外的点云,避免噪点影响长度计算 | 95 | [0, 100] | / |
| 边长类型 | 按实例点云的长边、短边过滤,长边、短边的长度不在范围内的实例将被过滤 | 实例点云短边 | 实例点云短边;实例点云长边;实例点云长边和短边 | / |
- 示例
1.4.5 基于分类器的类别ID过滤

- 功能介绍
基于分类器的类别 ID 过滤实例,不在参考类别内的实例将被过滤。
- 使用场景
在多类工件场景中,视觉模型可能会检测出多种类型的工件,但实际作业可能仅需其中某一种类别的工件,此时就可以使用该函数过滤掉不需要的工件
- 参数说明
默认值为[0],即默认保留类别 ID 为 0 的实例,类别 ID 不在列表内的实例将被过滤。
1.4.6 基于三通道颜色过滤

- 功能介绍
可通过三通道颜色阈值(HSV或者RGB)过滤掉实例。
- 使用场景
错误实例和正确实例颜色有明显区分的情况。
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 色域最大值 | 最大颜色值 | [180,255,255] | [[0,0,0],[255,255,255]] |
| 色域最小值 | 最小颜色值 | [0,0,0] | [[0,0,0],[255,255,255]] |
| 过滤百分比阈值 | 颜色通过率阈值 | 0.05 | [0,1] |
| 反向过滤 | 勾选则剔除颜色范围外的比例低于阈值的实例,不勾选则剔除实例图像中颜色范围内的比例低于阈值的实例 | 不勾选 | / |
| 颜色模式 | 颜色过滤中选择的颜色空间 | HSV色彩空间 | RGB色彩空间HSV色彩空间 |
- 示例

1.4.7 基于置信度过滤

- 功能介绍
根据实例的置信度分数过滤
- 使用场景
适用于实例的置信度相差较大的场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 参考置信度度 | 保留置信度大于阈值的实例,过滤置信度小于阈值的实例。 | 0.5 | [0,1] |
| 反转过滤结果 | 反转后,保留可见度置信度小于阈值的实例,过滤置信度大于阈值的实例。 | 不勾选 | / |
- 示例
1.4.8 基于点云数量过滤

- 功能介绍
根据降采样后的实例点云数量过滤
- 使用场景
实例点云带有较多噪声
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 点云数量最小值 | 点云数量的最小值 | 3500 | [1, 10000000] |
| 点云数量最大值 | 点云数量的最大值 | 8500 | [2, 10000000] |
| 过滤数量在区间内的实例 | 勾选则过滤点云数量在最小值和最大值区间内的实例,不勾选则过滤点云数量不在区间内的实例 | 不勾选 | / |
1.4.9 基于掩膜面积过滤

- 功能介绍
根据检测出实例的掩膜像素和(即像素面积)过滤图像掩膜。
- 使用场景
适用于实例掩膜面积相差较大的场景,通过设置掩膜的面积上限和下限来过滤图像掩膜中的噪声,提升图像识别的准确度,避免噪声给后续处理增加耗时。
- 参数设置说明
| 参数名 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 参考最小面积 | 该参数用于设置掩膜的最小过滤面积,掩膜面积低于这个值的实例会被过滤 | 1 | [1, 10000000] | 像素点 |
| 参考最大面积 | 该参数用于设置掩膜的最大过滤面积,掩膜面积高于这个值的实例会被过滤 | 10000000 | [2, 10000000] | 像素点 |
- 示例
1.4.10 基于可见度过滤

- 功能介绍
根据实例的可见度分数过滤
- 使用场景
适用于实例的可见度相差较大的场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 参考可见度阈值 | 保留可见度大于阈值的实例,过滤可见度小于阈值的实例。可见度用于判断图像中的实例可见的程度,工件被遮挡越多,可见度越低。 | 0.5 | [0,1] |
| 反转过滤结果 | 反转后,保留可见度小于阈值的实例,过滤可见度大于阈值的实例。 | 不勾选 | / |
1.4.11 过滤包围盒重叠的实例

- 功能介绍
过滤包围盒交叉重叠的实例
- 使用场景
适用于实例的包围盒相互交叉的场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 包围盒重叠比例阈值 | 包围盒交叉的面积与实例包围盒的面积的占比阈值 | 0.05 | [0, 1] |
| 过滤包围盒面积较大的实例 | 勾选则过滤两个包围盒交叉的实例中面积较大的实例,不勾选则过滤两个包围盒交叉的实例中面积较小的实例 | 勾选 | / |
- 示例

新增 过滤被包围实例,以默认值运行,在日志中查看实例包围盒交叉的情况,实例过滤后剩余2个实例

由日志可知,12个实例因为包围盒交叉被过滤掉,剩余2个包围盒没有交叉的实例

将 包围盒重叠比例阈值 设置为0.1,勾选 是否过滤较大的实例,在日志中查看实例例过滤过程,9个实例因为包围盒交叉面积与实例包围盒面积的占比大于0.1被过滤掉,3个实例因为包围盒交叉面积与实例包围盒面积的占比小于0.1被保留,2个实例包围盒没有交叉。


将 包围盒重叠比例阈值 设置为0.1,取消勾选 是否过滤较大的实例,在日志中查看实例例过滤过程,9个实例的包围盒交叉面积与实例包围盒面积的占比大于0.1,但其中2个实例因为包围盒面积小于与其交叉的实例被保留,因此7个实例被过滤,3个实例因为包围盒交叉面积与实例包围盒面积的占比小于0.1被保留,2个实例包围盒没有交叉。


1.4.12 【大师】基于掩膜/掩膜外接多边形面积比,过滤掩膜凹凸的实例

- 功能介绍
计算掩膜/掩膜外接多边形的面积比值,若小于设置的阈值则会过滤掉实例
- 使用场景
适用于工件掩膜存在锯齿/凹凸的情况。
- 参数说明
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 面积比阈值 | 掩膜/凸包面积比阈值,若小于设置的阈值则会过滤掉实例。 | 0.1 | [0,1] |
1.4.13 【大师】基于点云平均距离过滤

- 功能介绍
基于点云中点到拟合平面的距离的平均值进行过滤,剔除不平整的实例点云
- 使用场景
适用于平面型工件点云弯曲的场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 平面分割距离阈值(mm) | 在弯曲的实例点云中提取一个平面,与平面的距离小于该阈值的点视为该平面的点 | 10 | [-1000, 1000] | mm |
| 平均距离阈值(mm) | 实例点云中的点到提取平面的距离的平均值 | 20 | [-1000, 1000] | mm |
| 剔除平均距离小于阈值的实例 | 勾选则过滤点到提取平面的平均距离小于平均距离阈值的实例,不勾选则过滤点到提取平面的平均距离大于平均距离阈值的实例 | 不勾选 | / | / |
1.4.14 【大师】基于掩膜/包围盒面积比,过滤被遮挡的实例

- 功能介绍
计算掩膜/包围盒面积比值,比值不在最大最小范围内的实例将被过滤
- 使用场景
用于过滤被遮挡工件的实例
- 参数说明
,相反,代表可能被遮挡。
| 参数 | 说明 | 默认值 | 取值范围 |
|---|---|---|---|
| 最小面积比 | 掩膜/包围盒面积比例范围下限,比值越小,说明实例被遮挡程度越高 | 0.1 | [0,1] |
| 最大面积比 | 掩膜/包围盒面积比例范围上限,比值越接近1,说明实例被遮挡程度低 | 1.0 | [0,1] |
1.4.15 【大师】判断最上层实例是否全量检出

- 功能介绍
防呆机制之一,判断最上层的实例是否全部被检出,若存在没有被检出的最上层实例则会报错并结束工作流
- 使用场景
适用于拍一次抓多次或者必须要按顺序进行抓取的场景,防止因实例检出不完整造成漏抓影响后续作业
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 | 调参 |
|---|---|---|---|---|---|
| 距离阈值 | 用于判断最上层的工件,点与工件点云最高点的距离小于距离阈值,则认为这个点是最上层点云,否则认为这个点不是最上层点云。 | 5 | [0.1, 1000] | mm | 应当小于工件的高度 |
1.5 实例排序

- 功能介绍
根据选择的策略对实例进行分组、排序、提取
- 使用场景
拆垛、无序抓取、有序上下料场景通用
如果不需要排序,可以不配置具体的策略。
1.5.1 基准坐标系

- 功能介绍
为所有实例设定一个统一的坐标系,进行实例的分组排序
- 使用场景
拆垛场景、无序抓取场景、有序上下料场景通用
使用坐标相关的策略应当先设置基准坐标系
- 参数说明
| 参数 | 说明 | 图示 |
|---|---|---|
| 相机坐标系 | 坐标系原点在物体上方,Z轴正方向朝下;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| ROI坐标系 | 坐标系原点大致在垛中心,Z轴正方向朝上;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| 机械臂坐标系 | 坐标系原点在机械臂自身,Z轴正方向一般朝上;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| 像素坐标系 | 坐标系原点在RGB图的左顶点,是二维平面坐标系;X、Y取值是bbox识别框的x值、bbox识别框的y值,Z是0 | ![]() |
1.5.2 通用抓取策略

- 参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
| 策略 | 选择依据哪个值进行分组排序以及如何排序,包括实例点云中心XYZ坐标值、包围盒长宽比、实例点云中心距离ROI中心等可叠加多条,按顺序依次执行 | 实例点云中心X坐标值从小到大(mm) |
| 分组步长 | 依据选择的策略、按照步长将实例划分为若干组,分组步长即两组实例之间的间隔如策略选择”实例点云中心Z坐标值从大到小(mm)“,则将所有实例点云中心的Z坐标从大到小排序,然后按照步长把Z坐标分组,相应的实例也划分为若干组 | / |
| 提取前几组 | 分组排序之后,需要保留多少组实例 | 10000 |
| 策略名* | 说明 | 分组步长 | 提取前几组 | |
|---|---|---|---|---|
| 默认值 | 取值范围 | 默认值 | ||
| 实例点云中心XYZ坐标值从大到小/从小到大(mm) | 使用每个实例的点云中心的XYZ坐标值来进行分组排序 使用该策略进行排序前应当先设置基准坐标系 | 200.000 | (0, 10000000] | 10000 |
| 从实例点云中心XY坐标轴向的中间到两侧/从实例点云中心XY坐标轴的两侧到中间(mm) | 使用每个实例的点云中心的 XY 坐标值,按照 “中间到两侧” 或 “两侧到中间” 的方向进行分组排序 使用该策略进行排序前应当先设置基准坐标系 | 200.000 | (0, 10000000] | 10000 |
| 包围盒中心XY坐标值从大到小/从小到大(mm) | 使用像素坐标系下,每个实例的包围盒中心点的XY 坐标值进行分组排序 | 200.000 | (0, 10000000] | 10000 |
| 包围盒长宽比从大到小/从小到大 | 使用包围盒的长边/宽边的比值进行分组排序 | 1 | (0, 10000] | 10000 |
| 从包围盒中心XY坐标轴向的中间到两侧/两侧到中间(mm) | 使用包围盒的中心点的XY坐标值,按照 “中间到两侧” 或 “两侧到中间” 的方向进行分组排序 | 200.000 | (0, 10000000] | 10000 |
| 工件类型ID从大到小/从小到大 | 使用工件类型的ID进行分组排序,适用于多类工件场景 | 1 | [1, 10000] | 10000 |
| 局部特征ID从大到小/从小到大 | 使用局部特征的ID进行分组排序 | 1 | [1, 10000] | 10000 |
| 置信度从大到小/从小到大 | 使用每个实例的置信度进行分组排序 | 1 | (0, 1] | 10000 |
| 可见度从小到大/从大到小 | 使用每个实例的可见度进行分组排序 | 1 | (0, 0.1] | 10000 |
| 掩膜面积从大到小/从小到大 | 使用每个实例的掩膜面积进行分组排序 | 10000 | [1, 10000000] | 10000 |
| 实例点云中心距离ROI中心近到远/远到近(mm) | 使用每个实例的点云中心与ROI坐标系的中心的距离进行分组排序 | 200.000 | (0, 10000000] | 10000 |
| 实例点云中心距离机器人坐标原点近到远/远到近(mm) | 使用每个实例的点云中心与机器人坐标系的原点的距离进行分组排序 | 200.000 | (0, 10000000] | 10000 |
- 示例
1.5.3 自定义抓取策略

(1)功能说明
将抓取策略切换为自定义抓取策略,点击 新增 可增加一条自定义抓取策略。
自定义每个工件按照什么顺序抓取,使用通用抓取策略很难实现抓取或者因为点云噪点等问题很难调到合适的参数,可以考虑使用自定义抓取策略
自定义抓取策略适用于拆垛场景、有序上下料场景,无序抓取场景不适用,因为自定义抓取策略的工件必须是有序的(即工件的顺序固定)
自定义抓取策略只能和单个通用抓取策略组合使用,且策略只能选择Z坐标从小到大
(2)参数说明
| 参数 | 说明 | 默认值 | 取值范围 | 调参 |
|---|---|---|---|---|
| IOU阈值 | 表示标注的bbox框和检测出来的bbox框的重叠度阈值,通过重叠度来确定当前工件实例排序时应该选择哪一张图片上的排序方式。 | 0.7 | [0,1] | 阈值越大,匹配越严格,抗干扰性会越差,微小的形状或位置变化都可能导致匹配失败,可能匹配到错误的自定义策略,按错误的顺序进行排序 |
| 像素距离阈值 | 表示可以匹配上的bbox框和检测出来的bbox框在尺寸上的差异性。 | 100 | [0,1000] | 阈值越小,匹配越严格,抗干扰性也会更好。如果不同层之间的工件摆放比较相似,也可能误匹配自定义策略,导致排序顺序错误。 |
(3)选择基准坐标系
使用自定义抓取策略,只能选择相机坐标系或像素坐标系
如果有多层工件,则选择相机坐标系;如果只有一层工件,则选择像素坐标系
(4)策略、分组步长、提取前几组
| 参数 | 说明 | 默认值 |
|---|---|---|
| 策略 | 只能选择实例点云中心Z坐标值从大到小/从小到大(mm) | / |
| 分组步长 | 依据Z坐标从小到大策略,将实例的Z坐标从小到大排序,按照步长将实例划分为若干组 | 10000 |
| 提取前几组 | 分组排序之后,需要保留多少组实例 | 10000 |
(5)拍照取图/添加本地图像
点击拍照取图从当前连接的相机获取图像,或点击添加本地图像从本地导入图像,有多少层或有多少种工件的不同摆放形式,就需要拍照取图或添加本地图像得到多少张图片,如果每一层相同,只需要一张即可。鼠标右键点击图像可删除。
在获取的图像上长按拖动鼠标左键标注bbox框,DELETE键可逐步删除标注的bbox框。
2. 3D计算

2.1 预处理
3D计算的预处理是在对实例进行姿态估计、生成抓取点之前对3D点云进行处理,面型工件有序上下料(并行化)场景无需对3D点云进行处理。
2.2 点云匹配姿态估计
2.2.1 模板文件路径

- 功能
上传点云模板与场景的实例点云进行匹配
- 使用场景
面型工件有序上下料(并行化)场景
- 调参说明
该点云模板需使用模板生成脚本制作,制作方法如下:
- 复制场景的2D图、深度图、点云图
(1)选择要复制的历史数据的时间戳
从PickLight历史数据文件夹中选取一个时间戳文件夹(如 /home/xxx/PickLight/20240718201333036),复制其完整路径备用。
(2)下载模板生成脚本
注意:
模板生成脚本需要下载与软件版本相对应的版本,否则版本不兼容会导致点云模板生成失败。
模板生成脚本的下载目录不能有中文或特殊字符,建议存储在默认的下载目录C:
\Users\dex\Downloads
PickWiz 版本 > =1.8.0 模版生成脚本:点击查看完整代码
模板生成脚本
import argparse
import json
import math
import os
import re
import shutil
from copy import deepcopy
import cv2
import numpy as np
import open3d as o3d
from tqdm import tqdm
from PickLight.Utils.Convertor import generate_mask_from_points
from PickLight.Utils.Convertor import get_ratio_from_mask
from PickLight.Utils.Utility import FileOperation
try:
import glia
if not glia.__version__ >= "0.2.4":
raise RuntimeError("请将 glia 版本升级到 0.2.4 或者更高. 目前版本为 {}".format(glia.__version__))
from glia.features.image_pair_matcher import ImagePairMatcher
except ImportError as e:
print(f"警告: {e}")
ImagePairMatcher = None
try:
import torch
except ImportError:
torch = None
default_config_superpoint = {
'detector': {
'type': 'superpoint',
'params': {
'default': {
'keypoint_threshold': 0.01,
'max_keypoints': -1,
'use_edge': False,
'edge_kernel_size': 3,
},
'expand': {},
},
},
'matcher': {
'type': 'superglue',
'params': {
'default': {
'match_threshold': 0.01,
},
'expand': {},
},
},
'preprocess': {
'resize': False,
},
}
default_config_xfeat = {
'detector': {
'type': 'xfeat',
'params': {
'default': {
'keypoint_threshold': 0.01,
'top_k': 512,
},
'expand': {}
},
},
'matcher': {
'type': 'lighterglue',
'params': {
'default': {
'match_threshold': 0.01,
},
'expand': {}
},
},
'preprocess': {
'resize': False,
},
}
def file_transfer(args):
"""文件复制功能,从两个脚本合并而来"""
input_dir = args.data_dir
output_dir = args.output_dir
os.makedirs(output_dir, exist_ok=True)
# RGB+D 图像
try:
for root, dirs, files in os.walk(input_dir):
target_path = os.path.join(root, 'Builder', 'foreground', 'input')
if os.path.exists(target_path):
for file in os.listdir(target_path):
if file.endswith('.png') or file.endswith('.tiff'):
full_file_path = os.path.join(target_path, file)
shutil.copy(full_file_path, output_dir)
print(f'Copied: {file}')
except Exception as e:
raise ValueError(f"检查{input_dir}路径下是否存在Builder/foreground/input文件夹, {e}")
# PCD 点云文件
try:
for root, dirs, files in os.walk(input_dir):
target_path = os.path.join(root, 'Builder', 'foreground', 'output')
if os.path.exists(target_path):
for file in os.listdir(target_path):
if file.endswith('.ply'):
full_file_path = os.path.join(target_path, file)
shutil.copy(full_file_path, output_dir)
print(f'Copied: {file}')
except Exception as e:
print(f'{e}, 尝试第二个脚本的路径')
# 尝试第二个脚本的路径
try:
for root, dirs, files in os.walk(input_dir):
target_path = os.path.join(root, 'Builder', 'foreground', 'input')
if os.path.exists(target_path):
for file in os.listdir(target_path):
if file.endswith('.ply'):
full_file_path = os.path.join(target_path, file)
shutil.copy(full_file_path, output_dir)
print(f'Copied: {file}')
except Exception as e2:
raise ValueError(f"检查{input_dir}路径下是否存在Builder/foreground/output或input文件夹, {e2}")
# JSON 配置文件
try:
for root, dirs, files in os.walk(input_dir):
target_path = os.path.join(root, 'Builder', 'foreground', 'input')
if os.path.exists(target_path):
for file in os.listdir(target_path):
# 兼容两个脚本的不同要求
if file.endswith('.json') and (args.type in ['feat', 'both'] or 'camera_param' in file):
full_file_path = os.path.join(target_path, file)
shutil.copy(full_file_path, output_dir)
print(f'Copied: {file}')
except Exception as e:
raise ValueError(f"检查{input_dir}路径下是否存在JSON文件, {e}")
def _list_indices_by_json(dir_path):
"""列出目录中已有的 model_info_{i}.json 的索引列表"""
if not os.path.isdir(dir_path):
return []
pat = re.compile(r"model_info_(\d+)\.json$")
idxs = []
for f in os.listdir(dir_path):
m = pat.match(f)
if m:
idxs.append(int(m.group(1)))
return sorted(idxs)
def _get_next_index(dir_path):
"""返回可用的下一个索引"""
idxs = _list_indices_by_json(dir_path)
if not idxs:
return 0
return max(idxs) + 1
def _ensure_template_id_in_dir(dir_path, template_id_value):
"""为目录内所有 model_info_{i}.json 写入 template_id(若缺失则补齐)"""
for i in _list_indices_by_json(dir_path):
p = os.path.join(dir_path, f"model_info_{i}.json")
try:
d = json.load(open(p, "r", encoding="utf-8"))
except Exception:
continue
if "template_id" not in d:
d["template_id"] = int(template_id_value)
with open(p, "w", encoding="utf-8") as fw:
json.dump(d, fw, ensure_ascii=False, indent=4)
def _is_superglue_template_dir(dir_path):
"""粗略校验是否为superglue模板目录"""
if not os.path.isdir(dir_path):
return False
js = _list_indices_by_json(dir_path)
if not js:
return False
# 至少有 temp_0.png 或 depth_model_0.tiff
has_temp = any(os.path.exists(os.path.join(dir_path, f"temp_{i}.png")) for i in js)
has_depth = any(os.path.exists(os.path.join(dir_path, f"depth_model_{i}.tiff")) for i in js)
return has_temp or has_depth
def _copy_one_template_block(src_dir, src_idx, dst_dir, dst_idx, template_id):
"""将一套 index 对应的模板文件从src复制到dst,并改名为新索引;同时写入 template_id"""
patterns = [
("temp_{i}.png", "temp_{j}.png"),
("depth_model_{i}.tiff", "depth_model_{j}.tiff"),
("model_{i}.ply", "model_{j}.ply"),
("model_{i}_downsampled.ply", "model_{j}_downsampled.ply"),
("keypoints_{i}.ply", "keypoints_{j}.ply"),
("temp_vis_{i}.jpg", "temp_vis_{j}.jpg"),
("depth_mask_uint8_{i}.png", "depth_mask_uint8_{j}.png"),
]
os.makedirs(dst_dir, exist_ok=True)
for src_pat, dst_pat in patterns:
s = os.path.join(src_dir, src_pat.format(i=src_idx))
d = os.path.join(dst_dir, dst_pat.format(j=dst_idx))
if os.path.exists(s):
shutil.copy(s, d)
# 处理 model_info
src_info = os.path.join(src_dir, f"model_info_{src_idx}.json")
dst_info = os.path.join(dst_dir, f"model_info_{dst_idx}.json")
if os.path.exists(src_info):
try:
data = json.load(open(src_info, "r", encoding="utf-8"))
except Exception:
data = {}
data["template_id"] = int(template_id)
with open(dst_info, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def merge_superglue_folders(target_dir, source_dirs):
"""功能1:合并多个superglue模板目录到target_dir,顺序追加并写入template_id"""
if not _is_superglue_template_dir(target_dir):
raise RuntimeError(f"目标路径不是有效的superglue模板目录: {target_dir}")
for s in source_dirs:
if not _is_superglue_template_dir(s):
raise RuntimeError(f"来源路径不是有效的superglue模板目录: {s}")
# 目标已有模板写入 template_id=0(若缺失)
_ensure_template_id_in_dir(target_dir, 0)
next_idx = _get_next_index(target_dir)
# 从1开始给后续目录分配 template_id
for folder_tid, src in enumerate(source_dirs, start=1):
src_indices = _list_indices_by_json(src)
for i in src_indices:
_copy_one_template_block(src, i, target_dir, next_idx, folder_tid)
next_idx += 1
print(f"合并完成,输出在: {target_dir}")
def depth2img(
depth_map,
min_percentile=2,
max_percentile=98,
min_range=0.005):
"""Convert depth to rgb image.
Args:
depth_map (np.ndarray): The depth image.
Returns:
np.ndarray: The converted rgb image.
"""
valid_mask = np.isfinite(depth_map) & (depth_map > 0)
valid_depths = depth_map[valid_mask]
min_depth = np.percentile(valid_depths, min_percentile)
max_depth = np.percentile(valid_depths, max_percentile)
actual_range = max_depth - min_depth
# 如果实际范围小于最小变化范围, 将所有有效深度映射为单一颜色
if actual_range < min_range:
single_color_value = 0
gray_image = np.full_like(depth_map, single_color_value, dtype=np.uint8)
temp = cv2.applyColorMap(gray_image, cv2.COLORMAP_JET)
temp[~valid_mask] = 0
else:
depth_range = max(max_depth - min_depth, 1e-6)
normalized = np.clip((depth_map - min_depth) / depth_range, 0, 1)
gray_image = (normalized * 255).astype(np.uint8)
temp = cv2.applyColorMap(gray_image, cv2.COLORMAP_JET)
temp[~valid_mask] = 0
return temp
def _detect_group_dirs(root_dir):
"""功能2:检测 root_dir 下是否存在多组数据(按子目录作为一组)"""
group_dirs = []
for name in sorted(os.listdir(root_dir)):
p = os.path.join(root_dir, name)
if not os.path.isdir(p):
continue
# 需要同时包含 ply + 任一rgb + tiff + json(camera_param)
has_ply = any(fn.endswith(".ply") for fn in os.listdir(p))
has_img = any(fn.lower().endswith((".png", ".jpg", ".jpeg", ".bmp")) for fn in os.listdir(p))
has_tiff = any(fn.lower().endswith(".tiff") for fn in os.listdir(p))
has_json = False
for fn in os.listdir(p):
if fn.lower().endswith(".json"):
try:
d = json.load(open(os.path.join(p, fn), "r", encoding="utf-8"))
if "camera_param" in d:
has_json = True
break
except Exception:
pass
if has_ply and has_img and has_tiff and has_json:
group_dirs.append(p)
return group_dirs
def search_depth_values(indices, depth_mask, search_radius=3):
"""
在附近搜索一个非零的深度值并填充到深度掩码中
参数:
- indices: 关键点的索引数组,形状为 (N, 2)
- depth_mask: 初始深度掩码,形状为 (H, W)
- search_radius: 搜索半径,默认为 3
返回:
- depth_values: 每个关键点搜索到的深度值
"""
depth_values = np.full(indices.shape[0], -1, dtype=np.float32)
for idx, (x, y) in enumerate(indices):
if depth_mask[y, x] == 0:
xmin, xmax = max(0, x - search_radius), min(depth_mask.shape[1], x + search_radius + 1)
ymin, ymax = max(0, y - search_radius), min(depth_mask.shape[0], y + search_radius + 1)
search_area = depth_mask[ymin:ymax, xmin:xmax]
non_zero = search_area[search_area != 0]
if non_zero.size > 0:
depth_values[idx] = non_zero[0]
else:
depth_values[idx] = depth_mask[y, x]
return depth_values
def rotate_pcd(pcd, angle, original_center=None):
"""旋转点云"""
if original_center is None:
original_center = pcd.get_center()
t_1 = np.array(
[[1, 0, 0, -original_center[0]], [0, 1, 0, -original_center[1]], [0, 0, 1, -original_center[2]], [0, 0, 0, 1]]
)
theta = np.radians(angle)
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
t_2 = np.array([[cos_theta, -sin_theta, 0, 0], [sin_theta, cos_theta, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
t_3 = np.array(
[[1, 0, 0, original_center[0]], [0, 1, 0, original_center[1]], [0, 0, 1, original_center[2]], [0, 0, 0, 1]]
)
transform_all = np.dot(t_3, np.dot(t_2, t_1))
pcd_final = deepcopy(pcd).transform(transform_all)
return pcd_final, transform_all
def project_pcd_to_rgb(
input_dir,
output_dir,
angle,
auto_scale,
mask_kernel_size,
edge_kernel_size,
index=1,
use_edge=False,
use_depth = False,
background_color=0,
foreground_color=255,
template_id=None,
base_index=0,
feat_type = "superpoint",
):
"""将点云投影到RGB图像(用于SuperGlue模板)"""
os.makedirs(output_dir, exist_ok=True)
# 使用 base_index 读取基准模板
cam_k_path = os.path.join(input_dir, f"model_info_{base_index}.json")
cam_k = json.load(open(cam_k_path))
cam_k = cam_k["camera_param"]
cam_k = np.asarray(cam_k).reshape(3, 3).astype(np.float32)
pcd_path = os.path.join(input_dir, f"model_{base_index}.ply")
pcd = o3d.io.read_point_cloud(pcd_path)
temp_img_path = os.path.join(input_dir, f"temp_{base_index}.png")
temp_img = cv2.imread(temp_img_path)
project_img = np.zeros(temp_img.shape, dtype=np.uint8)
project_depth = np.zeros(temp_img.shape[:2], dtype=np.float32)
aabb = pcd.get_axis_aligned_bounding_box()
aabb_center = (aabb.get_min_bound() + aabb.get_max_bound()) / 2
pcd_transformed, transform_all = rotate_pcd(pcd, angle, aabb_center)
o3d.io.write_point_cloud(os.path.join(output_dir, f"model_{index}.ply"), pcd_transformed)
f_json_data = open(os.path.join(output_dir, f"model_info_{index}.json"), "w+")
json_saved_data = {}
json_saved_data["camera_param"] = cam_k.tolist()
json_saved_data["transform"] = transform_all.tolist()
json_saved_data["angle"] = angle
json_saved_data["feat_type"] = feat_type
json_saved_data["mask_kernel_size"] = mask_kernel_size
json_saved_data["edge_kernel_size"] = edge_kernel_size
json_saved_data["use_edge"] = use_edge
json_saved_data["use_depth"] = use_depth
if template_id is not None:
json_saved_data["template_id"] = int(template_id)
json.dump(json_saved_data, f_json_data, indent=4)
f_json_data.close()
rvec = np.array([0.0, 0.0, 0.0])
tvec = np.array([0.0, 0.0, 0.0])
distortion_zeros = np.zeros((5, 1), dtype=np.float32)
pcd_np = np.array(pcd_transformed.points)
points_2d, _ = cv2.projectPoints(pcd_np, rvec, tvec, cam_k, distortion_zeros)
points_2d = points_2d.squeeze(1).reshape(-1, 2)
color_bgr = np.asarray(pcd_transformed.colors)[:, ::-1] * 255
for i, pt in enumerate(points_2d):
project_img[round(pt[1]), round(pt[0]), :] = color_bgr[i]
project_depth[round(pt[1]), round(pt[0])] = pcd_np[i][2]
project_img = cv2.morphologyEx(
project_img, cv2.MORPH_CLOSE, np.ones((mask_kernel_size, mask_kernel_size), np.uint8)
)
if use_edge:
mask = np.any(project_img != 0, axis=2)
project_img[project_img == 0] = background_color
project_img[mask] = foreground_color
if use_depth:
project_img = depth2img(project_depth)
project_img = cv2.morphologyEx(project_img, cv2.MORPH_CLOSE, np.ones((2, 2), np.uint8))
cv2.imwrite(os.path.join(output_dir, f"depth_model_{index}.tiff"), project_depth)
cv2.imwrite(os.path.join(output_dir, f"temp_{index}.png"), project_img)
concat_img = cv2.hconcat([project_img, temp_img])
cv2.imwrite(os.path.join(output_dir, f"project_img_concat_{index}.jpg"), concat_img)
# 输出 keypoints.ply
if ImagePairMatcher is not None and torch is not None:
config = default_config_superpoint if feat_type == "superpoint" else default_config_xfeat
if feat_type == "superpoint":
config['detector']['params']['default']['use_edge'] = use_edge
config['detector']['params']['default']['edge_kernel_size'] = edge_kernel_size
image_pair_matcher = ImagePairMatcher(config)
image_pair_matcher.register([project_img])
keypoints_model_2d = image_pair_matcher.kpts_info_t['keypoints'][0].cpu().numpy().astype(np.int32)
Keypoint_3D_model = np.zeros((len(keypoints_model_2d), 3), dtype=np.float32)
depth_values = search_depth_values(keypoints_model_2d, project_depth, 5)
def get_rainbow_color(index, total_points):
hsv = np.array([[[int(255 * index / total_points), 255, 255]]], dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)[0][0]
return tuple(map(int, bgr))
temp_vis = deepcopy(project_img)
for i in range(len(keypoints_model_2d)):
x, y = keypoints_model_2d[i]
z = depth_values[i]
if z == -1:
Keypoint_3D_model[i] = [0, 0, 0]
continue
Keypoint_3D_model[i] = np.linalg.inv(cam_k) @ (np.array([x, y, 1]) * z)
color = get_rainbow_color(i, len(keypoints_model_2d))
center = (int(round(x)), int(round(y)))
cv2.circle(temp_vis, center, radius=1, color=color, thickness=-1)
cv2.imwrite(os.path.join(output_dir, f"temp_vis_{index}.jpg"), temp_vis)
rotated_model_kpts = o3d.geometry.PointCloud()
rotated_model_kpts.points = o3d.utility.Vector3dVector(Keypoint_3D_model)
rotated_model_kpts.colors = o3d.utility.Vector3dVector([[0, 1, 0] for _ in range(Keypoint_3D_model.shape[0])])
o3d.io.write_point_cloud(os.path.join(output_dir, f"keypoints_{index}.ply"), rotated_model_kpts)
if torch.cuda.is_available():
torch.cuda.empty_cache()
return True
def generate_ism_template(args):
"""生成ISM模板(第一个脚本的功能)"""
data_dir = args.data_dir
rotation_range = args.rot_range
rotation_interval = args.rot_interval
rotation_range_xy = args.rot_range_xy
depth_range = args.depth_range
depth_interval = args.depth_interval
template_path = None
ply_path = None
pcd = None
# 查找所需文件
for f in os.listdir(data_dir):
template_path = os.path.join(data_dir, "ism")
os.makedirs(template_path, exist_ok=True)
f_path = os.path.join(data_dir, f)
if f.endswith('.ply'):
ply_path = f_path
pcd = o3d.io.read_point_cloud(ply_path)
pcd.paint_uniform_color([0, 1.0, 0])
o3d.io.write_point_cloud(f"{template_path}/model.ply", pcd)
if f.endswith('.png') or f.endswith('.jpg') or f.endswith('.bmp'):
rgb = cv2.imread(f_path)
if len(rgb.shape) == 2:
rgb = cv2.cvtColor(rgb, cv2.COLOR_GRAY2RGB)
h, w, _ = rgb.shape
if f.endswith('.tiff'):
depth = cv2.imread(f_path, -1)
if f.endswith('.json'):
resource_manager = FileOperation.load_json(f_path)
cam_k = resource_manager["camera_param"]
cam_k = np.asarray(cam_k).reshape(3, 3).astype(np.float32)
# 检查文件是否存在
if ply_path is None:
raise ValueError(f"错误! 未在 {data_dir} 路径下找到 点云 文件, 支持后缀为 ply.\n")
if pcd is None:
raise ValueError(f"错误! {ply_path} 路径的点云文件读取失败.\n")
try:
rgb
except NameError:
raise ValueError(f"错误! 未能在 {data_dir} 路径下正确读取 彩色图像 文件, 支持后缀为 png/jpg/bmp.\n")
try:
depth
except NameError:
raise ValueError(f"错误! 未能在 {data_dir} 路径下正确读取 深度图像 文件, 支持后缀为 tiff.\n")
try:
cam_k
except NameError:
raise ValueError(f"错误! 未在 {data_dir} 路径下找到 配置参数 文件, 支持后缀为 json.\n")
# ISM prompt
pcd_center = pcd.get_center()
angles_z = range(-rotation_range, rotation_range, rotation_interval)
mask = generate_mask_from_points(np.array(pcd.points), cam_k, h, w, auto_scale=1)
# ISM prompt
mask_uint8 = cv2.normalize(mask, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
rgb_mask = deepcopy(rgb)
rgb_mask[mask == 0] = 0
# 将 mask 与 rgb 的前景质心平移到图像中心
coords = np.column_stack(np.where(mask_uint8 > 0))
if coords.size > 0:
cy, cx = coords.mean(axis=0)
center_x = w / 2.0
center_y = h / 2.0
dx = center_x - cx
dy = center_y - cy
T = np.float32([[1, 0, dx], [0, 1, dy]])
rgb_mask = cv2.warpAffine(
rgb_mask, T, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0)
)
mask_uint8 = cv2.warpAffine(
mask_uint8, T, (w, h), flags=cv2.INTER_NEAREST, borderMode=cv2.BORDER_CONSTANT, borderValue=0
)
if template_path is not None:
center = (w / 2, h / 2)
new_w = int(math.sqrt(w**2 + h**2))
new_h = new_w
main_i = 0
for angle in tqdm(angles_z, desc="生成ISM旋转模板"):
M = cv2.getRotationMatrix2D(center, angle, 1.0)
M[0, 2] += (new_w - w) / 2
M[1, 2] += (new_h - h) / 2
# 生成rgb
rotated_rgb = cv2.warpAffine(
rgb_mask, M, (new_w, new_h), borderMode=cv2.BORDER_REPLICATE, borderValue=(127, 127, 127)
)
save_name_rgb = f"rgb_{main_i}.png"
save_path_rgb = os.path.join(template_path, save_name_rgb)
cv2.imwrite(save_path_rgb, rotated_rgb)
# 生成mask
rotated_mask = cv2.warpAffine(
mask_uint8, M, (new_w, new_h), borderMode=cv2.BORDER_REPLICATE, borderValue=(127, 127, 127)
)
save_name_mask = f"mask_{main_i}.png"
save_path_mask = os.path.join(template_path, save_name_mask)
cv2.imwrite(save_path_mask, rotated_mask)
main_i += 1
angles_xy = range(-rotation_range_xy, rotation_range_xy + 1, rotation_range_xy) if rotation_range_xy > 0 else [0]
# 设置深度变化范围 (mm to meters)
if depth_interval > 0:
depth_shifts = np.arange(-depth_range, depth_range + depth_interval, depth_interval) / 1000.0
else:
depth_shifts = [0]
geometric_features_model = []
variants_meta = []
i = 0
for angle_z in tqdm(angles_z, desc="生成ISM几何特征"):
for angle_x in angles_xy:
for angle_y in angles_xy:
pcd_rotated = deepcopy(pcd)
# Z轴旋转
Rz = pcd_rotated.get_rotation_matrix_from_xyz((0, 0, np.deg2rad(angle_z)))
pcd_rotated.rotate(Rz, center=pcd_center)
# X轴小角度旋转
if angle_x != 0:
Rx = pcd_rotated.get_rotation_matrix_from_xyz((np.deg2rad(angle_x), 0, 0))
pcd_rotated.rotate(Rx, center=pcd_center)
# Y轴小角度旋转
if angle_y != 0:
Ry = pcd_rotated.get_rotation_matrix_from_xyz((0, np.deg2rad(angle_y), 0))
pcd_rotated.rotate(Ry, center=pcd_center)
for depth_shift in depth_shifts:
pcd_final = deepcopy(pcd_rotated)
if depth_shift != 0:
pcd_final.translate((0, 0, depth_shift))
# 从旋转和平移后的点云生成mask
mask = generate_mask_from_points(np.array(pcd_final.points), cam_k, h, w, auto_scale=2)
mask_uint8 = cv2.normalize(mask, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
obb_ratio = get_ratio_from_mask(mask_uint8)
mask_area = int(np.count_nonzero(mask_uint8 > 0))
if obb_ratio > 0 and mask_area > 0:
geometric_features_model.append([float(obb_ratio), float(mask_area)])
variants_meta.append(
{
"idx": i,
"angle_z": angle_z,
"angle_x": angle_x,
"angle_y": angle_y,
"depth_shift_m": float(depth_shift),
"obb_ratio": float(obb_ratio),
"mask_area": mask_area,
}
)
i += 1
meta = {"geometric_features_model": geometric_features_model, "variants": variants_meta, "num_variants": i}
with open(os.path.join(template_path, "prompt_meta.json"), "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
print(f"完成ISM模板生成: 元数据写入 {template_path}/prompt_meta.json")
return template_path
def generate_superglue_template(args, start_index=0, template_id=None):
"""生成SuperGlue模板(第二个脚本的功能)"""
if ImagePairMatcher is None:
raise ImportError("无法导入SuperGlueMatcher,请确保glia版本正确")
if torch is None:
raise ImportError("无法导入torch,请安装PyTorch")
data_dir = args.data_dir
auto_scale = args.auto_scale
mask_kernel_size = args.mask_kernel_size
edge_kernel_size = args.edge_kernel_size
background_color = args.background_color
foreground_color = args.foreground_color
voxel_size = args.down_sample
ply_path = None
pcd = None
# 查找所需文件
for f in os.listdir(data_dir):
f_path = os.path.join(data_dir, f)
if f.endswith('.ply'):
ply_path = f_path
pcd = o3d.io.read_point_cloud(ply_path)
if f.endswith('.png') or f.endswith('.jpg') or f.endswith('.bmp'):
rgb = cv2.imread(f_path)
h, w = rgb.shape[0], rgb.shape[1]
if f.endswith('.tiff'):
depth = cv2.imread(f_path, -1)
if f.endswith('.json') and 'camera_param' in f:
resource_manager = json.load(open(f_path))
cam_k = resource_manager["camera_param"]
cam_k = np.asarray(cam_k).reshape(3, 3).astype(np.float32)
# 检查文件是否存在
if ply_path is None:
raise ValueError(f"错误! 未在 {data_dir} 路径下找到 点云 文件, 支持后缀为 ply.\n")
if pcd is None:
raise ValueError(f"错误! {ply_path} 路径的点云文件读取失败.\n")
try:
rgb
except NameError:
raise ValueError(f"错误! 未能在 {data_dir} 路径下正确读取 彩色图像 文件, 支持后缀为 png/jpg/bmp.\n")
try:
depth
except NameError:
raise ValueError(f"错误! 未能在 {data_dir} 路径下正确读取 深度图像 文件, 支持后缀为 tiff.\n")
try:
cam_k
except NameError:
raise ValueError(f"错误! 未在 {data_dir} 路径下找到 配置参数 文件, 支持后缀为 json.\n")
mask = generate_mask_from_points(
np.array(pcd.points), cam_k, h, w, kernel_size=mask_kernel_size, auto_scale=auto_scale
)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((mask_kernel_size, mask_kernel_size), np.uint8))
rgb_mask = deepcopy(rgb)
rgb_mask[mask == 0] = background_color
depth_mask = deepcopy(depth)
depth_mask[mask == 0] = 0
if args.use_edge:
rgb_mask[mask != 0] = foreground_color
# 裁剪图像
coords = cv2.findNonZero(mask)
if coords is None or len(coords) == 0:
raise ValueError("mask 中没有非零点,无法进行裁剪")
# 获取初始边界框
x_bbox, y_bbox, w_bbox, h_bbox = cv2.boundingRect(coords)
# 以对角线为直径的圆, 再计算外接正方形, 避免旋转模板出画幅
diameter = math.ceil(np.sqrt(w_bbox*w_bbox + h_bbox*h_bbox))
rgb_mask_paded = np.zeros([diameter, diameter, 3], dtype=np.uint8)
depth_mask_paded = np.zeros([diameter, diameter], dtype=np.float32)
x_pad, y_pad = (diameter - w_bbox)//2, (diameter - h_bbox)//2
rgb_mask_paded[y_pad:y_pad + h_bbox, x_pad:x_pad + w_bbox] = rgb_mask[y_bbox : y_bbox + h_bbox, x_bbox : x_bbox + w_bbox]
depth_mask_paded[y_pad:y_pad + h_bbox, x_pad:x_pad + w_bbox] = depth_mask[y_bbox : y_bbox + h_bbox, x_bbox : x_bbox + w_bbox]
rgb_mask = deepcopy(rgb_mask_paded)
depth_mask = deepcopy(depth_mask_paded)
# 更新相机内参矩阵
cam_k[0, 2] = cam_k[0, 2] - x_bbox + x_pad # 调整cx (主点x坐标),主点的x方向移动了 x_expanded,所以减去
cam_k[1, 2] = cam_k[1, 2] - y_bbox + y_pad # 调整cy (主点y坐标),主点的y方向移动了 y_expanded,所以减去
# 统一输出目录:优先 args.output_feat_dir data_dir/superglue
superglue_path = getattr(args, "output_feat_dir", None) or os.path.join(data_dir, "feature matching")
os.makedirs(superglue_path, exist_ok=True)
index = int(start_index)
base_index = index
use_depth = args.use_depth
use_edge = args.use_edge
# 输出 model.ply
o3d.io.write_point_cloud(f"{superglue_path}/model_{index}.ply", pcd)
if voxel_size is not None:
pcd_down_sampled = deepcopy(pcd).voxel_down_sample(voxel_size=voxel_size)
o3d.io.write_point_cloud(f"{superglue_path}/model_{index}_downsampled.ply", pcd_down_sampled)
# 注册模版输出 temp.png keypoints.ply
temp = deepcopy(rgb_mask)
if use_depth:
temp = depth2img(depth_mask)
temp = cv2.morphologyEx(temp, cv2.MORPH_CLOSE, np.ones((2, 2), np.uint8))
cv2.imwrite(f"{superglue_path}/depth_model_{index}.tiff", depth_mask)
cv2.imwrite(f"{superglue_path}/temp_{index}.png", temp)
# 输出 keypoints.ply
config = default_config_superpoint if args.feat_type == "superpoint" else default_config_xfeat
if args.feat_type == "superpoint":
config['detector']['params']['default']['use_edge'] = use_edge
config['detector']['params']['default']['edge_kernel_size'] = edge_kernel_size
image_pair_matcher = ImagePairMatcher(config)
image_pair_matcher.register([temp])
keypoints_model_2d = image_pair_matcher.kpts_info_t['keypoints'][0].cpu().numpy().astype(np.int32)
Keypoint_3D_model = np.zeros((len(keypoints_model_2d), 3), dtype=np.float32)
depth_values = search_depth_values(keypoints_model_2d, depth_mask, 5)
temp_vis = deepcopy(temp)
def get_rainbow_color(index, total_points):
hsv = np.array([[[int(255 * index / total_points), 255, 255]]], dtype=np.uint8)
bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)[0][0]
return tuple(map(int, bgr))
for i in range(len(keypoints_model_2d)):
x, y = keypoints_model_2d[i]
z = depth_values[i]
if z == -1:
Keypoint_3D_model[i] = [0, 0, 0]
continue
Keypoint_3D_model[i] = np.linalg.inv(cam_k) @ (np.array([x, y, 1]) * z)
color = get_rainbow_color(i, len(keypoints_model_2d))
center = (int(round(x)), int(round(y)))
cv2.circle(temp_vis, center, radius=1, color=color, thickness=-1)
cv2.imwrite(f"{superglue_path}/temp_vis_{index}.jpg", temp_vis)
pcd_model_keypoints = o3d.geometry.PointCloud()
pcd_model_keypoints.points = o3d.utility.Vector3dVector(Keypoint_3D_model)
pcd_model_keypoints.colors = o3d.utility.Vector3dVector([[0, 1, 0] for _ in range(Keypoint_3D_model.shape[0])])
o3d.io.write_point_cloud(f"{superglue_path}/keypoints_{index}.ply", pcd_model_keypoints)
f_json_data = open(os.path.join(superglue_path, f"model_info_{index}.json"), "w+")
json_saved_data = {}
json_saved_data["camera_param"] = cam_k.tolist()
json_saved_data["transform"] = np.eye(4).tolist()
json_saved_data["angle"] = 0.0
json_saved_data["feat_type"] = args.feat_type
json_saved_data["mask_kernel_size"] = mask_kernel_size
json_saved_data["edge_kernel_size"] = edge_kernel_size
json_saved_data["use_edge"] = args.use_edge
json_saved_data["use_depth"] = args.use_depth
json_saved_data["downsample_size"] = args.down_sample
if template_id is not None:
json_saved_data["template_id"] = int(template_id)
json.dump(json_saved_data, f_json_data, indent=4)
f_json_data.close()
if args.multi_temp:
for k, angle in enumerate(args.angle):
project_pcd_to_rgb(
superglue_path,
superglue_path,
angle,
args.auto_scale,
mask_kernel_size,
edge_kernel_size,
index=base_index + 1 + k,
use_edge=args.use_edge,
use_depth=args.use_depth,
background_color=background_color,
foreground_color=foreground_color,
template_id=template_id,
base_index=base_index,
feat_type=args.feat_type,
)
# 组别的第0个模版点云另存为 multi_model_{template_id}.ply(若启用多模版)
if template_id is not None:
src = os.path.join(superglue_path, f"model_{base_index}.ply")
dst = os.path.join(superglue_path, f"multi_model_{template_id}.ply")
if os.path.exists(src):
shutil.copy(src, dst)
if voxel_size is not None:
# 生成 multi_model_{template_id}_downsampled.ply
pcd0 = o3d.io.read_point_cloud(src)
pcd0_ds = deepcopy(pcd0).voxel_down_sample(voxel_size=voxel_size)
o3d.io.write_point_cloud(
os.path.join(superglue_path, f"multi_model_{template_id}_downsampled.ply"), pcd0_ds
)
if torch.cuda.is_available():
torch.cuda.empty_cache()
print(f"完成SuperGlue模板生成: 文件保存在 {superglue_path}")
# 返回下一个可用索引(使用 base_index 计算)
end_next_index = base_index + (len(args.angle) + 1 if args.multi_temp else 1)
return superglue_path, end_next_index
def generate_template(args=None):
"""
生成模板的主函数,支持独立运行和外部调用
参数:
args: argparse.Namespace 或 dict 类型的参数
如果为 None,则从命令行解析参数
如果为 dict,则转换为 Namespace
返回:
tuple: (ism_path, feat_path) 或根据参数返回单个路径
"""
if args is None:
# 命令行调用模式
parser = create_parser()
args = parser.parse_args()
elif isinstance(args, dict):
# 外部调用,参数为字典
parser = create_parser()
args_ori = parser.parse_args()
for key, value in args.items():
if hasattr(args_ori, key):
setattr(args_ori, key, value)
args = args_ori
# 执行文件复制
if args.file_transfer:
if not args.output_dir:
raise ValueError("执行文件复制时需要指定 --output_dir 参数")
print("开始文件复制...")
file_transfer(args)
print(f"文件复制完成,文件保存在: {args.output_dir}")
return
# 生成ISM
arg_ism = deepcopy(args)
if args.type in ["ism", "both"]:
def _first_subdir(root: str) -> str:
try:
subs = sorted([e.path for e in os.scandir(root) if e.is_dir()])
return subs[0] if subs else root
except Exception:
return root
if getattr(args, "multi_template", False):
arg_ism.data_dir = _first_subdir(arg_ism.data_dir)
print("开始生成ISM模板...")
try:
ism_path = generate_ism_template(arg_ism)
print(f"ISM模板生成完成,保存在: {ism_path}")
except Exception as e:
print(f"生成ISM模板时出错: {e}")
if arg_ism.type == "ism":
raise
# 生成/处理SuperGlue
if args.type in ["feat", "both"]:
print("开始生成/处理SuperGlue模板...")
try:
if args.multi_template:
# 功能1:合并多个superglue目录
if len(args.feat_dirs) >= 2:
target_dir = args.feat_dirs[0]
source_dirs = args.feat_dirs[1:]
merge_superglue_folders(target_dir, source_dirs)
print(f"SuperGlue多目录合并完成,输出: {target_dir}")
else:
# 功能2:单目录多组 -> 统一写入一个目录,并从末尾连续编号
group_dirs = _detect_group_dirs(args.data_dir)
out_dir = args.output_feat_dir or os.path.join(args.data_dir, "feature matching")
os.makedirs(out_dir, exist_ok=True)
start_idx = _get_next_index(out_dir)
next_idx = start_idx
if len(group_dirs) >= 1:
for gid, gdir in enumerate(group_dirs):
args_one = deepcopy(args)
args_one.data_dir = gdir
args_one.output_feat_dir = out_dir # 统一输出目录
_, next_idx = generate_superglue_template(args_one, start_index=next_idx, template_id=gid)
print(f"SuperGlue多组生成完成,输出: {out_dir}")
else:
# 常规单组生成,但写入 template_id=0,也支持统一输出目录
_, _ = generate_superglue_template(
args,
start_index=next_idx,
template_id=0,
)
print(f"SuperGlue单组生成完成,输出: {out_dir}")
args.data_dir = out_dir
else:
# 原有逻辑:单组生成(不写template_id字段)
_ = generate_superglue_template(args) # 返回值未使用
print("SuperGlue模板生成完成")
except Exception as e:
print(f"生成/处理SuperGlue模板时出错: {e}")
if args.type == "feat":
raise
if args.type == "both":
print("所有模板生成完成!")
print(f"ISM模板保存在: {os.path.join(arg_ism.data_dir, 'ism')}")
print(f"Feat模板保存在: {os.path.join(args.data_dir, 'feature matching')}")
def create_parser():
"""创建参数解析器"""
parser = argparse.ArgumentParser(description="融合脚本:生成ISM和/或Feat模板")
parser.add_argument(
"--output_feat_dir",
type=str,
default=None,
help="指定Feat模板统一输出目录;不指定则写入 data_dir/feature matching",
)
# 基础参数
parser.add_argument("--data_dir", type=str, default=None, help="输入数据目录")
parser.add_argument(
"--type",
choices=["ism", "feat", "both"],
default="both",
help="选择生成的模板类型: ism, feat, 或 both",
)
parser.add_argument("--file_transfer", action="store_true", default=False, help="执行文件复制操作")
parser.add_argument("--output_dir", help="文件复制时的输出目录")
# ISM参数
parser.add_argument("--rot_range", type=int, default=60, help="Z-axis rotation range (degrees)")
parser.add_argument("--rot_interval", type=int, default=10, help="Z-axis rotation interval (degrees)")
parser.add_argument("--rot_range_xy", type=int, default=5, help="XY small rotation range (degrees)")
parser.add_argument("--depth_range", type=int, default=200, help="Z translation range (mm)")
parser.add_argument("--depth_interval", type=int, default=200, help="Z translation interval (mm)")
# SuperGlue参数
parser.add_argument("--auto_scale", type=float, default=0.1, help="点云到掩码的缩放比例")
parser.add_argument("--use_depth", action="store_true", default=False, help="使用深度图")
parser.add_argument("--use_edge", action="store_true", default=False, help="使用边缘模式")
parser.add_argument("--feat_type", choices=["superpoint", "xfeat"], default="superpoint", help="特征点提取器类型")
parser.add_argument("--edge_kernel_size", type=int, default=3, help="边缘核大小")
parser.add_argument("--mask_kernel_size", type=int, default=5, help="形态学操作核大小")
parser.add_argument("--multi_temp", action="store_true", default=False, help="生成多角度模板")
parser.add_argument("--angle", type=float, nargs="+", default=[90, 180, 270], help="旋转角度")
parser.add_argument("--down_sample", type=float, default=None, help="点云下采样体素大小(米), 设置为None禁用")
parser.add_argument("--background_color", type=int, default=0, help="边缘模式背景颜色")
parser.add_argument("--foreground_color", type=int, default=255, help="边缘模式前景颜色")
# 新增:多模版模式
parser.add_argument("--multi_template", action="store_true", default=False, help="启用SuperGlue多模版模式")
parser.add_argument(
"--feat_dirs",
nargs="+",
default=[],
help="多个superglue模板目录(功能1:合并多个已生成的feat模板目录)",
)
return parser
def main():
generate_template()
if __name__ == "__main__":
main()(3)利用模板生成脚本,复制历史数据
- 在模板生成脚本的下载目录中,鼠标右键点击空白处,出现"右键菜单",在"右键菜单"中点击
在终端中打开,打开Windows PowerShell终端,如下图所示。


- 在终端执行
conda activate pickwiz_py39命令进入到 pickwiz_py39 环境,如下图所示。

- 终端进入到 pickwiz_py39 环境后,继续执行以下命令,可根据模板生成脚本的名称、要复制的历史数据时间戳的路径、输出保存路径修改以下命令。
python generate_prompt_double_ism_feature.py #调用Python脚本,可根据模板生成脚本的名称修改
--data_dir "C:\Users\dex\kuawei_data\PickLight\20240617150557809" #要复制的历史数据时间戳的路径,可根据历史数据时间戳修改
--file_transfer --output_dir #文件传输和输出命令
"C:\Users\dex\Documents\PickWiz\new_project_22\superglue" #输出文件保存路径,可自行修改保存路径
--type feat #表示生成特征匹配模板示例:模板生成脚本的名称是 "generate_prompt_superglue.py";
要复制的历史数据的时间戳路径是 "D:\Pickwiz\new_project\data\PickLight\20250411144909289";
输出文件保存路径是"C:\Users\dex\Documents\PickWiz\new_project_1\data\PickLight"。
python generate_prompt_double_ism_feature.py #调用Python脚本“generate_prompt_superglue.py”
--data_dir "D:\Pickwiz\new_project\data\PickLight\20250411144909289" #要复制的历史数据时间戳的路径
--file_transfer --output_dir #文件传输和输出命令
"C:\Users\dex\Documents\PickWiz\new_project_1\data\PickLight" #输出文件保存路径
--type feat #表示生成特征匹配模板执行命令时修改脚本名称、历史数据时间戳路径、输出文件保存路径,如下图所示。

(4)执行完命令后,在保存路径下生成了4个文件,分别是场景的2D图像、场景的深度图、场景点云和相机内参

- 裁剪点云
在 meshlab 软件打开场景点云.ply文件,裁剪掉场景点云的噪点直至仅保留工件点云,然后直接点击覆盖保存。
裁剪点云时需要仔细保留完整的工件点云。
| 裁剪前 | 裁剪后 | 覆盖保存 |
|---|---|---|
![]() | ![]() | ![]() |
生成点云模板
在终端执行
conda activate pickwiz_py39命令进入到 pickwiz_py39 环境。

- 在 pickwiz_py39 环境下,执行如下命令,可根据工件特征修改以下命令。
脚本提供了--use_edge 、--multi_temp 参数,用于调整模板生成方式。 --use_edge 表示使用边缘检测,--multi_temp表示生成多方向模版用于来料方向不固定的场景。这两个参数默认不增加,表示不启用多模版和边缘检测,使用2D图生成点云模板。
当工件表面纹理不明显时,增加 --use_edge 参数,启用边缘检测强化图像特征,生成边缘增强的点云模板。
python generate_prompt_double_ism_feature.py #调用Python脚本
--data_dir "C:\Users\dex\Documents\lixin\unify_infer\superglue_model_gen\superglue" #输入上面第3步的保存路径
--type feat #表示生成特征匹配模板示例:当实际场景光照条件不稳定或工件表面纹理不明显、几何形状复杂时,可增加 --use_edge 参数,脚本会先对2D图像进行边缘检测,替代原本的2D图像进行匹配,匹配时会专注于工件的几何边缘特征,生成点云模板。
python generate_prompt_double_ism_feature.py #调用Python脚本
--data_dir "C:\Users\dex\Documents\PickWiz\new_project_1\data\PickLight" --use_edge #输入上面第3步的保存路径,并且增加--use_edge边缘检测参数
--type feat #表示生成特征匹配模板
- 执行指令后,在保存路径下生成了模板文件夹"feature matching"。

检查该文件夹下是否存在以下4个文件。

(注意灰度模式下模板图为灰度图,边缘模式下为二值图)
| 灰度模式 | ![]() |
|---|---|
| 边缘模式 | ![]() |
- 若来料包含多个方向,启用多模版方式的示例如下,其中
--angle后指定相对主模版的旋转角度,运行后会生成根据主模版旋转若干个角度的模版,如下所示
python generate_prompt_double_ism_feature.py --data_dir superglue-compressor --type feat --use_edge --multi_temp --angle 45 90 180 225 270如下图所示

导入点云模板、选择模板文件路径
将保存路径下 "feature matching"文件夹中的
model.ply文件作为点云模板导入到工件界面的点云文件中。

- 在
模板文件路径选择保存路径下“feature matching”文件夹的路径(如C:\Users\dex\Documents\PickWiz\new_project_1\data\PickLight\feature matching)。

- 模版生成脚本参数说明
| 参数名称 | 参数说明 | 取值建议 | 简单说明 |
|---|---|---|---|
| data_dir | 先验文件夹路径 | \ | 与先前生成数据脚本使用的文件路径一致 windows复制路径为单反斜杠 "\",报路径错误,需要将单反斜杠改为双反斜杠或改为取值建议中的正斜杠 "/" |
| file_transfer | 是否拷贝文件 | \ | \ |
| output_dir | 仅在 file_transfer 有效时起作用 | \ | \ |
| scale | 点云投影生成mask的缩放尺度 | 0.1 | 点云投影生成mask的缩放尺度 |
| use_edge | 是否启用边缘模式,不启用则为灰度模式 | \ | 多层堆叠时,下层物体与上层物体轮廓难以被模型区分,需使用灰度模式 |
| use_depth | 是否使用深度图模板 | \ | 开启后,会从深度图转彩图产生模板图,物体表面无明显纹理,且点云/深度图质量较好时可切换深度图模式 |
| multi_temp | 是否生成多方向模版 | \ | 默认为false,即默认仅生成单方向模版 |
| mask_kernel_size | 点云投影成mask时的闭运算卷积核大小 | 5 | 使用默认值 |
| edge_kernel_size | 边缘特征提取时的卷积核大小 | 3 | ![]() 数值越大,特征图越靠内 |
| angle | 多模版时,子模版相对主模版的旋转角度 | \ | 角度输入格式,可参考示例命令 |
| down_sample | 点云降采样倍数,降采样可降低时间节拍,但可能影响配准精度,需要验证后使用 | 0.001 | 表示点云体素降采样过程,点与点间隔大小参数 |
2.2.2 匹配置信阈值(mm)

- 功能
特征点匹配的可信度分数,分数越高,特征点质量越高,但是匹配到的特征点数量可能越少
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:0.01
取值范围:[0.001, 1.0]
2.2.3 特征扩增数量

- **功能 **
在原始特征点检测的基础上人工扩增特征点数量,防止因特征点过少导致匹配异常。
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:3
取值范围:[0, 99]
2.2.4 特征扩增范围

- **功能 **
特征点扩增的选取邻域范围
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:3
取值范围:[0, 99]
- 调参:
当场景点云质量较好时可适当调大该参数,反之场景点云质量差时调小该参数
2.2.7 最大迭代次数

- **功能 **
限制算法在粗匹配阶段的最大迭代次数,避免因无限循环或收敛过慢导致计算资源浪费
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:100
不建议修改,后续会隐藏该函数
2.2.8 包围盒尺寸系数

- **功能 **
动态调整包围盒的尺寸,控制检测到的包围盒的长宽缩放比例。
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:1.0
- 调参
减少背景干扰或相邻目标的误合并,缩小检测范围,系数 <1.0;
防止目标部分(如遮挡)被裁剪,放大检测范围,系数 >1.0;
可根据2D识别结果中的包围盒情况调整该系数
2.2.9 启用深度特征

- **功能 **
通过 SuperGlue 模型提取的特征替代传统的点云特征,用于解决复杂场景下的匹配异常。
- 使用场景
面型工件有序上下料(并行化)场景
- 调参
适用于表面光滑、重复纹理、光照变化大的工件,适用于多类工件混合上下料
2.2.10 启用边缘特征

- **功能 **
启用时,仅在物体边缘区域提取和匹配特征点
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
物体边缘区域(如轮廓、锐利过渡区),忽略平坦或纹理单一的区域。
模板制作时也需同步启用use_edge,保证特征一致性。
2.2.11 粗匹配评判阈值(mm)

- **功能 **
特征点粗匹配时,仅保留可信度高于此值的匹配点。
- 使用场景
面型工件有序上下料(并行化)场景
- 参数说明
默认值:10
取值范围:[0.1, 1000]
- 调参
低阈值(如5):减少误匹配,但可能丢失有效点。
高阈值(如20):保留更多匹配,但噪声增加。
2.2.12 物体姿态修正

精匹配搜索半径(mm)

- 功能
在精匹配过程中,模板点云与实例点云进行匹配,模板点云中每个点都需要在实例点云中搜索最邻近点。精匹配搜索半径既表示在实例点云中的搜索半径,又表示模板点云中的每个点与实例点云中的最邻近点的距离阈值。若点与最邻近点的距离小于精匹配搜索半径,则认为这两个点能够匹配,否则认为两个点不能匹配。
- 使用场景
面型工件有序上下料、面型工件无序抓取、面型工件定位装配场景
- 参数说明
默认值:10
取值范围:[1, 500]
单位:mm
- 调参
通常不更改
精匹配搜索模式

- 功能
在精匹配过程中,模板点云在实例点云中检索最邻近点的方式
- 使用场景
模板点云与实例点云的精匹配效果不佳,应调整该函数
- 参数说明
| 参数 | 说明 |
|---|---|
| 点到点 | 模板点云中的每个点在实例点云中搜索最邻近点(搜索半径内直线距离最短的点)适用于所有工件 |
| 点到面 | 模板点云中的每个点沿着其法向量在实例点云中搜索最邻近点适用于几何特征明显的工件 |
| 点到点和点到面两种方式结合 | 先采用点对点模式优化实例点云中的工件姿态,再采用点对面模式优化实例点云中的工件姿态适用于几何特征明显的工件
|
使用轮廓模式

- 功能
提取模板点云和实例点云中的轮廓点云进行粗匹配
- 使用场景
面型工件有序上下料、面型工件无序抓取、面型工件定位装配场景,若使用关键点进行粗匹配的结果不佳,应当勾选该函数使用轮廓点云再次进行粗匹配
- 调参
粗匹配的结果会影响精匹配结果,如果精匹配结果不佳,可勾选 使用轮廓模式
轮廓搜索范围(mm)

- 功能
在模板点云和实例点云中提取轮廓点云的搜索半径
- 使用场景
通用工件有序上下料、通用工件无序抓取、通用工件定位装配场景
- 参数说明
默认值:5
取值范围:[0.1, 500]
单位:mm
- 调参
取值较小,搜索轮廓点云的半径较小,适合提取细致的工件轮廓,但提取的轮廓可能包含离群点噪声;
取值较大,搜索轮廓点云的半径较大,适合提取较宽的工件轮廓,但提取的轮廓可能会忽略一些细节特征。
保存姿态估计[精匹配]数据

- 功能
勾选则保存精匹配数据
- 使用场景
面型工件有序上下料、面型工件无序抓取、面型工件定位装配、面型工件定位装配(仅匹配)
- 示例
精匹配数据保存在项目保存路径\项目文件夹\data\PickLight\历史数据时间戳\Builder\pose\output文件夹中。

2.3 空ROI判断

- 功能
判断ROI 3D内是否还有工件(点云)剩余,如果ROI 3D内的3D点的数量小于该值,表示没有工件点云剩余,此时不返回点云
- 参数说明
默认值:1000
取值范围:[0, 100000]
- 使用流程
设置ROI 3D最小点数判断阈值,小于该阈值即ROI 3D中工件点云不足,从而判断为无工件在ROI 3D中;
机器人配置中,新增视觉状态码,便于后续机器人进行信号处理。
3. 抓取点处理
本节主要对抓取点过滤和调整相关函数进行说明和调参建议
3.1 抓取点调整

3.1.1 当位姿不在角度范围内时,旋转位姿

- 功能介绍
当位姿不在设定的角度范围内时,将位姿绕固定轴逆时针旋转一定角度,若旋转后仍不在设定的角度范围内,则会发出警告。
- 使用场景
仅适用于拆垛场景,该函数可使机器人抓取时的方向保持稳定,避免抓取过程中出现末端工具反复旋转 180° 的情况,可防止绕线等异常。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 固定轴 | 抓取姿态的某个轴,将位姿绕该固定轴逆时针旋转 | Z轴 | X/Y/Z轴 | / |
| 旋转角度 | 位姿绕固定轴逆时针旋转的角度,通过调整旋转角度使抓取姿态满足角度范围 | 0 | [-360,360] | 角度 |
| 角度范围 | 抓取姿态的角度范围,根据物料的摆放情况、末端工具类型以及节拍等因素设置角度范围 | [0,180] | [-180,180] | 角度 |
| 使用当前机器人欧拉角 | 默认使用欧拉角“XYZ”进行位姿计算,勾选后使用当前机器人配置的欧拉角,可使位姿与机器人示教器保持一致。 | 不勾选 | / | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 机械臂坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 | / |
- 示例
不使用该函数,生成的抓取点如下图

使用该函数,默认值运行,实例0、1、2的抓取位姿RZ角度都在角度范围[0,180]内,因此不做处理,实例4的抓取姿态RZ角度为-90°,不在角度范围[0,180]内,因此实例4的抓取位姿绕固定轴Z轴旋转0°




如果要将实例4的抓取姿态RZ角度调整到角度范围内,可更改旋转角度为180,将实例4的抓取位姿绕固定轴Z轴旋转180°


3.1.2 旋转位姿,使旋转轴方向与目标轴方向一致

- 功能介绍
位姿绕固定轴旋转一次,并使旋转轴(根据右手定则确定)的方向与目标坐标系的目标轴正方向或负方向一致。
使用场景
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 旋转轴 | 抓取位姿的某个轴,根据右手定则确定,抓取位姿绕固定轴逆时针旋转一次,使旋转轴的方向与目标坐标系的目标轴正方向或负方向一致 | X轴 | X/Y/Z轴 |
| 固定轴 | 抓取位姿绕固定轴逆时针旋转一次,使旋转轴的方向与目标坐标系的目标轴正方向或负方向一致 | Z轴 | X/Y/Z轴 |
| 目标轴 | 目标坐标系的某个轴,抓取位姿绕固定轴逆时针旋转一次,使旋转轴的方向与目标坐标系的目标轴正方向或负方向一致 | X轴 | X/Y/Z轴 |
| 目标轴负方向 | 勾选则使旋转轴的方向与目标坐标系的目标轴负方向一致,不勾选则使旋转轴的方向与目标坐标系的目标轴正方向一致 | 不勾选 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 |
- 示例
3.1.3 旋转位姿,使旋转轴与目标轴夹角最小

- 功能介绍
将位姿绕固定轴分别旋转0、90、180、270度,分别计算旋转后的旋转轴与相机坐标系的目标轴正方向或负方向之间的夹角,最终输出旋转后夹角最小的位姿。
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 固定轴 | 抓取位姿的某个轴,将位姿绕该固定轴逆时针旋转 | Z轴 | X/Y/Z轴 |
| 旋转轴 | 抓取姿态的某个轴,旋转位姿时计算该旋转轴与目标轴正方向或负方向之间的夹角 | X轴 | X/Y/Z轴 |
| 目标轴 | 相机坐标系的某个轴,旋转位姿时计算旋转轴与该目标轴正方向或负方向之间的夹角 | X轴 | X/Y/Z轴 |
| 目标轴负方向 | 勾选则计算旋转轴与目标轴的负方向之间的夹角,不勾选则计算旋转轴与目标轴的正方向之间的夹角 | 勾选 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 |
- 示例


3.1.4 翻转位姿,使旋转轴与目标轴夹角最小

- 功能介绍
将抓取位姿绕固定轴旋转一次,使旋转轴与 ROI 坐标系中的目标轴的正方向或负方向之间形成的夹角是锐角。
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 固定轴 | 抓取位姿的某个轴,将位姿绕该固定轴逆时针旋转 | Z轴 | X/Y/Z轴 |
| 旋转轴 | 抓取位姿的某个轴,旋转位姿使该旋转轴的方向与目标轴的正方向或负方向一致 | X轴 | X/Y/Z轴 |
| 目标轴 | ROI坐标系中的某个轴,旋转位姿使旋转轴的方向与该目标轴的正方向或负方向一致 | X轴 | X/Y/Z轴 |
| 目标轴负方向 | 勾选则旋转位姿使旋转轴的方向与目标轴的负方向一致,不勾选则旋转位姿使旋转轴的方向与目标轴的正方向一致 | 勾选 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 |
- 示例


3.1.5 将位姿的轴指向 ROI 中心

- 功能
将抓取位姿绕固定轴旋转,使抓取位姿的指向轴指向ROI 中心。
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 指向轴 | 抓取位姿中需要调整的轴 | X轴 | X/Y/Z轴 |
| 固定轴 | 旋转时不变的轴 | Z轴 | X/Y/Z轴 |
| 反向对齐 | 勾选则将指向轴反向对齐roi中心,不勾选则将指向轴对齐roi中心 | 勾选 | / |
| 严格指向 | 勾选则强制旋转抓取位姿,使指向轴指向ROI中心 | 不勾选 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 |
- 示例


3.1.6 旋转位姿,使Z轴方向与目标坐标系的Z轴一致

- 功能介绍
旋转位姿,使位姿的Z轴方向与目标坐标系的Z轴一致
- 使用场景
通常仅在拆垛场景默认使用且不可删除,需要使抓取位姿的Z轴垂直于ROI坐标系的Z轴(四轴)或者与工件表面方向一致(六轴)
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 机器人构型 | 根据现场的机器人构型设置,可选四轴或六轴如果把六轴机器人实际当作四轴机器人使用,应设置为 四轴 | 四轴 | 四轴/六轴 |
| 使用 ROI Z轴作为目标方向 | 当机器人构型选择四轴时,勾选后则会将位姿绕X轴旋转,使旋转后的位姿Z轴方向与 ROI Z轴正方向 一致;不勾选则会将位姿绕X轴旋转,使旋转后的位姿Z轴方向与 相机坐标系的Z轴正方向 一致当机器人构型选择六轴时,无论勾选与否,均会将位姿绕X轴旋转,使旋转后的位姿Z轴方向与 物体自身坐标系的Z轴正方向 一致 | 不勾选 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 相机坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 |
- 示例
3.1.7 绕固定轴旋转位姿

- 功能介绍
将位姿绕固定轴旋转一定角度。
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 旋转角度 | 位姿绕固定轴逆时针旋转的角度 | 90 | [-360, 360] | 角度° |
| 固定轴 | 抓取位姿的某个轴,将位姿绕该固定轴逆时针旋转 | Z轴 | X/Y/Z轴 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 | / |
- 示例


3.1.8 平移位姿

- 功能介绍
将位姿沿平移轴移动一定距离。
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 平移量(mm) | 位姿沿平移轴移动的距离,平移量取值为正数表示向平移轴的正方向平移,平移量取值为负数表示向平移轴的负方向平移 | 0 | [-1000, 1000] | mm |
| 平移轴 | 位姿移动的方向 | X轴 | X/Y/Z轴 | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 机械臂坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 | / |
- 示例


3.1.9 抓取点示教

- 功能介绍
记录软件生成的抓取点坐标以及该工况下示教出来的抓取点坐标,利用两者的偏差输出转换后的抓取姿态。
- 使用场景
系统视觉生成抓取点明显存在有规律的偏差,机器人本体TCP坐标精度有限或难以标定时,可以用此方法直接按照规律的偏差映射到后续的抓取点,从而避开机器人TCP的标定
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 视觉位姿 | 检测结果的抓取坐标 | ||
| X(mm) | 视觉位姿X坐标 | 0.00 | ±10000000,表示不设限。 |
| Y(mm) | 视觉位姿Y坐标 | 0.00 | ±10000000,表示不设限。 |
| Z(mm) | 视觉位姿Z坐标 | 0.00 | ±10000000,表示不设限。 |
| RX(°) | 视觉位姿X轴旋转量 | 0.00 | ±180 |
| RY(°) | 视觉位姿Y轴旋转量 | 0.00 | ±180 |
| RZ(°) | 视觉位姿Z轴旋转量 | 0.00 | ±180 |
| 抓取位姿 | 手动示教的抓取点 | ||
| X(mm) | 抓取位姿X坐标 | 0.00 | ±10000000,表示不设限。 |
| Y(mm) | 抓取位姿Y坐标 | 0.00 | ±10000000,表示不设限。 |
| Z(mm) | 抓取位姿Z坐标 | 0.00 | ±10000000,表示不设限。 |
| RX(°) | 抓取位姿X轴旋转量 | 0.00 | ±180 |
| RY(°) | 抓取位姿Y轴旋转量 | 0.00 | ±180 |
| RZ(°) | 抓取位姿Z轴旋转量 | 0.00 | ±180 |
3.1.10 根据平面法线细化姿态函数

- 功能介绍
通过拟合平面的法线来校正工件的姿态,使工件姿态的Z轴方向与工件平面法线的方向保持一致
- 使用场景
工件中有平面,模板点云与实际点云匹配时平面的倾斜角度有偏差,使用该函数微调工件平面,提高抓取精度
不适用于拆垛场景
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 距离阈值 | 点云拟合平面的距离阈值 | 10 | [-1000, 1000] | mm |
| 保存可视化数据 | 勾选则将可视化数据保存到历史数据时间戳下 | 勾选 | / | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 相机坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 | / |
- 示例
3.1.11 基于轴间角度排序抓取点

- 功能
根据抓取位姿的某个轴与ROI的目标轴之间的夹角大小,对抓取点进行排序
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 轴选择 | 抓取位姿的某个轴 | Z轴 | X/Y/Z轴 |
| 目标轴选择 | ROI坐标系的某个轴 | Z轴 | X/Y/Z轴 |
| 选择反向 | 勾选则与目标轴负方向计算夹角,不勾选则与目标轴正方向计算夹角 | 不勾选 | / |
| 选择降序 | 勾选则根据夹角从小到大对抓取点进行排序,不勾选则根据夹角从大到小对抓取点进行排序 | 不勾选 | / |
3.1.12 【大师】旋转位姿,自动补偿与指定轴夹角过大的抓取姿态角度

- 功能介绍
判断抓取姿态的指定轴与目标轴形成的夹角角度是否在指定范围内,若不在则调整抓取姿态至指定范围内
- 使用场景
避免机器人末端工具与料框发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 角度范围 | 将抓取姿态调整至角度范围内 | 30 | [0, 180] | 角度° |
| 指定轴 | 抓取姿态的某个轴,将该轴调整至与roi坐标系的目标轴在角度范围内 | Z轴 | X/Y/Z轴 | / |
| 目标轴 | roi坐标系的某个轴,与抓取姿态的指定轴进行角度范围对比 | Z轴 | X/Y/Z轴 | / |
| 与roi的负半轴进行对比 | 不勾选则与roi坐标系的目标轴的正方向进行角度范围对比,勾选则与roi坐标系的目标轴的负方向进行角度范围对比 | 不勾选 | / | / |
| 自定义坐标系 | 抓取位姿所在的坐标系 | 默认坐标系 | 默认坐标系;相机坐标系;ROI坐标系;机械臂坐标系 | / |
3.1.13 【大师】对称中心姿态优化

- 功能
基于实例掩膜搜索工件的对称中心,结合实例的平面或ROI 3D中心点的姿态,计算最优抓取位姿
使用此函数前,应先确保实例掩膜是对称的
- 使用场景
适用于对称工件的实例掩膜也对称,但抓取点位姿不在期望中心附近的情况;同时,工件有可参考的平面,比如物体顶部有平面,或者存在ROI 3D可作为投影姿态参考。
可参考的项目场景有:刹车盘(通用圆)、耐火砖(拆垛)、对称的异形件、加油口等等。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 调参建议 |
|---|---|---|---|---|
| 对称中心类型 | 实例掩膜的对称类型 | 旋转对称 | 旋转对称:工件绕中心点旋转一定角度后,形状与原位置完全重合;镜像对称:工件以某个轴 / 平面为镜面,左右或上下两边完全对称。 | 圆形、矩形既是旋转对称的又是镜像对称的,优先使用旋转对称;梯形等仅沿某轴或平面两边对称的,选镜像对称。 |
| 高斯模糊程度 | 判断实际点云旋转后是否重合的宽容程度 | 3 | \[1,99\] |
|
| 旋转角度设置 | 当对称模式为旋转对称时,表示旋转角度间隔,即相邻两次旋转之间的角度差值。当对称模式为镜像对称时,表示旋转范围,即点云围绕对称轴可旋转的角度区间。 | 180 | \[1,360\] |
|
| 图像缩放比例 | 调整点云图的大小。该比例越大,点云图尺寸变小,占用显存降低,但图像细节损失大,导致计算精度降低 。 | 2 | \[1,10000000\] | |
| 搜索范围 | 基于初始确定的工件中心向外扩展搜索点云特征的范围,实际范围为(搜索范围*2*图像缩放比例) | 10 | \[1,10000000\] | 比如一个正方形工件,初始确定的工件中心位置是点O。如果设置搜索范围为10,图像缩放比例为1,那么实际搜索范围就是以点O为中心,边长为10×2×1=20的正方形区域。在这个区域内搜索点云特征,进一步确定工件的对称中心以及最优抓取位姿。再比如一个圆形工件,若搜索范围设为8,图像缩放比例为2,则实际搜索范围是以初始确定的工件中心为圆心、直径为8×2×2=32的圆形区域内,在这个区域内搜索点云特征,进一步确定工件的姿态以及最优抓取位姿。 |
| 使用ROI3D作为参考投影平面 | 勾选则将ROI3D作为参考投影平面 | 不勾选 | / | 点云无明显平面,难以确定投影平面时勾选;点云有清晰平面则不勾选。 |
| 保存对称中心过程数据 | 勾选则保存对称中心过程调试数据,可在项目文件夹`\项目名称`{=tex}`\data`{=tex}`\PickLight`{=tex}`\历史数据时间戳`{=tex}`\find`{=tex}\_symmetry_center文件夹查看 | 不勾选 | / | 需要检查具体过程图像时勾选 |
| 对称轴先验类型 | ``{=html}镜像对称``{=html}模式下生效,指定工件已知的对称类型,固定非对称朝向 | 自动搜索 | 自动搜索沿长轴对称沿短轴对称 | 工件的对称轴是长轴,选择 "沿长轴对称" 工件的对称轴是短轴,选择 "沿短轴对称" 不确定时,选择 "自动搜索" |
| 姿态调整类型 | 是否继承输入姿态相关信息 | 默认姿态 | 默认姿态继承旋转继承平移 | / |
| 对称分数阈值 | 对称分数小于该阈值的对称结果为异常结果,设置0时不过滤 | 0.0 | \[0.0, 1.0\] | / |
- 示例
3.2 抓取点过滤

3.2.1 基于精匹配分数过滤

- 功能介绍
基于姿态精匹配分数过滤抓取点
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 分数阈值 | 保留精匹配分数大于该阈值的抓取点 | 0.5 | [0, 1] |
- 示例
3.2.2 过滤被遮挡工件的抓取点

- 功能介绍
判断抓取工件的抓取点在ROI指定轴或抓取姿态轴向上的目标检测区域内,是否存在过多的遮挡物点云,若存在则判定为存在遮挡物,并过滤该抓取点。
- 使用场景
适用于拆垛和有序场景中,按层抓取工件,但模型识别到下层工件,抓取下层工件时夹具会与上层工件发生碰撞
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 长方体X方向长度 | 设置长方体在抓取姿态的X方向长度 | 1500 | [1, 10000] | mm |
| 长方体Y方向长度 | 设置长方体在抓取姿态的Y方向长度 | 1500 | [1, 10000] | mm |
| 长方体Z方向长度 | 设置长方体在抓取姿态的Z方向长度 | 800 | [1, 10000] | mm |
| 检测区域与抓取点原点距离阈值 | ROI轴向上,距离抓取点原点超过该距离阈值的长方体表面近邻区域,被认为是目标检测区域 | 50 | [1, 1000] | mm |
| 检测区域内点云数量阈值 | 目标检测区域内遮挡物点云数量超过该阈值,则认为该抓取点被遮挡 | 1000 | [0, 100000] | / |
| 指定轴向 | 基于姿态基准指定轴向,设定目标检测区域在长方体空间内的具体方位(如长方体前/后/左/右/上/下表面的近邻区域) | [0,0,-1] | [1,0,0]:X轴正方向[-1,0,0]:X轴负方向[0,1,0]:Y轴正方向[0,-1,0]:Y轴负方向[0,0,1]:Z轴正方向[0,0,-1]:Z轴负方向 | / |
| 使用ROI 3D姿态基准 | 勾选则根据ROI 3D姿态基准调整碰撞检测区域 | 不勾选 | / | / |
| 保存可视化数据 | 勾选则根据保存数据路径来存储可视化的数据,用于观察长方体生成是否合理,不勾选则不保存 | 不勾选 | / | / |
- 示例
3.2.3 基于抓取姿态角度范围过滤

- 功能介绍
判断抓取姿态的角度是否在约束的角度范围内,过滤所有不满足条件的抓取点。
- 使用场景
防止机械臂抓取姿态的角度异常导致发生碰撞。
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 角度过滤阈值 | 计算ROI 的指定轴与抓取姿态的指定轴之间的最大夹角,夹角大于当前阈值的抓取点将被过滤 | 30 | [-360, 360] | 角度° |
| ROI指定轴向取反 | 勾选则使用ROI指定轴的负方向进行角度计算,不勾选则使用ROI指定轴的正方向进行角度计算 | 勾选 | / | / |
| 抓取姿态指定轴 | 指定抓取位姿的某个轴计算夹角 | Z轴 | X/Y/Z轴 | / |
| ROI指定轴 | 指定ROI坐标系的某个轴计算夹角 | Z轴 | X/Y/Z轴 | / |
- 示例
3.2.4 过滤ROI 3D类型区外的抓取点

- 功能介绍
判断抓取点是否在ROI 3D的范围内,去除不在ROI 3D区域内的抓取点。
- 使用场景
防止抓取超出ROI区域,导致机械臂与目标物体发生碰撞。
- 参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
| ROI3D类型区 | 通常为”工作区”;“抓取区”是比”工作区”更小的ROI区域,可将抓取点限制在比”工作区”更小的ROI区域内,避免一些碰撞情况的发生。 | 工作区 |
- 示例
如下图所示,当ROI3D区域和ROI2D为a区域时,对应的抓取点在右上角



当ROI3D区域和ROI2D更改为b区域时,原抓取点不在ROI区域内,因此去除该抓取点,新的抓取点在b区域内生成。



3.2.5 【新】过滤工件与夹具存在碰撞的抓取点(含原函数)
【新】过滤工件与夹具存在碰撞的抓取点

- 功能介绍
夹具和抓取点附近点云的碰撞检测,与夹具接触的点云数量超过抓取碰撞阈值,则判定该工件的抓取点存在碰撞风险
- 使用场景
需要判断夹具和抓取工件附近点云的碰撞检测
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 碰撞阈值 | 碰撞距离阈值,场景与夹具表面距离小于该阈值,会被认为发生碰撞,阈值越大越严格,单位:mm | 7 | 1-1000 |
| 碰撞点云采样 | 碰撞点云采样大小:数值越大节拍越快,数值越小节拍越慢;限定范围:[0.0,1000.0],默认值:5.0,单位:mm | 5 | 1 - 1000 |
| 保存夹具碰撞检测的可视化数据 | 保存夹具与抓取工件碰撞检测的可视化数据 | 不勾选 | 勾选/不勾选 |
过滤工件与夹具存在碰撞的抓取点

- 功能介绍
夹具和抓取点附近点云的碰撞检测,与夹具接触的点云数量超过抓取碰撞阈值,则判定该工件的抓取点存在碰撞风险
- 使用场景
需要判断夹具和抓取工件附近点云的碰撞检测
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 抓取碰撞阈值 | 夹具在抓取点附近可以包含的最大点云数量,比如20,即夹具包含场景点云的数量超过20就认为是碰撞 | 20 | 0-10000 |
| 碰撞点云采样(m) | 碰撞区域点云降采样的大小。值越大,检测速度越快,但精度会降低。适用场景:高节拍需求场景 | 0.002 | 0.0001 - 0.5000 |
| 保存夹具碰撞检测的可视化数据 | 保存夹具与抓取工件碰撞检测的可视化数据 | 不勾选 | 勾选/不勾选 |
| 导入夹具模型 | 在文件夹中选择并导入用于碰撞检测的夹具模型 | / | / |
**夹具应该做简化处理,面片数量少于500**

3.2.6【大师】保留实例抓取点中姿态值最大/小的一个抓取点,过滤剩余抓取点

- 功能介绍
将姿态转换到指定坐标系,根据指定排序轴的数值对姿态进行排序,保留最大或最小的姿态,适用于圆柱工件保留顶部或底部的抓取点
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 |
|---|---|---|---|
| 指定坐标系 | 选择要将姿态转换到哪个坐标系进行处理 | ROI坐标系 | ROI坐标系/相机坐标系 |
| 指定排序轴 | 选择要按姿态的哪个轴的数值进行排序 | Z轴 | X/Y/Z轴 |
| 取最小值 | 勾选则保留排序轴最小值的姿态,不勾选则保留排序轴最大值的姿态 | 不勾选 | / |
- 示例
3.2.7 【大师】过滤与前N次抓取点相近的抓取点

- 功能介绍
若当前抓取点与缓存中的任一抓取点的变化量在阈值范围内,则该抓取点将被过滤
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 抓取点变化量上限(+) | ||||
| X(mm) | X坐标上限 | 2 | [0, 10000000] | mm |
| Y(mm) | Y坐标上限 | 2 | [0, 10000000] | mm |
| Z(mm) | Z坐标上限 | 2 | [0, 10000000] | mm |
| RX(°) | RX旋转量上限 | 1 | [0, 180] | 角度° |
| RY(°) | RY旋转量下限 | 1 | [0, 180] | 角度° |
| RZ(°) | RZ旋转量下限 | 1 | [0, 180] | 角度° |
| 抓取点变化量下限(-) | ||||
| X(mm) | X坐标下限 | 2 | [0, 10000000] | mm |
| Y(mm) | Y坐标下限 | 2 | [0, 10000000] | mm |
| Z(mm) | Z坐标下限 | 2 | [0, 10000000] | mm |
| RX(°) | RX旋转量下限 | 1 | [0, 180] | 角度° |
| RY(°) | RY旋转量下限 | 1 | [0, 180] | 角度° |
| RZ(°) | RZ旋转量下限 | 1 | [0, 180] | 角度° |
| 抓取点缓存个数 | 缓存的抓取点个数,当前抓取点比较完成后将实时增加到缓存中 | 5 | [1, 100] | / |
3.2.8 【大师】过滤与前N次工件姿态相近的工件姿态

- 功能介绍
若当前工件姿态与缓存中的任一工件姿态的变化量在阈值范围内,则该工件姿态将被过滤;当工件姿态判定相似,工件上的所有抓取点都会被过滤
- 参数说明
| 参数 | 说明 | 默认值 | 参数范围 | 单位 |
|---|---|---|---|---|
| 工件姿态变化量上限(+) | ||||
| X(mm) | X坐标上限 | 2 | [0, 10000000] | mm |
| Y(mm) | Y坐标上限 | 2 | [0, 10000000] | mm |
| Z(mm) | Z坐标上限 | 2 | [0, 10000000] | mm |
| RX(°) | RX旋转量上限 | 1 | [0, 180] | 角度° |
| RY(°) | RY旋转量下限 | 1 | [0, 180] | 角度° |
| RZ(°) | RZ旋转量下限 | 1 | [0, 180] | 角度° |
| 工件姿态变化量下限(-) | ||||
| X(mm) | X坐标下限 | 2 | [0, 10000000] | mm |
| Y(mm) | Y坐标下限 | 2 | [0, 10000000] | mm |
| Z(mm) | Z坐标下限 | 2 | [0, 10000000] | mm |
| RX(°) | RX旋转量下限 | 1 | [0, 180] | 角度° |
| RY(°) | RY旋转量下限 | 1 | [0, 180] | 角度° |
| RZ(°) | RZ旋转量下限 | 1 | [0, 180] | 角度° |
| 工件姿态缓存个数 | 缓存的视觉工件姿态个数,当前工件姿态比较完成后将实时增加到缓存中 | 5 | [1, 100] | / |
3.2.9 【大师】过滤抓取坐标上下限外的抓取点

- 功能介绍
保留某个参考抓取点指定范围内的其他抓取点,过滤异常抓取点。
- 使用场景
防止机械臂误抓取,保证抓取准确度。
该函数不适用于拆垛场景
- 参数说明
| 参数 | 说明 | 默认值 | 单位 |
|---|---|---|---|
| 参考抓取坐标 | |||
| X(mm) | 参考抓取点的X坐标 | 0 | mm |
| Y(mm) | 参考抓取点的Y坐标 | 0 | mm |
| Z(mm) | 参考抓取点的Z坐标 | 0 | mm |
| RX(°) | 参考抓取点的RX旋转量 | 0 | 角度 |
| RY(°) | 参考抓取点的RY旋转量 | 0 | 角度 |
| RZ(°) | 参考抓取点的RZ旋转量 | 0 | 角度 |
| 抓取坐标上限(+) | |||
| X(mm) | X坐标的上限, 例如参考抓取点的X坐标为100, 上限设置为10, 则允许的范围为: [100-下限, 110] | 10000000,表示不设限。 | mm |
| Y(mm) | Y坐标的上限, 例如参考抓取点的Y坐标为100, 上限设置为10, 则允许的范围为: [100-下限, 110] | 10000000 | mm |
| Z(mm) | Z坐标的上限, 例如参考抓取点的Z坐标为100, 上限设置为10, 则允许的范围为: [100-下限, 110] | 10000000 | mm |
| RX(°) | RX旋转量的上限, 例如参考抓取点的RX旋转量为180, 上限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -170], [180-下限, 180]] | 180,表示不设限。 | 角度° |
| RY(°) | RY旋转量的上限, 例如参考抓取点的RY旋转量为180, 上限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -170], [180-下限, 180]] | 180 | 角度° |
| RZ(°) | RZ旋转量的上限, 例如参考抓取点的RZ旋转量为180, 上限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -170], [180-下限, 180]] | 180 | 角度° |
| 抓取坐标下限(-) | |||
| X(mm) | X坐标的下限, 例如参考抓取点的X坐标为100, 下限设置为10, 则允许的范围为: [100-下限值, 110] | 10000000 | mm |
| Y(mm) | Y坐标的下限, 例如参考抓取点的Y坐标为100, 下限设置为10, 则允许的范围为: [100-下限, 110] | 10000000 | mm |
| Z(mm) | Z坐标的下限, 例如参考抓取点的Z坐标为100, 下限设置为10, 则允许的范围为: [100-下限, 110] | 10000000 | mm |
| RX(°) | RX旋转量的下限, 例如参考抓取点的RX旋转量为180, 下限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -180+上限], [170, 180]] | 180,表示不设限。 | 角度° |
| RY(°) | RY旋转量的下限, 例如参考抓取点的RY旋转量为180, 下限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -180+上限], [170, 180]] | 180 | 角度° |
| RZ(°) | RZ旋转量的下限, 例如参考抓取点的RZ旋转量为180, 下限设置为10, 则允许的范围为(默认度数会跳变): [[-180, -180+上限], [170, 180]] | 180 | 角度° |
3.3 抓取点排序

3.3.1 基准坐标系

- 功能介绍
为所有实例设定一个统一的坐标系,进行实例的分组排序
- 使用场景
拆垛场景、无序抓取场景、有序上下料场景通用
使用坐标相关的策略应当先设置基准坐标系
- 参数说明
| 参数 | 说明 | 图示 |
|---|---|---|
| 相机坐标系 | 坐标系原点在物体上方,Z轴正方向朝下;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| ROI坐标系 | 坐标系原点大致在垛中心,Z轴正方向朝上;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| 机械臂坐标系 | 坐标系原点在机械臂自身,Z轴正方向一般朝上;XYZ取值是物体中心点在该坐标系下的值 | ![]() |
| 像素坐标系 | 坐标系原点在RGB图的左顶点,是二维平面坐标系;X、Y取值是bbox识别框的x值、bbox识别框的y值,Z是0 | ![]() |
3.3.2 通用抓取策略

- 参数说明
| 参数 | 说明 |
|---|---|
| 策略 | 选择依据哪个值进行分组排序以及如何排序,包括抓取点中心X/Y/Z坐标值从大到小/从小到大(mm)、从抓取点XY坐标轴向的中间到两侧/从两侧到中间(mm)可叠加多条,按顺序依次执行 |
| 分组步长 | 依据选择的策略、按照步长将抓取点划分为若干组,分组步长即两组抓取点之间的间隔 |
| 提取前几组 | 分组排序之后,需要保留多少组实例 |
| 策略名* | 说明 | 分组步长 | 提取前几组 | |
|---|---|---|---|---|
| 默认值 | 取值范围 | 默认值 | ||
| 抓取点中心X/Y/Z坐标值从大到小/从小到大(mm) | 使用抓取点中心的X/Y/Z坐标值进行分组排序 | 200.000 | [0, 10000000] | 10000 |
| 从抓取点XY坐标轴向的中间到两侧/从两侧到中间(mm) | 使用抓取点中心的X/Y坐标值,按照 “中间到两侧” 或 “两侧到中间” 的方向进行分组排序 | 200.000 | [0, 10000000] | 10000 |
3.3.3 纸箱组合策略
为解决传统单抓拆垛效率低、适配场景有限的问题,PickWiz 在拆垛场景下新增纸箱组合策略,满足单次抓取多个工件的需求,支持 "尺寸一致的纸箱""矩形吸盘" 核心场景,覆盖更多实际项目场景。

3.3.3.1 多抓运行配置
(1)在麻袋单拆或者纸箱单拆场景中,开启视觉计算配置-视觉计算加速。
(2)在抓取点排序模块下,选择纸箱组合策略;

(3)策略选择:可选默认组合策略/沿纸箱位姿某一轴方向组合,两种找到可组合数量最多的纸箱的方式。

默认组合策略:沿一个纸箱位姿的X轴和Y轴方向,找到可组合数量最多的纸箱。
**沿纸箱某一位姿轴方向组合: **沿一个纸箱位姿的X轴或Y轴方向,找到可组合数量最多的纸箱,适用于纸箱摆放排成一条直线的场景。使用该策略时,需选择纸箱组合的方向,即抓取姿态X轴和抓取姿态Y轴。

注意:
组合方向与正负轴向有关,同轴同方向可进行组合,且组合后整堆纸箱的方向与组合前单个纸箱的方向一致,因此纸箱组合前需确保所有要组合的纸箱摆成同一个方向,统一要组合的纸箱坐标为同轴向。
(4)组合条件:确定哪些纸箱能组合、最多能组合多少个。
单行最大组合数:一行最多有几个纸箱组合,默认为2。
最大组合行数:最多几行纸箱进行组合,默认为1。
最大间距(mm):要组合的纸箱在 "组合方向" 上不能离太远, 在组合方向(轴方向)上,两个相邻纸箱或不同行纸箱之间的间距小于此值时,可组合为一组,默认为10。
- 举例:纸箱沿抓取姿态X轴找最多纸箱时,两个相邻纸箱在抓取姿态X轴方向的间距是 8 毫米(≤10),能组合;要是间距为 12 毫米(>10),则不能组合。
最大错位距离(mm):要组合的纸箱在 "垂直于组合方向" 上不能离太远,在垂直于组合方向(轴方向)上,两个相邻纸箱或不同行纸箱之间的错位距离小于此值时,可组合为一组,默认为10。
- 举例:纸箱沿抓取姿态X轴找最多纸箱时,两个相邻纸箱在抓取姿态Y轴方向上错开 了8 毫米(≤10),能组合;要是错开了 15 毫米(>10),就对不齐,没法一起抓。
最大角度偏差(°):要组合的纸箱,应当差不多朝同一个方向,在组合方向(轴方向)上,纸箱的旋转偏差角度小于此值时,可组合为一组,默认为10。
- 举例:如果有个纸箱与组合方向相比旋转了 5°,只要没超过 10°,就能组合;如果旋转了 15°(>10),朝向差太多,机器人抓的时候会歪,就不能组合。

3.3.3.2 机器人配置
(1)在机器人配置界面,在视觉计算通信报文-机器人发送至PickWiz指令-视觉检测发送指令中新增占位符:单行最大组合数与最大组合行数,如下图所示;

(2)在视觉计算通信报文-PickWiz发送至机器人指令-抓取相关信息-抓取工件时返回信息中,新增工件长度、工件宽度与工件朝向。

机器人配置完成后,点击运行按钮。
3.3.3.3 查看多抓运行结果
(1)在3D匹配视窗,将鼠标悬浮在实例上,可以查看到纸箱组合后单个实例的组合抓取信息,包含2D识别结果,抓取位姿和实例组合信息。

在可视化视窗点击右上角的设置按钮可以设置组合实例信息的显示情况。

鼠标右键点击实例,可以查看到单个实例的组合抓取信息和工件信息

(2)在2D识别视窗,可根据菜单栏相关组合按钮,查看组合后ID, 组合掩膜和组合包围盒














