关于相机标定的详细解释,请参考:Halcon相机标定


功能:相机在3D空间的标定,对象会倾斜和旋转。
THIK:标定的主要目的是确定相机的参数CamParam(如焦距、主点坐标、畸变系数、相机在世界坐标系中的位置和姿态等)。这些参数是准确测量和3D重建的基础。标定后测量标定板的边距与标志点的直径,并通过测量结果的标准差反映标定效果。

  1. 设置窗口

    1
    2
    3
    4
    5
    6
    dev_close_window ()
    dev_open_window (0, 0, 768, 576, 'black', WindowHandle)
    dev_update_off ()
    dev_set_draw ('margin')
    dev_set_line_width (3)
    set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
  2. 创建标定数据和相机参数,读取多张标定图像并寻找标定板,提取并显示标定板的轮廓与位置,最后进行相机标定,计算出相机参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    * 标定板描述文件
    CalTabDescrFile := 'caltab_big.descr'
    * 相机参数
    gen_cam_par_area_scan_division (0.008, 0, 0.0000086, 0.0000086, 384, 288, 768, 576, StartCamPar)
    * 创建标定数据对象
    create_calib_data ('calibration_object', 1, 1, CalibDataID)
    * 将相机参数设置到标定数据对象中
    set_calib_data_cam_param (CalibDataID, 0, [], StartCamPar)
    * 将标定板描述文件与标定数据对象关联
    set_calib_data_calib_object (CalibDataID, 0, CalTabDescrFile)

    NumImages := 10
    for I := 1 to NumImages by 1
    read_image (Image, 'calib/calib-3d-coord-' + I$'02d')
    dev_display (Image)
    Message := 'Find calibration plate in\nall calibration images (' + I + '/' + NumImages + ')'
    disp_message (WindowHandle, Message, 'window', 12, 12, 'black', 'true')
    * 在图像中查找标定板,根据标定数据ID和图像索引(I-1)进行查找
    find_calib_object (Image, CalibDataID, 0, 0, I - 1, [], [])
    * 从标定数据中获取相机的初始参数
    get_calib_data (CalibDataID, 'camera', 0, 'init_params', StartCamPar)
    * 获取标定过程中观察到的标定点的坐标和姿态
    get_calib_data_observ_points (CalibDataID, 0, 0, I - 1, Row, Column, Index, Pose)
    * 获取标定板的轮廓数据
    get_calib_data_observ_contours (Contours, CalibDataID, 'caltab', 0, 0, I - 1)
    * 生成一个十字形轮廓,中心在找到的标定点位置
    gen_cross_contour_xld (Cross, Row, Column, 6, 0.785398)
    dev_set_color ('green')
    dev_display (Contours)
    dev_set_color ('yellow')
    dev_display (Cross)
    endfor
    disp_continue_message (WindowHandle, 'black', 'true')
    stop ()
    * 执行相机标定,计算相机的实际参数
    calibrate_cameras (CalibDataID, Error)
    * 获取标定后的相机参数
    get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)
  3. 对每张图像进行测量,包括标定板宽度和孔半径。然后转换坐标系,将测量结果从图像坐标转换到世界坐标。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    for I := 1 to NumImages by 1
    read_image (Image, 'calib/calib-3d-coord-' + I$'02d')
    * 根据相机参数和标定数据,从图像中获取测量位置,包括板的中心行列和方向
    get_measure_positions (Image, PlateRegion, CalibDataID, I, Distance, Phi, RowCenter, ColumnCenter)
    * 生成一个矩形轮廓,位置和方向基于测量结果
    gen_rectangle2_contour_xld (Rectangle, RowCenter, ColumnCenter, Phi, Distance * 0.52, 8)
    * 生成一个测量矩形,用于后续的测量操作
    gen_measure_rectangle2 (RowCenter, ColumnCenter, Phi, Distance * 0.52, 8, 768, 576, 'nearest_neighbor', MeasureHandle)
    * 对图像进行测量,获取矩形边缘的位置、强度和测量结果
    measure_pos (Image, MeasureHandle, 1, 40, 'all', 'all', RowEdge, ColumnEdge, Amplitude, Distance1)

    Rows := [RowEdge[0],RowEdge[|RowEdge| - 1]]
    Columns := [ColumnEdge[0],ColumnEdge[|RowEdge| - 1]]
    * 生成一个十字形轮廓,表示测量的边缘位置
    gen_cross_contour_xld (Cross, Rows, Columns, 16, Phi)

    * 获取标定对象的姿态信息
    get_calib_data (CalibDataID, 'calib_obj_pose', [0,I - 1], 'pose', Pose)
    * 将图像坐标转换为世界坐标系
    image_points_to_world_plane (CamParam, Pose, Rows, Columns, 'm', SX, SY)
    * 测量出的宽度
    distance_pp (SY[0], SX[0], SY[1], SX[1], Width)

    dev_display (Image)
    dev_set_color ('white')
    dev_set_line_width (3)
    dev_display (Rectangle)
    dev_set_color ('green')
    dev_set_draw ('fill')
    dev_set_line_width (2)
    dev_display (Cross)
    dev_set_draw ('margin')
    disp_message (WindowHandle, 'Width = ' + (Width * 100)$'8.3f' + 'cm', 'window', 12, 12, 'black', 'true')
    disp_continue_message (WindowHandle, 'black', 'true')
    stop ()


    * 对标定板内区域进行圆形腐蚀操作,生成ROI
    erosion_circle (PlateRegion, ROI, 17.5)
    reduce_domain (Image, ROI, ImageReduced)
    * 边缘检测
    edges_sub_pix (ImageReduced, Edges, 'canny', 1, 20, 60)
    * 通过长度筛选
    select_contours_xld (Edges, SelectedEdges, 'contour_length', 20, 99999999, -0.5, 0.5)

    * 对选中的轮廓进行椭圆拟合,输出椭圆的参数(中心行列、角度、半径等)
    fit_ellipse_contour_xld (SelectedEdges, 'fitzgibbon', -1, 2, 0, 200, 3, 2, Row, Column, Phi, Radius1, Radius2, StartPhi, EndPhi, PointOrder)
    * 计算拟合得到的半径的平均值和标准偏差
    MeanRadius1 := mean(Radius1)
    MeanRadius2 := mean(Radius2)
    DevRadius1 := deviation(Radius1)
    DevRadius2 := deviation(Radius2)

    * 将选中的轮廓转换到世界坐标系
    contour_to_world_plane_xld (SelectedEdges, WorldCircles, CamParam, Pose, 'mm')

    * 对世界坐标系中的圆进行椭圆拟合,输出参数
    fit_ellipse_contour_xld (WorldCircles, 'fitzgibbon', -1, 2, 0, 200, 3, 2, Row, Column, Phi, RadiusW1, RadiusW2, StartPhi, EndPhi, PointOrder)
    MeanRadiusW1 := mean(RadiusW1)
    MeanRadiusW2 := mean(RadiusW2)
    DevRadiusW1 := deviation(RadiusW1)
    DevRadiusW2 := deviation(RadiusW2)

    dev_display (Image)
    dev_set_color ('yellow')
    dev_set_line_width (3)
    dev_display (SelectedEdges)
    Message := 'Measured dimensions of the ellipses'
    Message[0] := ' Mean Radius1; Mean Radius2; (Standard deviations [%])'
    * 图像坐标系下的平均半径及其标准偏差
    Message[1] := 'Image coordinates: ' + MeanRadius1$'5.2f' + 'px; ' + MeanRadius2$'5.2f' + 'px (' + (DevRadius1 / MeanRadius1 * 100)$'4.2f' + ', ' + (DevRadius2 / MeanRadius2 * 100)$'4.2f' + ')'
    * 世界坐标系下的平均半径及其标准偏差
    Message[2] := 'World coordinates: ' + (MeanRadiusW1 / 10)$'5.2f' + 'cm; ' + (MeanRadiusW2 / 10)$'5.2f' + 'cm (' + (DevRadiusW1 / MeanRadiusW1 * 100)$'4.2f' + ', ' + (DevRadiusW2 / MeanRadiusW2 * 100)$'4.2f' + ')'
    disp_message (WindowHandle, Message, 'window', 12, 12, 'black', 'true')
    if (I < 10)
    disp_continue_message (WindowHandle, 'black', 'true')
    stop ()
    else
    disp_end_of_program_message (WindowHandle, 'black', 'true')
    endif
    endfor