坚持原创,点击上方蓝字关注我!

相机和距离传感器的标定是机器人和计算机视觉领域中非常重要的基础问题之一。虽然MATLAB和OpenCV里都有对应的工具箱或者库函数,可以直接用来做相机标定,但如果需要同时标定多个相机(比如多目机器人、阵列相机),那这些传统的标定法将消耗掉研究者和开发者的大量时间和精力。

有没有一种省心的、全自动化的标定方法呢?

这里介绍一种可以实现全自动化对相机-相机、相机-距离传感器之间进行标定的方法,并且有论文和源码哦(见文末)!例如,该算法可以实现同时对三目相机 + 激光扫描仪(下图左)、双目相机 + Kinect (下图右)的标定。是不是觉得很赞!

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图1)

角点检测方法对比

这种全自动化标定方法比较复杂,本文重点介绍一下该方法在棋盘格角点检测方面的原理。

点击上方“计算机视觉life”,选择“星标”

我们知道棋盘格中角点检测方法是相机标定中重要的步骤之一。比如Opencv中的函数findChessboardCorners 就可以轻松实现棋盘格角点检测结果。如下图所示:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图2)

那为什么这里还要研究棋盘格角点检测呢?直接调函数不就OK了吗?

快速获得最新干货

转载自OpenCV学堂。本文涉及OpenCV单目相机标定,图像畸变校正

这是因为OpenCV函数虽然简单易用,但是在实际使用中,存在不少问题,这里列举几个:

需要提前指定棋盘格的行数列数作为输入。

相机标定定义与原理

需要多次拍摄获得多张不同方向角度的棋盘格图片进行标定。

鲁棒性差。棋盘格不能有干扰,否则就会使得角点检测失败,而且棋盘倾斜角度较大也会检测失败。

01

在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)。相机标定常见的分为:

无法同时进行多个相机的标定。

单目相机标定

本文的角点检测方法可以解决上述问题,主要体现在:

不需要提前指定棋盘格行数列数。

双目相机标定

相机标定是想从二维的图像中获取三维信息,实现图像的畸变校正、对象测量、三维重建等。由于光线投射导致实际对象物体跟投影到2D平面的图像不一致,幸运的是这种不一致性是稳定的,我们可以通过对相机标定,计算出畸变参数来实现对后续图像的畸变校正。根据标定技术不一样可以分为下面几类标定方法:

基于3D对象参照标定

基于2D平面标定

标定过程只需要拍摄一张包含多个棋盘的图片。

基于1D线性标定

鲁棒性好。因为是基于生长的算法,所以如果出现干扰,就会绕过干扰,生长出最大的棋盘。

自标定

最常见的相机成像方式是基于pinhole的模型、它的成像模型可以图示如下:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图3)

下面我们首先对这个相机成像模型做一番解释

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图4)

通过标定算法同时求出相机内参与外参。最常用的算法是张正友标定算法。OpenCV/Matlab中均已经实现该算法。

标定精度很高。

适用范围广。包括针孔相机、鱼眼相机、全景相机等。

基于生长的棋盘格角点检测原理

本文棋盘格角点检测算法主要分三个步骤:定位棋盘格角点位置;亚像素级角点和方向的精细化;优化能量函数、生长棋盘格。下面分别详细介绍。

1

标定板介绍与制作

02

定位棋盘格角点位置

要想实现对相机的标定,我们首先需要给相机找到个参考对象,常见的就是标定版的类型有如下几种

Chessboard

Circel-grid

RandPattern

要处理的图片中一般会包含很多非棋盘的自然或人工背景,所以第一步就是定位角点的位置。下图是角点检测原理示例。

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图5)

首先定义两种不同的角点原型。一种用于和坐标轴平行的角点(上图a),另一种用于旋转45°的角点(上图b)。根据实践经验可以发现,这两种简单的原型对于由透视变换引起的较大范围的变形的角点检测来说,已经足够。每个原型由4个滤波核{A,B,C,D}组成,用于后面和图像进行卷积操作。然后利用这两个角点原型来计算每个像素点与角点的相似程度(Corner likelihood),这里称之为角点概率。

我们得到的角点概率图虽然能够给出大致的角点范围,但是如何进一步得到角点精确的位置呢?下一步就是在角点概率图上利用非极大值抑制(non-maxima-suppression,NMS)来获得候选点。

ArUco

ChArUc

那么何为非极大值抑制呢?

非极大值抑制顾名思义就是抑制不是极大值的元素,搜索局部的极大值。这个局部代表的是一个邻域,邻域有两个参数可变,一是邻域的维数,二是邻域的大小。这里我们用NMS来选取那些邻域里分数最高的极大值像素点,同时抑制那些分数低的像素点。

然后用梯度统计的方法在一个局域的n x n邻域内验证这些候选点(上图e)。先对局域灰度图进行sobel滤波,然后计算加权方向直方图(32bins),用mean shift算法找到其中的两个主要的模态α1,α2,。根据边缘的方向,对于期望的梯度强度:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图6)

OpenCV源码在其sample/data目录下面一个自带的棋盘图(chessboard.png),显示如下:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图7)

