资讯详情

线结构光标定详细步骤与实现HALCON,带3D平面拟合

引用:线结构光校准的详细步骤和实现HALCON_冯相文要加油!-CSDN博客_halcon线结构光校准

线结构光校准的详细步骤HALCON

冯相文要加油 于 2021-08-23 12:08:05 发布 818 收藏 18 分类专栏: 机器视觉 文章标签: c c语言 计算机视觉 版权

机器视觉 该内容包含在专栏中 175 篇文章5 订阅 订阅专栏 这部分是HALCON一个官方的例子,以下是对这个复杂例子的一些理解,具体的每个代码都对应于相应的功能解释

具体例子如下:

如何实施校准光片测量系统?

测量系统由区域扫描摄像头和光投影仪(如激光投影仪)组成。投影仪的位置和方向相机是固定的。为了重建整个物体的表面(而不仅仅是单个轮廓),物体必须在测量系统下或在物体上移动。 因此,校准分三个步骤执行:

首先,我们使用标准摄像来确定相机的内外参数 校准程序。 然后,我们确定了光平面和世界坐标系统的方向。这是通过计算一个面向z=0的平面与轻平面重叠。 最后,我们校准了连续两个配置文件获取对象之间的移动。世界坐标系也表达了与这项运动相对应的变化。 最后的应用程序:我们展示了如何将校准转换应用于已同图像。 执行一些初始化 dev_update_off () dev_close_window () *///阅读图片 read_image (ProfileImage, 'sheet_of_light/connection_rod_001.png') *//获取图片大小 get_image_size (ProfileImage, Width, Height) *//创建窗口 dev_open_window (0, 0, Width, Height, 'black', WindowHandle) *//设置填充模式:只显示轮廓 dev_set_draw ('margin') *//设置宽度 dev_set_line_width (3) *//设置颜色 dev_set_color ('lime green') *//设置搜索表,将单通道图像的灰度值定义为屏幕上的灰度值或颜色。 dev_set_lut ('default') *//设置字体 set_display_font (WindowHandle, 14, 'mono', 'true', 'false') 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 上述代码读取激光图像,获取图像尺寸,设置一些字体大小、形状和颜色

读取的激光图像:

图像读取的一些信息:

第 1 部分:执行相机校准:执行相机校准: * ------- * * Initialize some parameters required for the camera calibration * ////初始化一些相机参数 gen_cam_par_area_scan_polynomial (0.0125, 0.0, 0.0, 0.0, 0.0, 0.0, 0.000006, 0.000006, 376.0, 120.0, 752, 240, StartParameters) *///以下是标定板文件的选择 CalTabDescription := 'caltab_30mm.descr' * Note that the thickness of the calibration target used for this example is 0.63 mm. * If you adapt this example program to your application, it is necessary to determine * the thickness of your specific calibration target and to use this value instead. *//下面是标定板的厚度。这个例子太多了。当我们自己使用它时,我们需要相应地修改它 CalTabThickness := .00063 *//这是图像数量 NumCalibImages := 20 * * Initialize a calibration data model * ///初始化校准模型,用校准助手生成代码时也会有这句话 create_calib_data ('calibration_object', 1, 1, CalibDataID) *//这个StartParameters事实上,这是我们最初始化的数据 set_calib_data_cam_param (CalibDataID, 0, [], StartParameters) *//这是标定物的一些参数,即上述数据 set_calib_data_calib_object (CalibDataID, 0, CalTabDescription) * * Collect mark positions and estimated poses for all * calibration images * ///在所有图片中找到标志点,并估计这些图像的位置 for Index := 1 to NumCalibImages by 1 *///循环读取文件夹中的多张图片 read_image (Image, 'sheet_of_light/connection_rod_calib_' Index$'.2') *//显示图片 dev_display (Image) *////在图片中找到标定物 find_calib_object (Image, CalibDataID, 0, 0, Index, [], []) *///以下两句话是在标定板中找到标志点和轮廓 get_calib_data_observ_points (CalibDataID, 0, 0, Index, Row, Column, _Index, Pose) get_calib_data_observ_contours (Contours, CalibDataID, 'caltab', 0, 0, Index) *//设置颜色 dev_set_color ('green') *//显示轮廓 dev_display (Contours) *///在中心点画绿色叉和外轮廓 gen_cross_contour_xld (Cross, Row, Column, 6, 0.785398) *//设置颜色 dev_set_color ('yellow') *////将叉叉设置为黄色 dev_display (Cross) endfor * * Perform the actual calibration *// 计算相机内外参矩阵的真实校准函数,CalibDataID只是一个句柄,供我们以后获取参数使用 *//Errors返回优化后的反投影均方根误差,单位为像素,用于反映优化是否成功。越接近0,效果越好(这里的解释是指一些在线搜索信息) calibrate_cameras (CalibDataID, Errors) *//显示标定成功 disp_message (WindowHandle, 'The camera calibration has been performed successfully', 'window', 12, 12, 'black', 'true') *//显示按下F5,Continue信息 disp_continue_message (WindowHandle, 'black', 'true') stop () * * -------

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 以上代码,即常见的相机校准,目的是校准相机,可以使用校准助手帮助我们拍摄校准图片,校准助手可以参考本文: HALCON标定助手使用实例 选择标定板文件参考: HALCON选择标定板文件

