1 | initial version |
Link to the corresponding Stack Overflow Q&A.
2 | No.2 Revision |
We placed a support request to MVTec and received the following answers (received on Thu NOV 21 2019; 09:27):
A conversion between OpenCV and HALCON calibration parameters is not possible.
Reason is that the HALCON polynomial model uses an equation system where the distorted image coordinates are given on the right. Thus the distortion coefficients describe this computation direction. On the contrary, the OpenCV implementation uses an equation system where the undistorted image coordinates are given on the right. Thus the distortion coefficients describe the inverted direction. Because of the high polynomial grade, a conversion is not possible.
To compare the equations, please refer to operator reference of calibrate_cameras and the OpenCV camera calibration tutorial.
The division model that can be inverted analytically does not exist in OpenCV.
We also got an hdev
script for an approximated mapping from HALCON to OpenCV parameters (received Thu NOV 21 2019; 16:27):
<?xml version="1.0" encoding="UTF-8"?>
<hdevelop file_version="1.2" halcon_version="19.05.0.0">
<procedure name="main">
<interface/>
<body>
<c>************************************************************************************************</c>
<c>* Parameter</c>
<c>************************************************************************************************</c>
<c></c>
<l>PathImg := './'</l>
<l>PathIdRect := '.rect.'</l>
<l>ZoomDisplay := 0.3</l>
<c></c>
<c>* Distortions from OpenCV</c>
<l>Distortions := [-0.161881, 0.092025, 0.000072, -0.000105, 0.000000]</l>
<c>*</c>
<c>* Camera matrices from OpenCV</c>
<l>* CamMatrixOpenCV := [1402.101918, 0.000000, 967.367190,\
0.000000, 1399.751916, 580.546496,\
0.000000, 0.000000, 1.000000]</l>
<c></c>
<c>* CamMatrixOpenCV</c>
<l>Cx := 967.3672</l>
<l>Cy := 580.546496</l>
<l>fxPix := 1402.101918</l>
<l>fyPix := 1399.751916</l>
<l>f := 0.00824144</l>
<c>*</c>
<l>* ProjectionOpenCV := [ fxPix, 0.000000, Cx, 0.000000,\
0.000000, fyPix, Cy, 0.000000,\
0.000000, 0.000000, 1.000000, 0.000000,\
0, 0, 0, 1 ]</l>
<c></c>
<c>************************************************************************************************</c>
<c>* Initialization</c>
<c>************************************************************************************************</c>
<c></c>
<l>dev_update_off ()</l>
<c></c>
<c>* Prepare the image data.</c>
<l>list_image_files (PathImg, 'png', [], ImageFilesAll)</l>
<l>ImageFilesRectified := regexp_select(ImageFilesAll, PathIdRect)</l>
<l>ImageFilesNonRectified := difference(ImageFilesAll, ImageFilesRectified)</l>
<l>if (|ImageFilesNonRectified| != |ImageFilesRectified|)</l>
<l> throw (['Uneven amounts of images found, please check the image pairs'])</l>
<l>endif</l>
<c></c>
<c>* Prepare the display</c>
<l>read_image (Image, ImageFilesNonRectified[0])</l>
<l>get_image_size (Image, Width, Height)</l>
<l>for I := 0 to 2 by 1</l>
<l> dev_open_window (0, I*(Width*ZoomDisplay+12), Width*ZoomDisplay, Height*ZoomDisplay, 'black', WindowHandles.at(I))</l>
<l> set_display_font (WindowHandles.at(I), 16, 'mono', 'true', 'false')</l>
<l>endfor</l>
<c></c>
<c>* Perform the calibration using an arbitrary grid (full image also possible but slow)</c>
<l>gen_grid_region (RegionGrid, 5, 5, 'points', Width, Height)</l>
<l>get_region_points (RegionGrid, Row, Col)</l>
<l>campar_opencv2halcon (Distortions, f, Cx, Cy, fxPix, fyPix, Row, Col, Width, Height, Error, CamParamsOpt)</l>
<l>change_radial_distortion_cam_par ('adaptive', CamParamsOpt, [0,0,0,0,0], CamParamOptRect) </l>
<l>dev_set_window (WindowHandles.at(0))</l>
<l>dev_disp_text ('Error (pxl): ' + Error, 'window', 'top', 'left', 'black', [], [])</l>
<l>dev_disp_text (['HALCON camera params:', CamParamsOpt], 'window', 'bottom', 'left', 'black', [], [])</l>
<l>disp_continue_message (WindowHandles.at(0), 'black', 'true')</l>
<l>stop ()</l>
<c></c>
<c>* Ignore errors at the close image border</c>
<l>Padding := min([Width, Height])/100</l>
<l>gen_rectangle1 (RoiDiff, Padding, Padding, Height-Padding, Width-Padding)</l>
<c></c>
<c></c>
<c>************************************************************************************************</c>
<c>* Initialization</c>
<c>************************************************************************************************</c>
<c></c>
<c>* Apply the calibration</c>
<l>for I := 0 to |ImageFilesNonRectified|-1 by 1</l>
<c> * Select the image pair</c>
<l> FileNameNonRectCurrent := ImageFilesNonRectified[I]</l>
<l> FileNameRectCurrent := regexp_select(ImageFilesRectified, split(FileNameNonRectCurrent, PathImg)[0])</l>
<c> </c>
<l> read_image (ImageNonRect, FileNameNonRectCurrent)</l>
<l> read_image (ImageRectOpenCV, FileNameRectCurrent)</l>
<c> </c>
<c> * Rectify the image using the HALCON calibration and compare it to the </c>
<c> * OpenCV ground truth</c>
<l> change_radial_distortion_image (ImageNonRect, ImageNonRect, ImageRectHALCON, CamParamsOpt, CamParamOptRect)</l>
<c></c>
<l> reduce_domain (ImageRectOpenCV, RoiDiff, ImageRectOpenCVReduced)</l>
<l> abs_diff_image (ImageRectOpenCVReduced, ImageRectHALCON, ImageAbsDiff, 1)</l>
<l> threshold (ImageAbsDiff, RegionDiff, 50, 255)</l>
<l> region_features (RegionDiff, 'area', AreaDiff)</l>
<c> </c>
<l> ImagesDisp := {ImageNonRect, ImageRectOpenCV, ImageRectHALCON}</l>
<l> DispText := ['Original image', 'Rect image (OpenCV)', 'Rect image (HALCON)']</l>
<l> for J := 0 to ImagesDisp.length()-1 by 1</l>
<l> dev_set_window (WindowHandles.at(J))</l>
<l> dev_display (ImagesDisp.at(J))</l>
<l> dev_disp_text (DispText[J], 'window', 'top', 'left', 'black', [], [])</l>
<l> endfor</l>
<l> if (AreaDiff>100)</l>
<l> dev_set_color ('#ff0000c0')</l>
<l> dev_display (RegionDiff)</l>
<l> smallest_rectangle1 (RegionDiff, Row1, Column1, Row2, Column2)</l>
<l> dev_set_color ('#ff000040')</l>
<l> gen_rectangle1 (Rectangle, Row1, Column1, Row2, Column2)</l>
<l> dev_disp_text ('Deviation detected', 'window', 'top', 'right', 'red', 'box', 'false')</l>
<l> stop ()</l>
<l> else</l>
<l> dev_disp_text ('No deviation', 'window', 'top', 'right', 'green', 'box', 'false')</l>
<l> endif</l>
<l>endfor</l>
<l>disp_end_of_program_message (WindowHandles.at(WindowHandles.length()-1), 'black', 'true')</l>
<l>stop ()</l>
<c></c>
<c>************************************************************************************************</c>
<c>* Clean up</c>
<c>************************************************************************************************</c>
<c></c>
<l>for I := 0 to WindowHandles.length()-1 by 1</l>
<l> dev_set_window (WindowHandles.at(I))</l>
<l> dev_close_window ()</l>
<l>endfor</l>
<l>dev_update_on ()</l>
</body>
<docu id="main">
<parameters/>
</docu>
</procedure>
<procedure name="campar_opencv2halcon">
<interface>
<ic>
<par name="OpenCv_Distortions" base_type="ctrl" dimension="0"/>
<par name="OpenCvF" base_type="ctrl" dimension="0"/>
<par name="OpenCv_Cx" base_type="ctrl" dimension="0"/>
<par name="OpenCv_Cy" base_type="ctrl" dimension="0"/>
<par name="OpenCv_fxPix" base_type="ctrl" dimension="0"/>
<par name="OpenCv_fyPix" base_type="ctrl" dimension="0"/>
<par name="RowObservations" base_type="ctrl" dimension="0"/>
<par name="ColObservations" base_type="ctrl" dimension="0"/>
<par name="WidthImage" base_type="ctrl" dimension="0"/>
<par name="HeightImage" base_type="ctrl" dimension="0"/>
</ic>
<oc>
<par name="Error" base_type="ctrl" dimension="0"/>
<par name="CamParamsOpt" base_type="ctrl" dimension="0"/>
</oc>
</interface>
<body>
<c>************************************************************************************************</c>
<c>* Prepare the OpenCV values as input for HALCON calibration</c>
<c>************************************************************************************************</c>
<c></c>
<c>* Extract the distortions</c>
<l>k_1 := OpenCv_Distortions[0]</l>
<l>k_2 := OpenCv_Distortions[1]</l>
<l>p_1 := OpenCv_Distortions[2]</l>
<l>p_2 := OpenCv_Distortions[3]</l>
<l>k_3 := OpenCv_Distortions[4]</l>
<c></c>
<c>* Image plane coord. system -> HALCONs "description plate"</c>
<l>x_ := (ColObservations - OpenCv_Cx)/OpenCv_fxPix</l>
<l>y_ := (RowObservations - OpenCv_Cy)/OpenCv_fyPix</l>
<c></c>
<c>* Calculate the distorted points using the OpenCV Model</c>
<l>r2 := x_*x_+y_*y_</l>
<c></c>
<l>x2_tmp := x_ * (1 + k_1 * r2 + k_2 * r2 * r2 + k_3 * r2 * r2 * r2)</l>
<l>x2_ := x2_tmp + (2.0 * p_1 *x_ * y_ + p_2*(r2+2.0*x_*x_))</l>
<c></c>
<l>y2_tmp := y_ * (1 + k_1*r2+k_2*r2*r2+k_3*r2*r2*r2)</l>
<l>y2_ := y2_tmp + (p_1 * (r2 + 2 * y_ * y_) + 2 * p_2 * x_ * y_)</l>
<c></c>
<c>* Image plane coord. system -> Image coord. system (= Pixel coord)</c>
<l>u := OpenCv_fxPix * x2_ + OpenCv_Cx</l>
<l>v := OpenCv_fyPix * y2_ + OpenCv_Cy</l>
<c></c>
<c>* Compute the sensor size</c>
<l>sx := OpenCvF/OpenCv_fxPix</l>
<l>sy := OpenCvF/OpenCv_fyPix</l>
<c></c>
<c></c>
<c>************************************************************************************************</c>
<c>* Perform a HALCON calibration</c>
<c>************************************************************************************************</c>
<c></c>
<l>create_calib_data ('calibration_object', 1, 1, CalibDataID)</l>
<c>* Define a calibration plate</c>
<l>tuple_gen_const (|x_|, OpenCvF, Zeroes)</l>
<l>set_calib_data_calib_object (CalibDataID, 0, [x_, y_, Zeroes])</l>
<c>* Define the start params</c>
<l>gen_cam_par_area_scan_polynomial (OpenCvF, 0, 0, 0, 0, 0, sx, sy, OpenCv_Cx, OpenCv_Cy, WidthImage, HeightImage, CameraParamStart)</l>
<l>set_calib_data_cam_param (CalibDataID, 0, [], CameraParamStart)</l>
<c>* Exclude all params we can set directly from OpenCV</c>
<l>set_calib_data (CalibDataID, 'camera', 0, 'excluded_settings', ['pose'])</l>
<l>set_calib_data (CalibDataID, 'camera', 0, 'excluded_settings', ['focus','cx','cy'])</l>
<c></c>
<c>* Set the observation points</c>
<l>hom_mat3d_identity (HomMat3DIdentity)</l>
<l>hom_mat3d_to_pose (HomMat3DIdentity, Pose)</l>
<l>set_calib_data_observ_points (CalibDataID, 0, 0, 0, v, u, 'all', Pose)</l>
<c>* Calibrate the camera and deliver the results</c>
<l>calibrate_cameras (CalibDataID, Error)</l>
<l>get_calib_data (CalibDataID, 'camera', 0, 'params', CamParamsOpt)</l>
<l>clear_calib_data (CalibDataID)</l>
<l>return ()</l>
</body>
<docu id="campar_opencv2halcon">
<parameters>
<parameter id="CamParamsOpt">
<sem_type>campar</sem_type>
</parameter>
<parameter id="ColObservations">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="Error">
<default_type>real</default_type>
<multivalue>false</multivalue>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="HeightImage">
<default_type>integer</default_type>
<sem_type>number</sem_type>
<type_list>
<item>integer</item>
</type_list>
<value_max>1</value_max>
<value_min>1</value_min>
</parameter>
<parameter id="OpenCvF">
<default_type>real</default_type>
<multivalue>false</multivalue>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="OpenCv_Cx">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="OpenCv_Cy">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="OpenCv_Distortions">
<default_type>real</default_type>
<mixed_type>optional</mixed_type>
<multivalue>true</multivalue>
<sem_type>number</sem_type>
<type_list>
<item>integer</item>
<item>real</item>
</type_list>
</parameter>
<parameter id="OpenCv_fxPix">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="OpenCv_fyPix">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="RowObservations">
<default_type>real</default_type>
<sem_type>number</sem_type>
<type_list>
<item>real</item>
</type_list>
</parameter>
<parameter id="WidthImage">
<default_type>integer</default_type>
<sem_type>number</sem_type>
<type_list>
<item>integer</item>
</type_list>
<value_max>1</value_max>
<value_min>1</value_min>
</parameter>
</parameters>
</docu>
</procedure>
</hdevelop>
MVTec commented the script as follows:
Basically, the idea is to perform a classic HALCON calibration. To do so, we prepare the input values for it using the formulas of the OpenCV documentation (i.e. the definition of the "calibration plate", distorted points using the OpenCV distortions). The HALCON calibration is then used to determine the HALCON-specific distortion values - the rest can be derived/taken directly from the OpenCV parameter set.
For validation, an image set with undistorted and OpenCV-rectified images can be used:
- Take the original (distorted) image and rectify it using the HALCON calibration.
- Compare it to the rectified image of OpenCV by abs_diff_image .
Please note that this was only tested on a single dataset/calibration. It would be meaningful to test this approach for different calibrations/cameras.
Link to the corresponding Stack Overflow Q&A.