OpenCV+相机校准和3D重建

Begone

发布日期: 2021-05-10 07:55:16 浏览量: 483
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

相机校准

相机校准至少需要10个测试图案,所需的重要输入数据是3D现实世界点集以及图像中这些点的相应2D坐标。3D点称为对象点,而2D图像点称为图像点

准备工作

除了棋盘,我们还可以使用圆形网格。 在这种情况下,我们必须使用函数cv.findCirclesGrid()来找到模式。 较少的图像足以使用圆形网格执行相机校准。

一旦找到拐角,就可以使用cv.cornerSubPix()来提高其精度。我们还可以使用cv.drawChessboardCorners()绘制图案。

  1. import numpy as np
  2. import cv2 as cv
  3. import glob
  4. # 终止条件
  5. criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
  6. # 准备对象点, 如 (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
  7. objp = np.zeros((6*7,3), np.float32)
  8. objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
  9. # 用于存储所有图像的对象点和图像点的数组。
  10. objpoints = [] # 真实世界中的3d点
  11. imgpoints = [] # 图像中的2d点
  12. images = glob.glob('*.jpg')
  13. for fname in images:
  14. img = cv.imread(fname)
  15. gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
  16. # 找到棋盘角落
  17. ret, corners = cv.findChessboardCorners(gray, (7,6), None)
  18. # 如果找到,添加对象点,图像点(细化之后)
  19. if ret == True:
  20. objpoints.append(objp)
  21. corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
  22. imgpoints.append(corners)
  23. # 绘制并显示拐角
  24. cv.drawChessboardCorners(img, (7,6), corners2, ret)
  25. cv.imshow('img', img)
  26. cv.waitKey(500)
  27. cv.destroyAllWindows()

校准

现在我们有了目标点和图像点,现在可以进行校准了。我们可以使用函数cv.calibrateCamera()返回相机矩阵,失真系数,旋转和平移矢量等。

  1. ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints,
  2. gray.shape[::-1], None, None)

不失真

现在,我们可以拍摄图像并对其进行扭曲。OpenCV提供了两种方法来执行此操作。但是,首先,我们可以使用cv.getOptimalNewCameraMatrix()基于自由缩放参数来优化相机矩阵。如果缩放参数alpha = 0,则返回具有最少不需要像素的未失真图像。因此,它甚至可能会删除图像角落的一些像素。如果alpha = 1,则所有像素都保留有一些额外的黑色图像。此函数还返回可用于裁剪结果的图像ROI。

使用cv.undistort()

这是最简单的方法。只需调用该函数并使用上面获得的ROI裁剪结果即可。

使用remapping

该方式有点困难。首先,找到从扭曲图像到未扭曲图像的映射函数。然后使用重映射功能。

  1. img = cv.imread('left12.jpg')
  2. h, w = img.shape[:2]
  3. newcameramtx, roi = cv.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
  4. # 1
  5. dst = cv.undistort(img, mtx, dist, None, newcameramtx)
  6. # 剪裁图像
  7. x, y, w, h = roi
  8. dst = dst[y:y+h, x:x+w]
  9. cv.imwrite('calibresult.png', dst)
  10. # 2
  11. mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
  12. dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
  13. # 裁剪图像
  14. x, y, w, h = roi
  15. dst = dst[y:y+h, x:x+w]
  16. cv.imwrite('calibresult.png', dst)

参数:重投影误差

重投影误差可以很好地估计找到的参数的精确程度。重投影误差越接近零,我们发现的参数越准确。给定固有,失真,旋转和平移矩阵,我们必须首先使用cv.projectPoints()将对象点转换为图像点。然后,我们可以计算出通过变换得到的绝对值和拐角发现算法之间的绝对值范数。为了找到平均误差,我们计算为所有校准图像计算的误差的算术平均值。

  1. mean_error = 0
  2. for i in xrange(len(objpoints)):
  3. imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
  4. error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
  5. mean_error += error
  6. print( "total error: {}".format(mean_error/len(objpoints)) )

姿态估计

先优化。然后使用函数cv.solvePnPRansac()计算旋转和平移。一旦有了这些变换矩阵,就可以使用它们将轴点投影到图像平面上。

  1. for fname in glob.glob('left*.jpg'):
  2. img = cv.imread(fname)
  3. gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  4. ret, corners = cv.findChessboardCorners(gray, (7,6),None)
  5. if ret == True:
  6. corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
  7. # 找到旋转和平移矢量。
  8. ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)
  9. # 将3D点投影到图像平面
  10. imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)
  11. img = draw(img,corners2,imgpts)
  12. cv.imshow('img',img)
  13. k = cv.waitKey(0) & 0xFF
  14. if k == ord('s'):
  15. cv.imwrite(fname[:6]+'.png', img)
  16. cv.destroyAllWindows()

对极几何