这是找到轮廓和关键点之后画的绿色的线和特征点:

将中心点叉转为黄色:

校准计算后获得的数据:

第 2 部分:校准线结构光平面和世界坐标系统的方向 * ------- * //dev_set_color定义用于显示图形窗口region,XLD和其他geometrical对象的颜色 dev_set_coloed (3) *//设定好一个阈值,下面的算子可以用上 MinThreshold := 80 *  * Definition of the world coordinate system (WCS): * Here, the WCS is defined implicitly by choosing one * specific calibration image. In order to take the thickness * of the calibration table into account, we shift the origin * of the pose that corresponds to the chosen calibration image * (and that was computed during the calibration). *//世界坐标系统的定义: *//在这里,WCS 通过选择一个来隐含定义特定校准图像。为了采取厚度校准表的考虑,我们转移原点对应于所选校准图像的姿势(这是在校准期间计算的)。 *//Index给19为了得到第19幅图像中标定物的位姿数据 Index := 19 *//CalibDataID就是上面我们标定出来的结果数据,获取标定数据 get_calib_data (CalibDataID, 'calib_obj_pose', [0,Index], 'pose', CalTabPose) *//平移CalTabPose的原点,沿着厚度方向CalTabThickness的大小,输出为新的位姿,平移是相对于姿势本身的局部坐标系执行的 set_origin_pose (CalTabPose, 0.0, 0.0, CalTabThickness, CameraPose) *//读取一幅图像 read_image (CalTabImage1, 'sheet_of_light/connection_rod_calib_' + Index$'.2') *//显示图片 dev_display (CalTabImage1) *//通过上面获得的句柄得到标定参数 get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParameters) *//显示一个3D坐标系统,是以我们转换之后的坐标原点来设置的 disp_3d_coord_system (WindowHandle, CameraParameters, CameraPose, .01) *//显示信息 disp_message (WindowHandle, 'World coordinate system', 'window', 12, 12, 'black', 'true') *//F5提示 disp_continue_message (WindowHandle, 'black', 'true') stop () *  * Definition of a temporary coordinate system (TCS): * The TCS is also defined implicitly by choosing another * calibration image. Here again we shift the origin of * the coordinate system in order to take the thickness * of the calibration table into account. *//临时坐标系统的定义: *//TCS 还通过选择另一个校准图像来隐含定义。在这里,我们再次改变坐标系统的起源,以考虑到校准表的厚度。 *//下面的过程和上面的完全一样,就是建立不同的坐标系 Index := 20 get_calib_data (CalibDataID, 'calib_obj_pose', [0,Index], 'pose', CalTabPose) set_origin_pose (CalTabPose, 0.0, 0.0, CalTabThickness, TmpCameraPose) read_image (CalTabImage2, 'sheet_of_light/connection_rod_calib_' + Index$'.2') dev_display (CalTabImage2) disp_3d_coord_system (WindowHandle, CameraParameters, TmpCameraPose, .01) disp_message (WindowHandle, 'Temporary coordinate system', 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () *  *  * Compute the 3D coordinates of the light line points * in the plane z=0 of the WCS * //在世界坐标系中Z等于0的平面计算激光线的点的3D坐标,此图像为实时图像。 dev_clear_window () read_image (ProfileImage1, 'sheet_of_light/connection_rod_lightline_019.png') *//计算图像中激光点的世界坐标,并且要以计算的那个平面为Z=0的平面 compute_3d_coordinates_of_light_line (ProfileImage1, MinThreshold, CameraParameters, [], CameraPose, X19, Y19, Z19) *//如果激光线的3D坐标的数模等于0,就中止程序,也就是说当点都是0的时候就不继续执行了 if (|X19| == 0 or |Y19| == 0 or |Z19| == 0)     dev_display (ProfileImage1)     disp_message (WindowHandle, 'The profile MUST be oriented horizontally\nfor successfull processing!\nThe program will exit.', 'window', 12, 12, 'black', 'true')     return () endif *  * Compute the 3D coordinates of the light line points * in the plane z=0 of the TCS *//计算临时坐标下的Z=0的激光点的3D坐标,和上面的过程类似 read_image (ProfileImage2, 'sheet_of_light/connection_rod_lightline_020.png') compute_3d_coordinates_of_light_line (ProfileImage2, MinThreshold, CameraParameters, TmpCameraPose, CameraPose, X20, Y20, Z20) if (|X20| == 0 or |Y20| == 0 or |Z20| == 0)     disp_message (WindowHandle, 'The profile MUST be oriented horizontally\nfor successfull processing!\nThe program will exit.', 'window', 12, 12, 'black', 'true')     return () endif *  * Fit the light plane in the 3D coordinates of the line * points computed previously. Note that this requires * nearly coplanar points. We must provide line points * recorded at -at least- two different heights, in order * to get an unambiguous solution. To obtain stable and * accurate results, acquire the light line points at the * bottom and at the top of the measurement volume. *// 将光平面安装在以前计算的线点的 3D 坐标中。请注意,这几乎需要平面点。 *//我们必须提供至少两个不同的高度记录的线点,以便得到一个明确的解决方案。 *//要获得稳定准确的结果,获取测量量底部和顶部的光线点。 *//通过两个面的点坐标拟合3D平面,该算子返回输入坐标的形心坐标(通过求平均值)和拟合平面的法向量(求奇异值分解)。 fit_3d_plane_xyz ([X19,X20], [Y19,Y20], [Z19,Z20], Ox, Oy, Oz, Nx, Ny, Nz, MeanResidual) *//如果点太少或者几乎是很接近就返回 if (|Nx| == 0 or |Ny| == 0 or |Nz| == 0)     disp_message (WindowHandle, 'Too few 3d points have been provided to fit the light plane,\nor the points are (nearly) collinear!\nThe program will exit.', 'window', 12, 12, 'black', 'true')     return () endif *//如果3D点之间平均剩余距离太大就返回 if (MeanResidual > 5e-5)     disp_message (WindowHandle, 'The light plane could not be fitted accurately!\nThe mean residual distance between the 3d-points and the\nfitted plane is too high (' + (MeanResidual * 1000)$'.3' + 'mm). Please check the\nquality and the correctness of those points.\nThe program will exit!', 'window', 12, 21, 'black', 'true')     return () endif *  * Compute the light plane pose: this pose must be oriented * such that the plane defined by z=0 coincides with the * light plane. * //get_light_plane_pose算子通过中心点和法向量计算出LightPlanePose就是光平面的3D表达式了 get_light_plane_pose (Ox, Oy, Oz, Nx, Ny, Nz, LightPlanePose) *//如果计算出来不是7个数据就说明出现错误返回 if (|LightPlanePose| != 7)     disp_message (WindowHandle, 'The pose of the light plane could not be\ndetermined. Please verify that the vector\npassed at input of the procedure\nget_light_plane_pose() is not null.\nThe program will exit!', 'window', 12, 12, 'black', 'true')     return () endif *//下面就是显示出标定数据 String := ['LightPlanePose: ','  Tx    = ' + LightPlanePose[0]$'.3' + ' m','  Ty    = ' + LightPlanePose[1]$'.3' + ' m','  Tz    = ' + LightPlanePose[2]$'.3' + ' m','  alpha = ' + LightPlanePose[3]$'.4' + '°','  beta  = ' + LightPlanePose[4]$'.4' + '°','  gamma = ' + LightPlanePose[5]$'.4' + '°','  type  = ' + LightPlanePose[6]] disp_message (WindowHandle, String, 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () dev_clear_window () *  * -------

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 set_origin_pose算子解释(帮助文档):