构造一个模板T:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图8)

(*表示互相关操作符)和角点概率图的乘积作为角点得分(Corner score),然后用阈值进行判断就得到了角点(上图f)选购攻略。

2

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图9)

在标定的时候,算法要求提供的棋盘格的宽度与高度,还有他们的间隔距离。需要特别注意是这里的宽高是指他们的内部交叉点的个数,以上图为例,它的大小为7x7而不是8x8。间隔是指棋盘格之间的距离,可以用像素距离表示,也可以用实际毫米为单位表示。

亚像素级角点和方向的精细化

但是上面得到的角点一般不是很精确,需要进一步对角点的位置以及边缘方向进行亚像素级精细化处理。

制作标定版与图像生成

假设c是理想的角点位置,p是c的局部邻域的一个像素点,gp是p点的图像梯度向量,那么就有:

03

最简单的办法就是把上述图像直接打印出来,贴到一个塑料底板上就好啦。如果是土豪可以直接购买各种玻璃底板的标定板。另外还有一个更恶搞的方法,连打印都省啦,直接把chessboard.png这张图在另外一台电脑的显示器上显示,然后把摄像头对着它各种牌即可完成图像数据采集。这个是我手写的采集程序代码,每次想保存图像的时候请安Q字母键即可,代码如下:

void create_images() {
    Mat frame;
    VideoCapture capture(0);
    int index = 1;
    while (true) {
        bool ret = capture.read(frame);
        flip(frame, frame, 1);
        if (!ret) break;
        imshow("frame", frame);
        char c = waitKey(50);
        printf("%d \n", c);
        if (c == 113) { // Q
            imwrite(format("D:/images/zsxq/%d.png", index), frame);
            index += 1;
        }
        if (c == 27) {
            break; // ESC
        }
    }
    capture.release();
}

记得拿着棋盘格图,在镜头面前各种摆POSE,这个是属于你的表演时间,不要客气!具体参考下图:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图10)

相机标定程序实现

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图11)

简单解释一下上式。原因如下图:中心绿色的点表示理想角点c,假如像素点p不在边界上,如下图标号为1的位置,平坦区域梯度gp为零向量,所以上式成立;假如像素点p在边界上,如下图标号为2的位置,梯度gp向量方向垂直向下,(p-c)方向水平向左,两向量方向互相垂直,所以上式仍成立。

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图12)

上图是理想情况下棋盘格,但是实际上边缘不可能这样锐利(只有一个像素大小),梯度方向也没有这么理想,最理想的角点c的位置就是我们在角点候选点c’的邻域Ni(c’)内找到满足以下式子的c’:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图13)

邻域的像素通过梯度幅值自动加权,上述式子右侧对c’求导并令其等于0,可以得到解析解:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图14)

下一步就是refine边缘方向矢量e1,e2,可以通过最小化normals的偏差和对应的梯度实现:

04

大家好,现在我们开始程序实现环节,OpenCV中在camera模块中已经实现了张正友标定算法。我们只需要正确调用,就可以计算出相机的内参与外参,完成相机的标定。具体的代码实现步骤如下:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图15)

3

优化能量函数生长棋盘格

定义棋盘格的能量函数如下:

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图16)

其中第一项E_corners是当前棋盘中角点总数的负值。 第二项E_structure描述了用两个相邻角点来预测第3个角点的匹配程度,分别对棋盘的每行和每列相邻的3个角点(triples)计算其结构能量,取其中的最大值作为该棋盘的结构能量。

定义相机标定的相关常量设置与变量

值得注意的是,由于结构能量约束中使用的是局域的线性约束,上述棋盘格生长方法可以扩展到鱼眼镜头拍摄的高畸变图像。

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图17)

不难发现,计算量和棋盘大小呈现指数关系,当棋盘尺寸较大时,计算量会非常惊人。所以在求解时不能使用穷举法搜索,在此使用的是一种离散优化策略,在实践中证明比较有效。具体过程见上图(a)。

// load image files
vector<string> files;
glob("D:/images/camera2d", files);

// 定义变量
vector<vector<Point2f>> imagePoints;
vector<vector<Point3f>> objectPoints;
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001);
int numCornersHor = 7;
int numCornersVer = 7;
int numSquares = 50;
vector<Point3f> obj;
for (int i = 0; i < numCornersHor; i++)
    for (int j = 0; j < numCornersVer; j++)
        obj.push_back(Point3f((float)j * numSquares, (float)i * numSquares, 0));

发现与绘制棋盘格位置

// 发现棋盘格与绘制
Size s;
for (int i = 0; i < files.size(); i++) {
    printf("image file : %s \n", files[i].c_str());
    Mat image = imread(files[i]);
    s = image.size();
    Mat gray;
    cvtColor(image, gray, COLOR_BGR2GRAY);
    vector<Point2f> corners;
    bool ret = findChessboardCorners(gray, Size(7, 7), corners, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
    if (ret) {
        cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), criteria);
        drawChessboardCorners(image, Size(7, 7), corners, ret);
        imagePoints.push_back(corners);
        objectPoints.push_back(obj);
        imshow("calibration-demo", image);
        waitKey(500);
    }
}

