opencv-相机校准和3D重建

参考:

1、http://docs.opencv.org/3.3.0/  官方文档api

2、http://docs.opencv.org/3.3.0/d6/d00/tutorial_py_root.html 官方英文教程

3、https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_tutorials.html

4、https://github.com/makelove/OpenCV-Python-Tutorial# 进阶教程

5、https://docs.opencv.org/3.3.0/index.html  官方英文教程

6、https://github.com/abidrahmank/OpenCV2-Python-Tutorials

7、https://www.learnopencv.com/

8、http://answers.opencv.org/questions/ OpenCV论坛

9、https://github.com/opencv/opencv   官方github

10、https://github.com/abidrahmank/OpenCV2-Python-Tutorials


注:安装的版本 opencv_python-3.3.0-cp36-cp36m-win_amd64.whl



参考:https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_tutorials.html




相机校准

代码

see  samples/cpp/left01.jpg -- left14.jpg

所以要在棋盘中找到模式,我们使用函数cv2.findChessboardCorners()。 我们还需要通过我们正在寻找什么样的模式,如8x8网格,5x5网格等。在这个例子中,我们使用7x6网格。 (通常棋盘有8x8平方和7x7内角)。 它返回角点和retval,如果获得pattern,则返回True。 这些角将被放置在一个顺序(从左到右,从上到下)

该函数可能无法在所有图像中找到所需的图案。 因此,一个很好的选择是编写代码,以便它启动相机并检查每个框架是否需要的图案。 一旦获得图案,找到角落并将其存储在列表中。 在下一帧之前还提供一些间隔,以便我们可以在不同的方向调整我们的棋盘。 继续此过程,直到获得所需数量的良好图案。 即使在这里提供的例子中,我们也不能确定出14张图像,有多少是好的。 所以我们读了所有的图像,并选取好的。


取代棋盘,我们可以使用一些圆形网格,但是使用函数cv2.findCirclesGrid()来查找模式。 据说使用圆形网格时图像数量就够少了。


一旦找到了角落,我们可以使用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('./left/*.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)

        corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
        imgpoints.append(corners2)

        # Draw and display the corners
        img = cv2.drawChessboardCorners(img, (7,6), corners2,ret)
        cv2.imshow('img',img)
        cv2.waitKey(500)

cv2.destroyAllWindows()


校准

所以现在我们有我们的对象点和图像点,我们准备去校准。 为此,我们使用函数cv2.calibrateCamera()。 它返回相机矩阵,失真系数,旋转和平移矢量等。

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

Undistortion

我们已经得到了我们正在尝试的。 现在我们可以拍摄一张图像,并且不要静音。 OpenCV有两种方法,我们将看到两者。 但在此之前,我们可以使用cv2.getOptimalNewCameraMatrix(),基于自由缩放参数来改进摄像机矩阵。 如果缩放参数alpha = 0,则会返回不失真的图像,并减少不必要的像素。 所以它甚至可以删除图像角落的一些像素。 如果alpha = 1,则所有像素都会保留一些额外的黑色图像。 它还返回可用于裁剪结果的图像ROI。

所以我们拍摄一个新的图像(在这种情况下是left12.jpg这是本章的第一个图像)

img = cv2.imread('left12.jpg')
h,  w = img.shape[:2]
newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))

1. Using cv2.undistort()
这是最短的路径。 只需调用该功能并使用上面获得的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)

2. Using remapping
这是弯曲的路径。 首先找到从失真图像到未失真图像的映射函数。 然后使用重映射功能。

# undistort
mapx,mapy = cv2.initUndistortRectifyMap(mtx,dist,None,newcameramtx,(w,h),5)
dst = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)

# crop the image
x,y,w,h = roi
dst = dst[y:y+h, x:x+w]
cv2.imwrite('calibresult.png',dst)

重新投影错误

重新投影误差给出了一个很好的估计,发现的参数是多么精确。 这应尽可能接近零。 给定内在,失真,旋转和平移矩阵,我们首先使用cv2.projectPoints()将对象点转换为图像点。 然后我们计算出我们与转换和角点发现算法之间的绝对范数。 为了找到平均误差,我们计算所有校准图像的误差的算术平均值。

mean_error = 0
for i in xrange(len(objpoints)):
    imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv2.norm(imgpoints[i],imgpoints2, cv2.NORM_L2)/len(imgpoints2)
    tot_error += error

print "total error: ", mean_error/len(objpoints)



姿势估计

首先,我们从以前的校准结果中加载相机矩阵和失真系数。

import cv2
import numpy as np
import glob

# Load previously saved data
with np.load('B.npz') as X:
    mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]

现在让我们创建一个函数,绘制棋盘中的角落(使用cv2.findChessboardCorners()获得)和轴点绘制3D轴。

def draw(img, corners, imgpts):
    corner = tuple(corners[0].ravel())
    img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
    img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
    img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
    return img

