多个相机标定在一起,即使用一个坐标系。其好处是可以一个大物料分成两个部分拍照,并且能准确测量出结果。
有些时候机构的限制或者拍照视野要求太大,我们就可以采用多相机拍照的方案。
这种需求,可以用下面的思路来完成标定:
用2个已经标定过的相机拍摄同一个标定板,建立一个世界坐标系
用九点标定获取世界坐标系,2个相机执行2次九点标定操作
或者,也可以参考下面的例子中的办法:
下面这个例子的功能是:
两个相机,各拍卡尺的一部分。
最后两个图像拼接到一起,并且进行高精度测量。
源代码如下:
* This example program shows how high precision mosaicking * can be performed with calibrated cameras. * * Further information can be found in the * Solution Guide III-C 3D Vision, Chapters 9 and 10. * This example program is used in chapter 9 to * illustrate calibrated mosaicking. * *示例程序展示了如何用校准的相机实现高精度的拼接。 *进一步的信息可以在解决方案指南III-C 3D视觉,第9章和第10章中找到。 *本示例程序在第9章中用于说明校准镶嵌。 dev_update_off () ImgPath := '3d_machine_vision/multiple_cameras/' * * Open two windows for the left and the right image. dev_close_window () read_image (Image1, ImgPath + 'camera1_ref') get_image_size (Image1, Width, Height) WindowScale := 0.66 dev_open_window (0, 0, Width * WindowScale, Height * WindowScale, 'black', WindowHandle1) dev_open_window (0, Width * WindowScale + 6, Width * WindowScale, Height * WindowScale,\ 'black', WindowHandle2) * * Set some parameters for both windows. dev_set_window (WindowHandle1) dev_set_color ('green') dev_set_draw ('margin') dev_set_line_width (2) dev_set_part (0, 0, Height - 1, Width - 1) set_display_font (WindowHandle1, 16, 'mono', 'true', 'false') * dev_set_window (WindowHandle2) dev_set_color ('green') dev_set_draw ('margin') dev_set_line_width (2) dev_set_part (0, 0, Height - 1, Width - 1) set_display_font (WindowHandle2, 16, 'mono', 'true', 'false') * * We assume that the two cameras are already calibrated * (we know the internal camera parameters). gen_cam_par_area_scan_division (0.01619, -734.789, 7.402e-006, 7.4e-006, 324.911,\ 256.894, 640, 480, CamParam1) gen_cam_par_area_scan_division (0.0162584, -763.35, 7.39842e-006, 7.4e-006, 324.176,\ 245.371, 640, 480, CamParam2) * * Read the images and display them. read_image (Image1, ImgPath + 'camera1_ref') read_image (Image2, ImgPath + 'camera2_ref') dev_set_window (WindowHandle1) dev_display (Image1) dev_set_window (WindowHandle2) dev_display (Image2) * * Prepare the camera calibration. CaltabName := 'caltab_30mm.descr' create_calib_data ('calibration_object', 2, 1, CalibDataID) set_calib_data_calib_object (CalibDataID, 0, CaltabName) set_calib_data_cam_param (CalibDataID, 0, [], CamParam1) set_calib_data_cam_param (CalibDataID, 1, [], CamParam2) * * Find and display the calibration plate in the images. dev_set_window (WindowHandle1) find_calib_object (Image1, CalibDataID, 0, 0, 0, [], []) get_calib_data_observ_points (CalibDataID, 0, 0, 0, RowCoord1, ColumnCoord1, Index1, Pose1) get_calib_data_observ_contours (Caltab1, CalibDataID, 'caltab', 0, 0, 0) dev_display (Caltab1) * dev_set_window (WindowHandle2) find_calib_object (Image2, CalibDataID, 1, 0, 0, [], []) get_calib_data_observ_points (CalibDataID, 1, 0, 0, RowCoord2, ColumnCoord2, Index2, Pose2) get_calib_data_observ_contours (Caltab2, CalibDataID, 'caltab', 1, 0, 0) dev_display (Caltab2) * disp_message (WindowHandle1, 'Calibration successful', 'window', 12, 12, 'black', 'true') disp_continue_message (WindowHandle1, 'black', 'true') stop () clear_calib_data (CalibDataID) * * Determine the offset between the calibration plate surface * and the object surface * 确定标定板表面与目标表面之间的偏移量 ThicknessCaliper := 2.9 / 1000.0 ThicknessPlate := 5.65 / 1000.0 DiffHeight := ThicknessPlate - ThicknessCaliper DistancePlates := 0.06488 * * Define the pixel size of the mosaic image (in meters). *定义马赛克图像的像素大小(以米为单位)。 PixelSize := 0.0001 * * For the first (left) image, the determination * of the necessary shift of the pose is straightforward. * You can define the upper left corner of the rectified image * in image coordinates * based on a preselected border width. *第一(左)图像,确定必要的转变的姿势很简单。 *您可以根据预先选择的边框宽度在图像坐标中定义校正图像的左上角 BorderInPercent := 7 get_image_size (Image1, WidthImage1, HeightImage1) UpperRow := HeightImage1 * BorderInPercent / 100.0 LeftColumn := WidthImage1 * BorderInPercent / 100.0 * * Then, this point must be transformed into world coordinates. *然后,这个点必须转换成世界坐标。 image_points_to_world_plane (CamParam1, Pose1, UpperRow, LeftColumn, 'm', LeftX, UpperY) *disp_circle(WindowHandle1,LeftX,UpperY,64) * * For the determination of the height of the rectified image * we need to define a point that lies near the lower border * of the first image. * 为了确定校正图像的高度,我们需要定义一个位于第一个图像的下边界附近的点。 LowerRow := HeightImage1 * (100 - BorderInPercent) / 100.0 * * Again, this point must be transformed * into the world coordinate system. *同样,这一点必须转换成世界坐标系。 image_points_to_world_plane (CamParam1, Pose1, LowerRow, LeftColumn, 'm', X1, LowerY) * * The height can be determined as the vertical distance * between the upper left point and the point near the * lower image border, expressed in pixels of the rectified image. *高度可以确定为左上点和靠近下图像边界的点之间的垂直距离,以校正图像的像素表示。 HeightRect := int((LowerY - UpperY) / PixelSize) * * Analogously, the width can be determined from a point t * hat lies in the overlapping area of the two images, * i.e., near the right border of the first image. *类似地,宽度可以由位于两个图像的重叠区域中的一个点来确定,在第一张图片的右边界附近。 OverlapInPercent := 20 RightColumn := WidthImage1 * (100 - OverlapInPercent / 2.0) / 100.0 image_points_to_world_plane (CamParam1, Pose1, UpperRow, RightColumn, 'm', RightX, Y1) WidthRect := int((RightX - LeftX) / PixelSize) * * With the shifted pose and the size of the rectified image, * the rectification map for the first image can be derived. *利用变换后的姿态和校正后图像的大小,可以得到第一幅图像的校正图。 set_origin_pose (Pose1, LeftX, UpperY, DiffHeight, PoseNewOrigin1) gen_image_to_world_plane_map (MapSingle1, CamParam1, PoseNewOrigin1, Width, Height, \ WidthRect, HeightRect, PixelSize, 'bilinear') * * Generate a new homogeneous transformation matrix. *生成一个新的齐次变换矩阵。 hom_mat3d_identity (HomMat3DIdentity) * * The second image must be rectified such that it fits exactly * to the right of the first rectified image. This means that the * upper left corner of the second rectified image must be identical * with the upper right corner of the first rectified image. * Therefore, we need to know the coordinates of the upper right corner * of the first rectified image in the coordinate system that is defined * by the calibration plate in the second image. * First, we express the upper right corner of the first rectified image * in the world coordinate system that is defined by the calibration plate * in the first image. It can be determined by a transformation from * the origin into the upper left corner of the * first rectified image (a translation) followed by a translation along * the upper border of the first rectified image. Together with the shift * that compensates the thickness of the calibration plate, this * transformation is represented by the homogeneous transformation matrix: *第二幅图像必须校正,使其正好适合第一幅校正图像的右侧。 *这意味着第二校正图像的左上角必须与第一校正图像的右上角相同。 *因此,我们需要知道第一个校正图像的右上角在由第二个图像中的校准板定义的坐标系中的坐标。 *首先,我们在世界坐标系中表示第一幅校正图像的右上角,该坐标系由第一幅图像中的校准板定义。 *它可以通过从原点到第一校正图像的左上角的变换(平移)以及沿着第一校正图像的上边界的平移来确定。 *与补偿校准板厚度的位移一起,该变换由齐次变换矩阵表示: hom_mat3d_translate_local (HomMat3DIdentity, LeftX + PixelSize * WidthRect, UpperY, DiffHeight, cp1Hur1) hom_mat3d_translate_local (HomMat3DIdentity, DistancePlates, 0, 0, cp1Hcp2) * * Then, we need the transformation between the two calibration plates of * the calibration object. The homogeneous transformation matrix cp1Hcp2 * describes how the world coordinate system defined by the calibration plate * in the first image is transformed into the world coordinate system * defined by the calibration plate in the second image. This transformation * must be known beforehand from a precise measurement of the calibration object. * From these two transformations, it is easy to derive * the transformation that transforms the world coordinate system * of the second image such that its origin lies in the upper left corner * of the second rectified image. For this, the two transformations * have to be combined appropriately. *然后,我们需要校准对象的两个校准板之间的转换。 *齐次变换矩阵cp1Hcp2描述如何将第一图像中校准板定义的世界坐标系转换为第二图像中校准板定义的世界坐标系。 *这种转换必须事先从校准对象的精确测量中得知。 *从这两个变换,很容易导出变换第二图像的世界坐标系的变换,使得其原点位于第二校正图像的左上角。 *为此,必须将这两个转换适当地结合起来。 hom_mat3d_invert (cp1Hcp2, cp2Hcp1) hom_mat3d_compose (cp2Hcp1, cp1Hur1, cp2Hul2) * * With this, the pose of the calibration plate in the second image * can be modified such that the origin of the world coordinate system * lies in the upper left corner of the second rectified image. *这样,可以修改第二图像中校准板的姿态,使得世界坐标系的原点位于第二校正图像的左上角。 pose_to_hom_mat3d (Pose2, cam2Hcp2) hom_mat3d_compose (cam2Hcp2, cp2Hul2, cam2Hul2) hom_mat3d_to_pose (cam2Hul2, PoseNewOrigin2) * * With the resulting new pose and the size of the rectified image, * which can be the same as for the first rectified image, * the rectification map for the second image can be derived. *根据得到的新姿势和校正图像的大小(其可以与第一校正图像相同),可以导出第二图像的校正地图。 gen_image_to_world_plane_map (MapSingle2, CamParam2, PoseNewOrigin2, Width, Height,\ WidthRect, HeightRect, PixelSize, 'bilinear') * * Open a new Graphics Window for the merged image. *打开合并图像的新图形窗口。 dev_open_window (Height * WindowScale, 0, Width * 2 * WindowScale, Height * WindowScale, \ 'black', WindowHandleCombined) set_display_font (WindowHandleCombined, 16, 'mono', 'true', 'false') dev_set_color ('green') dev_set_draw ('margin') ScalePlot := 200 RowPlot := 400 Coord := [0:2000] * * Process all image pairs in a loop. *循环处理所有图像对 for I := 1 to 3 by 1 * * Display both images. dev_set_window (WindowHandle1) read_image (Image1, ImgPath + 'camera1_' + I$'02d') get_image_size (Image1, WidthImage1, HeightImage1) dev_set_part (0, 0, HeightImage1 - 1, WidthImage1 - 1) dev_display (Image1) dev_set_window (WindowHandle2) read_image (Image2, ImgPath + 'camera2_' + I$'02d') get_image_size (Image2, WidthImage2, HeightImage2) dev_set_part (0, 0, HeightImage2 - 1, WidthImage2 - 1) dev_display (Image2) *tile_images(Image1, Image2, 2, 'vertical') * * Start the time measurement. count_seconds (TimeStart1) * * Rectify the image pair from the two-camera setup with map_image. *使用map_image校正两个摄像机设置中的图像对 map_image (Image1, MapSingle1, RectifiedImage1) map_image (Image2, MapSingle2, RectifiedImage2) concat_obj (RectifiedImage1, RectifiedImage2, Concat) * End the time measurement and calculate the difference. *结束时间测量并计算差异 count_seconds (TimeEnd1) Time1 := TimeEnd1 - TimeStart1 * dev_set_window (WindowHandleCombined) * Start the time measurement again. count_seconds (TimeStart2) * * Tile both images into one large image. * 将两个图像平铺成一个大图像 tile_images (Concat, Combined, 2, 'vertical') * * End the time measurement again and calculate the difference. count_seconds (TimeEnd2) Time2 := TimeEnd2 - TimeStart2 * * Display the combined image and the time measurement. get_image_size (Combined, WidthComb, HeightComb) dev_set_part (0, 0, HeightComb - 1, WidthComb - 1) dev_display (Combined) disp_message (WindowHandle1, 'Merge cameras: ' + (1000 * (Time1 + Time2))$'.3' + ' ms', \ 'window', 12, 12, 'black', 'true') * * In addition, we plot the accuracy of the mosaicking with a procedure. *此外,我们用一个程序来绘制马赛克的准确性。 plot_mosaicking_accuracy (Combined, WidthRect, HeightRect, WindowHandleCombined,\ Coord, ScalePlot, RowPlot) if (I < 3) disp_continue_message (WindowHandleCombined, 'black', 'true') stop () endif endfor
外部函数plot_mosaicking_accuracy参数如下:
* Segment graduation lines in combined image full_domain (Combined, Combined) mean_image (Combined, ImageMean, 9, 9) dyn_threshold (Combined, ImageMean, RegionDynThresh, 6, 'dark') connection (RegionDynThresh, ConnectedRegions) select_shape (ConnectedRegions, SelectedRegions, ['anisometry','anisometry'], 'or', [10,0], [70,0]) select_shape (SelectedRegions, SelectedRegions3, 'area', 'and', 10, 300) count_obj (SelectedRegions3, Number) if (Number > 10) elliptic_axis (SelectedRegions3, Ra, Rb, Phi) Angle := sort(Phi)[|Phi| / 2] select_shape (SelectedRegions3, SelectedRegions1, ['phi','phi'], 'or', [Angle - rad(5),Angle - rad(185)], [Angle + rad(5),Angle - rad(175)]) * Generate measure rectangle and measure distances * between graduation lines. union1 (SelectedRegions1, RegionUnion) gen_rectangle2 (Morph, 20, 20, Angle + rad(90), 20, 3) closing (RegionUnion, Morph, RegionClosing) connection (RegionClosing, ConnectedRegions1) select_shape_std (ConnectedRegions1, SelectedRegions2, 'max_area', 0) smallest_rectangle2 (SelectedRegions2, Row, Column, Phi1, Length1, Length2) gen_rectangle2 (Rectangle, Row, Column, Phi1, max([min([Length1 - 15,700]),5]), Length2 / 2) gen_measure_rectangle2 (Row, Column, Phi1, max([min([Length1 - 15,700]),5]), Length2 / 2, MapWidth * 2, MapHeight, 'bilinear', MeasureHandle) measure_pairs (Combined, MeasureHandle, 1, 5, 'negative', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance) close_measure (MeasureHandle) * Plot measurement error if (|RowEdgeFirst| > 10) Dist := (IntraDistance[0:|IntraDistance| - 2] + InterDistance) / 10.0 dev_display (Combined) dev_set_color ('green') dev_set_line_width (2) dev_display (Rectangle) Mean := mean(Dist) Deviation := deviation(Dist) Message := 'Mean Distance: ' + Mean$'.4' + ' mm' Message[1] := 'Deviation: ' + (Deviation * 1000)$'.4' + ' µm' Message[2] := 'Max. Error: ' + (max(abs(Dist - 1)) * 1000)$'.4' + ' µm' disp_message (WindowHandleCombined, Message, 'window', 12, 12, 'black', 'true') Coord2 := ColumnEdgeFirst[0:|ColumnEdgeFirst| - 2] / 10 if (abs(max(Dist - 1)) < 0.09) gen_contour_polygon_xld (ErrorFunction, (Dist - 1) * 10 * ScalePlot + RowPlot, Coord2 * 10) gen_contour_polygon_xld (ErrorZero, Dist * 0 + RowPlot, Coord2 * 10) gen_contour_polygon_xld (ErrorMin01mm, Dist * 0 - 0.1 * ScalePlot + RowPlot, Coord2 * 10) gen_contour_polygon_xld (ErrorPlus01mm, Dist * 0 + 0.1 * ScalePlot + RowPlot, Coord2 * 10) for I := 0 to |ColumnEdgeSecond| - 1 by 1 if (ColumnEdgeSecond[I] > MapWidth) break endif endfor gen_contour_polygon_xld (UpperMarkForSeem, [-0.1 * ScalePlot + RowPlot,-0.15 * ScalePlot + RowPlot], [659.5,659.5]) gen_contour_polygon_xld (LowerMarkForSeem, [0.1 * ScalePlot + RowPlot,0.15 * ScalePlot + RowPlot], [659.5,659.5]) dev_set_color ('cyan') dev_display (ErrorZero) dev_display (ErrorPlus01mm) dev_display (ErrorMin01mm) dev_display (UpperMarkForSeem) dev_display (LowerMarkForSeem) if (RowEdgeFirst[0] < RowEdgeFirst[|RowEdgeFirst| - 1]) ColumnText := ColumnEdgeFirst[0] else ColumnText := ColumnEdgeFirst[|ColumnEdgeFirst| - 1] - 120 endif disp_message (WindowHandleCombined, '+0.01mm', 'image', RowPlot - 51, ColumnText, 'cyan', 'false') disp_message (WindowHandleCombined, '-0.01mm', 'image', RowPlot + 18, ColumnText, 'cyan', 'false') dev_set_color ('yellow') dev_set_line_width (2) dev_display (ErrorFunction) * dev_set_line_width (1) * dev_set_color ('white') * gen_arrow_contour_xld (Arrow, RowEdgeSecond[I - 1], 659.5, -0.16 * ScalePlot + RowPlot, 659.5, 5, 5) * dev_display (Arrow) endif else dev_display (Combined) endif else dev_display (Combined) endif return ()
本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:


