HALCON 到 OpenCV 失真系数转换

HALCON to OpenCV Distortion Coefficients Convertion

我有一个经过校准的多摄像头系统。内部(焦距、畸变等)和外部(姿势)相机参数均已使用基于 HALCON 的程序进行了估算。现在,目标是编写一个 C++ 程序,读取相机参数,特别是 HALCON 估计的畸变系数(k1、k2、k3、p1、p2),并使用它们通过 OpenCV 对图像进行去畸变。不幸的是,到目前为止,我无法成功:HALCON 和 OpenCV 中使用的相同失真系数会生成截然不同的未失真图像!我想,问题在于 HALCON 和 OpenCV 解释失真系数的方式,甚至是它们执行去失真的方式。

这是我的 HALCON 代码,用于读取失真系数并使用它们来消除测试图像的失真:

* Read the internal camera calibratino parameters ('area_scan_polynomial' model)
read_cam_par('Calibration.dat', CamParam)

* Estimate camera parameters without distortion: set all distortion coefficients to [k1, k2, k3, p1, p2] = [0, 0, 0, 0, 0] 
change_radial_distortion_cam_par ('fixed', CamParam, [0, 0, 0, 0, 0], CamParamOut)

* Estimate camera matrix of distortion-free parameters (used for OpenCV)
cam_par_to_cam_mat(CamParamOut, CamMatrix, ImageWidth, ImageHeight)

* Generate map to use for undistortion. 
* Note the use of type 'coord_map_sub_pix' which is of type 'vector_field_absolute', 
* i.e. the values are 2D absolute coordinates of the corresponding undistorted pixel location
gen_radial_distortion_map(Map, CamParam, CamParamOut, 'coord_map_sub_pix')

* Read a test image and undistort it using the estimate map
read_image (Image, 'test.jpg')
map_image(Image, Map, ImageRectified)

我正在尝试使用以下代码在 OpenCV 中执行完全相同的操作:

Mat view , rview, mapx, mapy;

// Read the same test image as in HALCON
view = imread("test.jpg");

// Get the image size
const Size viewSize = view.size();

// Generate map to use for undistortion
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
    cameraMatrix, viewSize, CV_16SC2, mapx, mapy);

// Undistort the image using the estimated map
remap(view, rview, mapx, mapy, INTER_LINEAR);

OpenCV中的变量“cameraMatrix”等同于HALCON中的变量“CamMatrix”。 OpenCV 中的失真系数“distCoeffs”取自 HALCON (k1, k2, k3, p1, p2) 并按照文档以这种方式重新排列:

distCoeffs = (Mat_<double>(5, 1) << k1, k2, p2, p1, k3)

注意k3作为第五个参数提供,p2和p1互换。根据 HALCON 文档(https://www.mvtec.com/doc/halcon/12/en/calibrate_cameras.html,请参阅函数 calibrate_cameras),图像平面 (u, v) 中的未失真坐标是根据失真 (u', v') 计算得出的:

u = u' + u' (k1 r^2 + k2 r^4 + k3 r^6) + p1 (r^2 + 2 u'^2) + 2 p2 u' v'

v = v' + v' (k1 r^2 + k2 r^4 + k3 r^6) + p2 (r^2 + 2 v'^2) + 2 p1 u' v'

having r = sqrt(u'^2 + v'^2)

而在 OpenCV 中(https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html,请参阅函数 initUndistortRectifyMap),同样的未失真坐标估计类似,只有 p1 和 p2 被交换。

显然,OpenCV 和 HALCON 都将像素投影到图像平面中。也就是说,具有像素 (x, y) 的相应图像平面坐标计算为:

u' = x - cx / fx

v' = y - cy / fy

这些当然可以反投影重新得到对应的像素坐标:

x = u' * fx + cx

y = v' * fy + cy

根据文档,似乎一切都应该按预期工作。但是,我不明白为什么基于 HALCON 和 OpenCV 的代码仍然输出截然不同的结果。我注意到,为了产生与 HALCON 中类似的无失真结果(但不完全相同),我必须按比例缩小(约 100 倍!)OpenCV 中的失真系数。事实上,我注意到 HALCON 估计了巨大的失真系数。例如,为了在未失真的图像中产生可见的变化,我必须在 HALCON 中设置 k1=1000,而在 OpenCV 中已经设置 k1=1明显地改变了图像。对于某些失真系数,我什至不得不反转(带负号)值以获得朝类似方向发展的未失真结果...

我深入研究了 HALCON 不失真代码,并尝试按照文档手动估计不失真坐标 (u, v),这应该与“地图”中找到的坐标相对应。我这样做是为了确保“地图”确实按照我理解的 documentation/the 方式指定的方式进行了估计。然而,即使在这里,与“地图”中估计的结果相比,我也得到了非常不同的结果...为了进行测试,我使用了以下代码:

* Get the camera parameters from the calibration
get_cam_par_data (CamParam, 'k1', k1)
get_cam_par_data (CamParam, 'k2', k2)
get_cam_par_data (CamParam, 'k3', k3)
get_cam_par_data (CamParam, 'p1', p1)
get_cam_par_data (CamParam, 'p2', p2)
get_cam_par_data (CamParam, 'cx', cx)
get_cam_par_data (CamParam, 'cy', cy)
get_cam_par_data (CamParam, 'image_width', width)
get_cam_par_data (CamParam, 'image_height', height)

* Estimate the camera matrix, to read the focal length in pixel
cam_par_to_cam_mat(CamParamOut, CamMatrix, width, height)

* Extract the focal lenths in pixel from the estimated camera matrix (see above)
fx_px := CamMatrix[0]
fy_px := CamMatrix[4]

* Pick a pixel coordinate (I tried different values) in the image domain
x := 350
y := 450

* Convert into image plane coordinates
u_1 := (x - cx) / fx_px
v_1 := (y - cy) / fy_px

* Estimate the undistorted location u_2 and v_2
r2 := u_1 * u_1 + v_1 * v_1
u_2 := u_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + 2 * p1 * u_1 * v_1 + p2 * (r2 + 2 * u_1 * u_1)
v_2 := v_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + 2 * p2 * u_1 * v_1 + p1 * (r2 + 2 * v_1 * v_1)
    
* Back to pixel coordinate
x_1 := u_2 * fx_px + cx
y_1 := v_2 * fy_px + cy

* Compare the values with the value in Map (estimated as before). G_found and G_est should match!
G_found := [y_1, x_1]
get_grayval(Map, y, x, G_est)

我试着一次只关注几个失真系数,即只有 k1 > 0,其他都设置为 0。但是在大多数情况下,(除了 x = cx,y = cy 时的少数例外)未失真的坐标超过图像尺寸甚至变成负片。

这不是HALCON估计无畸变图坐标的方式吗?我错过了什么吗?应该如何转换这些失真系数以使 OpenCV 生成完全相同的未失真结果图像?任何帮助将不胜感激!

由于某些软件限制,仅使用 OpenCV 进行校准和不失真是有争议的,但不幸的是,这对我来说不是一个可接受的解决方案。

遗憾的是,HALCON 和 OpenCV 校准参数之间没有直接转换,因为底层模型和参数估计方式不同。 HALCON 参数描述了从变形坐标到未变形坐标的转换,而 OpenCV 参数描述了相反的转换。由于多项式等级较高,因此无法进行转换。可以解析反演的划分模型,在OpenCV中是没有的

我找到了我自己问题的答案。简而言之,答案是肯定的。是的,可以从 HALCON 转换为 OpenCV 失真参数,反之亦然。原因是,HALCON 和 OpenCV 显然 估计相同 底层模型。我做了几次成功的测试来证实这一点,我想分享我的见解。下面是我计算的将每个失真参数从 HALCON 转换为 OpenCV 的公式:

k1_opencv = k1_halcon * fmm * fmm;
k2_opencv = k2_halcon * fmm * fmm * fmm * fmm;
k3_opencv = k3_halcon * fmm * fmm * fmm * fmm * fmm * fmm;
p1_opencv = p2_halcon * fmm;    // Notice: swap
p2_opencv = p1_halcon * fmm;

请注意,fmm 是以毫米为单位的焦距,例如在哈尔康。 估计地图中估计的相同值的正确 HALCON 代码是:

get_cam_par_data (CamParam, 'sx', Sx) 
get_cam_par_data (CamParam, 'sy', Sy)

* Convert into image plane coordinates
u_1 := (x - cx) * sx
v_1 := (y - cy) * sy

* Estimate the undistorted location u_2 and v_2
r2 := u_1 * u_1 + v_1 * v_1
u_2 := u_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + 2 * p2 * u_1 * v_1 + p1 * (r2 + 2 * u_1 * u_1)
v_2 := v_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + 2 * p1 * u_1 * v_1 + p2 * (r2 + 2 * v_1 * v_1)

* Back to pixel coordinate
x_1 := u_2 / sx + cx
y_1 := v_2 / sy + cy

* Compare coordinates. NOTICE: we get the values from the distortion map
* going from UNdistorted to DIstorted coordinates!!!
G_found := [y_1, x_1]
gen_radial_distortion_map(MapUD, CamParamOut, CamParam, 'coord_map_sub_pix') 
get_grayval(MapUD, y, x, G_est)

关于我在问题中发布的初始代码,使用以毫米为单位的像素大小转换坐标 sxsy ,而不是焦距。另一个区别是我们将估计的坐标与来自 MapUD 的值进行比较,其中 MapUD(UNdistorted coordinate) := DIstorted coordinate。估计的坐标和地图中的坐标对应于图像边界处的舍入误差和条件。

OpenCV 改为执行以下操作(完全符合文档!):

float u = (x - cx) / fpx;
float v = (y - cy) / fpx;
float x_1 = u;
float y_1 = v;
float r2 = x_1 * x_1 + y_1 * y_1;
float x_2 = x_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + 2 * p1 * x_1 * y_1 + p2 * (r2 + 2 * x_1 * x_1);
float y_2 = y_1 * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) + p1 * (r2 + 2 * y_1 * y_1) + 2 * p2 * x_1 * y_1;
float map_x_est = x_2 * fpx + cx;
float map_y_est = y_2 * fpx + cy;

// Compare coordinates
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
        cameraMatrix, viewSize, CV_32FC1, mapx, mapy);
float map_x = mapx.at<float>(y, x);
float map_y = mapy.at<float>(y, x);

上述代码中,值map_x对应map_x_estmap_y 对应于 map_y_est 直到舍入误差。 如果我们使用与 HALCON 中相同的 cameraMatrix,并使用上述公式转换失真系数 distCoeffs,我们可以清楚地看到 OpenCV 变量 map_xmap_y 对应于 HALCON 的 MapUD 中的那些。我通过在 HALCON 和 OpenCV 中针对不同的失真参数一个一个地(在整个域中)输出映射值来测试这一点,获得相同的结果直到小误差 < 0.01 像素。

附加信息:MVTec 向我发送了一些 HALCON 代码,用于将手动估计的坐标与地图中的坐标进行比较。请注意,关于我的解决方案,它们是相反的,即从扭曲到未扭曲。在很多情况下,该代码 not 对我有用。欢迎试用并告诉我:

dev_close_window ()
dev_update_off ()

Width:=1600
Height:=1200
dev_open_window_fit_size(0, 0, Width, Height, -1, -1, WindowHandle)

gen_cam_par_area_scan_polynomial (0.008, 0, 0, 0, 0, 10, 5.2e-006, 5.2e-006, Width/2, Height/2, Width, Height, CamParam) 
change_radial_distortion_cam_par ('fixed', CamParam, [0, 0, 0, 0, 0], CamParamOut) 
gen_radial_distortion_map(Map, CamParam, CamParamOut, 'coord_map_sub_pix') 

get_cam_par_data (CamParam, 'k1', k1) 
get_cam_par_data (CamParam, 'k2', k2) 
get_cam_par_data (CamParam, 'k3', k3) 
get_cam_par_data (CamParam, 'p1', p1) 
get_cam_par_data (CamParam, 'p2', p2) 
get_cam_par_data (CamParam, 'cx', cx) 
get_cam_par_data (CamParam, 'cy', cy) 
get_cam_par_data (CamParam, 'sx', Sx) 
get_cam_par_data (CamParam, 'sy', Sy) 

* Select a valid point
Row := 86
Col := 89

get_grayval(Map, Row, Col, G_map)

get_domain (Map, Domain)
test_region_point (Domain, Row, Col, IsInside)
if (IsInside)
    * Check calculation
    GRow:=G_map[0]
    GCol:=G_map[1]

    U_1 := (GCol - cx) * Sx
    V_1 := (GRow - cy) * Sy 

    R_2 := U_1 * U_1 + V_1 * V_1 
    U_2 := U_1 * (1 + k1 * R_2 + k2 * R_2 * R_2 + k3 * R_2 * R_2 * R_2) + p1 * (R_2 + 2 * U_1 * U_1) + 2 * p2 * U_1 * V_1
    V_2 := V_1 * (1 + k1 * R_2 + k2 * R_2 * R_2 + k3 * R_2 * R_2 * R_2) + p2 * (R_2 + 2 * V_1 * V_1) + 2 * p1 * U_1 * V_1

    Col_calc := U_2 / Sx + cx
    Row_calc := V_2 / Sy + cy 

    G_input:=[Row, Col]
    G_calc:=[Row_calc, Col_calc]
    G_diff:=G_calc-G_input
    dev_inspect_ctrl ([G_input, G_calc, G_diff])
    stop()
else
    * Point is outside domain of distortion map
    stop()
endif

dev_close_inspect_ctrl ([G_input, G_calc, G_diff])
dev_clear_window ()
dev_update_on ()
disp_message (WindowHandle, 'No more lines to execute...', 'window', 12, 12, 'black', 'true')

我们向 MVTec 提出了支持请求并收到了以下答复(收到于 2019 年 11 月 21 日星期四;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.

我们还得到了一个 hdev 脚本,用于从 HALCON 到 OpenCV 参数的近似映射(收到 2019 年 11 月 21 日星期四;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&gt;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 -&gt; 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 -&gt; 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 对脚本的评论如下:

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:

  1. Take the original (distorted) image and rectify it using the HALCON calibration.
  2. 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.