关于单目视觉中相机标定的一些理解

开场白

大江东去,浪淘尽,千古风流人物。。。是的,您猜的没错,今天我们不讲三国,讲讲单目视觉中3个坐标系的故事;

概要

  • 坐标转换
  • 畸变校正
  • 总结
  • 引用

坐标转换

话说天下大势,合久必分分久必合。事情从盘古开天辟地,不对是一个叫L.达盖尔的小伙子,这个小伙子骨骼精奇,成天吃饱喝足没啥事就折腾出了个叫做截图器的东西,快门在手,天下我有。虽然已经是两百多年前的事,但我们每天都用“单反穷三代 摄影毁一生”表达着对这位开山之人的无尽思念;
古语有云,有些人走了,却没想让我们也活着----是的,说的就是他,这个小伙子走了,却给我们留下3个坐标系,使我终日不得开心颜。这3个让头疼的玩意分别是:世界坐标系相机坐标系图像坐标系。他三的关系是这样的:
关于单目视觉中相机标定的一些理解

在我看来,他们是这么想的:

 1. 世界坐标系:我是旁观者,你暗恋她我管不着,但是你的一点小动作我都看得到----用笛卡尔三维坐标系客观反应事物的位置;
 2. 相机坐标系:当局者迷,旁观者清,我就喜欢图像坐标系,管他们怎么看----用相机的眼睛看世界,我是世界的中心,全世界都是我的;
 3. 图像坐标系:我这么多愁善感,动不动就畸变,你们还是离我远点的好----图像像素位置的参考系,比如常见的以图像左上角所为像素原点;

坐标系转化

刚体变换:世界坐标系–>相机坐标系

旁观者(世界坐标系)问当事人(相机坐标系):她到底有啥好的呢,让你如此痴迷?
当事人说:只因为在人群中多看了她一眼。你过来,站我这个位置看看就知道了。

我们想把世界坐标系下的坐标转换到相机坐标下的坐标,如下图所示,可以通过刚体变换的方式。这个过程包括:旋转、跳跃,平移,so easy。

关于单目视觉中相机标定的一些理解
一个空间坐标系有6个*度,原点坐标(x,y,z)以及3根轴的方向;喊着口号,我们先做旋转:
关于单目视觉中相机标定的一些理解
旋转结束后两个坐标系的3个坐标轴方向就一样了,统一思想,一心向党。接着把两个坐标系的原点重合在一起,步调统一,听党指挥,能打战,打胜战;总结起来是这样的:
关于单目视觉中相机标定的一些理解

透视变换:相机坐标系–>图像坐标系

	旁观者(世界坐标系)凑了过来,当事人(相机坐标系)说:来,闭上一只眼睛,朝着那边看。

透视变换属于透视投影关系,从3D转换到2D。以坐标原点Oc作为透视中心,使用三角形比例关系进行长度转化;
关于单目视觉中相机标定的一些理解
我们已经将3维空间的事物转化到了2维图像中。花花世界是线性的,图像是离散的,所以还需要进行离散化操作,也就是像素化。上面图片反应的事最理想的状态,事实却不然,这个过程会发生畸变。畸变的事情我们稍等继续分解。转化结束后,遗留下一个问题,相机坐标系是中心对称的,也就是说得到的图像坐标也是中心对称的,不符合广大人民的价值观,所以还需要将坐标系原点从图像*移到图像左上角,这个过程没什么特别的不详细说明了;
关于单目视觉中相机标定的一些理解

总结上面的所有过程是这样的,多么优雅的公式,非常完美。
关于单目视觉中相机标定的一些理解
插上一嘴,整个转化过程也可以分为两部分:外部转化、内部转化:

  • 外部转化:世界坐标轴转为相机坐标系。所使用参数称为“外部参数”;

  • 内部转化:从相机坐标系到最终离散图像获取的过程,包括畸变校正;所使用参数称为“内部参数”;

     哈哈,陌上人如玉,君子世无双。
    

畸变校正

以上三者的关系也就讲的差不多了,但是姑娘所指的畸变是什么呢?这就得接着说一下畸变的故事了。由于本人水平有限,只能基于Halcon中的面阵相机畸变给大家大致介绍一下。
到目前接触的比较多的畸变有:径向畸变、切向畸变、薄棱镜畸变;

- 径向畸变

沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在普通廉价的镜头中表现更加明显,径向畸变主要包括桶形畸变和枕形畸变两种。

