无人自动驾驶技术之使用OpenCV进行相机校准

照相机与摄像头,是机器人,人工智能,计算机视觉,工业自动化甚至娱乐行业等多个领域的组成部分。在我们使用此设备时,不仅要了解照相原理外,需要使用特殊的技术对摄像头进行相机校准,特别在自动化驾驶上,需要实时的对照相机进行校准操作

我们需要确定真实世界中3D点与其在该校准相机捕获的图像中其对应2D投影(像素)之间的精确关系所需的有关相机的所有信息(参数或系数)。

1、相机/镜头系统的内部参数。例如镜头的焦距,光学中心和径向畸变系数。

2、外部参数:这是指摄像机相对于某些世界坐标系的方向(旋转和平移)。

使用OpenCV进行相机校准

要了解校准过程,我们首先需要了解照相成像的几何原理。

为了轻松理解问题,我们假设在一个房间内部署了一个摄像头。

给定这个房间中的3D点P,我们想在相机拍摄的图像中找到该3D点的像素坐标(u,v)。

1.世界坐标系

世界坐标系和摄影机坐标系通过旋转和平移关联。

这六个参数(3个用于旋转,3个用于平移)称为相机的外部参数。

要定义房间中点的位置,我们首先需要为此房间定义一个坐标系。

原点:我们可以任意固定房间的一角作为原点

X,Y,Z轴:我们还可以沿着地板的两个维度定义房间的X和Y轴,并沿着垂直墙定义Z轴。

2.相机坐标系

3.图像坐标系

显示了点P在图像平面上的投影。

通过对点的世界坐标进行旋转和平移,在相机的3D坐标系中获得点后,便可以将点投影到图像平面上,从而获得该点在图像中的位置。

当图像像素坐标系的原点位于左上角时,显示了一个更现实的场景。

固有相机矩阵需要考虑主要点的位置,轴的偏斜以及沿不同轴的潜在不同焦距。

将世界坐标系中的3D点投影到相机像素坐标的过程分为三个步骤。

  • 1、使用外部矩阵将3D点从世界坐标转换为摄像机坐标,该矩阵由两个坐标系之间的旋转和平移组成。
  • 2.摄像机坐标系中的新3D点使用内部矩阵投影到图像平面上,该矩阵由内部摄像机参数组成,例如焦距,光学中心等。
  • 3.要找到3D点在图像平面上的投影,我们首先需要使用外部参数(旋转)将点从世界坐标系转换为相机坐标系。

总之,相机校准算法具有以下输入和输出

输入:具有点的图像集合,这些点的2D图像坐标和3D世界坐标是已知的。

输出:3×3摄像机固有矩阵,每个图像的旋转和平移。

注意:在OpenCV中,相机固有矩阵没有偏斜参数。所以矩阵的形式是

通过以上的基础知识,我们开始我们的代码操作

步骤1:使用棋盘格图案定义现实世界的坐标

为什么在校准中广泛使用棋盘格图案?

棋盘图案独特且易于在图像中检测。不仅如此,棋盘上正方形的角对于它们的局部定位也是理想的,因为它们在两个方向上都有明显的渐变。此外,这些角还因为它们位于棋盘格线的交点而相关。所有这些事实均用于以棋盘格图案牢固地定位正方形的角。

第2步:从不同的角度捕获棋盘格的多个图像

接下来,我们保持棋盘格静止,并通过移动相机来拍摄棋盘格的多个图像。

或者,我们也可以保持相机恒定不变,并以不同的方向拍摄棋盘格图案。

步骤3:查找棋盘的二维坐标

现在,我们有多个棋盘图像。我们还知道世界坐标下棋盘上点的3D位置。我们需要的最后一件事是图像中这些棋盘格角的2D像素位置。

3.1查找棋盘角

OpenCV提供了一个内置函数findChessboardCorners ,该函数查找棋盘并返回角的坐标。

主要参数如下:

int cvFindChessboardCorners( const void* image, CvSize pattern_size,
                            CvPoint2D32f* corners, int* corner_count = NULL, 
                            int flags = CV_CALIB_CB_ADAPTIVE_THRESH );
参数说明
Image:     输入的棋盘图,必须是8位的灰度或者彩色图像。
pattern_size:    棋盘图中每行和每列角点的个数。
Corners:     检测到的角点
corner_count:     输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
Flags:    各种操作标志,可以是0或者下面值的组合:
CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)
将图像转换为黑白图,而不是一个固定的阈值。
CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定阈值或者自适应的阈值进行二值化之前,
先使用cvNormalizeHist来均衡化图像亮度。
CV_CALIB_CB_FILTER_QUADS - 使用其他的准则(如轮廓面积,周长,方形形状)
来去除在轮廓检测阶段检测到的错误方块。
函数根据是否检测到图案,输出为真还是假。

3.2优化棋盘格角

良好的校准与精度有关。为了获得良好的结果,重要的是获得具有亚像素精度的角的位置。

OpenCV提供cornerSubPix功能,接收原始图像和拐角位置,并在原始位置的一个小邻域内寻找最佳拐角位置。该算法本质上是迭代的,因此我们需要指定终止条件(例如,迭代次数和/或精度)

主要参数如下:


