Halcon标定板标定流程

一、相机内参标定

目的:标定内参的目的是消除镜头的畸变。

面阵相机的内参由一个8位的数组组成包括:

startCamPar:=[Focus,Kappa,Sx,Sy,Cx,Cy,ImageWidth,ImageHeight]

Focus代表焦距,按照我们镜头参数进行填写,远心镜头填写0
Kappa为畸变大小,因为在标定之前,所以默认填写0
Sx, Sy像元的宽高填写相机的像元尺寸。可以查相机手册,或者咨询相机厂家。
Cx, Cy填写图像的中心坐标
ImageWidth, ImageHeight填写图像的宽高

通过查阅手册我们可以知道除Kappa以外的其他参数,将已知信息填入进去,就是未标定的初始相机内参。

有了初始内参就可以用下面算子设置进去:

set_calib_data_cam_param (CalibDataID, 0, [], startCamPar)

标定完成之后我的会得到标定后的相机内参,和标定前的区别是计算出了Kappa的值,不再是0,其他参数大致不变,所以内参标定就是计算出畸变系数的过程。

二、相机外参标定

目的:决定摄像机坐标系与世界坐标系之间相对位置关系,有了位置关系Pose,就可以将像素坐标系和世界坐标系的点进行互相转化,达到了标定的应用目的,所以外参标定就是求解要定位的产品相对于相机的位姿(位置)。

3D姿态中的7个参数代表的意义:[0,0,0,0,0,0,0]
前六个代表平移和旋转量,最后一个代表OrderOfTransform,OrderOfRotform,ViewOfTransform的组合类型。

位姿pose的参考文章:HALCON中create_pose和位姿中7个参数_m0_37833782的博客-CSDN博客

相机pose的理解-halcon标定-少有人走的路

三、参考代码

一、获取内参,参考例程camera_calibration_internal

ImgPath := '3d_machine_vision/calib/'
dev_close_window ()
dev_open_window (0, 0, 652, 494, 'black', WindowHandle)
dev_update_off ()
dev_set_draw ('margin')
dev_set_line_width (3)
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
* 
* Calibrate the camera.
* 
*通过查阅手册等资料,创建初始的内参数据数组
gen_cam_par_area_scan_division (0.016, 0, 0.0000074, 0.0000074, 326, 247, 652, 494, StartCamPar)
*创建一个标定句柄,所有操作都在这个句柄上进行
create_calib_data ('calibration_object', 1, 1, CalibDataID)
*将上面创建的未标定的初始内参数据设置进去
set_calib_data_cam_param (CalibDataID, 0, [], StartCamPar)
*设置标定板的描述文件,用于后面从各个图片中查找标定板
set_calib_data_calib_object (CalibDataID, 0, 'caltab_30mm.descr')
NumImages := 10
* Note, we do not use the image from which the pose of the measurement plane can be derived
*10张标定板在视野中不同位置的图片
for I := 1 to NumImages by 1
    read_image (Image, ImgPath + 'calib_' + I$'02d')
    dev_display (Image)
    *自动查到标定板的位置信息
    find_calib_object (Image, CalibDataID, 0, 0, I, [], [])
endfor
*执行了find_calib_object之后就可以进行标定了,这里执行了10次就是标定了10个位置
calibrate_cameras (CalibDataID, Error)    *启动标定
*标定完成之后就可以用下面算子获得标定后的相机内参了
get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)
* Write the internal camera parameters to a file
*将标定后的相机内参保存成文件以使用
write_cam_par (CamParam, 'camera_parameters.dat')
Message := 'Interior camera parameters have'
Message[1] := 'been written to file'
disp_message (WindowHandle, Message, 'window', 12, 12, 'red', 'false')

二、获取外参,参考例程camera_calibration_external

*方法一、标定板确定位姿