set_origin_pose算子计算出来的结果:

set_origin_pose算子计算出来的结果:

compute_3d_coordinates_of_light_line算子的解释:

compute_3d_coordinates_of_light_line算子计算的结果:

fit_3d_plane_xyz算子计算结果:

fit_3d_plane_xyz算子参数:

get_light_plane_pose算子计算结果:

LightPlanePose数据类型:

下面就是显示出来的标定数据,和上面的一样,这样就标定出来了我们所要的光平面表达式:

第 3 部分:在获取连续两个配置文件之间校准对象的移动 * ------- *  * In order to determine the movement of the object between * two successive profile images, we acquire two images of a * calibration table which describe the same movement. * In order to get a good accuracy, we usually move the * calibration table by more than one step. *// 为了确定物体在连续两个配置文件图像之间的移动,我们需要获取一个标定板的两个图像,该图像描述了相同的运动。 *// 为了获得良好的准确性,我们通常将校准表移动不止一步,需要多次移动标定板。 *// 再读取两幅图像 read_image (CaltabImagePos1, 'sheet_of_light/caltab_at_position_1.png') read_image (CaltabImagePos20, 'sheet_of_light/caltab_at_position_2.png') *//需要移动19次 StepNumber := 19 *  * Set the optimized camera parameter as new start camera parameters for the * calibration data model to extract the following poses using * these calibrated parameters. * //* 将优化的相机参数设置为校准数据模型的新启动相机参数,以便使用这些校准参数提取以下姿势。 set_calib_data_cam_param (CalibDataID, 0, [], CameraParameters) * Compute the pose of the calibration table in each image * //计算每幅图像中的标定板的位姿,从校准数据模型中提取未优化的姿势 find_calib_object (CaltabImagePos1, CalibDataID, 0, 0, NumCalibImages + 1, [], []) * Extract the unoptimized pose from the calibration data model * //获取位姿信息 get_calib_data_observ_points (CalibDataID, 0, 0, NumCalibImages + 1, Row1, Column1, Index1, CameraPosePos1) find_calib_object (CaltabImagePos20, CalibDataID, 0, 0, NumCalibImages + 2, [], []) get_calib_data_observ_points (CalibDataID, 0, 0, NumCalibImages + 2, Row1, Column1, Index1, CameraPosePos20) * Clear the model * //删除标定模型 clear_calib_data (CalibDataID) *  * Compute the coordinates of the origin of the calibration * table in the two positions with respect to the world * coordinate system and determine the coordinates of the * corresponding translation vector