发现棋盘格显示如下(我是直接打印OpenCV自带那张图的)

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图18)

相机校正-计算内参数

解释一下:

// 相机校正
Mat intrinsic = Mat(3, 3, CV_32FC1);
Mat distCoeffs;
vector<Mat> rvecs;
vector<Mat> tvecs;
intrinsic.ptr<float>(0)[0] = 1;
intrinsic.ptr<float>(1)[1] = 1;
calibrateCamera(objectPoints, imagePoints, s, intrinsic, distCoeffs, rvecs, tvecs);

畸变图像校正

05

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图19)

关于畸变类型,常见的图像畸变类型有径向与切向畸变、OpenCV中的相机标定方法只能对径向畸变有效,使用内参对畸变图像实现校正。相关的代码如下:

给定一个种子角点,我们沿着其边缘方向搜索产生一个初始化的种子棋盘格。该棋盘格有3x3个角点,有2x2个棋盘(见上图(a)中最左侧)。然后以此种子棋盘格为基础,分别从最外沿的4个边缘去生长棋盘格,生成4个新的棋盘格proposals。如果其中能量最小的那个棋盘格的能量值比棋盘格扩展前的能量更减少了,就说明生长成功,用这个新的棋盘格代替原来棋盘格。继续生长,直到4个 方向新棋盘格能量不再减少为止。上图(c)是最终的检测结果。

 

另外,为了在单张图片中生长出多个棋盘格,我们把每个角点都尝试作为种子点用上述方法去生长,这样会产生多个重叠的棋盘格。我们只保留能量函数最小的那个作为最终的结果,其他的重叠部分去掉即可。

// 畸变校正
for (int i = 0; i < files.size(); i++) {
    Mat dst;
    Mat image = imread(files[i]);
    undistort(image, dst, intrinsic, distCoeffs);
    imshow("image", image);
    imshow("undistort image", dst);
    waitKey(1000);
}

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图20)

从零开始学习三维视觉核心技术SLAM,扫描查看介绍,3天内无条件退款

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图21)早就是优势,学习切忌单打独斗,这里有教程资料、练习作业、答疑解惑等,优质学习圈帮你少走弯路,快速入门!

关于优化部分不再 介绍,这部分可以参考文末的论文。

先来欣赏一下该方法的结果吧,棋盘格生长结果如下图所示。

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图22)

结构恢复和棋盘匹配结果如下图所示。最上面的一行是单目广角和鱼眼相机标定图像;中间一行是双目相机和Kinect相机标定图;最下面一行是三目相机和Velodyne HDL 64线激光扫描仪标定图。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、算法竞赛、检测分割识别、三维视觉、医学影像、自动驾驶、计算摄影等群(以后会逐渐细分),请扫描下面号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关群。请勿在群内发送广告,否则会请出群,谢谢理解~

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图23)

推荐阅读

实战 | 相机标定

实战 | 图像矫正技术

实战 | Unity下ARKit与OpenCV的结晶

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图24)

总结讨论

实战 | 基于SegNet和U-Net的遥感图像语义分割

本文介绍的全自动化标定方法的优势:

实战 | 文字定位与切割

1、可以同时计算多相机的内参、外参,以及相机-距离传感器之间的变换矩阵,时间在一分钟内

2、算法在不同的光照条件下仍然鲁棒

实战 | 源码入门之Faster RCNN

实战 | 自己实现扫描全能王

实战 | 用OpenCV轻松生成国庆版头像

3、标定过程完全不需要人工干预,真正全自动化

本文算法有什么缺点吗?

该算法受限于棋盘格的约束,所以当棋盘被部分遮挡时,检测结果就不尽如人意。如下图,算法找到的棋盘格是在其约束条件下最好的结果。但是左下方的棋盘因为是次优的所以被忽略掉。这在鱼眼相机标定中非常不利。因为鱼眼镜头越靠近边缘畸变越大,越难以被检测到,但是却对计算标定参数越重要。下一篇我再介绍其他的棋盘格角点检测方法,可以解决这个问题。

实战 | OpenCV 实现多张图像拼接

实战 | 教你自动查找拍糊的图

我用MATLAB撸了一个2D LiDAR SLAM

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图25)

论文及源码

本文算法对应的参考论文:

Geiger A, Moosmann F, Car Ö, et al. Automatic camera and range sensor calibration using a single shot[C]//Robotics and Automation (ICRA), 2012 IEEE International Conference on. IEEE, 2012: 3936-3943.

本文算法工程及源码:

实战 | 哪个瞬间让你突然觉得CV技术真有用?

http://www.cvlibs.net/software/libcbdetect/

干货 | 史上最全 OpenC

本文算法在线标定toolbox:

http://www.cvlibs.net/software/calibration/

点击图片查看相关阅读

实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图26)实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图27)实战 | OpenCV相机标定与畸变校正一次拍摄搞定多相机自动化标定(图28) 打赏
  • 打赏支付宝扫一扫
  • 打赏扫一扫