2021-05-23

相机标定

相机标定原理

四种坐标系

1.首先要明确四个坐标系:世界坐标系、相机坐标系、图像坐标系、像素坐标系
2.世界坐标系:也称为测量坐标系,是用户定义的三维世界的坐标系,为了描述目标物在真实世界里的位置而被引入,也就是以其为基准可以描述相机和待测物体的空间位置。

3.相机坐标系:在相机上建立的坐标系,为了从相机的角度描述物体位置而定义,作为沟通世界坐标系和图像/像素坐标系的中间一环。
4.图像坐标系:为了描述成像过程中物体从相机坐标系到图像坐标系的投影透射关系而引入,方便进一步得到像素坐标系下的坐标。
5.像素坐标系:为了描述物体成像后的像点在数字图像上(相片)的坐标而引入,是我们真正从相机内读取到的信息所在的坐标系。

坐标系变换过程

1.世界坐标系和相机坐标系的转换:
在这里插入图片描述

转换过程:
在这里插入图片描述

同理,绕X,Y轴旋转得到:
在这里插入图片描述
于是可以得到旋转矩阵R=R1R2R3 ,于是得到P点在相机坐标系中的坐标:
在这里插入图片描述

从世界坐标系变换到相机坐标系属于刚体变换:即物体不会发生形变,只需要进行旋转和平移。其中,R为旋转矩阵,T为平移向量。

2.相机坐标系和图像坐标系变换:
变换过程:
在这里插入图片描述
其中f:相机焦距,等于O与Oc的距离。这一过程进行了从三维坐标到二维坐标的转换,也即投影透视过程(用中心投影法将形体投射到投影面上,从而获得的一种较为接近视觉效果的单面投影图,也就是使我们人眼看到景物近大远小的一种成像方式)。这一过程为理想情况,即不考虑畸变。
但是在相机生产的过程中,往往相机的和图像的坐标系往往是有偏移的,即相机的光心C(有时也叫主点)和图像坐标系的中心有些偏移,这就是主点偏移。二者发生了偏移之后,就要考虑到这个偏移因素,因此修改上面得到的矩阵,加入相机的位移参数后,得到下面的矩阵。其中x0表示x方向的位移,y0表示y方向的位移。并将其简写成x~K[I | 0]X的形式,我把K称作相机的内部参数矩阵。相机内部参数主要是焦距f、歪斜、主点等内部参数。
在这里插入图片描述

3.像素坐标系和图像坐标系变换:
变换过程:
在这里插入图片描述
小孔成像模型虽然充分考虑了相机内部参数对成像的影响,但没有考虑成像系统另一个重要的部分,镜头。透过镜头边缘的光线很容易产生径向畸变,光线离镜头中心越远,畸变越大。相机的镜头分为多种:广角镜头、鱼眼镜头等。这种镜头拍摄出来的照片和普通镜头拍摄出来的不一样,会发生图像的畸变。图像发生了畸变后,映射到的二维平面上的某个点,就不是它在平面上的实际位置,实际正确的位置应该发生一些畸变的偏移。畸变的类型主要有三种:正常无畸变桶形畸变枕形畸变,如下图所示:
在这里插入图片描述
因而总的变换过程为:
在这里插入图片描述

相机标定作用

在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。
1.进行摄像机标定的目的:求出相机的内、外参数,以及畸变参数。
相机标定可以校正这种镜头畸变矫正畸变,生成矫正后的图像;另一个是根据获得的图像重构三维场景。
2.标定相机后通常是想做两件事:一个是由于每个镜头的畸变程度各不相同,通过相机标定可以校正这种镜头畸变矫正畸变,生成矫正后的图像;另一个是根据获得的图像重构三维场景。

实现相机标定方法和代码

方法

棋盘是一块由黑白方块间隔组成的标定板,我们用它来作为相机标定的标定物(从真实世界映射到数字图像内的对象)。之所以我们用棋盘作为标定物是因为平面棋盘模式更容易处理(相对于复杂的三维物体),但与此同时,二维物体相对于三维物体会缺少一部分信息,于是我们会多次改变棋盘的方位来捕捉图像,以求获得更丰富的坐标信息。
标定步骤

1、打印一张棋盘格,把它贴在一个平面上,作为标定物。
2、通过调整标定物或摄像机的方向,为标定物拍摄一些不同方向的照片。
3、从照片中提取棋盘格角点。
4、估算理想无畸变的情况下,五个内参和六个外参。
5、应用最小二乘法估算实际存在径向畸变下的畸变系数。
6、极大似然法,优化估计,提升估计精度。

