一. 边界提取的常规思路
3D鞋实物图如下所示:
鞋点胶的点云边界提取的常规思路:
(一) 法向量(很重要,本文基础内容暂时不做介绍,后面补充)
(二) 切平面(简单但适用性不强,本文使用该方式供初学者一个学习思路):
- 读取点云模型数据;
read_object_model_3d(Operator)
- 分割出鞋子点云集合;
connection_object_model_3d(Operator)
select_object_model_3d(Operator)
- 仿射变换到长轴与X平行的位置(基准位置);
- 定义切平面,求鞋的
3D
点云集合跟切平面的点云交集; - 把交集的点云映射成
2D
的XLD
轮廓,求每段XLD
轮廓的起点和终点坐标; - 把得到的每段XLD轮廓的起点和终点坐标再映射转换成
3D
点云坐标; - 显示鞋的点云集合以及鞋的外边界点云集合;
- 若要配合机器人点胶的话,还需将外边界点云坐标排序、连接并指定机器人的运行轨迹,将外边界点云坐标转换成机器人坐标;
二. 3D鞋点胶的点云边界提取具体步骤
2.1、读取点云模型数据
如图所示:
read_object_model_3d ('./2020-01-14-16832.om3', 'm', [], [], ObjectModel3D, Status) dev_close_window () dev_open_window (0, 0, 512, 512, 'black', WindowHandle) visualize_object_model_3d (WindowHandle, ObjectModel3D, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
交互式显示函数 visualize_object_model_3d ,相比较于显示函数 disp_object_model_3d
的不同:
visualize_object_model_3d(Operator) — Interactively display 3D object models. 交互式显示,可以点击旋转,缩放。 disp_object_model_3d(Operator) — Display 3D object models. 只是展示。
2.2、去噪(得到鞋子的点云数据集合)
如图所示:
* ['distance_3d', 1]点云距离在1mm之内的算作一个点云连通域集合 connection_object_model_3d (ObjectModel3D, 'distance_3d', 1, ObjectModel3DConnected) * 获取每个点云连通域集合包含的点个数(因为鞋子点云连通域包含的点云个数最多) get_object_model_3d_params (ObjectModel3DConnected, 'num_points', GenParamValue) * 筛选点数(获取点云最多的集合,即鞋子点云),2e7 = 2*10的七次方 select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 20000, 2e7, ObjectModel3DSelected) visualize_object_model_3d (WindowHandle, ObjectModel3DSelected, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut1)
Halcon
例程中关于点云筛选的两种不同方式【select_points_object_model_3d_by_density.hdev】和【select_object_model_3d.hdev】:
2.3、将鞋点云集合变换到原始坐标系下主轴-x y z(基准)
所谓主轴:用最小外接长方体将点云囊括,获取其长轴、短轴以及高度。
而鞋点云的长轴、短轴和高度相对于坐标系X、Y、Z轴,会有一个姿态关系,这里称作 pose
。
姿态Pose
由7
个参数,包括3个平移(x、y、z)
+3个旋转(Rx、Ry、Rz)
+1个旋转类型(旋转顺序
)—— 常规有0(先绕X
旋转,在绕Y
旋转,最后绕Z
旋转)和1(和0相反)。
我们目的就是将鞋点云变换到标准位置,让其长轴与X轴重合,短轴与Y轴重合,高度与Z轴重合,好处是切平面与鞋面垂直,与轴平行,沿轴切,不会乱。整齐。
鞋子点云在刚体/仿射变换后的姿态所图所示:
* 'principal_axes'代表求主轴,并输出当前主轴姿态Pose moments_object_model_3d (ObjectModel3DSelected, 'principal_axes', Pose) * 姿态翻转 pose_invert (Pose, PoseInvert) * 点云模型刚体变换 rigid_trans_object_model_3d (ObjectModel3DSelected, PoseInvert, ObjectModel3DStandard) visualize_object_model_3d (WindowHandle, ObjectModel3DStandard, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
moments_object_model_3d(Operator)
:确定主轴X轴方向,这样做的原因是为了方便沿着x轴做切平面的时候,方便分割。这就是确定长轴的原因。
标准位置的好处:做切平面与鞋面垂直。与轴平行,沿轴切,不会乱。整齐。
其参数二含义:
'central_moment_2_points'
:计算点云模型的二阶矩(方差、即点云X、Y、Z
的波动情况),输出X、Y、Z
的方差,XY、XZ、YZ
协方差(关联性)。'mean_points'
:计算点云模型的一阶矩(均值、即点云X、Y、Z
的均值大小),输出X、Y、Z
的均值。'principal_axes'
:计算点云模型的主轴,输出姿态Pose
(3个平移(x、y、z)
+3个旋转(Rx、Ry、Rz)
+`1个旋转类型)。
其中刚体变换
等价于仿射变换
的如下方式:
* 点云模型仿射变换 pose_to_hom_mat3d (PoseInvert, HomMat3D) affine_trans_object_model_3d (ObjectModel3DSelected, HomMat3D, ObjectModel3DAffineTrans)
2.5、求鞋点云最小外接box(选做)
若觉得鞋点云主轴(长、宽和高轴)与坐标轴(X、Y和Z轴)基本重合,也可忽略这一步。
因为鞋长轴可能并不是直线,所以2.4得到的鞋点云长轴可能不一定和X
轴重合,这时我们可以求鞋点云最小外接box
继续调整转正鞋长轴和X
轴之间的位置关系,使鞋点云转正。
1)三角曲面重建
求鞋点云的最小外接box
之前可以做一个三角网格曲面重建(点集→曲面),使得点云更加圆滑。
1、三角曲面重建:将无序点云三角化。内部算子实际使用的是贪婪投影三角法:将有向点云投影到一个二维平面内,做平面内三角化, 最后根据平面内的三角拓扑关系,生成一个三角网格曲面模型。此外,若使用膨胀腐蚀(参数3,本文默认[]),就会使网格变大变小。 2、优化项:三角曲面重建需要点时间。如果点数太多的话,可以简化点云(暂不做演示)。 3、局限性:三角曲面重建比较适用于表面连续比较光滑的曲面,或者点云密度比较均匀的情况,速度会比较快。否则会出现因点云不连续导致的奇形怪状,甚至悬空点等。
* 对鞋点云进行三角曲面重建(点数多会有点耗时) triangulate_object_model_3d (ObjectModel3DStandard, 'greedy', [], [], TriangulatedObjectModel3D, Information) visualize_object_model_3d (WindowHandle, TriangulatedObjectModel3D, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
2)求鞋点云最小外接box
* Pose0:box的主轴(长、宽和高轴)相对于坐标轴(X、Y和Z轴)的姿态 * Length1、Length2、Length3:box长宽高 smallest_bounding_box_object_model_3d (TriangulatedObjectModel3D, 'oriented', Pose0,Length1, Length2, Length3) * 求出来的box主轴长轴并不一定和X轴完全重合,下面需要调整 pose_invert (Pose0, Pose1) //姿态翻转 rigid_trans_object_model_3d (TriangulatedObjectModel3D, Pose1, TransObjectModel3D) visualize_object_model_3d (WindowHandle, TransObjectModel3D, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
截止到这里,我们就算将第三步的【将鞋点云集合变换到原始坐标系下主轴-x y z(基准)】完成了,下面将鞋子和盒子一起显示一下:
smallest_bounding_box_object_model_3d (TransObjectModel3D, 'oriented', PoseBox,Length1, Length2, Length3) gen_box_object_model_3d (PoseBox, Length1, Length2, Length3, PoseBoxOriented) *['green','gray',0.5,'true'] 0.5代表盒子透明度,1表示不透明,0表示完全透明 visualize_object_model_3d (WindowHandle,[TransObjectModel3D,PoseBoxOriented], [],[], ['color_0','color_1','alpha_1','disp_pose'], ['green','gray',0.5,'true'],'RectBOX', [], [], Pose)
最小外接Box
的计算可参考Halcon
示例【smallest_bounding_box_object_model_3d.hdev
】:
2.6、在Box中做切平面(切X轴),求与鞋子轮廓的交线
在X
轴,做切平面,每次沿X轴移动指定均匀长度。切平面与鞋子点云有一个点云交集,把点云交集映射到二维平面上形成一个XLD
二维的轮廓交线,交线的两端(起点、终点)就是我们需要的轮廓点。
切平面沿X
轴方向一段段的对鞋子点云做切割,需要引入一个循环问题:
参考Halcon示例:inspect_3d_surface_intersections.hdev
本小结主要由几个步骤组成:
- 求出切平面与鞋点云的
XLD
轮廓;(知识点1:intersect_plane_object_model_3d
求交平面 ) - 做姿态转换以及内参1:1转换(图像尺寸和实际尺寸1:1,这样比较好转换),将
XLD
轮廓投影变换到二维XLD
进行处理;(知识点2:三维到二维XLD
轮廓转换) - 对轮廓行方向点坐标进行排序,获得最大最小点(轮廓边缘);(知识点3:排序求出二维
XLD
轮廓的起点、终点坐标) - 将所有的二维
XLD
轮廓起点、终点坐标再转换成三维点云坐标(行坐标对应Y
坐标,列坐标对应Z
坐标);(知识点4:二维XLD
轮廓转换到三维点云,因为比例是1:1,姿态直接转就行) - 显示;
代码如下:
* 5.在box中做切平面,求与轮廓的交线 objectsOut:=[] Index_S:=[] Index_E:=[] color_S:=[] color_E:=[] colorsOut:=[] colorvaluesOut:=[] all_x:=[] all_y:=[] * 做切平面与鞋子的点云相交 for index1:=0 to 45 by 1 * 求交线点云(做切平面) CutPlanePose := PoseBox CutPlanePose[0]:=PoseBox[0]-Length1/2+(index1)*4+3 //pose中的第五个参数值x:第一个为沿着x轴的平移,保证循环时可以沿着x轴正方向重复以一个固定距离做切割 * //需要从Box最左边开始做切割,所以:PoseBox[0]-Length1/2,每次循环沿着x轴往右移动4个单位 CutPlanePose[3]:=0 CutPlanePose[4]:=90 //pose中的第五个参数值Ry:绕着y轴(绿色)旋转90度,让平面立起来做切割 CutPlanePose[5]:=0 gen_plane_object_model_3d (CutPlanePose, [-1,-1,1,1] * 90, [-1,1,1,-1] * 90, IntersectionPlane) * visualize_object_model_3d (WindowHandle, [TransObjectModel3D,IntersectionPlane], [], Pose, ['disp_pose'], ['true'], [], [], [], PoseOut1) intersect_plane_object_model_3d (TransObjectModel3D, CutPlanePose, ObjectModel3DIntersection) * visualize_object_model_3d (WindowHandle, ObjectModel3DIntersection, [], [], [], [], [], [], [], PoseOut2) * 为了求交线轮廓的起点和终点,需要变换到二维XLD(跟相机标定将世界坐标系转换到图像像素坐标系一个原理,需要内外参) * 图像中的点云都是物理点坐标,都是世界坐标系下的姿态(图像相对于世界坐标系下的姿态),所以需要翻转姿态,转换成图像坐标系下的姿态 pose_invert (CutPlanePose, PoseInvert) * 确定投影平面在前面 get_object_model_3d_params (ObjectModel3DIntersection, 'diameter_axis_aligned_bounding_box', Diameter) PoseInvert[2] := PoseInvert[2] +Diameter * 用平行于投影平面的相机(1:1的比例) Scale:=1 CamParam := [0,0,1.0 / Scale,1.0 / Scale,0,0,500,500] //人为构建相机内参(焦距、畸变系数、图像中心点、宽高) project_object_model_3d (IntersectionXld, ObjectModel3DIntersection, CamParam, PoseInvert, 'data', 'lines') * 转换过来之后就存二维图像处理XLD轮廓,将轮廓的所有上下起点和终点求出来就是我们需要的鞋子边界了 count_obj (IntersectionXld, Number) Rows:=[] Columns:=[] Row:=[] Column:=[] * 可能会有断开的轮廓,这里遍历一下获取所有轮廓点 for I:=1 to Number by 1 select_obj (IntersectionXld, EdgeContour, I) get_contour_xld(EdgeContour, Row,Column) Rows:=[Rows,Row] Columns:=[Columns,Column] endfor * 点按照由上到下排序 tuple_sort_index (Rows, Indices) tuple_length(Rows,Length) OrderRow:=[] OrderColumn:=[] if(Length>=1) for Row_Index:=0 to Length-1 by 1 OrderRow:=[OrderRow,Rows[Indices[Row_Index]]] OrderColumn:=[OrderColumn,Columns[[Row_Index]]] endfor endif gen_contour_polygon_xld (Intersection, OrderRow, OrderColumn) * 求最大和最小点(行方向) tuple_sort_index (OrderRow, Indices) tuple_length(OrderRow,Length) * 起点(xld) StartRow:=OrderRow[Indices[0]] StartColumn:=OrderColumn[Indices[0]] * 终点(xld) EndRow:=OrderRow[Indices[Length-1]] EndColumn:=OrderColumn[Indices[Length-1]] gen_cross_contour_xld(StartXP, StartRow, StartColumn, 6, 0.795296) gen_cross_contour_xld(EndXP, EndRow,EndColumn, 6, 0.795296) dev_display(Intersection) * 转成点云的坐标 StartPose:=[CutPlanePose[0],StartRow,-StartColumn, 0,0,0,0] EndPose :=[CutPlanePose[0],EndRow,-EndColumn, 0,0,0,0] gen_sphere_object_model_3d (StartPose, 2, StartPoint) gen_sphere_object_model_3d (EndPose , 2, EndPoint ) * visualize_object_model_3d (WindowHandle, [StartPoint,EndPoint], [], [], [], [], [], [], [], PoseOut4) * 所有对点的边界点集合 objectsOut := [objectsOut,StartPoint] objectsOut := [objectsOut,EndPoint ] * 显示时的颜色 Index_S:= 0+index1*2 Index_E:= 0+index1*2+1 color_S:='color_'+Index_S color_E:='color_'+Index_E colorsOut := [colorsOut,color_S] colorsOut := [colorsOut,color_E] colorvaluesOut := [colorvaluesOut,'blue'] colorvaluesOut := [colorvaluesOut,'blue'] all_x:=[all_x,CutPlanePose[0]] all_y:=[all_y,StartRow] all_x:=[all_x,CutPlanePose[0]] all_y:=[all_y,EndRow] endfor *显示外边界模型点云 visualize_object_model_3d (WindowHandle,[objectsOut,TransObjectModel3D], [],[],[colorsOut],[colorvaluesOut], [], '', [], Pose) *2二维显示 dev_open_window (0, 0, 512, 512, 'black', WindowHandle1) dev_set_color('red') gen_cross_contour_xld(Start,all_x,all_y, 3, 0.885398)
效果展示:
注意:当前的算法稳定性不好,而且有些边缘处理粗糙,点云转换成2D进行处理虽然简单一些,但有时候会造成一些精度损失,本文仅供给初学者提供一个解决该问题的思路。最好的做法我们应该更多的关注点云本身的特征进行处理,比如基于法向量求解、凸包法形成曲面的变化、根据点云之间的邻域关系等方式。
点云模型以及完整代码:https://pan.baidu.com/s/15nXvEC8HbzvGM3_DuyCEbg 提取码:wznw
下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。 |