基于线结构光模型的工件孔洞检查
文章目录
- 1,目的。
- 2,难点。
- 3,处理流程图。
- 3,程序分析。
- 3.1,建立参考物3D对象。
- 3.2,创建表面匹配模型。
- 3.3,匹配被测对象获取位姿,被测对象与参考对像对齐。
- 3.4,分析获取表示异常的点云。
- 3.5,对异常点云进行分类定性。
- 3.5.1,对点云进行平面拟合,分析(重点)。
- 3.5.2,确定表示孔洞缺失、多余、偏大、偏小的点云。
- 3.5.3,点云对称性分析,确定表示孔洞错位的点云
- 3.5.4,表示未确定异常原因的点云。
- 3.6,显示。
- 4,完整程序。
1,目的。
从提供的视差图中分析工件的孔洞是否存在缺失,多余,偏大,偏小,错位等异常。
如下所示:
2,难点。
- 拟合出通过点云所有点的
2D Region
,并通过对2D Region
进行相应2D计算判断出被拟合点云表示的是孔洞缺失、多余、偏大、偏小哪一种情况。 - 异常点云对称性分析,查找出对称的点云,从而确定哪些点云表示孔洞错位。
3,处理流程图。
3,程序分析。
3.1,建立参考物3D对象。
* ------------------1,创建用作参照物的3D对象* 1.1,读取描述3D对象轮廓的视差图。
* Create a sheet-of-light model to collect the disparities
* and set the calibration information for the reconstruction.
* tif文件:标准的位图文件,部分设备生成的深度图,点云数据采用此格式存储
read_image (Disparity, 'sheet_of_light/injection_mold_01_disparity')
get_image_size (Disparity, Width, Height)
* 处理的光条数量(即扫描次数)
NumProfiles := Height
gen_rectangle1 (Rectangle, 0, 0, Height - 1, Width - 1)* 1.2,创建线结构光三维测量模型
create_sheet_of_light_model (Rectangle, [], [], SheetOfLightModelID)
* 相机内参
CameraParam := [0.016,-1250.34,4.65114e-006,4.65e-006,623.43,515.23,1280,1024]
* 相机外参
CameraPose := [0.0013952482819,-0.0025835234583,-0.53764418409,337.80940798,359.84685912,358.46883832,0]
* 光平面在世界坐标系的位姿
LightplanePose := [-0.047567497224,-0.73091602504,0.0016815300297,271.32238394,0.40428170089,359.66058824,0]
* 移动时位姿变化/步(该位姿在世界坐标系)
MovementPose := [-2.5e-005,-1.5e-4,-1.25e-006,0.0,0.0,0.0,0]
* 定义校准模式,决定输出的坐标性类型
set_sheet_of_light_param (SheetOfLightModelID, 'calibration', 'xyz')
* 定义三维坐标输出的单位,m表示米
set_sheet_of_light_param (SheetOfLightModelID, 'scale', 'm')
* 设置相机的内参
set_sheet_of_light_param (SheetOfLightModelID, 'camera_parameter', CameraParam)
* 设置相机的姿态
set_sheet_of_light_param (SheetOfLightModelID, 'camera_pose', CameraPose)
* 光平面位姿(激光平面)
set_sheet_of_light_param (SheetOfLightModelID, 'lightplane_pose', LightplanePose)*定义被测物体在连续采集过程中的运动轨迹(如旋转平台或平移导轨的位姿变换,该位姿是相对于世界坐标系)
set_sheet_of_light_param (SheetOfLightModelID, 'movement_pose', MovementPose)
*
* Init display
dev_update_off ()
dev_close_window ()
dev_open_window_fit_image (Disparity, 0, 0, 500, 480, WindowHandle1)get_window_extents (WindowHandle1, Row, Column, Width1, Height1)
dev_open_window_fit_image (Disparity, 0, Width1 + 8, 500, 480, WindowHandle2)
set_display_font (WindowHandle1, 16, 'mono', 'true', 'false')
set_display_font (WindowHandle2, 16, 'mono', 'true', 'false')
dev_set_window (WindowHandle1)
*
* 1.3,定义异常类型对应的文本和颜色
ErrorTypesText := ['unknown','additional hole','missing hole','hole too large','hole too small','wrong position','unknown (missing in test object)','unknown (missing in reference object)']
ErrorTypesColor := ['white','magenta','red','blue','yellow','cyan','white','white']
*
* Set the disparity image of the reference object
* 1.4,将视差图图像设置到 线结构光测量模型中
set_profile_sheet_of_light (Disparity, SheetOfLightModelID, [])
* * 1.5,获取标准参照物的3D对象
get_sheet_of_light_result_object_model_3d (SheetOfLightModelID, ReferenceOrig)
* 显示原始图
visualize_object_model_3d (WindowHandle1, ReferenceOrig, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut2)
* 去除参照物的背景
select_points_object_model_3d (ReferenceOrig, 'point_coord_z', 0.01, 1, Reference)
* 1.6,显示参考对象(标准物)
visualize_object_model_3d (WindowHandle2, Reference, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut2)
* Clean up
clear_object_model_3d (ReferenceOrig)
如图所示:参考对象
3.2,创建表面匹配模型。
* -----------------------2,创建基于表面的 3D 匹配模型,采样距离与物体直径的比例为:0.02
*-------------------------
create_surface_model (Reference, 0.02, [], [], SurfaceModelID)
*
* Display reference object
dev_clear_window ()
get_window_extents (WindowHandle1, Row2, Column2, Width2, Height2)
dev_set_part (0, 0, Height2 - 1, Width2 - 1)
Introduction := '这个例子展示了一个校准的'
Introduction[1] := '用于表面比较的线性结构光.'
Introduction[2] := '在这个例子中展示:'
Introduction[3] := '检查孔的位置,数量,大小是否有异常.'
disp_message (WindowHandle1, Introduction, 'window', 12, 12, 'white', 'false')* 2.1,创建标准 3D 模型显示位姿
create_pose (0.0244755,0.0707748,2.10414,-0.0,0.0,-0.0, 'Rp+T', 'gba', 'point', PoseDisplay)
Title := '参考对象(标准物)'
Instructions[0] := 'Rotate: Left button'
Instructions[1] := 'Zoom: Shift + left button'
Instructions[2] := 'Move: Ctrl + left button'* 2.2,显示参考对象
visualize_object_model_3d (WindowHandle2, Reference, [], PoseDisplay, ['lut','intensity'], ['color1','coord_z'], Title, [], Instructions, PoseOut)
*
* Display the reference object in the second window for comparison with the
* test object
dev_set_window (WindowHandle2)
get_window_extents (WindowHandle2, Row2, Column2, Width2, Height2)
dev_set_part (0, 0, Height2 - 1, Width2 - 1)
dev_clear_window ()
disp_object_model_3d (WindowHandle2, Reference, [], PoseDisplay, ['lut','intensity'], ['color1','coord_z'])
disp_message (WindowHandle2, 'Reference object', 'window', 12, 12, 'black', 'true')
*
3.3,匹配被测对象获取位姿,被测对象与参考对像对齐。
*---------------------------3,查找被测 3D 对象
* Main loop
NumScenes := 6
MaxDist := 0.001
for SceneIndex := 1 to NumScenes by 1dev_set_window (WindowHandle1)dev_clear_window ()disp_message (WindowHandle1, 'Match test object with reference object ...', 'window', 12, 12, 'black', 'true')* 3.1, 读取被测的视差图像read_image (Disparity, 'sheet_of_light/injection_mold_' + SceneIndex$'02d' + '_disparity')* 3.2,复位线结构光模型中的视差图像. 其他的设置保持不变reset_sheet_of_light_model (SheetOfLightModelID)* 3.3,设置需要测量轮廓的视差图set_profile_sheet_of_light (Disparity, SheetOfLightModelID, [])* 3.4,使用线性结构光测量模型中解析出 待检测的 3D 对象get_sheet_of_light_result_object_model_3d (SheetOfLightModelID, TestObjectOrig)* 去除背景select_points_object_model_3d (TestObjectOrig, 'point_coord_z', 0.01, 1, TestObject)clear_object_model_3d (TestObjectOrig)*3.5,显示被测的3D对象点云visualize_object_model_3d (WindowHandle1, TestObject, [], [], ['lut','intensity'], ['color1','coord_z'], [], [], [], PoseOut3)disp_message (WindowHandle1, 'Match test object ' + SceneIndex + ' with reference object...', 'window', 12, 12, 'black', 'true')wait_seconds (1)* 3.6,检测标准 3D 模型 相对于 被测 3D 模型的位姿 find_surface_model (SurfaceModelID, TestObject, 0.02, 0.5, 0, 'false', 'pose_ref_sub_sampling', 1, Pose, Score, NotUsed)* 被测 3D 点云模型相对于标准 3D 模型的位姿pose_invert (Pose, PosesInvert)* 3.7,刚体变化检测对象到参考对象rigid_trans_object_model_3d (TestObject, PosesInvert, TestObjectTrans)* 3.8,显示测试对象对齐后状态disp_object_model_3d (WindowHandle1,TestObjectTrans, [], PoseDisplay, ['lut','intensity','disp_pose'], ['color1','coord_z','true'])
如图所示,对齐前:
对齐后:
3.4,分析获取表示异常的点云。
*---------------------4,分析被测对象与参考 3D 对象的点云差异* 4.1,----情况1:被测 3D 对象点云缺失(有额外的孔洞)----* 4.1.1,测量3D参考对象与被测3D对象的各点的距离distance_object_model_3d (Reference, TestObjectTrans, [], 0.0, [], [])* 4.1.2,筛选出被测 3D 对象缺少的点云select_points_object_model_3d (Reference, '&distance', MaxDist, 1, ObjectModel3DThresholded)ToClear := ObjectModel3DThresholded* 4.1.3,显示被测 3D 对象相对参照 3D 对象缺失的点云disp_object_model_3d (WindowHandle1, ObjectModel3DThresholded, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'])* 4.1.4,处理 被测 3D 对象相对参照 3D 对象缺失的点云get_object_model_3d_params (ObjectModel3DThresholded, 'num_points', NumPoints)if (NumPoints > 0)* Calculate connected components, the distance threshold should* be greater than the distance between two scan lines (> MovementPose[1])connection_object_model_3d (ObjectModel3DThresholded, 'distance_3d', 0.001, ObjectModel3DConnected)ToClear := [ToClear,ObjectModel3DConnected]* 计算测试对象上缺少的点云(保留主要部分,去掉噪声)select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 200, 1000000, SurfacePointsMissingInTestObject)elseSurfacePointsMissingInTestObject := []endif* Clean upclear_object_model_3d (ToClear)* *----4.2,情况2:被测 3D 对象点云多余(缺少孔洞)----* 4.2.1,反过来测量被测3D 对象与 参考3D 对象的各点距离,求出被测3D 对象多出的点云distance_object_model_3d (TestObjectTrans, Reference, [], 0.0, [], [])* 4.2.2,筛选出被测 3D 对象多出的点云块select_points_object_model_3d (TestObjectTrans, '&distance', MaxDist, 1, ObjectModel3DThresholded)ToClear := ObjectModel3DThresholdedtry* Calculate connected components, the distance threshold should* be greater than the distance between two scan lines (> MovementPose[1])connection_object_model_3d (ObjectModel3DThresholded, 'distance_3d', 0.001, ObjectModel3DConnected)ToClear := [ToClear,ObjectModel3DConnected]* ,处理 被测 3D 对象相对参照 3D 对象多出的点云select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 200, 1000000, SurfacePointsMissingInReference)catch (Exception)SurfacePointsMissingInReference := []endtry* Clean upclear_object_model_3d (ToClear)
表示异常的点云: 可见被测对象缺少孔洞
3.5,对异常点云进行分类定性。
3.5.1,对点云进行平面拟合,分析(重点)。
*---------------------------------5,对被测物的异常点云块进行处理* Collect and classify errors* 5.1,被测3D 对象缺少,多出的点云部分集合ErrorObjects := [SurfacePointsMissingInTestObject,SurfacePointsMissingInReference]* 异常点云块提示信息ErrorSources := [gen_tuple_const(|SurfacePointsMissingInTestObject|,'missing in test'),gen_tuple_const(|SurfacePointsMissingInReference|,'missing in reference')]ErrorTypes := []gen_empty_obj (ErrorPatterns)* 异常的点云块数量NumErrors := |ErrorObjects|* 对异常部分点云块进行处理if (NumErrors > 0)for ErrorIndex := 0 to NumErrors - 1 by 1* 异常的点云ErrorObject := ErrorObjects[ErrorIndex]* 异常点云的文本信息ErrorSource := ErrorSources[ErrorIndex]* * 5.2,为异常的点云中拟合出一个通过这些点的Region* a plane through the points and then projecting the points into this plane.create_region_from_error_points (Region, ErrorObject)* * 5.3,分析所得到的二维拟合图形的形状 (the region)fill_up (Region, RegionFillUp)difference (RegionFillUp, Region, RegionDifference)circularity (Region, CircularityRegDilation)circularity (RegionFillUp, CircularityRegFillUp)circularity (RegionDifference, CircularityRegDifference)* area_center (Region, AreaRegDilation, Row1, Column1)area_center (RegionFillUp, AreaRegFillUp, Row1, Column1)area_center (RegionDifference, AreaRegDifference, Row1, Column1)*
3.5.2,确定表示孔洞缺失、多余、偏大、偏小的点云。
* 5.4,给异常点云块分类,确定对应的异常类型MinCircularity := 0.7if (CircularityRegDilation > MinCircularity and real(AreaRegDifference) / AreaRegDilation < 0.1)if (ErrorSource == 'missing in test')* 5.4.1,原因1:被测3D 对象孔洞多余ErrorType := 1else* 5.4.2,原因2:被测3D 对象孔洞缺少ErrorType := 2endifelseif (CircularityRegFillUp > MinCircularity and CircularityRegDifference > MinCircularity and real(AreaRegDifference) / AreaRegDilation > 0.1)if (ErrorSource == 'missing in test')* 5.4.3,原因3:被测 3D 对象 孔洞偏大ErrorType := 3else* 5.4.4,原因4:被测 3D 对象 孔洞偏小ErrorType := 4endifelse* 5.4.0,原因0:未知原因ErrorType := 0endif*ErrorType:0 原因未知,1被测 3D 对象孔洞多余,2 被测 3D 对象 缺少孔洞* 3:被测 3D 对象孔洞偏大, 4 被测 3D 对象孔洞偏小* 5 wrong position* 6 unknown (missing in test object)* 7 unknown (missing in reference object)ErrorTypes[ErrorIndex] := ErrorType* 连接由异常点云块拟合出来的异常区域concat_obj (ErrorPatterns, Region, ErrorPatterns)endfor
3.5.3,点云对称性分析,确定表示孔洞错位的点云
* 5.5,对表示异常的点云进行分类* 5.5.1,类型1:表示缺失,多余,偏大,偏小类型的点云对象MaskKnown := ErrorTypes [!=] 0* 注意如果MaskKnown的数组长度与ErrorObjects数组长度不一致将无法执行select_mask* select_mask执行的结果为MaskKnown成员为true则保留ErrorObjects对应位置的对象ErrorObjectsKnown := select_mask(ErrorObjects,MaskKnown)ErrorTypesKnown := select_mask(ErrorTypes,MaskKnown)* 5.5.2,类型2:表示孔洞错位的点云对* 5.5.2.1,被测3D对象缺少的点云(这些点云未明确错误类型)MaskUnknownMIT := ErrorTypes [==] 0 and ErrorSources [==] 'missing in test'ErrorObjectsUnknownMIT := select_mask(ErrorObjects,MaskUnknownMIT)* 5.5.2.2,被测3D对象多余的点云(这些未明确错误类型)MaskUnknownMIR := ErrorTypes [==] 0 and ErrorSources [==] 'missing in reference'ErrorObjectsUnknownMIR := select_mask(ErrorObjects,MaskUnknownMIR)* ErrorObjectsWrongPos := []ErrorTypesWrongPos := []* MergedMIT := gen_tuple_const(|ErrorObjectsUnknownMIT|,false)MergedMIR := gen_tuple_const(|ErrorObjectsUnknownMIR|,false)* 对未明确异常原因的点云块进一步进行分类if (|ErrorObjectsUnknownMIT| > 0 and |ErrorObjectsUnknownMIR| > 0)for IndexMIT := 0 to |ErrorObjectsUnknownMIT| - 1 by 1if (MergedMIT[IndexMIT])continueendiffor IndexMIR := 0 to |ErrorObjectsUnknownMIR| - 1 by 1if (MergedMIR[IndexMIR])continueendif* Test if there are symmetric error objects* The parameter MaxHoleDiameter is used to speed up the search and to* make it more robust, because we are only interested in symmetric* error objects that are not further apart from each other than* the size of a hole.*判断是否有对称的错误对象,如果是则认为是孔洞位置错误*参数MaxHoleDiameter是用来加快搜索和*使它更健壮,因为我们只对对称感兴趣错误对象之间的距离小于洞的大小MaxHoleDiameter := 0.02*表示孔洞位置偏离的点云块对*=======*======= 判断被测物缺少的点云块与多余的点云块的对称性,如果对称性高则说明两个点云对称,即可判定孔洞错位test_symmetry_object_model_3d (ErrorObjectsUnknownMIT[IndexMIT], ErrorObjectsUnknownMIR[IndexMIR], MaxHoleDiameter, Rating)if (Rating > 70)* Merge the two symmetric errors into a new error object* 合并两个对称点云get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_x', X1)get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_y', Y1)get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_z', Z1)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_x', X2)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_y', Y2)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_z', Z2)* 根据点 生成 3D 对象gen_object_model_3d_from_points ([X1,X2], [Y1,Y2], [Z1,Z2], ObjectModel3D)* Remember the new objectErrorObjectsWrongPos := [ErrorObjectsWrongPos,ObjectModel3D]* 表示错位的孔洞ErrorTypesWrongPos := [ErrorTypesWrongPos,5]* Mark merged objectMergedMIT[IndexMIT] := trueMergedMIR[IndexMIR] := truebreakendifendforendfor* Clean up* MergedMIT:表示错位的点云,如果元素为true则是孔洞错位,否则为未知
3.5.4,表示未确定异常原因的点云。
*5.5.3,类型3:unknown (missing in test object)for IndexMIT := |MergedMIT| - 1 to 0 by -1if (MergedMIT[IndexMIT])* 从表示未知原因的异常点云块集合中去除错位的点云块clear_object_model_3d (ErrorObjectsUnknownMIT[IndexMIT])tuple_remove (ErrorObjectsUnknownMIT, IndexMIT, ErrorObjectsUnknownMIT)endifendfor* 5.5.4,unknown (missing in reference object)for IndexMIR := |MergedMIR| - 1 to 0 by -1if (MergedMIR[IndexMIR])clear_object_model_3d (ErrorObjectsUnknownMIR[IndexMIR])tuple_remove (ErrorObjectsUnknownMIR, IndexMIR, ErrorObjectsUnknownMIR)endifendforendif*
3.6,显示。
* 5.6,表示缺陷的点云对象汇总* ErrorObjectsKnown:表示孔洞缺少,偏大,偏小,多余的点云块;ErrorObjectsWrongPos:位置错误的点云块* ErrorObjectsUnknownMIT:unknown (missing in test object)* ErrorObjectsUnknownMIR:unknown (missing in reference object)ErrorObjectsFinal := [ErrorObjectsKnown,ErrorObjectsWrongPos,ErrorObjectsUnknownMIT,ErrorObjectsUnknownMIR]* 缺陷点云对象类型ErrorTypesFinal := [ErrorTypesKnown,ErrorTypesWrongPos,gen_tuple_const(|ErrorObjectsUnknownMIT|,6),gen_tuple_const(|ErrorObjectsUnknownMIR|,7)]elseErrorObjectsFinal := []ErrorTypesFinal := []endif* * 6,显示dev_set_window (WindowHandle1)dev_clear_window ()* TestObjectTrans:变换后的被测对象;ErrorObjectsFinal:表示被测对象异常部分的点云块ObjectModelsForVisualization := [TestObjectTrans,ErrorObjectsFinal]VisParamNames := ['point_size','alpha','point_size_0','alpha_0','color_0','intensity_0','color_' + [1:|ErrorTypesFinal|]]VisParamValues := [5.0,0.3,3.0,1.0,'gray','coord_z',subset(ErrorTypesColor,ErrorTypesFinal)]Labels := ['',subset(ErrorTypesText,ErrorTypesFinal)]* visualize_object_model_3d (WindowHandle1, ObjectModelsForVisualization, [], PoseDisplay, VisParamNames, VisParamValues, ['Test object ' + SceneIndex,'Number of errors: ' + NumErrors], Labels, Instructions, PoseOut1)dev_set_window (WindowHandle1)* * Clean up memoryclear_object_model_3d ([TestObject,TestObjectTrans,ErrorObjectsFinal])
endfor
* Clean up memory
clear_object_model_3d (Reference)
clear_sheet_of_light_model (SheetOfLightModelID)
clear_surface_model (SurfaceModelID)
4,完整程序。
*参考案例库:check_for_holes_sheet_of_light.hdev* 这个例子展示了线结构光模型的用法
* 通过点云表面比较,检查孔洞的缺失,多余,偏大,偏小与错位。* 结果是否对齐显示
DisplayAlignmentResult := false* ------------------1,创建用作参照物的3D对象* 1.1,读取描述3D对象轮廓的视差图。
* Create a sheet-of-light model to collect the disparities
* and set the calibration information for the reconstruction.
* tif文件:标准的位图文件,部分设备生成的深度图,点云数据采用此格式存储
read_image (Disparity, 'sheet_of_light/injection_mold_01_disparity')
get_image_size (Disparity, Width, Height)
* 处理的光条数量(即扫描次数)
NumProfiles := Height
gen_rectangle1 (Rectangle, 0, 0, Height - 1, Width - 1)* 1.2,创建线结构光三维测量模型
create_sheet_of_light_model (Rectangle, [], [], SheetOfLightModelID)
* 相机内参
CameraParam := [0.016,-1250.34,4.65114e-006,4.65e-006,623.43,515.23,1280,1024]
* 相机外参
CameraPose := [0.0013952482819,-0.0025835234583,-0.53764418409,337.80940798,359.84685912,358.46883832,0]
* 光平面在世界坐标系的位姿
LightplanePose := [-0.047567497224,-0.73091602504,0.0016815300297,271.32238394,0.40428170089,359.66058824,0]
* 移动时位姿变化/步(该位姿在世界坐标系)
MovementPose := [-2.5e-005,-1.5e-4,-1.25e-006,0.0,0.0,0.0,0]
* 定义校准模式,决定输出的坐标性类型
set_sheet_of_light_param (SheetOfLightModelID, 'calibration', 'xyz')
* 定义三维坐标输出的单位,m表示米
set_sheet_of_light_param (SheetOfLightModelID, 'scale', 'm')
* 设置相机的内参
set_sheet_of_light_param (SheetOfLightModelID, 'camera_parameter', CameraParam)
* 设置相机的姿态
set_sheet_of_light_param (SheetOfLightModelID, 'camera_pose', CameraPose)
* 光平面位姿(激光平面)
set_sheet_of_light_param (SheetOfLightModelID, 'lightplane_pose', LightplanePose)*定义被测物体在连续采集过程中的运动轨迹(如旋转平台或平移导轨的位姿变换,该位姿是相对于世界坐标系)
set_sheet_of_light_param (SheetOfLightModelID, 'movement_pose', MovementPose)
*
* Init display
dev_update_off ()
dev_close_window ()
dev_open_window_fit_image (Disparity, 0, 0, 500, 480, WindowHandle1)get_window_extents (WindowHandle1, Row, Column, Width1, Height1)
dev_open_window_fit_image (Disparity, 0, Width1 + 8, 500, 480, WindowHandle2)
set_display_font (WindowHandle1, 16, 'mono', 'true', 'false')
set_display_font (WindowHandle2, 16, 'mono', 'true', 'false')
dev_set_window (WindowHandle1)
*
* 1.3,定义异常类型对应的文本和颜色
ErrorTypesText := ['unknown','additional hole','missing hole','hole too large','hole too small','wrong position','unknown (missing in test object)','unknown (missing in reference object)']
ErrorTypesColor := ['white','magenta','red','blue','yellow','cyan','white','white']
*
* Set the disparity image of the reference object
* 1.4,将视差图图像设置到 线结构光测量模型中
set_profile_sheet_of_light (Disparity, SheetOfLightModelID, [])
* * 1.5,获取标准参照物的3D对象
get_sheet_of_light_result_object_model_3d (SheetOfLightModelID, ReferenceOrig)
* 显示原始图
visualize_object_model_3d (WindowHandle1, ReferenceOrig, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut2)
* 去除参照物的背景
select_points_object_model_3d (ReferenceOrig, 'point_coord_z', 0.01, 1, Reference)
* 1.6,显示参考对象(标准物)
visualize_object_model_3d (WindowHandle2, Reference, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut2)
* Clean up
clear_object_model_3d (ReferenceOrig)
*
*------------------------
* -----------------------2,创建基于表面的 3D 匹配模型,采样距离与物体直径的比例为:0.02
*-------------------------
create_surface_model (Reference, 0.02, [], [], SurfaceModelID)
*
* Display reference object
dev_clear_window ()
get_window_extents (WindowHandle1, Row2, Column2, Width2, Height2)
dev_set_part (0, 0, Height2 - 1, Width2 - 1)
Introduction := '这个例子展示了一个校准的'
Introduction[1] := '用于表面比较的线性结构光.'
Introduction[2] := '在这个例子中展示:'
Introduction[3] := '检查孔的位置,数量,大小是否有异常.'
disp_message (WindowHandle1, Introduction, 'window', 12, 12, 'white', 'false')* 2.1,创建标准 3D 模型显示位姿
create_pose (0.0244755,0.0707748,2.10414,-0.0,0.0,-0.0, 'Rp+T', 'gba', 'point', PoseDisplay)
Title := '参考对象(标准物)'
Instructions[0] := 'Rotate: Left button'
Instructions[1] := 'Zoom: Shift + left button'
Instructions[2] := 'Move: Ctrl + left button'* 2.2,显示参考对象
visualize_object_model_3d (WindowHandle2, Reference, [], PoseDisplay, ['lut','intensity'], ['color1','coord_z'], Title, [], Instructions, PoseOut)
*
* Display the reference object in the second window for comparison with the
* test object
dev_set_window (WindowHandle2)
get_window_extents (WindowHandle2, Row2, Column2, Width2, Height2)
dev_set_part (0, 0, Height2 - 1, Width2 - 1)
dev_clear_window ()
disp_object_model_3d (WindowHandle2, Reference, [], PoseDisplay, ['lut','intensity'], ['color1','coord_z'])
disp_message (WindowHandle2, 'Reference object', 'window', 12, 12, 'black', 'true')
*
*---------------------------3,查找被测 3D 对象
* Main loop
NumScenes := 6
MaxDist := 0.001
for SceneIndex := 1 to NumScenes by 1dev_set_window (WindowHandle1)dev_clear_window ()disp_message (WindowHandle1, 'Match test object with reference object ...', 'window', 12, 12, 'black', 'true')* 3.1, 读取被测的视差图像read_image (Disparity, 'sheet_of_light/injection_mold_' + SceneIndex$'02d' + '_disparity')* 3.2,复位线结构光模型中的视差图像. 其他的设置保持不变reset_sheet_of_light_model (SheetOfLightModelID)* 3.3,设置需要测量轮廓的视差图set_profile_sheet_of_light (Disparity, SheetOfLightModelID, [])* 3.4,使用线性结构光测量模型中解析出 待检测的 3D 对象get_sheet_of_light_result_object_model_3d (SheetOfLightModelID, TestObjectOrig)* 去除背景select_points_object_model_3d (TestObjectOrig, 'point_coord_z', 0.01, 1, TestObject)clear_object_model_3d (TestObjectOrig)*3.5,显示被测的3D对象点云visualize_object_model_3d (WindowHandle1, TestObject, [], [], ['lut','intensity'], ['color1','coord_z'], [], [], [], PoseOut3)disp_message (WindowHandle1, 'Match test object ' + SceneIndex + ' with reference object...', 'window', 12, 12, 'black', 'true')wait_seconds (1)* 3.6,检测标准 3D 模型 相对于 被测 3D 模型的位姿 find_surface_model (SurfaceModelID, TestObject, 0.02, 0.5, 0, 'false', 'pose_ref_sub_sampling', 1, Pose, Score, NotUsed)* 被测 3D 点云模型相对于标准 3D 模型的位姿pose_invert (Pose, PosesInvert)* 3.7,刚体变化检测对象到参考对象rigid_trans_object_model_3d (TestObject, PosesInvert, TestObjectTrans)* 3.8,显示测试对象对齐后状态
* disp_object_model_3d (WindowHandle1,TestObjectTrans, [], PoseDisplay, ['lut','intensity','disp_pose'], ['color1','coord_z','true'])visualize_object_model_3d (WindowHandle1,TestObjectTrans, [], PoseDisplay, ['lut','intensity','disp_pose'],['color1','coord_z','true'], [], [], [], PoseOut4)if (DisplayAlignmentResult)* ...and together with the reference object (if desired, see above)visualize_object_model_3d (WindowHandle1, [Reference,TestObjectTrans], [], PoseDisplay, ['color_0','color_1'], ['forest green','gray'], 'Result of surface based matching:\nTest object (gray) and reference (green)', [], [], PoseOut)endif* *---------------------4,分析被测对象与参考 3D 对象的点云差异* 4.1,----情况1:被测 3D 对象点云缺失(有额外的孔洞)----* 4.1.1,测量3D参考对象与被测3D对象的各点的距离distance_object_model_3d (Reference, TestObjectTrans, [], 0.0, [], [])* 4.1.2,筛选出被测 3D 对象缺少的点云select_points_object_model_3d (Reference, '&distance', MaxDist, 1, ObjectModel3DThresholded)ToClear := ObjectModel3DThresholded* 4.1.3,显示被测 3D 对象相对参照 3D 对象缺失的点云disp_object_model_3d (WindowHandle1, ObjectModel3DThresholded, [], [], ['lut','intensity','disp_pose'], ['color1','coord_z','true'])* 4.1.4,处理 被测 3D 对象相对参照 3D 对象缺失的点云get_object_model_3d_params (ObjectModel3DThresholded, 'num_points', NumPoints)if (NumPoints > 0)* Calculate connected components, the distance threshold should* be greater than the distance between two scan lines (> MovementPose[1])connection_object_model_3d (ObjectModel3DThresholded, 'distance_3d', 0.001, ObjectModel3DConnected)ToClear := [ToClear,ObjectModel3DConnected]* 计算测试对象上缺少的点云(保留主要部分,去掉噪声)select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 200, 1000000, SurfacePointsMissingInTestObject)elseSurfacePointsMissingInTestObject := []endif* Clean upclear_object_model_3d (ToClear)* *----4.2,情况2:被测 3D 对象点云多余(缺少孔洞)----* 4.2.1,反过来测量被测3D 对象与 参考3D 对象的各点距离,求出被测3D 对象多出的点云distance_object_model_3d (TestObjectTrans, Reference, [], 0.0, [], [])* 4.2.2,筛选出被测 3D 对象多出的点云块select_points_object_model_3d (TestObjectTrans, '&distance', MaxDist, 1, ObjectModel3DThresholded)ToClear := ObjectModel3DThresholdedtry* Calculate connected components, the distance threshold should* be greater than the distance between two scan lines (> MovementPose[1])connection_object_model_3d (ObjectModel3DThresholded, 'distance_3d', 0.001, ObjectModel3DConnected)ToClear := [ToClear,ObjectModel3DConnected]* ,处理 被测 3D 对象相对参照 3D 对象多出的点云select_object_model_3d (ObjectModel3DConnected, 'num_points', 'and', 200, 1000000, SurfacePointsMissingInReference)catch (Exception)SurfacePointsMissingInReference := []endtry* Clean upclear_object_model_3d (ToClear)* *---------------------------------5,对被测物的异常点云块进行处理* Collect and classify errors* 5.1,被测3D 对象缺少,多出的点云部分集合ErrorObjects := [SurfacePointsMissingInTestObject,SurfacePointsMissingInReference]* 异常点云块提示信息ErrorSources := [gen_tuple_const(|SurfacePointsMissingInTestObject|,'missing in test'),gen_tuple_const(|SurfacePointsMissingInReference|,'missing in reference')]ErrorTypes := []gen_empty_obj (ErrorPatterns)* 异常的点云块数量NumErrors := |ErrorObjects|* 对异常部分点云块进行处理if (NumErrors > 0)for ErrorIndex := 0 to NumErrors - 1 by 1* 异常的点云ErrorObject := ErrorObjects[ErrorIndex]* 异常点云的文本信息ErrorSource := ErrorSources[ErrorIndex]* * 5.2,为异常的点云中拟合出一个通过这些点的Region* a plane through the points and then projecting the points into this plane.create_region_from_error_points (Region, ErrorObject)* * 5.3,分析所得到的二维拟合图形的形状 (the region)fill_up (Region, RegionFillUp)difference (RegionFillUp, Region, RegionDifference)circularity (Region, CircularityRegDilation)circularity (RegionFillUp, CircularityRegFillUp)circularity (RegionDifference, CircularityRegDifference)* area_center (Region, AreaRegDilation, Row1, Column1)area_center (RegionFillUp, AreaRegFillUp, Row1, Column1)area_center (RegionDifference, AreaRegDifference, Row1, Column1)* * 5.4,给异常点云块分类,确定对应的异常类型MinCircularity := 0.7if (CircularityRegDilation > MinCircularity and real(AreaRegDifference) / AreaRegDilation < 0.1)if (ErrorSource == 'missing in test')* 5.4.1,原因1:被测3D 对象孔洞多余ErrorType := 1else* 5.4.2,原因2:被测3D 对象孔洞缺少ErrorType := 2endifelseif (CircularityRegFillUp > MinCircularity and CircularityRegDifference > MinCircularity and real(AreaRegDifference) / AreaRegDilation > 0.1)if (ErrorSource == 'missing in test')* 5.4.3,原因3:被测 3D 对象 孔洞偏大ErrorType := 3else* 5.4.4,原因4:被测 3D 对象 孔洞偏小ErrorType := 4endifelse* 5.4.0,原因0:未知原因ErrorType := 0endif*ErrorType:0 原因未知,1被测 3D 对象孔洞多余,2 被测 3D 对象 缺少孔洞* 3:被测 3D 对象孔洞偏大, 4 被测 3D 对象孔洞偏小* 5 wrong position* 6 unknown (missing in test object)* 7 unknown (missing in reference object)ErrorTypes[ErrorIndex] := ErrorType* 连接由异常点云块拟合出来的异常区域concat_obj (ErrorPatterns, Region, ErrorPatterns)endfor* 5.5,对表示异常的点云进行分类* 5.5.1,类型1:表示缺失,多余,偏大,偏小类型的点云对象MaskKnown := ErrorTypes [!=] 0* 注意如果MaskKnown的数组长度与ErrorObjects数组长度不一致将无法执行select_mask* select_mask执行的结果为MaskKnown成员为true则保留ErrorObjects对应位置的对象ErrorObjectsKnown := select_mask(ErrorObjects,MaskKnown)ErrorTypesKnown := select_mask(ErrorTypes,MaskKnown)* 5.5.2,类型2:表示孔洞错位的点云对* 5.5.2.1,被测3D对象缺少的点云(这些点云未明确错误类型)MaskUnknownMIT := ErrorTypes [==] 0 and ErrorSources [==] 'missing in test'ErrorObjectsUnknownMIT := select_mask(ErrorObjects,MaskUnknownMIT)* 5.5.2.2,被测3D对象多余的点云(这些未明确错误类型)MaskUnknownMIR := ErrorTypes [==] 0 and ErrorSources [==] 'missing in reference'ErrorObjectsUnknownMIR := select_mask(ErrorObjects,MaskUnknownMIR)* ErrorObjectsWrongPos := []ErrorTypesWrongPos := []* MergedMIT := gen_tuple_const(|ErrorObjectsUnknownMIT|,false)MergedMIR := gen_tuple_const(|ErrorObjectsUnknownMIR|,false)* 对未明确异常原因的点云块进一步进行分类if (|ErrorObjectsUnknownMIT| > 0 and |ErrorObjectsUnknownMIR| > 0)for IndexMIT := 0 to |ErrorObjectsUnknownMIT| - 1 by 1if (MergedMIT[IndexMIT])continueendiffor IndexMIR := 0 to |ErrorObjectsUnknownMIR| - 1 by 1if (MergedMIR[IndexMIR])continueendif* Test if there are symmetric error objects* The parameter MaxHoleDiameter is used to speed up the search and to* make it more robust, because we are only interested in symmetric* error objects that are not further apart from each other than* the size of a hole.*判断是否有对称的错误对象,如果是则认为是孔洞位置错误*参数MaxHoleDiameter是用来加快搜索和*使它更健壮,因为我们只对对称感兴趣错误对象之间的距离小于洞的大小MaxHoleDiameter := 0.02*表示孔洞位置偏离的点云块对*=======*======= 判断被测物缺少的点云块与多余的点云块的对称性,如果对称性高则说明两个点云对称,即可判定孔洞错位test_symmetry_object_model_3d (ErrorObjectsUnknownMIT[IndexMIT], ErrorObjectsUnknownMIR[IndexMIR], MaxHoleDiameter, Rating)if (Rating > 70)* Merge the two symmetric errors into a new error object* 合并两个对称点云get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_x', X1)get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_y', Y1)get_object_model_3d_params (ErrorObjectsUnknownMIT[IndexMIT], 'point_coord_z', Z1)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_x', X2)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_y', Y2)get_object_model_3d_params (ErrorObjectsUnknownMIR[IndexMIR], 'point_coord_z', Z2)* 根据点 生成 3D 对象gen_object_model_3d_from_points ([X1,X2], [Y1,Y2], [Z1,Z2], ObjectModel3D)* Remember the new objectErrorObjectsWrongPos := [ErrorObjectsWrongPos,ObjectModel3D]* 表示错位的孔洞ErrorTypesWrongPos := [ErrorTypesWrongPos,5]* Mark merged objectMergedMIT[IndexMIT] := trueMergedMIR[IndexMIR] := truebreakendifendforendfor* Clean up* MergedMIT:表示错位的点云,如果元素为true则是孔洞错位,否则为未知*5.5.3,类型3:unknown (missing in test object)for IndexMIT := |MergedMIT| - 1 to 0 by -1if (MergedMIT[IndexMIT])* 从表示未知原因的异常点云块集合中去除错位的点云块clear_object_model_3d (ErrorObjectsUnknownMIT[IndexMIT])tuple_remove (ErrorObjectsUnknownMIT, IndexMIT, ErrorObjectsUnknownMIT)endifendfor* 5.5.4,unknown (missing in reference object)for IndexMIR := |MergedMIR| - 1 to 0 by -1if (MergedMIR[IndexMIR])clear_object_model_3d (ErrorObjectsUnknownMIR[IndexMIR])tuple_remove (ErrorObjectsUnknownMIR, IndexMIR, ErrorObjectsUnknownMIR)endifendforendif* * 5.6,表示缺陷的点云对象汇总* ErrorObjectsKnown:表示孔洞缺少,偏大,偏小,多余的点云块;ErrorObjectsWrongPos:位置错误的点云块* ErrorObjectsUnknownMIT:unknown (missing in test object)* ErrorObjectsUnknownMIR:unknown (missing in reference object)ErrorObjectsFinal := [ErrorObjectsKnown,ErrorObjectsWrongPos,ErrorObjectsUnknownMIT,ErrorObjectsUnknownMIR]* 缺陷点云对象类型ErrorTypesFinal := [ErrorTypesKnown,ErrorTypesWrongPos,gen_tuple_const(|ErrorObjectsUnknownMIT|,6),gen_tuple_const(|ErrorObjectsUnknownMIR|,7)]elseErrorObjectsFinal := []ErrorTypesFinal := []endif* * 6,显示dev_set_window (WindowHandle1)dev_clear_window ()* TestObjectTrans:变换后的被测对象;ErrorObjectsFinal:表示被测对象异常部分的点云块ObjectModelsForVisualization := [TestObjectTrans,ErrorObjectsFinal]VisParamNames := ['point_size','alpha','point_size_0','alpha_0','color_0','intensity_0','color_' + [1:|ErrorTypesFinal|]]VisParamValues := [5.0,0.3,3.0,1.0,'gray','coord_z',subset(ErrorTypesColor,ErrorTypesFinal)]Labels := ['',subset(ErrorTypesText,ErrorTypesFinal)]* visualize_object_model_3d (WindowHandle1, ObjectModelsForVisualization, [], PoseDisplay, VisParamNames, VisParamValues, ['Test object ' + SceneIndex,'Number of errors: ' + NumErrors], Labels, Instructions, PoseOut1)dev_set_window (WindowHandle1)* * Clean up memoryclear_object_model_3d ([TestObject,TestObjectTrans,ErrorObjectsFinal])
endfor
* Clean up memory
clear_object_model_3d (Reference)
clear_sheet_of_light_model (SheetOfLightModelID)
clear_surface_model (SurfaceModelID)
本地函数:create_region_from_error_points (Region, ErrorObject)
拟合出一个通过点云所有点的平面
* 获取 3D 点云错误对象
get_object_model_3d_params (ErrorObject, 'point_coord_x', X)
get_object_model_3d_params (ErrorObject, 'point_coord_y', Y)
get_object_model_3d_params (ErrorObject, 'point_coord_z', Z)
* 确定一个通过所有点的近似平面的法向量
fit_plane (X, Y, Z, NX, NY, NZ, C)
hnf_to_hom_mat3d (NX, NY, NZ, C, HomMat3D)
* Project all points into the plane (just set Qz to zero)
affine_trans_point_3d (HomMat3D, X, Y, Z, Qx, Qy, Qz)
* Create a region from the 2D points
* - First, determine the necessary resolution
* 创建分类器
create_class_knn (2, KNNHandle)
Seq := [0:2:2 * |Qx| - 2]
Features[Seq] := Qx
Features[Seq + 1] := Qy
* 添加样本
add_sample_class_knn (KNNHandle, Features, 0)
* 训练分类器
train_class_knn (KNNHandle, [], [])
set_params_class_knn (KNNHandle, ['method','k'], ['neighbors_distance',4])
classify_class_knn (KNNHandle, Features, Result, Rating)
Seq4 := [0:4:4 * |Qx| - 4]
DistancesClosest := Rating[Seq4 + 1]
DistancesAway := Rating[Seq4 + 3]
clear_class_knn (KNNHandle)
Resolution := median(DistancesClosest) / sqrt(2)
DilationRad := median(DistancesAway) / Resolution
QxMin := min(Qx)
QxMax := max(Qx)
QyMin := min(Qy)
QyMax := max(Qy)
DQx := QxMax - QxMin
DQy := QyMax - QyMin
* - Then create the region from the appropriately scaled point coordinates
Rows := int((Qy - QyMin) / Resolution + 0.5)
Cols := int((Qx - QxMin) / Resolution + 0.5)
gen_region_points (RegionRaw, Rows, Cols)
* Dilate the region to fill up gaps between the points
dilation_circle (RegionRaw, Region, DilationRad)
return ()
本地函数: test_symmetry_object_model_3d
评价两块点云的对称性
* OM1:被测 3D 对象缺少的点云块
* OM2:被测 3D 对象多余的点云块
Inspection := false
* 确定对称平面的假设
get_object_model_3d_params (OM1, 'point_coord_x', X1)
get_object_model_3d_params (OM1, 'point_coord_y', Y1)
get_object_model_3d_params (OM1, 'point_coord_z', Z1)
get_object_model_3d_params (OM2, 'point_coord_x', X2)
get_object_model_3d_params (OM2, 'point_coord_y', Y2)
get_object_model_3d_params (OM2, 'point_coord_z', Z2)
Center1 := [mean(X1),mean(Y1),mean(Z1)]
Center2 := [mean(X2),mean(Y2),mean(Z2)]
* 125*125:=96*96+75*75+28*28
Distance := sqrt(sum((Center1 - Center2) * (Center1 - Center2)))
if (Distance > MaxDistance)* 等级Rating := 0return ()
endif
a:=[1,34,38]
b:=sqrt(sum(a*a))
NSymm := Center2 - Center1
NSymm := NSymm / sqrt(sum(NSymm * NSymm))
PSymm := Center1 + 0.5 * (Center2 - Center1)
CSymm := sum(NSymm * PSymm)
if (CSymm < 0)NSymm := -NSymmCSymm := -CSymm
endif
hnf_to_hom_mat3d (NSymm[0], NSymm[1], NSymm[2], CSymm, HomMat3D)
hom_mat3d_to_pose (HomMat3D, Pose)
*
*
* Test hypothesis for symmetry plane
*
* Transform all points into the coordinate system in which the
* symmetry plane is the plane z=0
hom_mat3d_invert (HomMat3D, HomMat3DInvert)
affine_trans_point_3d (HomMat3DInvert, X1, Y1, Z1, X1T, Y1T, Z1T)
affine_trans_point_3d (HomMat3DInvert, X2, Y2, Z2, X2T, Y2T, Z2T)
*
* Create first KNN classifier to determine distance to closest points
create_class_knn (3, KNN1)
Seq1 := [0:3:3 * |X1T| - 3]
Features1 := gen_tuple_const(3 * |X1T|,0)
Features1[Seq1] := X1T
Features1[Seq1 + 1] := Y1T
Features1[Seq1 + 2] := Z1T
add_sample_class_knn (KNN1, Features1, 0)
train_class_knn (KNN1, [], [])
*
* Create second KNN classifier to determine distance to closest points
create_class_knn (3, KNN2)
Seq2 := [0:3:3 * |X2T| - 3]
Features2 := gen_tuple_const(3 * |X2T|,0)
Features2[Seq2] := X2T
Features2[Seq2 + 1] := Y2T
Features2[Seq2 + 2] := Z2T
add_sample_class_knn (KNN2, Features2, 0)
train_class_knn (KNN2, [], [])
set_params_class_knn (KNN2, ['method','k'], ['neighbors_distance',1])
*
* Determine the distance to the closest point in the other
* set of points for all points mirrored at the hypothedical
* symmetry plane. Note, mirroring is ease because we have
* transformed the points into the coordinate system in which
* the symmetry plane is the plane z=0. So mirroring is
* just switching the sign of the z coordinate.
*
* - Determine mean distance within point set 1
set_params_class_knn (KNN1, ['method','k'], ['neighbors_distance',2])
classify_class_knn (KNN1, Features1, Result1, Rating1)
Rating1 := subset(Rating1,[1:2:|Rating1| - 1])
* - Determine mean distance within point set 2
set_params_class_knn (KNN2, ['method','k'], ['neighbors_distance',2])
classify_class_knn (KNN2, Features2, Result2, Rating2)
Rating2 := subset(Rating2,[1:2:|Rating2| - 1])
* - Determine mean distance of mirrored points from point set 1 within point set 2
set_params_class_knn (KNN1, ['method','k'], ['neighbors_distance',1])
Features1[Seq1 + 2] := -Z1T
classify_class_knn (KNN2, Features1, Result1In2, Rating1In2)
* - Determine mean distance of mirrored points from point set 2 within point set 1
Features2[Seq2 + 2] := -Z2T
classify_class_knn (KNN1, Features2, Result2In1, Rating2In1)
*
*
MeanRating1 := mean(Rating1)
StdDevRating1 := deviation(Rating1)
MeanRating2 := mean(Rating2)
StdDevRating2 := deviation(Rating2)
*
PercBadRating2In1 := sum(Rating2In1 [>] MeanRating1 + 3 * StdDevRating1) / real(|Rating2In1|) * 100.0
PercBadRating1In2 := sum(Rating1In2 [>] MeanRating2 + 3 * StdDevRating2) / real(|Rating1In2|) * 100.0
Rating := 100.0 - mean([PercBadRating2In1,PercBadRating1In2])
*
if (Inspection)dev_get_window (WindowHandle)gen_object_model_3d_from_points (X1T, Y1T, Z1T, OM1T)gen_object_model_3d_from_points (X2T, Y2T, Z2T, OM2T)gen_object_model_3d_from_points (X1T, Y1T, -Z1T, OM1TM)gen_object_model_3d_from_points (X2T, Y2T, -Z2T, OM2TM)visualize_object_model_3d (WindowHandle, [OM1T,OM2TM,OM2T], [], [], ['color_0','color_1','color_2','disp_pose'], ['red','green','gray','true'], 'Rating: ' + Rating, [], [], PoseOut)clear_object_model_3d ([OM1T,OM2T,OM1TM,OM2TM])
endif
*
* Clean up
clear_class_knn (KNN1)
clear_class_knn (KNN2)
*
return ()