那么如前所述,我们创建终止标准,对象点(棋盘中的角点的3D点)和轴点。 轴点是用于绘制轴的3D空间中的点。 我们绘制长度为3的轴(根据我们基于该尺寸进行校准,单位将以棋形尺寸计算)。 所以我们的X轴从(0,0,0)到(3,0,0)绘制,所以对于Y轴。 对于Z轴,它从(0,0,0)到(0,0,-3)绘制。 负数表示它被画向相机。

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)

axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3)

现在,像往常一样,我们加载每个图像。 搜索7x6网格。 如果发现,我们用subcorner像素进行细化。 然后计算旋转和翻译,我们使用函数cv2.solvePnPRansac()。 一旦我们这些转换矩阵,我们使用它们将我们的轴点投影到图像平面。 简单来说,我们在3D空间中找到与(3,0,0),(0,3,0),(0,0,3)中的每一个对应的图像平面上的点。 一旦我们得到它们,我们使用我们的draw()函数从第一个角落中的每个点绘制线条。 完成!

for fname in glob.glob('left*.jpg'):
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, (7,6),None)

    if ret == True:
        corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)

        # Find the rotation and translation vectors.
        rvecs, tvecs, inliers = cv2.solvePnPRansac(objp, corners2, mtx, dist)

        # project 3D points to image plane
        imgpts, jac = cv2.projectPoints(axis, rvecs, tvecs, mtx, dist)

        img = draw(img,corners2,imgpts)
        cv2.imshow('img',img)
        k = cv2.waitKey(0) & 0xff
        if k == 's':
            cv2.imwrite(fname[:6]+'.png', img)

cv2.destroyAllWindows()

渲染立方体

如果要绘制立方体,请修改draw()函数和轴点,如下所示。

修改draw()函数:

def draw(img, corners, imgpts):
    imgpts = np.int32(imgpts).reshape(-1,2)

    # draw ground floor in green
    img = cv2.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)

    # draw pillars in blue color
    for i,j in zip(range(4),range(4,8)):
        img = cv2.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)

    # draw top layer in red color
    img = cv2.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)

    return img

修改轴点。 它们是3D空间中的立方体的8个角落:

axis = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0],
                   [0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])



对极几何

代码

所以首先我们需要找到两个图像之间可能的匹配来找到基本矩阵。 为此,我们使用基于FLANN的匹配器和比例测试的SIFT描述符。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img1 = cv2.imread('myleft.jpg',0)  #queryimage # left image
img2 = cv2.imread('myright.jpg',0) #trainimage # right image

sift = cv2.SIFT()

# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

# FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

good = []
pts1 = []
pts2 = []

# ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)

现在我们有两个图像的最佳匹配的列表。 让我们找到基本矩阵。

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)

# We select only inlier points
pts1 = pts1[mask.ravel()==1]
pts2 = pts2[mask.ravel()==1]

接下来我们找到epilines。 与第一图像中的点相对应的上游被绘制在第二图像上。 所以提到正确的图像在这里很重要。 我们得到一系列的行。 所以我们定义一个新的函数来在图像上绘制这些行。

def drawlines(img1,img2,lines,pts1,pts2):
    ''' img1 - image on which we draw the epilines for the points in img2
        lines - corresponding epilines '''
    r,c = img1.shape
    img1 = cv2.cvtColor(img1,cv2.COLOR_GRAY2BGR)
    img2 = cv2.cvtColor(img2,cv2.COLOR_GRAY2BGR)
    for r,pt1,pt2 in zip(lines,pts1,pts2):
        color = tuple(np.random.randint(0,255,3).tolist())
        x0,y0 = map(int, [0, -r[2]/r[1] ])
        x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1] ])
        img1 = cv2.line(img1, (x0,y0), (x1,y1), color,1)
        img1 = cv2.circle(img1,tuple(pt1),5,color,-1)
        img2 = cv2.circle(img2,tuple(pt2),5,color,-1)
    return img1,img2
现在我们在这两幅图像中找到了上epilines ,画出它们。
# Find epilines corresponding to points in right image (second image) and
# drawing its lines on left image
lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1,1,2), 2,F)
lines1 = lines1.reshape(-1,3)
img5,img6 = drawlines(img1,img2,lines1,pts1,pts2)

# Find epilines corresponding to points in left image (first image) and
# drawing its lines on right image
lines2 = cv2.computeCorrespondEpilines(pts1.reshape(-1,1,2), 1,F)
lines2 = lines2.reshape(-1,3)
img3,img4 = drawlines(img2,img1,lines2,pts2,pts1)

plt.subplot(121),plt.imshow(img5)
plt.subplot(122),plt.imshow(img3)
plt.show()




立体图像的深度图

代码

以下代码片段显示了创建视差图的简单过程。

import numpy as np
import cv2
from matplotlib import pyplot as plt

imgL = cv2.imread('tsukuba_l.png',0)
imgR = cv2.imread('tsukuba_r.png',0)

stereo = cv2.createStereoBM(numDisparities=16, blockSize=15)
disparity = stereo.compute(imgL,imgR)
plt.imshow(disparity,'gray')
plt.show()

Exercises

  1. OpenCV samples contain an example of generating disparity map and its 3D reconstruction. Check stereo_match.py in OpenCV-Python samples.






  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值