双目视觉标定-2.单目视觉标定opencv代码实战

本篇文章会通过代码来进一步解释如何进行双目视觉标定,需要配合上一篇理论一起食用,还没有看过理论篇的宝子,链接在这里:双目视觉标定-1视觉成像理论知识-CSDN博客

目录

1. 什么是相机标定 Camera Calibration

2. 用Opencv进行相机标定

3. 相机标定的三种方法

4. 相机标定具体步骤及实现

       a. 用已知大小棋盘格进行真实世界3D坐标的定义 

       b. 从不同的角度获取棋盘格照片

       c. 用findChessboardCorners寻找在不同照片中3D坐标对应的2d图像坐标

        c.1 寻找棋盘格的corner

        c.2 找到更精确棋盘格corner

       d. 用calibrateCamera寻找相机参数

       e. 细化相机矩阵

       f. 让图像返璞归真(取消失真)

5. 单目视觉标定代码展示

6总结


1. 什么是相机标定 Camera Calibration

估计相机参数的过程称为相机标定。

看过上一篇的小伙伴应该对于从3d世界坐标转换到2d图像坐标用到的相机参数有些印象,但可能不多,毕竟刚开始掌握知识。我们的目标就是用一系列已知的3d现实世界坐标以及对应的图像坐标,寻找内参矩阵K,旋转矩阵R,平移向量t

相机参数一共有两种:

1. 内参 Internal Parameters

        a.光学中心optical center

        b焦距focal length

        c还没提到的镜头的转盘畸变系数radial distortion coefficient

2. 外参 External Parameters

        a.旋转矩阵

        b.平移向量

下面就是经过参数标定的畸变图像结果:

2. 用Opencv进行相机标定

上一篇我们是分布进行每个步骤的表示,现在我们直接把从现实世界的3d坐标(Xw,Yw,Zw,1)转换为图像世界的2d pixel坐标(u, v)一步表示为下方的方程:

P Projection Matrix 3×4的矩阵包含两个大矩阵的乘法,3×3内参矩阵 Intrinsic Matrix K和3×4 Extrinsic Matrix(由3×3旋转矩阵和3×1平移向量组成)

3. 相机标定的三种方法

1. 校准图案:从不同的角度捕获已知尺寸的物体或图案的多个图像。我们将在这篇文章中学习的基于棋盘chess board的方法就属于这一类。我们还可以使用已知尺寸的圆形图案来代替棋盘图案。(重点掌握)
2. 几何线索:利用场景中其他几何线索,例如直线和消失点,可用于校准。
3. 基于深度学习:当我们对成像设置的控制很少时(例如,我们只有场景的单个图像),仍然可以使用基于深度学习的方法获得相机的校准信息。

4. 相机标定具体步骤及实现

       a. 用已知大小棋盘格进行真实世界3D坐标的定义 

棋盘格图案在图像中清晰且易于检测。还有棋盘上正方形的角非常适合定位它们,在两个方向上都有急剧变化的梯度。此外,这些角还由于它们位于棋盘线的交叉点而相互连接。所有这些事实都用于可靠地定位棋盘图案中正方形的角点。
真实世界的坐标被固定在这个贴在墙上的棋盘格,我们选择的3d点是角点,任意交点都可以被选择为原点,Xw,Yw沿着墙,Zw轴垂直于墙。为了简单起见,我们可以说棋盘在 XY 平面上保持静止(因此 Z=0 ),并且相机相应地移动。这种考虑有助于我们仅找到 X,Y 值。

现在,对于 X,Y 值,我们可以简单地将点写为 (0,0)、(1,0)、(2,0)...,这表示点的位置。在这种情况下,我们得到的结果将以棋盘正方形的大小为单位。但如果我们知道正方形的尺寸(例如 30 毫米),我们可以将值传递为 (0,0)、(30,0)、(60,0)、...。因此,我们得到的结果以毫米为单位。

 棋盘格交点真实世界3D坐标定义:

CHECKERBOARD = (6,9)
objp = np.zeros((1,CHECKERBOARD[0]*CHECKERBOARD[1],3), np.float32)
objp[0,:,:2] = np.meshgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.transpose(-1,2)

可跳过的部分:

这里的代码比较难理解,我来讲解一下,其实差不多知道是啥就可以了,不用非得搞懂:
np.zeros((1,CHECKERBOARD[0]*CHECKERBOARD[1],3)
第一个参数:1-创建一个3d array(取一整个棋盘格)
第二个参数:CHECKERBOARD[0]*CHECKERBOARD[1]-一共有6×9个子集(取棋盘格中每个点)
第二个参数:每个向量包含三个维度(每个点包含xyz三个维度)

 np.meshgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]]
创建两个2d array,两个的大小都是6×9,第一个每行都是一样的,列从0到5,第二个每列是一样的,每行从0到8。进行T运算之后会变成一组组2d array,transpose(-1,2),行数是自行制定的,列数是2,代表了(x,y)

objp[0,:,:2] 就是第一个array(当然只有一个),取所有点的前两个坐标的值等于刚才meshgrid得到的东西,也就是所有z的坐标是0,整体坐标形如(000)(010),反正能代表所有的棋盘格交点

检测到的棋盘格corner的结果 

       b. 从不同的角度获取棋盘格照片

拍摄不同角度的图片,也可以保持相机不动,让棋盘格动;这两种方法的数学结果是一样。

       c. 用findChessboardCorners寻找在不同照片中3D坐标对应的2d图像坐标

        c.1 寻找棋盘格的corner