首先我们需要在两个图像之间找到尽可能多的匹配项,以找到基本矩阵。为此,我们将SIFT描述符与基于FLANN的匹配器和比率测试结合使用。

  1. import numpy as np
  2. import cv2 as cv
  3. from matplotlib import pyplot as plt
  4. img1 = cv.imread('myleft.jpg',0) #索引图像 # left image
  5. img2 = cv.imread('myright.jpg',0) #训练图像 # right image
  6. sift = cv.SIFT()
  7. # 使用SIFT查找关键点和描述符
  8. kp1, des1 = sift.detectAndCompute(img1,None)
  9. kp2, des2 = sift.detectAndCompute(img2,None)
  10. # FLANN 参数
  11. FLANN_INDEX_KDTREE = 1
  12. index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
  13. search_params = dict(checks=50)
  14. flann = cv.FlannBasedMatcher(index_params,search_params)
  15. matches = flann.knnMatch(des1,des2,k=2)
  16. good = []
  17. pts1 = []
  18. pts2 = []
  19. # 根据Lowe的论文进行比率测试
  20. for i,(m,n) in enumerate(matches):
  21. if m.distance < 0.8*n.distance:
  22. good.append(m)
  23. pts2.append(kp2[m.trainIdx].pt)
  24. pts1.append(kp1[m.queryIdx].pt)

现在,我们获得了两张图片的最佳匹配列表。 让我们找到基本面矩阵。

  1. pts1 = np.int32(pts1)
  2. pts2 = np.int32(pts2)
  3. F, mask = cv.findFundamentalMat(pts1,pts2,cv.FM_LMEDS)
  4. # 我们只选择内点
  5. pts1 = pts1[mask.ravel()==1]
  6. pts2 = pts2[mask.ravel()==1]

接下来,我们找到Epilines。在第二张图像上绘制与第一张图像中的点相对应的Epilines。因此,在这里提到正确的图像很重要。我们得到了一行线。因此,我们定义了一个新功能来在图像上绘制这些线条。

  1. def drawlines(img1,img2,lines,pts1,pts2):
  2. ''' img1 - 我们在img2相应位置绘制极点生成的图像
  3. lines - 对应的极点 '''
  4. r,c = img1.shape
  5. img1 = cv.cvtColor(img1,cv.COLOR_GRAY2BGR)
  6. img2 = cv.cvtColor(img2,cv.COLOR_GRAY2BGR)
  7. for r,pt1,pt2 in zip(lines,pts1,pts2):
  8. color = tuple(np.random.randint(0,255,3).tolist())
  9. x0,y0 = map(int, [0, -r[2]/r[1] ])
  10. x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1] ])
  11. img1 = cv.line(img1, (x0,y0), (x1,y1), color,1)
  12. img1 = cv.circle(img1,tuple(pt1),5,color,-1)
  13. img2 = cv.circle(img2,tuple(pt2),5,color,-1)
  14. return img1,img2

现在,我们在两个图像中都找到了Epiline并将其绘制。

  1. # 在右图(第二张图)中找到与点相对应的极点,然后在左图绘制极线
  2. lines1 = cv.computeCorrespondEpilines(pts2.reshape(-1,1,2), 2,F)
  3. lines1 = lines1.reshape(-1,3)
  4. img5,img6 = drawlines(img1,img2,lines1,pts1,pts2)
  5. # 在左图(第一张图)中找到与点相对应的Epilines,然后在正确的图像上绘制极线
  6. lines2 = cv.computeCorrespondEpilines(pts1.reshape(-1,1,2), 1,F)
  7. lines2 = lines2.reshape(-1,3)
  8. img3,img4 = drawlines(img2,img1,lines2,pts2,pts1)
  9. plt.subplot(121),plt.imshow(img5)
  10. plt.subplot(122),plt.imshow(img3)
  11. plt.show()

立体图像的深度图

  1. import numpy as np
  2. import cv2 as cv
  3. from matplotlib import pyplot as plt
  4. imgL = cv.imread('tsukuba_l.png',0)
  5. imgR = cv.imread('tsukuba_r.png',0)
  6. stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
  7. disparity = stereo.compute(imgL,imgR)
  8. plt.imshow(disparity,'gray')
  9. plt.show()

结果受到高度噪声的污染。通过调整numDisparities和blockSize的值,可以获得更好的结果。

参数:

  • texture_threshold:过滤出纹理不足以进行可靠匹配

  • 区域斑点范围和大小:基于块的匹配器通常会在对象边界附近产生“斑点”,其中匹配窗口捕获一侧的前景和背景
    在另一场景中,匹配器似乎还在桌子上投影的纹理中找到小的虚假匹配项。为了消除这些伪像,我们使用由speckle_size和speckle_range参数控制的散斑滤镜对视差图像进行后处理。speckle_size是将视差斑点排除为“斑点”的像素数。speckle_range控制必须将值差异视为同一对象的一部分的程度

  • 视差数量:滑动窗口的像素数。它越大,可见深度的范围就越大,但是需要更多的计算

  • min_disparity:从开始搜索的左像素的x位置开始的偏移量

  • uniqueness_ratio:另一个后过滤步骤。如果最佳匹配视差不足够好于搜索范围中的所有其他视差,则将像素滤出。如果texture_threshold和斑点过滤仍在
    通过虚假匹配,则可以尝试进行调整

  • prefilter_size和prefilter_cap:预过滤阶段,可标准化图像亮度并增强纹理,以准备块匹配
    通常,你不需要调整这些

上传的附件

发送私信

我也许不完美,但我一直在做自己

19
文章数
31
评论数
最近文章
eject