一、概述
最近有一个多相机标定的项目,大概是4个相机来标定,同一坐标系,然后拼接图,之前双相机标定的时候也大概看看,所以今天就找了那个halcon 案例多学一下,后面我打算做一个对位贴合的东西,类似于VM的那种,最后我想把整个流程封装起来。
思路:
1、做联合标定,每个相机的内参标定,但是外参第二个相机是基于第一相机的
2、获得标定板(只有一个标定板)相机的第一个姿态,作为第一个相机的决定姿态 FirstPose
这个FirstPose 是对世界坐标的
3、对第一个相机的绝对姿态还原高度,由于那个标定板是有高度的
从此开始,第二个参数的pose 是基于第一个相机的
4、获得第二个相机的相对第一个相机的姿态
5、我们第二个相机是姿态的,那么我们想要从第二个相机-》世界坐标===>pose2->FirstPose-》世界坐标
6、把那个尺子的高度也加进去,生成映射
7、还原图像 拼接图
二、算子解释
create_calib_data
* 创建一个Halcon标定数据模型(即建立标定对象),用于存储相机标定的过程、标定数据以及相机标定或手眼标定的结果
* 'calibration_object'类型用于基于从对象标定的观测中提取的度量信息来校准一个或多个相机的相机内参和相机姿态(相机外参);
* 2 表示两个连个相机
* 1 表示的是一个标定板
* CalibDataID 标定的数据
create_calib_data ('calibration_object', 2, 1, CalibDataID)
set_calib_data_cam_param
* set_calib_data_cam_param( : : CalibDataID, CameraIdx, CameraType, CameraParam : ) 功能:在标定数据模型中设置相机的类型和初始参数。
* 参数1:CalibDataID:标定数据模型句柄;
* 参数2:CameraIdx:摄像机索引,默认值为0;
* 参数3:CameraType:摄像机类型,默认值: [];
* 参数4:CameraParam:摄像机初始内参。
set_calib_data_cam_param (CalibDataID, 0, [], StartCamParam1)
set_calib_data_calib_object
*标定文件 这个是相机标定的重要部分
* set_calib_data_calib_object( : : CalibDataID, CalibObjIdx, CalibObjDescr : )功能:在标定模型中定义标定对象(设置标定板描述文件)。
* 参数1:CalibDataID:标定数据模型句柄;
* 参数2:CalibObjIdx:标定板索引,默认值0;
* 参数3:CalibObjDescr:标定板三维点坐标或标定板描述文件名
set_calib_data_calib_object (CalibDataID, 0, 'calplate_160mm.cpd')
find_calib_object
* find_calib_object(Image : : CalibDataID, CameraIdx, CalibObjIdx, CalibObjPoseIdx, GenParamName, GenParamValue : )功能:找到Halcon标定板,并在标定数据模型中设置提取的点和轮廓。
* 参数1:Image:输入图像;
* 参数1:CalibDataID: 标定数据模型句柄;
* 参数2:CameraIdx: 摄像机索引,默认值为0;
* 参数3:CalibObjIdx:标定板索引,默认值0;
* 参数4:CalibObjPoseIdx:观察到的标定版的索引;
* 参数5:GenParamName:待设置的通用参数的名称,默认值[];
* 参数6:GenParamValue:待设置的通用参数的值,默认值[]。
*第一个相机(坐标的)
find_calib_object (ImageCam1, CalibDataID, 0, 0, I, [], [])
get_calib_data
* get_calib_data( : : CalibDataID, ItemType, ItemIdx, DataName : DataValue):查询存储在标定模型中的数据(比如相机的内参和外参)。
* 控制输入参数1:CalibDataID:标定数据模型句柄;
* 控制输入参数2:ItemType:数据类型,'camera':表示要获取数据类型是与摄像机相关数据; 'calib_obj_pose':表示要获取数据类型与标定板位姿相关数据
* 控制输入参数3:ItemIdx:ItemIdx:输入参数,ItemType='camera'时,ItemIdx表示摄像机索引;ItemType='calib_obj_pose'时,ItemIdx是一个数组[CalibObjIdx, CalibObjPoseIdx],其中CalibObjIdx表示标定板索引,CalibObjPoseIdx表示参考位姿的图像索引;
* 控制输入参数4:DataName:输入要查询的属性名,'params'表示摄像机内参数; 'pose'表示摄像机外参数;
get_calib_data_observ_contours
*get_calib_data_observ_contours( : Contours : CalibDataID, ContourName, CameraIdx, CalibObjIdx, CalibObjPoseIdx : ) 功能:从标定数据模型中提取轮廓。
*图像输出参数:Contours:输出轮廓;
*控制输入参数1:CalibDataID:标定数据模型句柄;
*控制输入参数2:ContourName:待返回的轮廓对象的名称,Default value: 'marks':提取标定板所有标记点的轮廓,'caltab':提取标定板中心6个标记点的轮廓;
*控制输入参数3:CameraIdx:摄像机索引,默认值为0;
*控制输入参数4:CalibObjIdx:标定板索引,默认值0;
*控制输入参数5:CalibObjPoseIdx:观察到的标定板位姿的索引。
get_calib_data_observ_contours (ContoursCam1, CalibDataID, 'marks', 0, 0, I)
camera-pose
获得基于第一个相机的第二个相机的Pose
get_calib_data (CalibDataID, 'camera', 1, 'pose', RelPose2)
*relative 相对
* To get the absolute pose of the second camera, its relative pose needs
* to be inverted and combined with the absolute pose of the first camera.
*为了获得第二个相机的绝对姿态,需要将其相对姿态反转并与第一个相机的绝对姿态合并
set_origin_pose
*[0,0] --》 获得第一个标定板第一张图形的姿态(这里只有一个标定板)
*我这里是10组图像 只有一个标定板,所以
get_calib_data (CalibDataID, 'calib_obj_pose', [0,0], 'pose', Pose1)
PP:=Pose1
* Since the calibration plate has a certain thickness, the pose of the first camera
* needs to be corrected by the thickness, which is 4mm here.
* set_origin_pose( : : PoseIn, DX, DY, DZ : PoseNewOrigin):计算原始的3D位姿经过向量平移之后新的位姿。
* 控制输入参数1:PoseIn:原始的3D位姿;
* 控制输入参数2:DX:沿着世界坐标的X轴的平移量;
* 控制输入参数3:DY:沿着世界坐标Y轴的平移量;
* 控制输入参数4:DZ:沿着世界坐标Z轴的平移量; 高度 0.004 4mm 表示的标定板的厚度是4mm
* 控制输出参数:PoseNewOrigin:输出新的位姿。
set_origin_pose (Pose1, 0, 0, 0.004, Pose1)
结果:
CalibDataID
image_points_to_world_plane
* image_points_to_world_plane( : : CameraParam, WorldPose, Rows, Cols, Scale : X, Y):将图像点变换到世界坐标系的z=0平面中,
* 并返回它们在3D坐标中的X和Y值。
* Map1 输出隐射
* 控制输入参数1: CameraParam:相机内参;
* 控制输入参数2:WorldPose:相机坐标系中世界坐标系的三维姿态(相机外参);
* 控制输入参数3: (Rows, Cols):待转换点的坐标;
* 控制输入参数4:Scale:比例或尺寸,Default value: 'm';
* 控制输出参数:X:世界坐标系中点的X坐标;
* 控制输出参数:Y:世界坐标系中点的Y坐标。
gen_image_to_world_plane_map
*gen_image_to_world_plane_map( : Map : CameraParam, WorldPose, WidthIn, HeightIn, WidthMapped, HeightMapped, Scale, MapType : )
*—生成一个投影图,该投影图描述图像平面与世界坐标系的平面z = 0之间的映射。
* Map [OUT] 输出的映射图
* CameraParam [IN] 相机内参
* WorldPose [IN] 世界坐标系在摄像机坐标系中的3D位姿
* WidthIn [IN] 要转换的图像的宽
* HeightIn [IN] 要转换的图像的高
* WidthMapped [IN] 映射图的宽
* HeightMapped [IN] 映射图的高
* Scale [IN] 坐标系的单位尺寸
* 1um的像素大小意味着变换后的图像中的像素对应于测量平面中的1um x 1um区域,默认单位是m
* Scale=sqrt(像素当量(m))
* MapType[IN] 映射的算法类型
Scale2:=sqrt(4.40189e-06/100)//4.40189e-06mm 是上面标定相机内参得到的像素当量
gen_image_to_world_plane_map (Map1, CamParam1, WorldPose1, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale2, 'bilinear')
gen_image_to_world_plane_map (Map2, CamParam2, WorldPose2, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale2, 'bilinear')
三、halcon 代码
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)
* 创建一个Halcon标定数据模型(即建立标定对象),用于存储相机标定的过程、标定数据以及相机标定或手眼标定的结果
* 'calibration_object'类型用于基于从对象标定的观测中提取的度量信息来校准一个或多个相机的相机内参和相机姿态(相机外参);
* 2 表示两个连个相机
* 1 表示的是一个标定板
* CalibDataID 标定的数据
create_calib_data ('calibration_object', 2, 1, CalibDataID)
* set_calib_data_cam_param( : : CalibDataID, CameraIdx, CameraType, CameraParam : ) 功能:在标定数据模型中设置相机的类型和初始参数。
* 参数1:CalibDataID:标定数据模型句柄;
* 参数2:CameraIdx:摄像机索引,默认值为0;
* 参数3:CameraType:摄像机类型,默认值: [];
* 参数4:CameraParam:摄像机初始内参。
set_calib_data_cam_param (CalibDataID, 0, [], StartCamParam1)
set_calib_data_cam_param (CalibDataID, 1, [], StartCamParam2)
*标定文件 这个是相机标定的重要部分
* set_calib_data_calib_object( : : CalibDataID, CalibObjIdx, CalibObjDescr : )功能:在标定模型中定义标定对象(设置标定板描述文件)。
* 参数1:CalibDataID:标定数据模型句柄;
* 参数2:CalibObjIdx:标定板索引,默认值0;
* 参数3:CalibObjDescr:标定板三维点坐标或标定板描述文件名
set_calib_data_calib_object (CalibDataID, 0, 'calplate_160mm.cpd')
*
*
for I := 0 to NumCalibImages - 1 by 1
* 分别读取左右两个相机的图片
read_image (ImageCam1, ImagePath + '/calib_cam_1_' + (I + 1)$'02d')
read_image (ImageCam2, ImagePath + '/calib_cam_2_' + (I + 1)$'02d')
*
* 找到标定板 分别开始标定
* find_calib_object(Image : : CalibDataID, CameraIdx, CalibObjIdx, CalibObjPoseIdx, GenParamName, GenParamValue : )功能:找到Halcon标定板,并在标定数据模型中设置提取的点和轮廓。
* 参数1:Image:输入图像;
* 参数1:CalibDataID: 标定数据模型句柄;
* 参数2:CameraIdx: 摄像机索引,默认值为0;
* 参数3:CalibObjIdx:标定板索引,默认值0;
* 参数4:CalibObjPoseIdx:观察到的标定版的索引;
* 参数5:GenParamName:待设置的通用参数的名称,默认值[];
* 参数6:GenParamValue:待设置的通用参数的值,默认值[]。
*第一个相机(坐标的)
find_calib_object (ImageCam1, CalibDataID, 0, 0, I, [], [])
*get_calib_data_observ_contours( : Contours : CalibDataID, ContourName, CameraIdx, CalibObjIdx, CalibObjPoseIdx : ) 功能:从标定数据模型中提取轮廓。
*图像输出参数:Contours:输出轮廓;
*控制输入参数1:CalibDataID:标定数据模型句柄;
*控制输入参数2:ContourName:待返回的轮廓对象的名称,Default value: 'marks':提取标定板所有标记点的轮廓,'caltab':提取标定板中心6个标记点的轮廓;
*控制输入参数3:CameraIdx:摄像机索引,默认值为0;
*控制输入参数4:CalibObjIdx:标定板索引,默认值0;
*控制输入参数5:CalibObjPoseIdx:观察到的标定板位姿的索引。
get_calib_data_observ_contours (ContoursCam1, CalibDataID, 'marks', 0, 0, I)
dev_set_window (WindowHandle1)
dev_display (ImageCam1)
dev_display (ContoursCam1)
* get_calib_data_observ_points (CalibDataID, 0, 0, 0, Row, Column, Index, Pose)
dev_disp_text ('Image ' + (I + 1) + '/' + NumCalibImages + ' (Camera 1)', 'window', 'top', 'left', 'black', [], [])
* 第二个相机
find_calib_object (ImageCam2, CalibDataID, 1, 0, I, [], [])
get_calib_data_observ_contours (ContoursCam2, CalibDataID, 'marks', 1, 0, I)
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
stop ()
*
* calibrate_cameras( : : CalibDataID : Error)功能:标定相机的内参和外参。
* 控制输入参数:CalibDataID:标定数据模型句柄;
* 控制输出参数:Error:均方根误差 (RMSE)
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 ()
*
* ========================================获取标定的参数=====================================================
NumObjects := 2
NumObjImages := 2
*
* get_calib_data( : : CalibDataID, ItemType, ItemIdx, DataName : DataValue):查询存储在标定模型中的数据(比如相机的内参和外参)。
* 控制输入参数1:CalibDataID:标定数据模型句柄;
* 控制输入参数2:ItemType:数据类型,'camera':表示要获取数据类型是与摄像机相关数据; 'calib_obj_pose':表示要获取数据类型与标定板位姿相关数据
* 控制输入参数3:ItemIdx:ItemIdx:输入参数,ItemType='camera'时,ItemIdx表示摄像机索引;ItemType='calib_obj_pose'时,
* ItemIdx是一个数组[CalibObjIdx, CalibObjPoseIdx],
* 其中CalibObjIdx表示标定板索引,CalibObjPoseIdx表示参考位姿的图像索引;
* 控制输入参数4:DataName:输入要查询的属性名,'params'表示摄像机内参数; 'pose'表示摄像机外参数;
get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam1)
get_calib_data (CalibDataID, 'camera', 1, 'params', CamParam2)
*
* Get pose with respect to the first camera (reference camera). The
* calibration image which lies in the same plane as the object is
* used as the rectification plane (the world plane the object
* images will be mapped onto). So e.g. if the object lies flat
* on the measurement plane, a calibration plate pose which lies in the
* same flat plane should also be used. Here, we take the first calibration
* image [CalibObjIdx,CalibObjPoseIdx] = [0,0] for the reference pose
*[0,0] --》 获得第一个标定板第一张图形的姿态(这里只有一个标定板)
*我这里是10组图像 只有一个标定板,所以 [0,0] 表示第一个标定板的第一张姿态
get_calib_data (CalibDataID, 'calib_obj_pose', [0,0], 'pose', FirstPose)
PP:=FirstPose
* Since the calibration plate has a certain thickness, the pose of the first camera
* needs to be corrected by the thickness, which is 4mm here.
* set_origin_pose( : : PoseIn, DX, DY, DZ : PoseNewOrigin):计算原始的3D位姿经过向量平移之后新的位姿。
* 控制输入参数1:PoseIn:原始的3D位姿;
* 控制输入参数2:DX:沿着世界坐标的X轴的平移量;
* 控制输入参数3:DY:沿着世界坐标Y轴的平移量;
* 控制输入参数4:DZ:沿着世界坐标Z轴的平移量; 高度 0.004 4mm 表示的标定板的厚度是4mm
* 控制输出参数:PoseNewOrigin:输出新的位姿。
set_origin_pose (FirstPose, 0, 0, 0.004, FirstPose)
*
* Get the pose of the second camera which is given relative to the first camera.
* 获得第二个相机相对于第一个相机姿态RelPose2 ,因为此时第一个相机的姿态是[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0]
get_calib_data (CalibDataID, 'camera', 1, 'pose', RelativePose2BaseFirstCamera)
*relative 相对
* To get the absolute pose of the second camera, its relative pose needs
* to be inverted and combined with the absolute pose of the first camera.
*为了获得第二个相机的绝对姿态,需要将其相对姿态反转并与第一个相机的绝对姿态合并
pose_invert (RelativePose2BaseFirstCamera, RelativePose2BaseFirstCameraInverted)
*第二个相机的相对逆姿态转为绝对姿态 世界坐标
pose_compose (RelativePose2BaseFirstCameraInverted, FirstPose, AbsolutePose2)
*
* Set width, height and scale for the target image so that the full object
* image fits well after the mapping.
get_image_size (ImageCam1, Width, Height)
TargetWidth := 1340
TargetHeight := 800
Scale := 0.0002
*
* The mapped images have some black borders which are cut off for display.
Borders := [125,110,665,1222]
*
* For the mapping of the object images, the camera poses need to be corrected
* again by the thickness of the objects. Here we use two different objects:
* A ruler with a thickness of about 2.5mm and a thin brochure which can be
* approximated with 0mm thickness.
*上面我们设置了标定板的高度是0.004,我们在原来的标定图像的基础上减去了0.004,但是这里由于我们测试的是一把尺子,其高度是
*0.0025,那么我们就要加上这个0.0025,也就说上面0.004是还原到平台,这里在平台上方尺子,所以要加上0.0025
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')
*
* Perform mosaicking on the example images.
* Therefore, we create a map which projects the camera images onto
* the rectification plane. Since this map depends on the thickness of
* the object, we generate it once for each object.
for OIdx := 0 to NumObjects - 1 by 1
*获得世界坐标系的姿态,由于Z的方向上我们上升了HeightCorrections,可以设XY,也可以不设XY,这里是设了的
* As mentioned above, the z component of the world poses needs to be corrected by the
* thickness of the object. Also correct the x and y values to move the origin of the
* calibration plate to the bottom right of the images. (Usually, the origin should be
* at (0,0). But since the x- and y-axis of the poses point to the reverse direction here, we
* also move the origin accordingly.)
set_origin_pose (FirstPose, -0.14, -0.07, HeightCorrections[OIdx], WorldPose1)
set_origin_pose (AbsolutePose2, -0.14, -0.07, HeightCorrections[OIdx], WorldPose2)
*
* Generate mappings to map the images to worldplane.
*gen_image_to_world_plane_map( : Map : CameraParam, WorldPose, WidthIn, HeightIn, WidthMapped, HeightMapped, Scale, MapType : )
*—生成一个投影图,该投影图描述图像平面与世界坐标系的平面z = 0之间的映射。
* Map [OUT] 输出的映射图
* CameraParam [IN] 相机内参
* WorldPose [IN] 世界坐标系在摄像机坐标系中的3D位姿
* WidthIn [IN] 要转换的图像的宽
* HeightIn [IN] 要转换的图像的高
* WidthMapped [IN] 映射图的宽
* HeightMapped [IN] 映射图的高
* Scale [IN] 坐标系的单位尺寸
* 1um的像素大小意味着变换后的图像中的像素对应于测量平面中的1um x 1um区域,默认单位是m
* Scale=sqrt(像素当量(m))
* MapType[IN] 映射的算法类型
* 计算出连个映射关系
Scale2:=sqrt(4.40189e-06/100)//4.40189e-06mm 是上面标定相机内参得到的像素当量
gen_image_to_world_plane_map (Map1, CamParam1, WorldPose1, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale2, 'bilinear')
gen_image_to_world_plane_map (Map2, CamParam2, WorldPose2, WidthCam1, HeightCam1, TargetWidth, TargetHeight, Scale2, 'bilinear')
*
* Map the object images.
for IIdx := 1 to NumObjImages by 1
ObjImageIdx := 2 * OIdx + IIdx
read_image (ImageCam1, ImagePath + '/obj_cam_1_' + ObjImageIdx$'02d')
read_image (ImageCam2, ImagePath + '/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, FirstPose, 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, AbsolutePose2, 0.05)
*
* Rectify images by mapping them into world coordinates.
*矫正图像通过上面得到的映射关系
* 映射图像
* Image [IN] 原始图
* Map [IN] 映射图
* ImageMapped [OUT] 映射后的图像
map_image (ImageCam1, Map1, ImageWorld1)
map_image (ImageCam2, Map2, ImageWorld2)
*
* Stitching the mapped images together.
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, 10)
*
* 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