*外参的标定流程和内参一样,标定流程结束后内参和外参数据(就是位姿)都已经存在了
*同样用get_calib_data获得外参pose
get_calib_data (CalibHandle, 'calib_obj_pose', [0, 1], 'pose', CameraPose)
*第三个参数ItemIdx由两个参数的元组组成。[0, 1]代表第0号标定板的第1张图片中的标定位姿。
*我们标定了10张标定板在不同位置的图片,所以得到了10个位姿数据,选择其中一个作为参考位姿使用,
*以后我们要定位的产品的位置也要和参考位姿的位置一样,就可以使用该位姿实现像素坐标和世界坐标
*点位的转换
clear_calib_data (CalibDataID)

*方法二、至少3点确定位姿

*世界坐标系中的4个点
X := [0,50,100,80]
Y := [5,0,5,0]
Z := [0,0,0,0]
*对应像素坐标系中的4个点
RCoord := [414,227,85,128]
CCoord := [119,318,550,448]
*加载相机内参
read_cam_par ('camera_parameters.dat', CamParam)
*从图像点和世界坐标系点的对应中直接计算出位姿
vector_to_pose (X, Y, Z, RCoord, CCoord, CamParam, 'iterative', 'error', FinalPose, Errors)


*(可选)set_origin_pose对一个位姿进行平行转换,和仿射变换功能一样
*所有方法最后求出的位姿都是默认在平面上的,没有考虑标定板的厚度,因此可以
*使用此方法在设置标定板的厚度,优化位姿参数
set_origin_pose (FinalPose, 0, 0, 0.00075, FinalPose)
*保存位姿数据为文件
write_pose (FinalPose, 'pose_from_three_points.dat')


三、实际应用

(1)点坐标转换
dev_update_window ('on')
dev_display (Image)
while (1)
    *获取鼠标点像素坐标
    get_mbutton (WindowHandle, Row, Column, Button)
    if (Button == 4)
        break
    endif
    dev_display (Image)
    dev_set_color ('green')
    disp_cross (WindowHandle, Row, Column, 6, 0)
    *使用下面算子,使用指定的内参和外参,将图像坐标转换为世界坐标
    image_points_to_world_plane (CamParam, FinalPose, Row, Column, 1, X1, Y1)
    disp_message (WindowHandle, 'X = ' + X1, 'window', 320, 400, 'red', 'false')
    disp_message (WindowHandle, 'Y = ' + Y1, 'window', 340, 400, 'red', 'false')
endwhile

(2)测量直线距离
* 该例程在世界坐标系中划出一个范围,测量包含在其中的多个直线的距离
*思路:在世界坐标系中确定ROI范围,转换到图像坐标系中的ROI,在图像中测量直线间的
*像素距离,将像素点转化位世界坐标系中的点,求出实际距离
dev_set_color ('red')
dev_display (Image)
* 设置世界坐标系中的ROI区域
ROI_X_WCS := [-2,-2,112,112]
ROI_Y_WCS := [0,0.5,0.5,0]
ROI_Z_WCS := [0,0,0,0]
*将位姿转换为3D变换矩阵,hom_mat3d_to_pose:3D变换矩阵转换为位姿
pose_to_hom_mat3d (FinalPose, CCS_HomMat_WCS)
*将世界坐标系中的点转换到相机坐标系中
affine_trans_point_3d (CCS_HomMat_WCS, ROI_X_WCS, ROI_Y_WCS, ROI_Z_WCS, CCS_RectangleX, CCS_RectangleY, CCS_RectangleZ)
*将相机坐标系中的点转换到图像坐标系中
project_3d_point (CCS_RectangleX, CCS_RectangleY, CCS_RectangleZ, CamParam, RectangleRow, RectangleCol)
*到这里就把世界坐标系中绝对ROI范围的点转换到了图像坐标系
*生成图像中的ROI
gen_region_polygon_filled (ROI, RectangleRow, RectangleCol)
*求最小外接矩形
smallest_rectangle2 (ROI, RowCenterROI, ColCenterROI, PhiROI, Length1ROI, Length2ROI)
* 准备测量,创建测量工具
gen_measure_rectangle2 (RowCenterROI, ColCenterROI, PhiROI, Length1ROI, Length2ROI, 652, 494, 'bilinear', MeasureHandle)
*启动测量
measure_pairs (Image, MeasureHandle, 0.4, 5, 'all_strongest', 'all', RowEdgeFirst, ColumnEdgeFirst, AmplitudeFirst, RowEdgeSecond, ColumnEdgeSecond, AmplitudeSecond, IntraDistance, InterDistance)
close_measure (MeasureHandle)
dev_display (Image)
disp_message (WindowHandle, 'Measuring the position of the pitch lines', 'window', 450, 25, 'red', 'false')
dev_set_color ('green')
*求出每条直线线宽的中点
RowPitchLine := (RowEdgeFirst + RowEdgeSecond) / 2.0
ColPitchLine := (ColumnEdgeFirst + ColumnEdgeSecond) / 2.0
disp_cross (WindowHandle, RowPitchLine, ColPitchLine, 6, 0)
*将图像坐标系中的直线上的点转换到世界坐标系
image_points_to_world_plane (CamParam, FinalPose, RowPitchLine, ColPitchLine, 1, X1, Y1)
for I := 1 to |X1| by 1
    *设置文本在窗体的输出位置
    set_tposition (WindowHandle, RowEdgeFirst[I - 1] + 5, ColumnEdgeFirst[I - 1] - 20)
    if (I == |X1|)
        set_tposition (WindowHandle, RowEdgeFirst[I - 1], ColumnEdgeFirst[I - 2])
    endif
    *展示信息
    write_string (WindowHandle, X1[I - 1]$'.3f' + 'mm')