* //计算标定板在两个位置相对于世界坐标系的原点坐标,并确定相应平移向量的坐标 * //平移CameraPosePos1和20的原点,输出为新的位姿 set_origin_pose (CameraPosePos1, 0.0, 0.0, CalTabThickness, CameraPosePos1) set_origin_pose (CameraPosePos20, 0.0, 0.0, CalTabThickness, CameraPosePos20) *//将CameraPosePos1和CameraPosePos20位姿转换为齐次变换矩阵 pose_to_hom_mat3d (CameraPosePos1, HomMat3DPos1ToCamera) pose_to_hom_mat3d (CameraPosePos20, HomMat3DPos20ToCamera) pose_to_hom_mat3d (CameraPose, HomMat3DWorldToCamera) *//再把上面的齐次变换矩阵转换为齐次3D变换矩阵 hom_mat3d_invert (HomMat3DWorldToCamera, HomMat3DCameraToWorld) *//两个矩阵相乘见下面截图有算法 hom_mat3d_compose (HomMat3DCameraToWorld, HomMat3DPos1ToCamera, HomMat3DPos1ToWorld) hom_mat3d_compose (HomMat3DCameraToWorld, HomMat3DPos20ToCamera, HomMat3DPos20ToWorld) *//对点做仿射变换,得到相对于世界坐标系的起始坐标和终点坐标,下面有截图算法 affine_trans_point_3d (HomMat3DPos1ToWorld, 0, 0, 0, StartX, StartY, StartZ) affine_trans_point_3d (HomMat3DPos20ToWorld, 0, 0, 0, EndX, EndY, EndZ) *//这里是创建位姿,算法比较复杂在帮助文档内有 create_pose (EndX - StartX, EndY - StartY, EndZ - StartZ, 0, 0, 0, 'Rp+T', 'gba', 'point', MovementPoseNSteps) *//这里就是计算出来的在两个连续的轮廓图像间物体的移动标定结果 MovementPose := MovementPoseNSteps / StepNumber String := ['MovementPose: ','  Tx    = ' + MovementPose[0]$'.3' + ' m','  Ty    = ' + MovementPose[1]$'.3' + ' m','  Tz    = ' + MovementPose[2]$'.3' + ' m','  alpha = ' + MovementPose[3] + '°','  beta  = ' + MovementPose[4] + '°','  gamma = ' + MovementPose[5] + '°','  type  = ' + MovementPose[6]] disp_message (WindowHandle, String, 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle, 'black', 'true') stop () dev_clear_window () *  * -------

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 hom_mat3d_invert算子原理:

hom_mat3d_compose算子原理:

affine_trans_point_3d算法原理:

create_pose算子运行结果:

MovementPose数据格式:

