教程:使用iPhone相机和openCV来完成3D重建(第二部分)

欢迎来到关于立体重建三部曲的第2部。在本节中,我们将讨论如何校准您的相机。

就像之前所提到的,照相机的镜头使你拍的照片失真。这在三维重建中是很麻烦的,所以我们需要纠正这个问题。在校正之前,我们需要知道我们所使用的相机的内部参数。

有时这些参数是未知的,但幸运的是,OpenCV有一个专门针对这个的算法, 我们可以应用该算法开始我们的3D重建。

在整个设计过程中,我们会用到Python 3.7.1, OpenCV 3.4.4. 还有一些python第三方库: Numpy, Glob,tqdm和Pillow。因此,首先确保这些工具都已经安装好。

OpenCV中摄像机的标定过程是让计算机用棋盘图形扫描一幅图像,用不同的图像多次识别内部的角。



opencv 显示三维坐标 opencv生成3d图像_opencv calibration

OpenCV提供的模型校准示例

大多数关于相机校准的教程都是关于网络摄像机或其他摄像机的,因此它们是为分析每一个单独的帧(或多个帧)而定制的。

在我们的情况下,我们想校准的是手机摄像头,所以我们不能这样做。为了让计算机正确地校准手机摄像头,它需要几个相同模式的例子。

在处理视频流时,我们可以分析用户在摄像机前移动时的每一帧,直到移动的模型被多次检测到(这通常是一个任意的测量,但一般是至少有10次检测)。

因为我们不处理视频流,所以我们必须为相同的模型拍多张照片。

步骤1. 得到一个棋盘图案.

前往这个链接,把这个棋盘图案打印在一张纸上。为求完美结果,确保每个正方形都是30毫米长(尽管这不是特别重要)。

步骤2. 把棋盘图案挂在白色的墙上.

当我运行这个算法时,我注意到背景中的东西越多,计算相机矩阵的时间就越长。所以我用了一面白色的墙,把棋盘图案贴在上面。确保它是完全平坦的。



opencv 显示三维坐标 opencv生成3d图像_3D_02

根据棋盘图案重新装饰房子

步骤3. 拍几张图案的照片.

这一步很重要,确保你拍的照片有很好的光照,并且图案是从不同的角度拍摄的。还要确保图案位于屏幕的不同部分。

如果你只拍摄居中的照片,可能会发生错误的校准。确保你的照片有很多变化。



opencv 显示三维坐标 opencv生成3d图像_OpenCV_03

这是一个很好的关于如何拍摄图案的例子。来自乌特卡什·西纳。

在Utkarsh Sinah的博客(AI shack)中可以找到关于相机校准和如何捕捉图案的丰富资源。如果你对C++校准相机的方法感兴趣,你应该去看看。

需要指出的是,并不是所有的图片都适合检测模式。而你事先很难知道哪些照片会起作用,因此拍尽可能多的照片是一个好主意。我拍了64张。

步骤4:让我们开始代码部分

一旦你拍了足够多的照片,是时候写一些代码了(记住整个代码都在这里)。第一步是选择棋盘大小。虽然大小完全是任意的,但建议您选择一个不对称的大小(即矩形,而不是正方形)。此处,我选择了7X5。

import cv2
 
  
 
import numpy as np 
 
  
 
import glob
 
  
 
from tqdm import tqdm
 
  
 
import PIL.ExifTags
 
  
 
import PIL.Image
 
  
 
#============================================
 
  
 
# Camera calibration
 
  
 
#============================================
 
  
 
#Define size of chessboard target.
 
  
 
chessboard_size = (7,5)
 
  
 
第二步是定义一个网格来存储所有点。存储的点需要是有序的,如:(0,0,0),(1,0,0),(2,0,0)….,(6,5,0)
#Define arrays to save detected points
 
  
 
obj_points = #3D points in real world space 
 
  
 
img_points = #3D points in image plane
 
  
 
#Prepare grid and points to display
 
  
 
objp = np.zeros((np.prod(chessboard_size),3),dtype=np.float32)
 
  
 
objp[:,:2] = np.mgrid[0:chessboard_size[0], 
 
  
 
0:chessboard_size[1]].T.reshape(-1,2)
 
  
 
因为我们要处理几个图像,所以我们可以使用glob迭代地打开它们。此外,由于OpenCv中的角点检测算法需要一些时间来处理,因此我们可以用tqdm包裹我们的循环,以了解距离处理上一个图像已经有多长时间了,还剩下多少图像没有处理。
#read images
 
  
 
calibration_paths = glob.glob('./calibration_images/*')
 
  
 
#Iterate over images to find intrinsic matrix
 
  
 
for image_path in tqdm(calibration_paths):
 
  
 
#Load image
 
  
 
image = cv2.imread(image_path)
 
  
 
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
 
  
 
print("Image loaded, Analizying...")
 
  
 
#find chessboard corners
 
  
 
ret,corners = cv2.findChessboardCorners(gray_image, 
 
  
 
chessboard_size, None)
 
  
 
if ret == True:
 
  
 
print("Chessboard detected!")
 
  
 
print(image_path)
 
  
 
#define criteria for subpixel accuracy
 
  
 
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 
 
  
 
30, 0.001)
 
  
 
#refine corner location (to subpixel accuracy) based on criteria.
 
  
 
cv2.cornerSubPix(gray_image, corners, (5,5), (-1,-1), criteria)
 
  
 
obj_points.append(objp)
 
  
 
img_points.append(corners)
 
  
 
在这个循环里,所有的魔法都在发生。加载图像后,我们必须将先其转换为灰度图,然后使用findchessboardcorners算法。
此算法将返回检测到的角点和一个名为ret的标志而且如果算法能够检测到模式,则返回true。
为了提高标定算法的精度,将角点位置细化到亚像素精度。在这种情况下,我们必须定义定位所需的标准。
我们采用了如果的标准定义:标准=(类型、迭代次数、精度)。在这个例子中,我们告诉算法我们关心迭代次数和精度(cv2.term_criteria_eps+cv2.term_criteria_max_iter),我们选择了30次迭代,精度为0.001。
cv2.cornerSubPix是一种专注于重新定位点的算法。它接收图像、角点、窗口大小、零区域和实际条件作为输入。窗口大小是搜索区域。
关注这个算法不是很重要,我只是决定对参数进行评论,因为大多数教程只是对这个算法进行了润色。如果想要了解有关其工作原理的更多信息,请查看此处。
分析完所有图片后,我们运行cv2.calibratecamera算法。这是输出相机参数的算法。该算法返回摄像机矩阵(k)畸变系数(dist)和旋转和平移矢量(rvecs和tvecs)。
#Calibrate camera
 
  
 
ret, K, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points,gray_image.shape[::-1], None, None)
 
  
 
#Save parameters into numpy file
 
  
 
np.save("./camera_params/ret