findChessboardCorners 寻找棋盘格角点并返回2d pixel坐标

retval, corners = cv2.findChessboardCorners(image, patternSize, flags)

输入:
image:棋盘格照片(自己照的多角度照片,必须是 8 位灰度或彩色图像
patternSize 每个棋盘行和列的内角数(patternSize = cvSize (points_per_row,points_per_colum) = cvSize(columns,rows) )
flags 只有当事情进展不顺利时你才需要担心这些,使用默认值
输出:
retval boolean,检测到corner为真,未检测到为假
corners 检测到的角点的数量 

        c.2 找到更精确棋盘格corner

好的校准取决于精度。为了获得良好的结果,获得亚像素级精度的角点位置非常重要!!!

OpenCV 的函数cornerSubPix 接收原始图像和角点位置,并在原始位置的小邻域内寻找最佳角点位置。该算法本质上是迭代的,因此我们需要指定终止标准(例如迭代次数和/或准确性)

cv2.cornerSubPix(image, corners, winSize, zeroZone, criteria)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

winSize 搜索窗口边长的一半。
ZeroZone 搜索区域中间的死区大小的一半,在该死区上不进行下面公式中的求和。有时使用它来避免自相关矩阵可能存在的奇异性, (-1,-1)的值表示不存在这样的尺寸。 

cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER 决定的是终止标准, +是或者,只要其中之一达到了就可以终止,cv2.TERM_CRITERIA_EPS取决于希望的准确性accuracy,cv2.TERM_CRITERIA_MAX_ITER取决于希望的最大迭代数,30是最大迭代数,0001希望的正确率

       d. 用calibrateCamera寻找相机参数

retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objectPoints, imagePoints, imageSize)

给这个方法提供3d真实世界坐标以及对应的2d图像坐标,然后就可以返回我们需要的矩阵了
cameraMatrix 内参矩阵
distCoeffs 畸形参数(我下一篇会讲吧,大懒蛋不一定写)
rvecs 旋转矩阵
tvecs 平移矩阵

       e. 细化相机矩阵

现在,我们可以拍摄或者选取图像,相机会让其失真。
cv.getOptimalNewCameraMatrix() 基于自由缩放参数来细化相机矩阵

如果缩放参数 alpha=0,则返回具有最少不需要像素的未失真图像。因此它甚至可能会删除图像角落的一些像素。如果 alpha=1,则保留所有像素并保留一些额外的黑色图像。此函数还返回可用于裁剪图像ROI的变量(ROI region of interest 我们感兴趣的部分)

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

        f. 让图像返璞归真(取消失真)

方法一:这是最简单的方法,直接调用方程去除失真部分,然后用得到的ROI数值裁剪图像

# undistort
dst = cv.undistort(img, mtx, dist, None, newcameramtx)
 
# crop the image
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)


方法二:首先寻找从失真图像到非失真图像的mapping方程,然后用remap关联得到最终结果

initUndistortRectifyMap(),在只有一个相机的时候,newCameraMatrix被设成相同的camera matrix,在双目相机的情况下,newCameraMatrix被设成用完stereoRectify得到的P1或者P2

# undistort
mapx, mapy = cv.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), 5)
dst = cv.remap(img, mapx, mapy, cv.INTER_LINEAR)
 
# crop the image
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
cv.imwrite('calibresult.png', dst)

两种方法都可以得到以下相同的结果,你可以观察到图中的直线变直了,然后你可以保存这些相机矩阵,为了后续的使用 

5. 单目视觉标定代码展示

import cv2
import numpy as np
import os
import glob
 
# Defining the dimensions of checkerboard
CHECKERBOARD = (6,9)
# Criteria of termination standard
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
 
# Creating vector to store vectors of 3D points for each checkerboard image
objpoints = []
# Creating vector to store vectors of 2D points for each checkerboard image
imgpoints = [] 
 
 
# Defining the world coordinates for 3D points
objp = np.zeros((1, CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
objp[0,:,:2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
prev_img_shape = None
 
# Extracting path of individual image stored in a given directory
images = glob.glob('./images/*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    # Find the chess board corners
    # If desired number of corners are found in the image then ret = true
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE)
     
    """
    If desired number of corner are detected,
    we refine the pixel coordinates and display 
    them on the images of checker board
    """
    if ret == True:
        objpoints.append(objp)
        # refining pixel coordinates for given 2d points.
        corners2 = cv2.cornerSubPix(gray, corners, (11,11),(-1,-1), criteria)
         
        imgpoints.append(corners2)
 
        # Draw and display the corners
        img = cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret)
     
    cv2.imshow('img',img)
    cv2.waitKey(0)
 
cv2.destroyAllWindows()
 
h,w = img.shape[:2]
 
"""
Performing camera calibration by 
passing the value of known 3D points (objpoints)
and corresponding pixel coordinates of the 
detected corners (imgpoints)
"""
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
 
print("Camera matrix : \n")
print(mtx)
print("dist : \n")
print(dist)
print("rvecs : \n")
print(rvecs)
print("tvecs : \n")
print(tvecs)

6总结

终于把单目视觉标定搞完了,但是双目的会比这个更加复杂一点,我争取快速产出双目视觉标定的原理以及相应代码,请看到的这里的大家给本大懒蛋点赞,你们的鼓励是我更新的动力,家人们,有问题请留言,我看到了会回复的,下期再见:)

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值