第 4 部分:将校准转换应用于已经获得的差异图像。 * ------- *  * Read an already acquired disparity map from file read_image (Disparity, 'sheet_of_light/connection_rod_disparity.tif') * Create a model and set the required parameters * //创建一个矩形框并且设置一些位置参数 gen_rectangle1 (ProfileRegion, 120, 75, 195, 710) *//创建激光三角的模型 create_sheet_of_light_model (ProfileRegion, ['min_gray','num_profiles','ambiguity_solving'], [70,290,'first'], SheetOfLightModelID) *//设置激光三角的参数 set_sheet_of_light_param (SheetOfLightModelID, 'calibration', 'xyz') set_sheet_of_light_param (SheetOfLightModelID, 'scale', 'mm') set_sheet_of_light_param (SheetOfLightModelID, 'camera_parameter', CameraParameters) 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) *  * Apply the calibration transforms and * get the resulting calibrated coordinates *//计算三种标定结果 apply_sheet_of_light_calibration (Disparity, SheetOfLightModelID) get_sheet_of_light_result (X, SheetOfLightModelID, 'x') get_sheet_of_light_result (Y, SheetOfLightModelID, 'y') get_sheet_of_light_result (Z, SheetOfLightModelID, 'z') clear_sheet_of_light_model (SheetOfLightModelID) *  * Display the resulting Z-coordinates * //显示生成的 Z 坐标结果 dev_close_window () get_image_size (Disparity, Width, Height) dev_open_window (Height + 10, 0, Width * .5, Height * .5, 'black', WindowHandle3) set_display_font (WindowHandle3, 14, 'mono', 'true', 'false') dev_set_lut ('temperature') dev_display (Z) disp_message (WindowHandle3, 'Calibrated Z-coordinates', 'window', 12, 12, 'black', 'true') *  * Display the resulting Y-coordinates * //显示生成的 Y 坐标结果 dev_open_window ((Height + 10) * .5, 0, Width * .5, Height * .5, 'black', WindowHandle2) set_display_font (WindowHandle2, 14, 'mono', 'true', 'false') dev_display (Y) disp_message (WindowHandle2, 'Calibrated Y-coordinates', 'window', 12, 12, 'black', 'true') *  * Display the resulting X-coordinates * //显示生成的 X 坐标结果 dev_open_window (0, 0, Width * .5, Height * .5, 'black', WindowHandle1) dev_display (X) set_display_font (WindowHandle1, 14, 'mono', 'true', 'false') disp_message (WindowHandle1, 'Calibrated X-coordinates', 'window', 12, 12, 'black', 'true')

* //显示重建后的3D模型 * //注意,摄像机观察物体穿过的平面的那一部分 * //生成3D模型 * //下面这部分是实例中没有的,我再另一处文章中拷过来的,我试了一下有问题 get_sheet_of_light_result_object_model_3d (SheetOfLightModelID, ObjectModel3D) gen_sheet_of_light_object_model_3d (SheetOfLightModelID, 0.1, 0.05, 0.3, OM3DLightPlane, OM3DMovement, OM3DCamera, OM3DCone) dev_open_window (0, Width * .5 + 10, Width, Height * 1.5, 'black', WindowHandle) dev_set_lut ('default') set_display_font (WindowHandle, 14, 'mono', 'true', 'false') visualize_object_model_3d (WindowHandle, [ObjectModel3D,OM3DLightPlane,OM3DMovement,OM3DCamera,OM3DCone], [], [-0.002989894371,0.1325031046,8.667736001,288.0583956,2.798360231,297.2537796,0], ['alpha_1','alpha_3','alpha_4','alpha_5','color_0','color_3','color_4','color_5'], [0.5,0.5,0.5,0.5,'blue','green','green','green'], 'Setup with reconstructed object', [], [], PoseOut)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 综上所述 线结构光标定的流程有三步:

普通的相机标定

标定激光平面 a,两幅图像考虑完厚度之后,平移变换得到一个世界坐标,一个临时坐标 b,调用compute_3d_coordinates_of_light_line计算出两幅图片中光线上面的3D点坐标并且利用这些点调用fit_3d_plane_xyz计算出拟合激光面 c,调用get_light_plane_pose算子计算出上面拟合的激光面的参数

物体移动的标定 a,计算两幅图像的对应的两个位姿 b,通过一系列矩阵变换的算子,计算出两个图像中物体分别的3D坐标,就能求出物体的移动是多少了 ———————————————— 版权声明:本文为CSDN博主「冯相文要加油呀」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_51229250/article/details/119862087

标签: 06丝印z19三极管

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台