endfor
disp_continue_message (WindowHandle, 'black', 'true')
stop ()


(3)以指定点为中心,将图像转换成指定位姿的位置
dev_close_inspect_ctrl (YOfContour)
dev_close_inspect_ctrl (XOfContour)
* Now, transform the whole image
WidthMappedImage := 652
HeightMappedImage := 494
dev_display (Image)
* First, determine the scale for the mapping
* (here, the scale is determined such that in the
* surroundings of the points P0 and P1, the image scale of the
* mapped image is similar to the image scale of the original image)
*分别计算出世界坐标系和相应的图像坐标系中两点的距离
distance_pp (X[0], Y[0], X[1], Y[1], DistP0P1WCS)
distance_pp (RCoord[0], CCoord[0], RCoord[1], CCoord[1], DistP0P1PCS)
*求出世界坐标和图像坐标的比例关系
Scale := DistP0P1WCS / DistP0P1PCS
* Then, determine the parameter settings for set_origin_pose such
* that the point given via get_mbutton will be in the center of the
* mapped image
dev_display (Image)
disp_message (WindowHandle, 'Define the center of the mapped image', 'window', 12, 12, 'red', 'false')
*提取屏幕中的一点,后期以图像中这一点像素的位置将图片移动到屏幕中心
get_mbutton (WindowHandle, CenterRow, CenterColumn, Button1)
*将点转换为世界坐标系坐标
image_points_to_world_plane (CamParam, FinalPose, CenterRow, CenterColumn, 1, CenterX, CenterY)
*设置图片要转换的位姿,并将根据上面选的点平移图像
set_origin_pose (FinalPose, CenterX - Scale * WidthMappedImage / 2.0, CenterY - Scale * HeightMappedImage / 2.0, 0, PoseNewOrigin)
*生成一个从图像到世界坐标系的投影
gen_image_to_world_plane_map (Map, CamParam, PoseNewOrigin, 652, 494, WidthMappedImage, HeightMappedImage, Scale, 'bilinear')
*对图像应用投影
map_image (Image, Map, ImageMapped)
dev_clear_window ()
dev_display (ImageMapped)
* In the case that only one image has to be mapped, the operator
* image_to_world_plane can be used instead of the operators
* gen_image_to_world_plane_map and map_image.
*(可选)在只需要映射一个图像的情况下,可以使用操作符image_to_world_plane
*来代替操作符gen_image_to_world_plane_map和map_image。
image_to_world_plane (Image, ImageMapped, CamParam, PoseNewOrigin, WidthMappedImage, HeightMappedImage, Scale, 'bilinear')

四、补充,获取标定板的描述文件

上一篇:pyDash:一个基于 web 的 Linux 性能监测工具


下一篇:trajectory_planner_ros.cpp 源码解读