halcon 3D包含以下几个模块:
- 3D Matching(3D匹配)
- 3D Object Model(3D模型)
- 3D Reconstruction(3D重构)
- 3D Transformations(3D转换)
1. 3D Matching
1.1 3D Box3D盒查找器
不需要对象模型,只需要盒子尺寸,适用于箱子抓取的场景。
算子 | 说明 |
---|---|
find_box_3d | 可输入箱体的形状参数,来查找点云中对应的箱体。 |
1.2 Shape-Based基于形状的匹配
利用CAD模型,在2D图像上进行物体的空间定位,需要进行相机标定拿到内参。
正如前文《手眼标定》所说:
基于形状匹配是指在二维图像中搜索三维CAD模型实例。通过操作符find_shape_model_3d
计算并返回物体空间姿态。这个方法虽然拿不到点云,但它最终还是能计算出物体的空间位姿,挺牛逼的,就是性能、精度都很差。
它是通过一种基于视图的方法来计算的。通过虚拟球面视图的方式,降低算法复杂度:
假设物体置于球心,并对球心进行观测,创建多个位置的虚拟相机,我们可以通过将物体投影到每一个虚拟相机的像平面上获取相机的拍到的视图,注册为模板,在实际匹配时,会在每个虚拟相机的位置进行匹配计算,找到最佳位姿。注意还需计算不同半径下的虚拟相机。
创建模板的时候,利用CAD三维信息,进行多虚拟相机投影的计算,计算量很大。在后面使用时,需要对数以千计的视图进行匹配计算,很难满足实际的应用需求,还是推荐直接上3D相机获取到点云信息后进行匹配。
因为用的不多,这里不再介绍。
1.3 Surface-Based基于表面的匹配
可通过点云或CAD模型来进行空间定位。
算子 | 说明 |
---|---|
clear_surface_matching_result | 清除找到的结果内存。 |
clear_surface_model | 清除模型内存 |
create_surface_model | 创建一个surface的模型,来为后面的匹配做准备。该算子输入 xyz_to_object_model_3d或者read_object_model_3d得到的对象。这个算子非常重要,它可以决定后续的匹配是基于表面、边缘还是视角,不同的方法对应不同的要求,比如如果要计算2D/3D的边缘匹配结果来提升精度,则必须要求模型的点法向量向内,并且模型必须包含三角形或多边形网格 |
find_surface_model | 查找最佳匹配位姿。分了三个步骤:首先近似匹配,找到大概的位姿;第二步稀疏匹配,提升匹配精度;第三步稠密匹配,找到个更精确的位姿。 |
1.4 Deformable Surface-Based基于表面的形变匹配
用于空间物体扭曲变形下的匹配。
1.5 3D Gripping Point Detection3D夹持点检测
是一种基于深度学习的方法。
2. 3D Object Model
2.1 Creation创建
算子 | 说明 |
---|---|
clear_object_model_3d | 释放3D对象模型的内存 |
copy_object_model_3d | 拷贝一个3D模型 |
deserialize_object_model_3d | 对序列化的3D模型进行反序列化。 |
gen_box_object_model_3d | 创建一个盒子3D对象模型 |
gen_cylinder_object_model_3d | 创建一个圆柱3D对象模型 |
gen_empty_object_model_3d | 创建一个空的3D对象模型 |
gen_object_model_3d_from_points | 从3D点云数据来创建3D对象模型 |
gen_plane_object_model_3d | 创建一个平面的3D对象模型 |
gen_sphere_object_model_3d | 通过位姿创建一个球形的3D对象模型 |
gen_sphere_object_model_3d_center | 通过(xyz)点创建一个球形的3D对象模型 |
read_object_model_3d | 从一个CAD文件中读取一个3D对象模型 |
remove_object_model_3d_attrib | 移除3D对象模型指定属性信息,例如点向量、三角形等 |
remove_object_model_3d_attrib_mod | 移除3D对象模型一些属性信息 |
serialize_object_model_3d | 序列化一个3D对象模型 |
set_object_model_3d_attrib | 给3D对象模型设置一些属性信息 |
set_object_model_3d_attrib_mod | 给3D对象模型设置一些属性信息 |
read_object_model_3d | 从一个CAD文件中读取一个3D对象模型 |
union_object_model_3d | 将多个3D对象模型合并为一个 |
write_object_model_3d | 写3D对象模型到文件 |
2.2 Features特征
算子 | 说明 |
---|---|
area_object_model_3d | 计算3D对象模型所有面的面积 ,次模型必须具有面,比如box模型 |
distance_object_model_3d | 计算两个点云间的距离 |
get_object_model_3d_params | 获取3D对象模型的属性 |
max_diameter_object_model_3d | 计算3D对象模型的最大凸包直径 |
moments_object_model_3d | 计算3D对象模型的二阶平均矩或中心矩 |
select_object_model_3d | 根据一些全局特征从一堆3D对象模型中选择指定特征的模型,这些特征比如平均x坐标、矩信息,面数、点数等.注意和后面点云分割select_points_object_model_3d的区别。下面是从多个对象模型中选择出的。 |
smallest_bounding_box_object_model_3d | 最小外接方框。 |
smallest_sphere_object_model_3d | 最小内接球 |
volume_object_model_3d_relative_to_plane | 获取3D对象模型相对于一个平面的体积 |
2.3 Segmentation分割
算子 | 说明 |
---|---|
segment_object_model_3d | 将一个3D点集按照指定的特征分割成特征相似的子集,并进行基本形状拟合(可选),这些特征例如点的最大法向量差、子集所需最小点数等。如果选择基本形状拟合,则可拟合成基本形状(圆柱、球、箱)。如果不选择拟合,则生成的指示一堆子集,后续也可通过fit_primitives_object_model_3d来拟合为基本形状。最终可输出多个拟合的模型。 |
fit_primitives_object_model_3d | 将3D对象模型拟合为基本的形状,可拟合成圆柱、球、箱,一旦拟合成功,就可以拿到这些形状的基本信息,比如球半径等 |
select_points_object_model_3d | 根据一些特征分割出想要的点云,这些特征比如指定的x坐标、z坐标、邻域距离范围等等。下图按z范围去掉了底面。注意该它的对象模型还是1个。connection之后会产生多个模型,这时候就得配合select_object_model_3d |
reduce_object_model_3d_by_view | 将3D对象模型的点投影到相机的图像平面坐标系中,此时变为了2D图像,然后在2D图像上给定一个region,只保留该region内的3D对象模型。注意需要提供相机内参,不常用。 |
2.4 Transformations
算子 | 说明 |
---|---|
affine_trans_object_model_3d | 进行3D对象模型的仿射变换。 |
connection_object_model_3d | 按照给定的特征条件,进行点云筛选,比如点之间的距离、法向量夹角等等。注意会构建多个点云模型下图是按照点之间的距离进行的筛选。。 |
convex_hull_object_model_3d | 创建给定3D对象模型的凸包模型。 |
edges_object_model_3d | 查找3D对象模型的边缘轮廓。 |
fuse_object_model_3d | 将3D对象模型进行曲面拟合。 |
intersect_plane_object_model_3d | 计算一个平面和3D模型的界面轮廓。 |
object_model_3d_to_xyz | 3D对象模型转为xyz数值,可选参数如果是’from_xyz_map’,仅在 xyz_to_object_model_3d算子转的模型后使用。 |
xyz_to_object_model_3d | xyz转为3D对象模型。 |
prepare_object_model_3d | 调用其他算子如create_shape_model_3d/ segment_object_model_3d/distance_object_model_3d等等接口的点云预操作,如果后续要多次调用相关算子,可通过预处理运算来提速,减少重复的操作。当然该算子也可以不使用。 |
project_object_model_3d | 将3D对象模型先转换到相机坐标系下,再根据内参投影到像平面上。 |
projective_trans_object_model_3d | 用一个使用vector_to_hom_mat3d.创建的转换矩阵(刚性/仿射/相似/投影矩阵),对3D对象模型进行空间变换。 |
rigid_trans_object_model_3d | 只对3D对象模型进行刚性变换。 |
register_object_model_3d_global | 对多个相似3D对象模型进行点云融合重构,这个不需要进行标定,挺有意思。下图为第一个模型:后面是多个模型通过重叠部分迭代计算,融合在一起,可以看到模型更加完整了: |
register_object_model_3d_pair | 通过比对两个3D对象模型的重叠程度,计算位姿变换关系。 |
affine_trans_object_model_3d | 将3D对象模型的一个视角下的位姿模型转到图片格式。 |
sample_object_model_3d | 对3D对象模型进行采样,注意原始点云的点可能会被修改,比如’accurate’模式会遍历所有点,在指定半径的球内寻找其他点,如果有,则保留球内所有点的重心。 |
smooth_object_model_3d | 点云平滑滤波,包含两种方式:基于3D空间曲面多项式拟合的mls算法和基于2D深度图的图像滤波算法。后者速度更快。 |
surface_normals_object_model_3d | 为3D对象模型生成三角面的表面。 |
3. 3D重构
包含双目、深度聚焦、多2d相机视角重建、线激光重构四种方法。这里就不罗列了。
4. Transformations
4.1 Pose
pose位姿数组,描述的是刚性三维变换。形式为 [ T x , T y , T z , R x , R y , R z , C o d e ] [Tx,Ty,Tz,Rx,Ry,Rz,Code] [Tx,Ty,Tz,Rx,Ry,Rz,Code],分别对应了平移、旋转还有类型编码。可通过create_pose算子创建。
create_pose( : : TransX, TransY, TransZ, RotX, RotY, RotZ, OrderOfTransform, OrderOfRotation, ViewOfTransform : Pose)
OrderOfTransform可以是Rp+T’和Rp-T’,这里R是旋转,p是点,T是平移。
OrderOfRotation可选’gba’‘abg’‘rodriguez’,其中gba表示RxRyRz的顺序,按新旧轴可以选择从左往右还是从右往左读,后面会有说明。abg对应RzRyRx。这里rodriguez表示罗德里格旋转公式,此时3个旋转量对应了向量的方向,向量的长度定义为了
ViewOfTransform可选为’point’和 ‘coordinate_system’,后者将旋转的角度取了反!(名称容易误导)
。
标准的位姿变换(Rp+T,gba,point),推荐用这种:
注意这里是Ht*Hr,顺序不能错了。
对于一个点的转换公式:
不标准的Rp-T,这里T取了负值,并且RT矩阵反过来了!:
类型编码如下:
算子 | 说明 |
---|---|
convert_pose_type | 改变Pose的类型,即改为不同code下的表达方式。 |
**get_pose_type ** | 获取pose对应type类型。 |
create_pose | 创建Pose。 |
deserialize_pose | 反序列化Pose。 |
serialize_pose | 序列化Pose。 |
dual_quat_to_pose | 对偶四元数转位姿。 |
pose_to_dual_quat | Pose转对偶四元数。 |
pose_to_quat | Pose转四元数。 |
quat_to_pose | 四元数转Pose。 |
get_circle_pose | 计算标定板上亚像素圆的位姿。 |
get_rectangle_pose | 计算亚像素矩形框的位姿。 |
pose_average | 计算一堆Pose旋转和平移的带权平均值。 |
pose_compose | 两个Pose转为矩阵后相乘。 |
proj_hom_mat2d_to_pose | 函数根据描述2D世界坐标(单位为米)和2D图像坐标之间关系的单应性矩阵Homography计算姿态。 |
write_pose | 写Pose到文件。 |
read_pose | 从文件读取Pose。 |
set_origin_pose | 按照自身坐标系,平移该Pose的原点位置。 |
vector_to_pose | PNP算法,PnP算法通过一组3D点和它们对应的2D图像点来估计相机的姿态。 |
4.3 3D Transformations
homMat3D变换矩阵形式
算子 | 说明 |
---|---|
affine_trans_point_3d | 对一个3D点进行仿射变换,比如通过 hom_mat3d_identity, hom_mat3d_scale, hom_mat3d_rotate, hom_mat3d_translate和pose_to_hom_mat3d创建的变换矩阵。 |
deserialize_hom_mat3d | 将一个SerializedItemHandle反序列化为hommat3D矩阵。 |
serialize_hom_mat3d | 将HomMat3D序列化 |
hom_mat3d_compose | 实现两个homMat3D矩阵相乘。 |
hom_mat3d_transpose | 转置 |
point_pluecker_line_to_hom_mat3d | 普吕克坐标下转换 |
pose_to_hom_mat3d | pose位姿数组转HomMat3D矩阵 |
vector_to_hom_mat3d | 根据两个坐标系下的3D点数组,拟合homMat3D矩阵 |
hom_mat3d_determinant | 创建一个3D齐次变换阵 |
hom_mat3d_invert | 求逆 |
hom_mat3d_rotate | 绕一个固定点(Px,Py,Pz)进行旋转,注意R在homMat3D矩阵左边!绕旧轴旋转 |
hom_mat3d_rotate_local | 绕HomMat3D自身进行转,R乘到后面了!绕新轴旋转 |
hom_mat3d_scale | 绕一个固定点,乘以一个缩放系数矩阵等价于(左边): |
hom_mat3d_scale_local | homMat3D直接进行缩放,右边: |
hom_mat3d_to_pose | HomMat3D矩阵转pose位姿数组 |
hom_mat3d_translate | HomMat3D左乘一个平移矩阵,按旧轴平移 |
hom_mat3d_translate_local | HomMat3D右乘平移矩阵,按新轴平移,最终结果: |
projective_trans_hom_point_3d | 对一个3D点待Pw进行投影变换 |
projective_trans_hom_point_3d | 对一个3D点进行投影变换 |
4.4 新/旧轴总结
对于hom_mat3d_rotate/hom_mat3d_rotate_local等旧轴和新轴的区别,这里用2d下的类似算子进行验证,这样比较直观。
对图像先旋转30度,再沿x轴平移100个像素。
- 套用hom_mat2d_rotate_local 和hom_mat2d_translate_local,因为是对新轴进行的,所以平移是按照旋转之后的坐标系沿x平移。
- 套用hom_mat2d_rotate和hom_mat2d_translate,因为是对旧轴进行的,所以平移还是按照之前的图像坐标系平移。
上代码,local模式:
read_image (Image, 'forest_road')
hom_mat2d_identity (HomMat2DIdentity) //这里先创建默认齐次矩阵,在这个基础上添加平移或旋转量
hom_mat2d_rotate_local (HomMat2DIdentity, rad(30), HomMat2DRotate_local)
affine_trans_image (Image, ImageAffineTrans, HomMat2DRotate_local, 'nearest_neighbor', 'false')
hom_mat2d_translate_local(HomMat2DRotate_local, 100, 0, HomMat2DTranslate_local)
affine_trans_image (Image, ImageAffineTrans, HomMat2DTranslate_local, 'nearest_neighbor', 'false')
可以看到,原图像被旋转了30度,左上角的点坐标变为了(86,50),由三角形斜边长度为50/sin30=100,正好和平移的100匹配上了。
非local模式,这里固定点是(0,0),和上面的local默认旋转轴中心一致,方便对比:
hom_mat2d_rotate (HomMat2DIdentity, rad(30), 0, 0, HomMat2DRotate)
affine_trans_image (Image, ImageAffineTrans, HomMat2DRotate, 'nearest_neighbor', 'false')
hom_mat2d_translate (HomMat2DRotate, 100, 0, HomMat2DTranslate)
affine_trans_image (Image, ImageAffineTrans, HomMat2DTranslate, 'nearest_neighbor', 'false')
可以看出,旋转之后,由沿垂直x方向平移了100,也就是原始的图像坐标系为“旧轴”。
还有如下结论:
- 想对已知的点或者图像进行空间变换,首先使用hom_mat2d_identity创建一个齐次基本矩阵,然后将平移和旋转往上加
- 当旋转中心一样时,连续旋转一个角度,比如旋转1次再旋转1次,local和非local结果都是一样的。带上平移就不一样了。
- 相机外参的位姿Pose中的,旋转和平移用的是旧轴还是新轴的方式呢?根据halcon给出的create_pose关于Rgba帮助文档:
If you start from the right, the rotations are always performed relative to the global (i.e., fixed or “old”) coordinate system. Thus, can be read as follows: First rotate around the z-axis, then around the “old” y-axis, and finally around the “old” x-axis. In contrast, if you read from the left to the right, the rotations are performed relative to the local (i.e., “new”) coordinate system. Then, corresponds to the following: First rotate around the x-axis, the around the “new” y-axis, and finally around the “new(est)” z-axis.
如果您从右边开始读取,旋转始终相对于全局(即固定或“旧”)坐标系执行。因此,可以按照以下方式解读:首先绕z轴旋转,然后绕“旧” y轴旋转,最后绕“旧” x轴旋转。相反,如果您从左到右阅读,则旋转相对于局部(即“新”)坐标系执行。然后,对应于以下操作:首先绕x轴旋转,然后绕“新” y轴旋转,最后绕“最新的”z轴旋转。
从右到左读取对应于以下算子调用顺序:
hom_mat3d_identity (HomMat3DIdent)
hom_mat3d_rotate (HomMat3DIdent, RotZ, 'z', 0, 0, 0, HomMat3DRotZ)
hom_mat3d_rotate (HomMat3DRotZ, RotY, 'y', 0, 0, 0, HomMat3DRotYZ)
hom_mat3d_rotate (HomMat3DRotYZ, RotX, 'x', 0, 0, 0, HomMat3DXYZ)
相反,从左向右读取对应于以下算子调用顺序:
hom_mat3d_identity (HomMat3DIdent)
hom_mat3d_rotate_local (HomMat3DIdent, RotX, 'x', HomMat3DRotX)
hom_mat3d_rotate_local (HomMat3DRotX, RotY, 'y', HomMat3DRotXY)
hom_mat3d_rotate_local (HomMat3DRotXY, RotZ, 'z', HomMat3DXYZ)
也就是说,在使用Pose和点或图像进行变换时,因为点都是乘在变换矩阵左边,直接计算即可,不必区分新旧轴,只有对homMat3D变换时,才需要考虑新旧轴。但Pose下的阅读顺序,可以帮助我们“观察”点或图像是如何变过去的,有助于拿到一个Pose,肉眼大概分析出是否正确。