相机造成的失真类型
如何找到相机的内在和外在特性
如何基于这些特性来消除图像失真
基础
一些针孔相机会对图像造成严重失真。两种主要的畸变是径向畸变和切向畸变。
径向变形会使直线看起来是弯曲的。点离图像中心越远,径向失真就越大。例如,下面显示了一个图像,其中棋盘的两个边缘用红线标记。但是,你可以看到棋盘的边界不是一条直线,与红线不匹配。所有预期的直线都凸起了。
径向畸变可以表示如下:
类似地,由于图像拍摄透镜没有完全平行于成像平面对准,所以发生切向失真。因此,图像中的某些区域看起来可能比预期的更近。切向畸变的量可以表示如下:
简而言之,我们需要找到五个参数,即失真系数,由下式给出:
除此之外,我们还需要一些其他信息,比如相机的内在和外在参数。固有参数是特定于相机的。它们包括像焦距(fx,fy)和光学中心(cx,cy)这样的信息。焦距和光学中心可用于创建相机矩阵,该矩阵可用于消除特定相机镜头造成的失真。相机矩阵对于特定的相机是唯一的,因此一旦计算出来,它就可以在同一相机拍摄的其他图像上重复使用。它表示为3x3矩阵。
外部参数对应于将3D点的坐标转换为坐标系的旋转和平移矢量。
对于立体应用,需要首先校正这些失真。为了找到这些参数,我们必须提供一些定义良好的模式(例如棋盘)的样本图像。我们找到了一些我们已经知道相对位置的特定点(例如棋盘上的直角)。我们知道这些点在现实世界空间中的坐标,也知道图像中的坐标。所以我们可以求解失真系数。为了获得更好的结果,我们至少需要10个测试模式。
如上所述,我们需要至少10个测试模式来进行相机校准。OpenCV附带了一些棋盘的图像(请参阅samples/data/left01.jpg–left14.jpg),因此我们将使用这些图像。考虑一个棋盘的图像。相机校准所需的重要输入数据是3D真实世界点的集合以及图像中这些点的对应2D坐标。2D图像点是可以的,我们可以很容易地从图像中找到。(这些图像点是棋盘中两个黑色方块相互接触的位置)
那么来自真实世界空间的3D点呢?这些图像是从静态相机中拍摄的,棋盘被放置在不同的位置和方向。所以我们需要知道(X,Y,Z)值。但为了简单起见,我们可以说棋盘在XY平面上保持静止(所以Z=0总是),相机也相应地移动了。这种考虑有助于我们只找到X,Y值。现在,对于X,Y值,我们可以简单地将点传递为(0,0),(1,0),(2,0)。。。其表示点的位置。在这种情况下,我们得到的结果将以棋盘正方形的大小为尺度。但是,如果我们知道正方形的大小(比如30毫米),我们可以将值传递为(0,0)、(30,0),(60,0)、。因此,我们得到了以毫米为单位的结果。(在这种情况下,我们不知道正方形的大小,因为我们没有拍摄这些图像,所以我们以正方形的大小通过)。
3D点称为对象点,2D图像点称为图像点。
Setup
因此,要在棋盘中找到图案,我们可以使用函数cv.findChessboardCorners()。我们还需要传递我们正在寻找的图案类型,如8x8网格、5x5网格等。在本例中,我们使用7x6网格。(通常棋盘有8x8个方格和7x7个内角)。它返回角点和retval,如果获得图案,该值将为True。这些角将按顺序(从左到右,从上到下)放置
Note
此功能可能无法在所有图像中找到所需的图案。因此,一个好的选择是编写代码,这样,它启动相机并检查每一帧所需的模式。获得图案后,找到角落并将其存储在列表中。此外,在阅读下一帧之前提供一些间隔,以便我们可以将棋盘调整到不同的方向。继续此过程,直到获得所需数量的良好图案。即使在这里提供的例子中,我们也不确定给出的14个图像中有多少是好的。因此,我们必须阅读所有的图像,只拍摄好的图像。
我们可以使用圆形网格来代替棋盘。在这种情况下,我们必须使用函数cv.findCirclesGrid()来查找模式。较少的图像足以使用圆形网格执行相机校准。
一旦我们找到了角,我们可以使用cv.canerSubPix()来提高它们的准确性。我们也可以使用cv.drawChessbordCorners()来绘制图案。所有这些步骤都包含在下面的代码中:
点击查看代码
import numpy as np
import cv2 as cv
import glob
# termination criteria
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('*.jpg')
for fname in images:
img = cv.imread(fname)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv.findChessboardCorners(gray, (7,6), None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
# Draw and display the corners
cv.drawChessboardCorners(img, (7,6), corners2, ret)
cv.imshow('img', img)
cv.waitKey(500)
cv.destroyAllWindows()
一张画有图案的图片如下所示:
校准
现在我们有了目标点和图像点,我们就可以进行校准了。我们可以使用函数cv.calirateCamera(),它返回相机矩阵、失真系数、旋转和平移矢量等。
ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
Undistortion
现在,我们可以拍摄一张图片并不侵权。OpenCV提供了两种方法。然而,首先,我们可以使用cv.getOptimalNewCameraMatrix()基于自由缩放参数来细化相机矩阵。如果缩放参数alpha=0,它将返回具有最小不需要像素的未失真图像。因此,它甚至可以去除图像角落的一些像素。如果alpha=1,则所有像素都会保留一些额外的黑色图像。此函数还返回可用于裁剪结果的图像ROI。
因此,我们拍摄一张新的图像(在本例中为left12.8jpg。这是本章的第一张图像)
点击查看代码
img = cv.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))