1.Halcon圖像拼接算法在之前的文章里也寫過,主要是硬拼接和特征點拼接兩種方式,今天增加另一種拼接圖像的方式。應用場景是多個相機聯合一起拍大尺寸的物體,并且相機視野之間存在重疊區域。通過在同一個標定板上面標定,計算兩個相機之間位相對外參矩陣實現拼接。
2.代碼分析
dev_update_off ()
*
- Path to the calibration and object images.
ImagePath := ‘3d_machine_vision/calibrated_mosaic/’ - Display workflow explanation text.
dev_close_window ()
dev_open_window (0, 0, 600, 300, ‘black’, WindowHandle1)
set_display_font (WindowHandle1, 16, ‘mono’, ‘true’, ‘false’)
dev_disp_workflow_text ()
stop () - Display calibration explanation text.
dev_clear_window ()
dev_disp_calibration_text ()
stop ()
dev_close_window ()
dev_open_window (0, 0, 600, 480, ‘black’, WindowHandle1)
dev_open_window (0, 605, 600, 480, ‘black’, WindowHandle2)
set_display_font (WindowHandle1, 16, ‘mono’, ‘true’, ‘false’)
set_display_font (WindowHandle2, 16, ‘mono’, ‘true’, ‘false’)
*
- *********** Step 1: Calibration of the cameras ***********
- Number of calibration images.
NumCalibImages := 10 - 從每個攝像頭讀取一張圖像,以獲得攝像頭的寬度和高度。
read_image (ImageCam1, ImagePath + ‘/calib_cam_1_01’)
read_image (ImageCam2, ImagePath + ‘/calib_cam_2_01’)
get_image_size (ImageCam1, WidthCam1, HeightCam1)
get_image_size (ImageCam2, WidthCam2, HeightCam2) - 設置內部相機參數的初始值。
gen_cam_par_area_scan_division (0.012, 0, 4.4e-6, 4.4e-6, WidthCam1 / 2, HeightCam1 / 2, WidthCam1, HeightCam1, StartCamParam1)
gen_cam_par_area_scan_division (0.012, 0, 4.4e-6, 4.4e-6, WidthCam2 / 2, HeightCam2 / 2, WidthCam2, HeightCam2, StartCamParam2) - 創建標定模型,并設置參數,此模型同時可以校準兩個相機
create_calib_data (‘calibration_object’, 2, 1, CalibDataID)
set_calib_data_cam_param (CalibDataID, 0, [], StartCamParam1)
set_calib_data_cam_param (CalibDataID, 1, [], StartCamParam2)
set_calib_data_calib_object (CalibDataID, 0, ‘calplate_160mm.cpd’) - 依次訪問兩個相機的標定板圖片,進行標定操作
for I := 0 to NumCalibImages - 1 by 1- Read the calibration images.
read_image (ImageCam1, ImagePath + ‘/calib_cam_1_’ + (I + 1)KaTeX parse error: Double subscript at position 56: …+ '/calib_cam_2_?' + (I + 1)‘02d’) - Find the calibration plate and store observations
- in the calibration data model.
find_calib_object (ImageCam1, CalibDataID, 0, 0, I, [], [])
get_calib_data_observ_contours (ContoursCam1, CalibDataID, ‘marks’, 0, 0, I)
find_calib_object (ImageCam2, CalibDataID, 1, 0, I, [], [])
get_calib_data_observ_contours (ContoursCam2, CalibDataID, ‘marks’, 1, 0, I) - Display calibration images and observed contours.
dev_set_window (WindowHandle1)
dev_display (ImageCam1)
dev_display (ContoursCam1)
dev_disp_text (‘Image ’ + (I + 1) + ‘/’ + NumCalibImages + ’ (Camera 1)’, ‘window’, ‘top’, ‘left’, ‘black’, [], [])
dev_set_window (WindowHandle2)
dev_display (ImageCam2)
dev_display (ContoursCam2)
dev_disp_text (‘Image ’ + (I + 1) + ‘/’ + NumCalibImages + ’ (Camera 2)’, ‘window’, ‘top’, ‘left’, ‘black’, [], [])
disp_continue_message (WindowHandle2, ‘black’, ‘true’)
stop ()
endfor
- Read the calibration images.
*同時校準兩個攝像頭.
calibrate_cameras (CalibDataID, Errors)
dev_set_window (WindowHandle1)
dev_disp_text (‘Calibration successful’, ‘window’, ‘top’, ‘left’, ‘green’, [], [])
dev_set_window (WindowHandle2)
dev_disp_text (‘Calibration successful’, ‘window’, ‘top’, ‘left’, ‘green’, [], [])
disp_continue_message (WindowHandle2, ‘black’, ‘true’)
stop ()
*
- ******************* Step 2: Mosaicking *******************
- Display mosaicking explanation text.
dev_close_window ()
dev_close_window ()
dev_open_window (0, 0, 600, 300, ‘black’, WindowHandle1)
set_display_font (WindowHandle1, 16, ‘mono’, ‘true’, ‘false’)
dev_disp_mosaicking_text ()
stop () - Number of objects.
NumObjects := 2 - Number of images per object.
NumObjImages := 2 - 獲取兩個相機各自的標定好的參數,相機內參
get_calib_data (CalibDataID, ‘camera’, 0, ‘params’, CamParam1)
get_calib_data (CalibDataID, ‘camera’, 1, ‘params’, CamParam2) - 獲取相對于第一個相機(參考相機)的位姿
get_calib_data (CalibDataID, ‘calib_obj_pose’, [0,0], ‘pose’, Pose1)
*設置標定板厚度4mm,注意算子需要用的單位是米m
set_origin_pose (Pose1, 0, 0, 0.004, Pose1) - 獲取第二臺相機相對于第一臺相機的位姿.
get_calib_data (CalibDataID, ‘camera’, 1, ‘pose’, RelPose2)
*為了獲得第二個相機的絕對姿態,它的相對姿態需要
*以被反轉并與第一相機的絕對姿態相結合。
pose_invert (RelPose2, RelPose2Inverted)
pose_compose (RelPose2Inverted, Pose1, Pose2)
*設置目標圖像寬度、高度和比例,以便整個對象
*映射后圖像擬合良好。
TargetWidth := 1340
TargetHeight := 800
*像素當量(1顆像素0.0002m),注意單位是m/pixel
Scale := 0.0002
- 設置區域,可以裁剪掉有黑邊的圖像區域
Borders := [125,110,665,1222]
*為了映射對象圖像,需要校正相機的姿態
*再次取決于物體的厚度。這里我們使用兩個不同的對象:
*一把厚度約為2.5mm的尺子和一本薄薄的小冊子,可以
*近似為0mm厚度。
HeightCorrections := [-0.0025,0]
*
dev_close_window ()
dev_open_window (0, 0, 600, 480, ‘black’, WindowHandle1)
dev_open_window (0, 605, 600, 480, ‘black’, WindowHandle2)
dev_open_window (535, 0, 740, 360, ‘black’, WindowHandle3)
set_display_font (WindowHandle1, 16, ‘mono’, ‘true’, ‘false’)
set_display_font (WindowHandle2, 16, ‘mono’, ‘true’, ‘false’)
set_display_font (WindowHandle3, 16, ‘mono’, ‘true’, ‘false’)
for OIdx := 0 to NumObjects - 1 by 1
* 設置矯正圖像原點位置和標定板厚度
set_origin_pose (Pose1, -0.14, -0.07, HeightCorrections[OIdx], WorldPose1)
set_origin_pose (Pose2, -0.14, -0.07, HeightCorrections[OIdx], WorldPose2)
*
* 生成矯正矩陣
gen_image_to_world_plane_map (Map1, CamParam1, WorldPose1, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale, ‘bilinear’)
gen_image_to_world_plane_map (Map2, CamParam2, WorldPose2, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale, ‘bilinear’)
*
* 矯正圖像
for IIdx := 1 to NumObjImages by 1
ObjImageIdx := 2 * OIdx + IIdx
read_image (ImageCam1, ImagePath + ‘/obj_cam_1_’ + ObjImageIdxKaTeX parse error: Double subscript at position 62: …h + '/obj_cam_2_?' + ObjImageIdx’02d’)
*
* Display input images and camera poses.
dev_set_window (WindowHandle1)
dev_display (ImageCam1)
dev_disp_text (‘Camera 1 image’, ‘window’, ‘top’, ‘left’, ‘black’, [], [])
disp_3d_coord_system (WindowHandle1, CamParam1, Pose1, 0.05)
dev_set_window (WindowHandle2)
dev_display (ImageCam2)
dev_disp_text (‘Camera 2 image’, ‘window’, ‘top’, ‘left’, ‘black’, [], [])
disp_3d_coord_system (WindowHandle2, CamParam2, Pose2, 0.05)
*
* 通過將圖像映射到世界坐標系來優化圖像。
map_image (ImageCam1, Map1, ImageWorld1)
map_image (ImageCam2, Map2, ImageWorld2)
*
* 將映射的圖像拼接在一起。
get_domain (ImageWorld1, Domain1)
get_domain (ImageWorld2, Domain2)
intersection (Domain1, Domain2, RegionIntersection)
paint_region (RegionIntersection, ImageWorld1, ImageWorld1Blackended, 0, ‘fill’)
full_domain (ImageWorld1Blackended, ImagePart1)
full_domain (ImageWorld2, ImagePart2)
add_image (ImagePart1, ImagePart2, ImageFull, 1, 0)
*
* Rotate image and remove the black borders for display.
rotate_image (ImageFull, ImageRotated, 12, ‘constant’)
gen_rectangle1 (RectangleDomain, Borders[0], Borders[1], Borders[2], Borders[3])
reduce_domain (ImageRotated, RectangleDomain, ImageReduced)
crop_domain (ImageReduced, ImageReduced)
mirror_image (ImageReduced, ImageReduced, ‘row’)
mirror_image (ImageReduced, ImageResult, ‘column’)
*
* Display the result image.
dev_set_window (WindowHandle3)
dev_display (ImageResult)
dev_disp_text (‘Result image’, ‘window’, ‘top’, ‘left’, ‘black’, [], [])
if (ObjImageIdx < NumObjects * NumObjImages)
disp_continue_message (WindowHandle3, ‘black’, ‘true’)
stop ()
else
dev_disp_text (‘End of program’, ‘window’, ‘bottom’, ‘right’, ‘black’, [], [])
endif
endfor
endfor