本文为 AI 研习社编译的技术博客,原标题 :
3D Reconstruction with Stereo Images -Part 1: Camera Calibration
作者 | Keenan James
翻译 | 通夜、Hasekiel_learn
校对 | 酱番梨 审核 | 约翰逊·李加薪 整理 | 立鱼王
原文链接:
https://medium.com/@dc.aihub/3d-reconstruction-with-stereo-images-part-1-camera-calibration-d86f750a1ade
注:本文的相关链接请访问文末【阅读原文】
引言:后续的一系列文章会尝试解释用于从2D图片提取3D信息的一些重要工具和技术。3D重建对于很多应用来说是一个非常有用的工具,他可以构建人脸、场景、或其他物体的3D模型。这种模型是通过计算2D图像像素中的深度信息得到的。
(图 3D重建)
因为现在针孔数码相机已经相对比较便宜也容易买到,我们可以看到,相比其他的一些更加昂贵的技术(比方说Lidar),这种(数码相机的)手段为3D扫描提供了更实用的一种方法。而且,随着智能手机、监控技术、物联网的兴起,标准的2D相机早已在我们日常生活中得到普及。因此,这种2D手段使得3D重建在现有设备上的部署和应用成为可能,大大减少了资金上的门槛。
在本文中我们会研究如何使用双目图像(stereo images)实施3D重建。
双目图像需要两个相机分别拍摄图片,利用两张图片计算3D空间中的一个点。本质上是先把两张图片对应同一空间点的像素匹配,接着利用对极几何(epipolar geometry)计算该点在3D空间的坐标。处理了所有相关像素之后,最终得到的是被拍摄物体的3D点图(point map)。
(图)
然而,在实践中,3D重建远没有这么简单,需要更多步骤才能得到准确的3D点图。这个系列的文章会有四篇,每篇都涵盖一些实施有效3D重建需要弄懂的重要技术。该篇参考了Open CV文档中的教程,可以通过下面的链接中查看:
https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_calib3d/py_table_of_contents_calib3d/py_table_of_contents_calib3d.html
这四篇文章包括:
相机校准
姿态估计
对极几何
立体图像的深度图
在本系列的最后,我希望你能够理解如何使用此工具集处理三维重建或三维扫描问题。有关详细内容请查看上面的Open CV文档。
第一篇文章的目的是帮助你了解在使用普通针孔相机拍摄的照片中常见的相机变形。我们还将学习相机的内部参数和外部参数之间的定义和区别,以及为什么在我们的代码中需要它们。一旦找到了这些参数,我们就可以使用Open CV对图像进行变形校正。这是全面三维重建的第一步。
普通针孔相机通过两个主要因素使图像失真。径向畸变;这本质上使得直线在图像中呈现出轻微的曲线。线离图像中心越远,径向畸变的影响越严重。下面我们可以看到一个棋盘的例子,它的曲线特征由图像上方的红色直线突出显示。
为了纠正这个问题,并相应地校准相机,我们使用下面的方程。
针孔相机产生畸变的另一个因素是切向畸变。当使用的相机镜头与图像平面不完全对齐时,就会发生这种形式的失真。假设一个相机面对一个特定的方向,为了避免在图像中出现切向畸变,覆盖针孔光传感器的镜头必须完全垂直于相机所面对的方向。然而事实并非如此,因为大多数镜头都有轻微的倾斜,这导致物体在图像的一边比另一边更靠近相机。虽然人们用肉眼乍一看是几乎不可能看到这种失真的,但是为了进行三维重建,我们将需要使用一个方程来纠正图像。
这些方程的目的是确定我们的代码需要的五个参数,称为失真系数。这些信息将用于执行我们的Open CV包中专门用来3D重建的函数。
除了畸变系数,我们还需要识别相机的内部和外部参数。
内部参数是指相机的特定信息,如焦距和光的中心。
焦距是摄影镜头的基本描述,通常用(mm)表示。焦距与给定透镜的大小无关,而是指光线在光学距离上的一点,在这一点上,光线会聚在一起,使底层的数字传感器生成清晰而详细的图像。对于我们的例子,它用下面的符号表示。
透镜的光学中心是指光线通过其曲面时不发生偏转的一个点。由于透镜具有凸凹形状,透镜的任何其他点都会使光线向光学中心偏转或偏离光学中心。在我们的例子中,它用下面的符号表示。
在我们的代码中,内部参数包含在一个3×3矩阵中,如下所示。
外部参数是指描述相机在三维空间中相对位置的信息;比如旋转和平移向量。
对于我们针对立体图像的应用程序,在进一步尝试进行三维重建之前,需要首先使用上述参数纠正失真。要发现这些参数,我们需要提供一个定义良好的对象的示例图像,该对象的一般维度已经知道。利用物体在二维平面上的坐标与三维空间中真实物体的已知维数相匹配,可以计算出Open CV模块中所需要的畸变系数。幸运的是,我们将能够使用上面显示的棋盘作为我们的校准对象,并且Open CV中提供了该图片。
虽然提供至少10张图片来有效校准我们的相机是一种很好的做法,但是为了简单起见,我们只举一个例子。
我们可以使用已知对象的图像来提取能够校准我们的相机的信息。我们需要的是一组二维图像平面内的物体的二维坐标,以及它在现实世界空间中的三维坐标。二维坐标称为图像点,三维坐标称为目标点。
图像点很容易确定,因为它只是简单地测量图像上的一个点与用X、Y坐标表示的其余点之间的关系。而目标点更难计算。我们需要知道的是物体在真实空间中的X,Y,Z坐标。为了简单起见,我们假设物体固定在xy平面上,因此Z的值总是0。这样一来,我们就可以只用X和Y作为三维空间中一个点的位置。用这种方法,我们可以有效地描述被描绘对象的大小和位置。
在我们的例子中,我们想用X和Y值来描述棋盘上单个正方形的大小。我们应该注意到,因为我们使用的是提供给我们的图片,而不是我们自己拍摄的,所以我们不知道图片中棋盘的确切大小。如果是我们自己拍的,我们就能够将特定的值(尺度或其他)传递到我们的坐标系。因为我们没有这个信息,所以我们仍然可以继续使用棋盘上单个正方形的大小作为度量标准。例如,正方形左下角的点可以表示为0,0,而右上角是1,1。假设棋盘上的所有方块大小相同,那么我们就可以根据图像中相同点的二维坐标引用这些信息来推断其在三维现实空间中的位置。
接下来,我们可以继续编写代码,这些代码将在我们的棋盘中找到模式。我们可以从使用函数cv2.findChessBoardCorners()开始。这个函数需要特定的网格信息,比如8 x 8或4 x 4。在我们的例子中,我们会找到一个7×6的网格。这个函数返回的是棋盘每个角落的图像点坐标和一个布尔值,该值表示是否找到了完整的棋盘。
一旦发现棋盘的角,建议使用cv2.cornerSubPix()函数进一步提高其准确性,然后使用cv2.drawChessboardCorners()绘制覆盖在图像上的棋盘图案。
下面所示的代码涵盖了我们到目前为止所完成的工作。
import numpy as np
import cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.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 = cv2.imread(fname)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (7,6),None)
# If found, add object points, image points (after refining them)
if ret == True:
objpoints.append(objp)
cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
imgpoints.append(corners)
# Draw and display the corners
cv2.drawChessboardCorners(img, (7,6), corners2,ret)
cv2.imshow('img',img)
cv2.waitKey(500)
cv2.destroyAllWindows()
上面的代码将生成如下图,图中的棋盘上突出显示了一个7 x 6的网格。
我们终于可以继续校准我们的相机和纠正我们的图像。为此,我们将使用函数cv2.calibrateCamera()。它返回相机矩阵和畸变系数,包括旋转和平移向量为我们的外在值。
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1],None,None)
在这一部分,我们现在能够使用Open CV的其他函数来对一个图像进行畸变校正。然而,第一步是使用cv2.getOptimalNewCameraMatrix()来完善含有我们内部参数值的相机矩阵。代码如下所示。
img = cv2.imread('left12.jpg')
h, w = img.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
接下来我们可以继续校正畸变。下一个代码块显示了Open CV中用于执行此任务的方法的最简单实现。我们将使用前一个函数返回的ROI来裁剪得到的图像。
# undistort
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)
结果如下图所示,它描绘了一个未失真的棋盘。
这篇文章的目的是提高我对计算机视觉应用程序的理解,希望它也能帮助其他人。
作者:Keenan James,导师:Amit Maraj教授
想要继续查看该篇文章相关链接和参考文献?
点击底部【阅读原文】即可访问:
https://ai.yanxishe.com/page/TextTranslation/1399
AI求职百题斩 · 每日一题
给 AI研习社 设置星标,快速查看最新内容