相似案例:Halcon三维测量(3):鞋底涂胶+边缘提取
Halcon学习方法强调:从案例当中学习:最重要是思路和算子的用法、为我们所用。不要机械式套用。
视觉:
视觉项目思路:(算法+软件框架)
1、需求分析:2D还是3D的?
2、项目评估仿真:
3、合同
4、方案设计:(硬件方案、软件方案)点云直接买现成的,因为精度更高。
5、精度和速度,考虑硬件方案:
6、概要设计
7、详细设计
8、编码
9、测试调试
10、交付
11、维护
鞋点胶边界检测思路:
1、点的法向量重建:K邻域、体素。法向量非常重要:
2、当前教程:(不是通用、不太灵活的方法)(初学者)
1)获取点云切平面
2)切平面与点云的交线
3)交线在点云上的起点、终点求出来
4)起点、终点就是外边界。
起点、终点如何求?
将交平面转化为一个2D图像求XLD轮廓。XLD上的起点、终点可求。
激光三角成像:线轮廓+移动
第一步:读入点云(鞋)的文件。
第二步:分割出鞋子的点云集合。(去除噪声等。)
第三步:仿射变换到与长轴和短轴平行的位置。(3D一样可以仿射变换):方便切平面的定义。
第四步:定义切平面,求鞋的3D点云集合跟切平面的点云交集。
第五步:把交集的点云坐标映射成2D的XLD轮廓,求每段轮廓起点、终点坐标。
第六步:把得到的每段XLD的起点、终点坐标又映射转换成3D点云坐标。
第七步:显示鞋的点云集合以及鞋的外边界的点云集合。
dev_close_window ()
dev_update_off ()
dev_open_window (0, 0, 512, 512, 'black', WindowHandle)
* 1、读取点云文件
read_object_model_3d ('./2.om3', 'mm', [], [], ObjectModel3D, Status)
visualize_object_model_3d (WindowHandle, ObjectModel3D, [], [], [], [], [], [], [], PoseOut)
对visualize_object_model_3d参数的详解:visualize_object_model_3d (窗口句柄, 3D模型, [内参], [外参], [参数名], [参数名对应的值], [标题信息], [模型信息], [窗口提示信息:比如旋转、放大、缩小], PoseOut)
双击【变量窗口】中的ObjectModel3D变量可以打开【三维对象模型检查】。
点击勾选【Display Models】,会打开【显示三维对象模型】,在显示窗口左下方状态栏中可以显示模型的姿态信息。(7个数字:偏移X,偏移Y,偏移Z,偏转x,偏转y,偏转z,旋转类型(旋转顺序));
在【显示三维对象模型】窗口上面工具栏可以【插入代码】:
插入代码如下:
CamParam := ['area_scan_division',0.06,0,8.5e-006,8.5e-006,468,161,936,323]
Pose := [-899.05,-1306.69,750249,3.85575,13.3872,36.4233,0]
GenParamName := ['colored','alpha','color_0']
GenParamValue := [12,0.9,'cyan']
disp_object_model_3d (200000, ObjectModel3D, CamParam, Pose, GenParamName, GenParamValue)
这里的这个显示函数为disp_object_model_3d,相比于visualize_object_model_3d的交互式显示不同。看F1帮助文档的描述:
visualize_object_model_3d — Interactively display 3D object models
disp_object_model_3d — Display 3D object models.
一个交互式显示,可以点击旋转,缩放,一个只是展示。
插入的代码:
halcon中可以用create_pose — Create a 3D pose.来创建一个姿态。
在【显示三维对象模型】中:【Base Parameter】有点云的中心点、直径、外接矩形框。
在视频中作者特别强调了:【案例教程中】【方法】【三维对象处理】中【moments_object_model_3d.hdev】这个教程。显示方法。
前面英文就说如果点云有山脊状走势,那么山脊更适合做X轴方向,我们原来的坐标系就需要转到以山脊走向为X轴的坐标系统下。
选坐标轴的一个原则是:沿着数据方差最大的方向作为主轴。
In this example the color encodes the value of the z-component of the data。对z轴高度信息颜色编码。
x-axis in red, the y-axis in green and the z-axis in blue.
第32行和第33行:显示参数列表。
GenParamName := ['lut','color_attrib','light_position','disp_pose','alpha']
GenParamValue := ['color1','coord_z','0.0 0.0 -0.3 1.0','true',0.9]
根据z轴坐标的高度不同,颜色不同。disp_pose显示坐标系:true。
第39行和第40行:创建姿态。
create_pose (-0.0005, -0.0005, 0.04, 280, 0, 20, 'Rp+T', 'gba', 'point', DispPose1)
create_pose (0, -0.0005, 0.04, 280, 0, 20, 'Rp+T', 'gba', 'point', DispPose2)
此外,在第四步中会介绍提到的两种3D点云模型刚体变换:一、放射变换:affine_trans_object_model_3d和二、刚体变换:rigid_trans_object_model_3d。
通过connection_object_model_3d基于某种特征(三角网格(需要计算生成三角网格)、距离、)来将连通域断开,以此来实现:区域切割。独立集合对象。
关于connection_object_model_3d算子,作者推荐核心案例教程:【方法】【三维对象处理】【connection_object_model_3d.hdev】
通过分割完成后,用get_object_model_3d_params来计算每个连通域的点云个数。
connection_object_model_3d (ObjectModel3D, 'distance_3d', 1000, ObjectModel3DConnected)
get_object_model_3d_params (ObjectModel3DConnected, 'num_points', GenParamValue)
关于点云筛选的两个案例:【select_points_object_model_3d_by_density.hdev】和【select_object_model_3d.hdev】代表了点云筛选的两种方法。
*3、滤波筛选:
select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 120000, 200000, ObjectModel3DSelected)
visualize_object_model_3d (WindowHandle, ObjectModel3DSelected[1], [], PoseOut, ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut2)
所谓主轴:用最小外接长方体将点云囊括,长轴与X轴重合,短轴与Y轴重合,高度与Z轴重合。重合移动肯定会有一个姿态关系:pose。
moments_object_model_3d (ObjectModel3DSelected, 'principal_axes', Pose)第二个参数意思求主轴,第三个参数输出pose姿态。moments_object_model_3d 用来求矩,还有两个参数分别是一阶矩,二阶矩。
然后将姿态反变换:求姿态的反变换:这样变换就可以将长轴作为X轴,变换就涉及刚体变换(仿射变换)。变换就需要用到矩阵和姿态。
点云模型刚体变换
rigid_trans_object_model_3d:刚体变换算子。既可以平移,也可以旋转。
* 4、将鞋点云集合变换到原始坐标系下主轴-X、Y、Z轴
* 第二个参数意思求主轴,第三个参数,输出pose姿态。
moments_object_model_3d (ObjectModel3DSelected[1], 'principal_axes',PoseOut1)
pose_invert (PoseOut1, PoseInvert)
* 这样变换就可以将长轴作为X轴,变换就涉及刚体变换(仿射变换)。变换就需要用到矩阵和姿态。
* rigid_trans_object_model_3d (ObjectModel3DSelected[1], PoseInvert, ObjectModel3DRigidTrans)
* visualize_object_model_3d (WindowHandle, ObjectModel3DRigidTrans, [], PoseInvert, [], [], [], [], [], PoseOut)
用仿射变换一样可以达到同样的效果。 放射变换需要的是一个矩阵。
将姿态变换为矩阵:pose_to_hom_mat3d
仿射变换:affine_trans_object_model_3d
* 用仿射变换也可以实现这个变换:
pose_to_hom_mat3d (PoseInvert, HomMat3D) \\ 将姿态变换为矩阵
affine_trans_object_model_3d (ObjectModel3DSelected[1], HomMat3D, ObjectModel3DAffineTrans)
visualize_object_model_3d (WindowHandle, ObjectModel3DAffineTrans, [], PoseInvert, ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut3)
moments_object_model_3d 确定主轴X轴方向,这样做的原因是为了方便沿着x轴做切平面的时候,方便分割。这就是确定长轴的原因。
标准位置的好处:做切平面与鞋面垂直。与轴平行,沿轴切,不会乱。整齐。
推荐案例【smallest_bounding_box_object_model_3d.hdev】中的smallest_bounding_box_object_model_3d算子完成。
求box之前可以做一个三角曲面重建:使得点云更加圆滑。
三角曲面重建。将无序点云三角化。内部算子实际使用的是贪婪投影三角法。将有向点云投影到一个二维平面内,平面内三角化。根据平面内的三角拓扑关系,生成一个三角网格曲面模型。如果使用膨胀腐蚀,就会使网格变大变小。
*三角曲面重建。将无序点云三角化。内部算子实际使用的是贪婪投影三角法。将有向点云投影到一个二维平面内,平面内三角化。根据平面内的三角拓扑关系,生成一个三角网格曲面模型。如果使用膨胀腐蚀,就会使网格变大变小。
triangulate_object_model_3d (ObjectModel3DAffineTrans, 'greedy', [], [], TriangulatedObjectModel3D, Information)
* 三角曲面重建需要点时间。如果点数太多的话,可以简化点云:
* 三角曲面重建比较适用于表面连续比较光滑的曲面,或者点云密度比较均匀的情况。速度会比较快。否则会出现因点云不连续导致的奇形怪状。甚至悬空点。
visualize_object_model_3d (WindowHandle, TriangulatedObjectModel3D, [], PoseOut3, ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut4)
* 三角曲面重建需要点时间。如果点数太多的话,可以简化点云:
* 三角曲面重建比较适用于表面连续比较光滑的曲面,或者点云密度比较均匀的情况。速度会比较快。否则会出现因点云不连续导致的奇形怪状。甚至悬空点。
求最小外接box:
* 最小外接box
smallest_bounding_box_object_model_3d (TriangulatedObjectModel3D, 'oriented', Pose, Length1, Length2, Length3)
gen_box_object_model_3d (Pose, Length1, Length2, Length3, ObjectModel3D1)
pose_invert (Pose, PoseInvert1)
rigid_trans_object_model_3d (TriangulatedObjectModel3D, PoseInvert1, ObjectModel3DRigidTrans)
visualize_object_model_3d (WindowHandle, ObjectModel3DRigidTrans, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut5)
* 联合显示:
visualize_object_model_3d (WindowHandle, [ObjectModel3DRigidTrans,ObjectModel3D1], [], [], ['color_0','color_1','alpha_1', 'disp_pose'], ['green','gray',0.5,'true'], 'RectBOX', [], [], PoseOut2)
特别注意联合显示时的操作:要注意将外面图像设置透明度。
在X轴,做切平面,每次沿X轴移动指定均匀长度。切平面与点云有一个点云交集,把点云交集映射到二维平面上形成一个XLD二维的轮廓交线,交线的两端(起点、终点)就是我们需要的轮廓点。
做切平面需要给出切平面的姿态。
一段段的做切割:肯定要引入一个循环问题:
推荐案例:inspect_3d_surface_intersections.hdev,
*5、求切平面交线点云
for Index1 := 0 to 50 by 1
CutPlanePose := Pose
CutPlanePose[0]:=Pose[0]-Length1/2+(Index1)*40+3 // pose中的参数值:第一个为沿着x轴的平移。
CutPlanePose[3]:=0
CutPlanePose[4]:=90 // pose第五个值为绕着y轴旋转90度,立起来。
CutPlanePose[5]:=0
* 产生平面,去看一下切平面的情况。第一个参数是姿态pose,第二、第三个参数是限定大小。
gen_plane_object_model_3d (CutPlanePose, [-1, -1, 1, 1]*1, [-1, 1, 1, -1]*1,IntersectionPlane)
* visualize_object_model_3d (WindowHandle, [ObjectModel3DRigidTrans,IntersectionPlane], [], Pose, ['color_0','color_1','alpha_1', 'disp_pose'], ['green','gray',0.5,'true'], [], [], [], PoseOut)
visualize_object_model_3d (WindowHandle, [aim_object,IntersectionPlane], [], Pose, ['disp_pose'], ['true'], [], [], [], PoseOut)
* 得到XLD交线。
intersect_plane_object_model_3d (IntersectionPlane, CutPlanePose, ObjectModel3DIntersection)
visualize_object_model_3d (WindowHandle, ObjectModel3DIntersection, [], [], [], [], [], [], [], PoseOut7)
* 然后求起点、终点。先变换到2维XLD。
pose_invert (CutPlanePose,PoseInvert2)
*确定投影平面在前面
get_object_model_3d_params (ObjectModel3DIntersection, 'diameter_axis_aligned_bounding_box', Diameter)
PoseInvert2[2]:=PoseInvert2[2]+Diameter // 切平面往上抬升。沿着z轴往上升高一个直径大小。凸显效果哦。
// 基于长轴x轴切割,投影在yoz投影面上。
* 用平行于投影平面的相机(1:1的比例)
Scale:=1
CamParam:=[0, 0, 1.0 /Scale, 1.0/Scale, 0, 0, 500, 500]
project_object_model_3d (IntersectionXld, ObjectModel3DIntersection, CamParam, PoseInvert2, '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, Row1, Colum) // 获得XLD关键点坐标。
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, Row[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]]
dev_display (Intersection)
gen_cross_contour_xld (StartXP, StartRow, StartColumn, 6, 0.795296)
gen_cross_contour_xld (EndXP, EndRow, EndColumn, 6, 0.785398)
* 转成点云的坐标
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)
* dev_display (Intersection)
visualize_object_model_3d (WindowHandle, [StartPoint, EndPoint], [], [], [], [], [], [], [], PoseOut6)
* 所有对点的边界点集合
objectsOut:=[objectOut, StartPoint]
objectsOut:=[objectOut, 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,ObjectModel3DRigidTrans], [], [], ['colorsOut','color_88'], ['colorvaluesOut','red'], [], [], [], PoseOut2)
* 二维显示
dev_open_window (0, 0, 512, 512, 'black', WindowHandle1)
dev_set_color ('red')
gen_cross_contour_xld (Start, all_x, all_y, 3, 0.785398)
基于当前的算法稳定不好,而且有些边缘处理粗糙。最好的做法是基于法向量求解。