关于单目视觉中相机标定的一些理解

- 切向畸变

切向畸变主要由镜头安装导致,当透镜不完全平行于图像平面的时候产生切向畸变
关于单目视觉中相机标定的一些理解

- 薄棱镜畸变

薄棱镜畸变一般由镜头设计和加工安装误差导致,一般情况下,可忽略此畸变。

Halcon畸变校正

接下来,我们使用Halcon继续介绍。Halcon内将畸变分为面阵相机畸变和线扫相机畸变两大类型:
面阵相机畸变又可以细分为8类,分类情况请见下图,看着就非常复杂的样子。在使用Halcon做畸变校正时需要明确畸变类型。

关于单目视觉中相机标定的一些理解
上图中每个参数的含义请参考calibrate_cameras算子。

畸变校正步骤

关于单目视觉中相机标定的一些理解

请看具体代码:
(代码连接)

create_calib_data ('calibration_object', 1, 1, CalibDataID)   			//生成标定操作句柄

CalTabDescrFile := 'caltab_big.descr'
set_calib_data_calib_object (CalibDataID, 0, CalTabDescrFile)			//明确标定板类型

StartCamPar := [0.008,0,0.0000086,0.0000086,384,288,768,576]
//明确畸变类型,初始化系统内部参数
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', StartCamPar)		

NumImages := 10
read_image (Images, 'calib/calib-3d-coord-' + [1:NumImages]$'02d')
for I := 1 to NumImages by 1
    select_obj (Images, Image, I)
    dev_display (Image)
    Message := 'Find calibration plate in\nall calibration images (' + I + '/' + NumImages + ')'
    disp_message (WindowHandle1, Message, 'window', 12, 12, 'black', 'true')
    * Find the calibration plate
    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 (WindowHandle1, 'black', 'true')
stop ()

calibrate_cameras (CalibDataID, Error)			//计算畸变系数

get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)	//获取内部参数
gen_empty_obj (Maps)
for I := 1 to NumImages by 1
    select_obj (Images, Image, I)
    * Obtain the pose of the calibration table
    get_calib_data (CalibDataID, 'calib_obj_pose', [0,I - 1], 'pose', Pose)	//获取外部参数
    														
    set_origin_pose (Pose, -1.125, -1.0, 0, PoseNewOrigin)	//设置图像坐标原点
    * Generate map
    gen_image_to_world_plane_map (MapSingle, CamParam, PoseNewOrigin, 768, 576, 900, 800, 0.0025, 'bilinear')
										//生成表述畸变关系的投影图

    concat_obj (Maps, MapSingle, Maps)
endfor
clear_calib_data (CalibDataID)
* 
* Map the images
* 
dev_open_window (0, 391, 900 / 2, 800 / 2, 'black', WindowHandle2)
set_display_font (WindowHandle2, 14, 'mono', 'true', 'false')
Button := 0
NumImage := 1
for I := 1 to NumImages by 1
    dev_set_window (WindowHandle1)
    dev_set_part (0, 0, 575, 767)
    dev_clear_window ()
    select_obj (Images, Image, I)
    dev_display (Image)
    select_obj (Maps, MapSingle, I)
    map_image (Image, MapSingle, ImageMapped)			//使用投影图计算畸变校正后的图片
    dev_set_window (WindowHandle2)
    dev_set_part (0, 0, 799, 899)
    dev_clear_window ()
    dev_display (ImageMapped)
    Message := 'Calibration image (' + I + '/' + NumImages + ')'
    disp_message (WindowHandle1, Message, 'window', 12, 12, 'black', 'true')
    Message := 'Mapped image'
    disp_message (WindowHandle2, Message, 'window', 12, 12, 'black', 'true')
    if (I < NumImages)
        disp_continue_message (WindowHandle1, 'black', 'true')
        stop ()
    endif
endfor

来看一眼运行结果,效果卡卡赞:
关于单目视觉中相机标定的一些理解

总结

本人水平水平实在有限,希望对各位有所帮助,若有不足之处请各位不惜赐教。
故事结尾姑娘依然没松口,以上解释比较简单,畸变的世界还是相当复杂的,还需要不断学习:衣沾不足惜,但使愿无违。以上就是今天的全部故事了。

引用

图像坐标:我想和世界坐标谈谈(B)
单目视觉标定:世界坐标系、相机坐标系、图像坐标系、像素坐标系——简单粗暴,粗暴