不同棋盘方位图像
在这里插入图片描述

代码

import cv2
import numpy as np
import glob

# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格模板规格
w = 14  # 内角点个数,内角点是和其他格子连着的点
h = 9

# 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)
# 储存棋盘格角点的世界坐标和图像坐标对
objpoints = []  # 在世界坐标系中的三维点
imgpoints = []  # 在图像平面的二维点

images = glob.glob('D:\\PicTest\\*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 找到棋盘格角点
    # 棋盘图像(8位灰度或彩色图像)  棋盘尺寸  存放角点的位置
    ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
    # 如果找到足够点对,将其存储起来
    if ret == True:
        cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        objpoints.append(objp)
        imgpoints.append(corners)
        # 将角点在图像上显示
        cv2.drawChessboardCorners(img, (w, h), corners, ret)
        cv2.imshow('findCorners', img)
        cv2.waitKey(1000)
cv2.destroyAllWindows()

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

print(("ret:"), ret)
print(("mtx:\n"), mtx)  # 内参数矩阵
print(("dist:\n"), dist)  # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print(("rvecs:\n"), rvecs)  # 旋转向量  # 外参数
print(("tvecs:\n"), tvecs)  # 平移向量  # 外参数
# 去畸变
img2 = cv2.imread('D:\\PicTest\\5_d.jpg')
h, w = img2.shape[:2]

newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 0, (w, h))  # 自由比例参数

# 去畸变
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 根据前面ROI区域裁剪图片
x, y, w, h = roi
dst = dst[y:y + h, x:x + w]
cv2.imwrite('calibresult.jpg', dst)

total_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
    total_error += error
print(("total error: "), total_error / len(objpoints))

实验结果
ret: 9.28218323095522
mtx:
[[7.61465775e+03 0.00000000e+00 9.06338004e+02]
[0.00000000e+00 6.84475558e+03 6.97243685e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist:
[[-2.52936750e+00 1.50341136e+01 1.77168935e-01 3.18208310e-02
-3.71063785e+01]]
rvecs:
[array([[ 0.11392687],
[-0.03244757],
[-1.56131725]]), array([[ 0.50750244],
[-0.22711402],
[-2.8731636 ]]), array([[-0.40091124],
[ 0.39636284],
[ 0.01376945]]), array([[-0.13934606],
[-0.61601869],
[ 0.02899512]]), array([[-0.46980777],
[-0.47264142],
[-1.98337532]]), array([[-0.46980777],
[-0.47264142],
[-1.98337532]]), array([[-0.48438869],
[-0.42326498],
[-1.53835189]]), array([[-0.47487739],
[-0.41486999],
[-0.95612162]]), array([[-0.35364995],
[-0.3414694 ],
[-0.6245722 ]]), array([[-0.14223955],
[-0.25104682],
[-0.02205898]]), array([[-0.39437907],
[ 0.48401325],
[ 0.8007061 ]]), array([[-0.3936917 ],
[ 0.45796181],
[ 1.48527598]]), array([[-0.45767925],
[ 0.46559209],
[ 2.07591881]]), array([[-0.43273026],
[ 0.71106484],
[ 1.04722654]])]
tvecs:
[array([[-2.87844923],
[16.52515532],
[53.56730883]]), array([[14.45263489],
[ 7.94222401],
[47.28026382]]), array([[ 1.30191591],
[-2.09057826],
[51.66590437]]), array([[-3.03455597],
[ 4.29301474],
[48.79829544]]), array([[ 0.64620716],
[13.47853036],
[54.63516938]]), array([[ 0.64620716],
[13.47853036],
[54.63516938]]), array([[-1.37870029],
[12.08316454],
[53.64370441]]), array([[-3.88761776],
[ 9.40263851],
[60.91491931]]), array([[-5.03452141],
[10.98152808],
[64.04447984]]), array([[-4.21329416],
[ 4.89153705],
[65.96870262]]), array([[ 1.37761993],
[ 5.67903796],
[70.68467035]]), array([[ 5.92250686],
[ 1.93366624],
[61.43757431]]), array([[ 9.04416316],
[ 8.66784967],
[67.01723666]]), array([[ 8.95659641],
[-4.25288033],
[62.35039789]])]
total error: 0.7677098809195071

Process finished with exit code 0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值