void cv::cornerSubPix(
cv::InputArray image, // 输入图像
cv::InputOutputArray corners, // 角点(既作为输入也作为输出)
cv::Size winSize, // 区域大小为 NXN; N=(winSize*2+1)
cv::Size zeroZone, // 类似于winSize,但是总具有较小的范围,Size(-1,-1)表示忽略
cv::TermCriteria criteria // 停止优化的标准
 );
参数解析:
第一个参数是输入图像,和cv::goodFeaturesToTrack()中的输入图像是同一个图像。
第二个参数是检测到的角点,即是输入也是输出。
第三个参数是计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)。
第四个参数作用类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))。
第五个参数用于表示计算亚像素时停止迭代的标准,可选的值有cv::TermCriteria::MAX_ITER 、cv::TermCriteria::EPS(可以是两者其一,或两者均选),
前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。

步骤4:校准相机

校准的最后一步是将世界坐标中的3D点及其在所有图像中的2D位置传递给OpenCV的calibrateCamera方法。

retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(
  objectPoints, imagePoints, imageSize)
第一个参数objectPoints,为世界坐标系中的三维点。
需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标;
第二个参数imagePoints,为每一个内角点对应的图像坐标点;
第三个参数imageSize,为图像的像素尺寸大小
,在计算相机的内参和畸变矩阵时需要使用到该参数;
第四个参数cameraMatrix为相机的内参矩阵;
第五个参数distCoeffs为畸变矩阵;
第六个参数rvecs为旋转向量;
第七个参数tvecs为位移向量;
第八个参数flags为标定时所采用的算法。有如下几个参数:
     CV_CALIB_USE_INTRINSIC_GUESS:
    使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。 
   CV_CALIB_FIX_PRINCIPAL_POINT:
  在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。 
  CV_CALIB_FIX_ASPECT_RATIO:
 固定fx/fy的比值,只将fy作为可变量,进行优化计算。
当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到。 
        CV_CALIB_ZERO_TANGENT_DIST:设定切向畸变参数(p1,p2)为零。 
        CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变在优化中保持不变。 
        CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。
 如果没有设置,则只计算其它5个畸变参数。
第九个参数criteria是最优迭代终止条件设定。

在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,标定的结果是生成相机的内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像都会生成属于自己的平移向量和旋转向量。

利用求得的相机的内参和外参数据,可以对图像进行畸变的矫正,使用undistort函数实现,其函数原型如下

 cv2.undistort()函数
第一个参数src,输入参数,代表畸变的原始图像;
第二个参数cameraMatrix,为之前求得的相机的内参矩阵;
第三个参数distCoeffs,为之前求得的相机畸变矩阵;
第四个参数dst,矫正后的输出图像,跟输入图像具有相同的类型和大小;
第五个参数newCameraMatrix,默认跟cameraMatrix保持一致;

完整代码:

根据以上的步骤,我们使用OpenCV来进行相机的校准操作

import cv2
import numpy as np
import os
import glob
# 定义checkboard的尺寸
CHECKERBOARD = (6,9)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 保存checkerboard image 的 3D points
objpoints = []
# 保存checkerboard image 的 2D points
imgpoints = []
# 定义世界 坐标系3D points
objp = np.zeros((1, CHECKERBOARD[0]*CHECKERBOARD[1], 3), np.float32)
objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
prev_img_shape = None

代码截图

首先我们定义一下需要校准的初始化参数,包括世界坐标系以及checkboard的参数

# 获取所有图片的地址
images = glob.glob('./images/*.jpg')
for fname in images:
  img = cv2.imread(fname)
  gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
  # findChessboardCorners函数,该函数查找棋盘并返回角的坐标
  ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH+
  cv2.CALIB_CB_FAST_CHECK+cv2.CALIB_CB_NORMALIZE_IMAGE)

  if ret == True:
    objpoints.append(objp)
    # 接收原始图像和拐角位置,并在原始位置的一个小邻域内寻找最佳拐角位置
    corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
    imgpoints.append(corners2)
    img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2,ret)

  cv2.imshow('img',img)
  cv2.waitKey(0)

代码截图

然后使用findChessboardCorners函数查找棋盘并返回角的坐标,然后使用cornerSubPix函数在原始位置的一个小邻域内寻找最佳拐角位置

cv2.destroyAllWindows()
h,w = img.shape[:2]
# calibrateCamera相机校准
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
print("Camera matrix : \n")
print(mtx)
print("dist : \n")
print(dist)
print("rvecs : \n")
print(rvecs)
print("tvecs : \n")
print(tvecs)

代码截图

经过以上步骤,便可以使用calibrateCamera函数对相机进行校准了,
我们也可以打印一下相机校准后的所有参数,到这里相机校准已经完成,
我们在校准后,需要对原始图片进行校正,使用到的函数便是undistort函数

# undistort对图像进行畸变的矫正
img = cv2.imread(images[0])
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
mapx,mapy=cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
cv2.imshow("undistorted image",dst)
cv2.waitKey(0)

代码截图

以上便是完整的相机校准的所有步骤,利用此步骤,我们可以使用到人工智能的自动化驾驶项目上,特别是一些车道线的检测,后期我们会介绍车道线的检测,哪里都需要相机校准的操作。

校准后的图片如下:

https://m.toutiao.com/is/iLjn9d26/ 人工智能研究所: 视频动画详解Transformer模型–Attention is all you need.