随着科技和经济的蓬勃发展,机器人作业、汽车自动导航等技术已经得到广泛应用,在很大程度上推动了社会生产力的发展。不论是主动光学视觉传感或者是被动光学视觉传感,要从图像推知物空间的三维信息,或反之,从空间三维信息推知二维图像坐标,都必须确定相机在参考坐标系中的空间位置和取向,以及相机本身的几何和光学参数为解决这个问题所需用到相机标定技术
相机标定的作用在于消除由于相机产生的图像畸变,从而校正图像,为处理计算出精确数值提供可能。
由此,相机标定成了系统准确的先决条件。
从真实的三维世界坐标,可以得到二维的相机坐标,
但是我们从二维的相机坐标,能否准确的推算出真实的三维世界的坐标呢?这就是相机标定的意义。
相机标定指建立相机图像像素位置与场景点位置之间的关系,根据相机成像模型,由特征点在图像中坐标与世界坐标的对应关系,求解相机模型的参数。相机需要标定的模型参数包括内部参数和外部参数。
针孔相机成像原理其实就是利用投影将真实的三维世界坐标转换到二维的相机坐标上去,其模型示意图如下图所示:
从图中我们可以看出,在世界坐标中的一条直线上的点在相机上只呈现出了一个点,其中发生了非常大的变化,同时也损失和很多重要的信息,这正是我们3D重建、目标检测与识别领域的重点和难点。实际中,镜头并非理想的透视成像,带有不同程度的畸变。理论上镜头的畸变包括径向畸变和切向畸变,切向畸变影响较小,通常只考虑径向畸变。
径向畸变:
径向畸变主要由镜头径向曲率产生(光线在远离透镜中心的地方比靠近中心的地方更加弯曲)。导致真实成像点向内或向外偏离理想成像点。其中畸变像点相对于理想像点沿径向向外偏移,远离中心的,称为枕形畸变;径向畸点相对于理想点沿径向向中心靠拢,称为桶状畸变。
1. 打印一张棋盘方格图并贴在一个平面上
2. 从不同角度拍摄若干张模板图像
3. 检测出图像中的特征点
4. 由检测到的特征点计算出每幅图像中的平面投影矩阵H
5. 确定出摄像机的参数
由检测到的特征点计算出每幅图像中的平面投影矩阵H:
其中,(U,V, W)为在世界坐标系下一 点的物理坐标,
(u,v)为该点对应的在像素坐标系下的像索坐标,Z 为尺度因子,
dX为一个单位的单位长度,dY就是单位宽度
内参是摄像机内部的参数,和外界无关;
相机内部参数/内方位元素:焦距(f)、像主点坐标、畸变参数(θ为像素点的倾斜角,
畸变因子为:[-(fcotθ/dX)]-描述dX和dY之间的关系)
外参是摄像机坐标系和世界坐标系的关系。
相机外部参数/外方位元素:旋转®、平移(T)
一般可假设模板落在世界坐标系的Z=0的平面上。设K为摄像机的内参数矩
阵,r是R的第i个向量,那么对模板平面上的每一个点,都有
在一幅图像中可以检测到多个特征点,这样由式(3.16),多个对应点的方程叠加起来可以看成Sh’ =0,通过求该方程的最小二乘解可得到h’,也就是sTs 最小特征值所对应的特征向量,进而得到H。
注意H与真正的单应性矩阵之间可能相差一个比例因子。
首先令hi表示H的每一列向量,于是有
在立体视觉系统中,双目摄像机与单摄像机标定的差别是,需要通过定标,
测量双摄像机之间的相对位置。用两个摄像机同时观察周围环境,在标定中,我们可以用单摄像机定标方法分别得到两个摄像机各自的内外参数,如果外参数分别用R1,t1,与R2,t2表示,
则R1,t1表示C1摄像机与世界坐标系之间的相对位置,
2R,t2表示C2摄像机与世界坐标系之间的相对位置。对任意一点P,如它在世界坐
标系、C1坐标系与C2坐标系下的非齐次坐标分别为x(w) ,x(c1),x(c2),则
# -*- codeing =utf-8 -*-
# @Time : 2021/5/18 9:32
# @Author : ArLin
# @File : demo1.py
# @Software: PyCharm
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('./pc/*.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:
# 角点精确检测
# 输入图像 角点初始坐标 搜索窗口为2*winsize+1 死区 求角点的迭代终止条件
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()
#标定、去畸变
# 输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# mtx:内参数矩阵
# dist:畸变系数
# rvecs:旋转向量 (外参数)
# tvecs :平移向量 (外参数)
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('pc/009.jpg')
h,w = img2.shape[:2]
# 我们已经得到了相机内参和畸变系数,在将图像去畸变之前,
# 我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,
# 通过设定自由自由比例因子alpha。当alpha设为0的时候,
# 将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;
# 当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉
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)
# 反投影误差
# 通过反投影误差,我们可以来评估结果的好坏。越接近0,说明结果越理想。
# 通过之前计算的内参数矩阵、畸变系数、旋转矩阵和平移向量,使用cv2.projectPoints()计算三维点到二维图像的投影,
# 然后计算反投影得到的点与图像上检测到的点的误差,最后计算一个对于所有标定图像的平均误差,这个值就是反投影误差。
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))
glob
是Python自带的一个文件操作相关的模块,可以查询符合自己要求的文件
支持通配符操作:,?[] 这三个通配符
,代表0个或多个字符
?代表一个字符
[]匹配指定范围内的字符
该方法返回所有匹配的文件路径列表,该方法需要一个参数用来指定匹配的路径字符串(本字符串可以为绝对路径也可以为相对路径),其返回的文件名只包括当前目录里的文件名,不包括子文件夹里的文件
glob.glob(r’c:.txt’)
获得C盘下的所有txt文件
glob.glob(r’E:\pic**.jpg’)
获得指定目录下的所有jpg文件
使用相对路径:
glob.glob(r’…/.py’)
标定棋盘格角点检测:
def findChessboardCorners(image, patternSize, corners=None, flags=None):
参数分别表示为:
image:棋盘图像,8位灰度或彩色图像。
patternSize[]:棋盘的尺寸,注意应为内角点个数,内角点是和其他格子连着的点。
corners[]:存放角点的位置。
flags:迭代的准则。
返回值:
retval:是否检测出角点。
corners:角点的位置
角点精确检测:
def cornerSubPix(image, corners, winSize, zeroZone, criteria):
参数:
image:输入图像,8位或者float型。
corners:角点初始坐标。
winsize:搜索窗口为2*winsize+1。
zerozone:死区,不计算区域,避免自相关矩阵的奇异性。没有死区,参数为(-1,-1)
criteria:求角点的迭代终止条件。
返回值:
corner:角点位置。
def drawChessboardCorners(image, patternSize, corners, patternWasFound):
image:输入图像,8位或者float型。
patternSize[]:棋盘的尺寸,注意应为内角点个数,内角点是和其他格子连着的点。
corners:角点初始坐标。
patternWasFound:标志位,检测是否所有board都被检测到,若为是,则将角点连线,否则不连线。
标定、去畸变:
retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv.calibrateCamera( objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]])
objectPoints:世界坐标系里的位置。
imagePoints: 像素坐标。
imageSize:为图像的像素尺寸大小。c
ameraMatrix:3*3矩阵,相机内参数矩阵。
disCoeffs:畸变矩阵
rvecs:旋转向量
tvecs:位移向量
flags:标定采用的算法
criteria:迭代终止条件设定。
纠正图像:我们已经得到了相机内参和畸变系数,在将图像去畸变之前,我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,通过设定自由自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。
retval, validPixROI = cv.getOptimalNewCameraMatrix(
cameraMatrix, distCoeffs, imageSize,
alpha[, newImgSize[, centerPrincipalPoint]] )
imageSize:原始图像尺寸。
newImageSize:校正后图像尺寸。
alpha:取0或1。
centerPrincipalPoint:是否作用于中心,默认为opencv自己根据图像选择位置。
ret: 4.61014226588199
mtx:#内参数矩阵(内参数指的是3D到2D投影过程中用到的投影矩阵):
[[1.17553897e+03 0.00000000e+00 3.08746918e+02]
[0.00000000e+00 1.26980815e+03 2.62757509e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
dist:#畸变系数(畸变指的是镜头质量等原因导致的2D点的偏移)
[[ 1.03960338e+00 6.08373668e+00 3.51428663e-02 -2.69472143e-03
-4.99055288e+02]]
rvecs:#旋转向量 (外参数-外参数矩阵是世界坐标系相对于摄像机坐标系的旋转平移关系)
[array([[-0.28297379],
[-0.25945171],
[-3.08760396]]), array([[-0.79069866],
[-0.92010917],
[-1.42738888]]), array([[0.07458916],
[0.46670102],
[3.09751359]]), array([[-0.14980926],
[-0.38450918],
[-3.10633433]]), array([[0.65872331],
[0.32977214],
[3.00943076]]), array([[ 1.28769715],
[-0.46680562],
[-2.67886617]]), array([[ 0.66564206],
[-0.45470194],
[-2.92616865]]), array([[0.07458916],
[0.46670102],
[3.09751359]]), array([[-0.25367525],
[-0.7731668 ],
[-3.0131955 ]]), array([[-0.75879392],
[ 0.89867373],
[ 1.46549472]]), array([[ 1.30746799],
[-0.18730123],
[-2.85439204]]), array([[-1.08946239],
[-0.61286472],
[-2.57616031]])]
tvecs:#平移向量 (外参数)
[array([[ 6.14315254],
[ 3.52644195],
[37.77193718]]), array([[-3.9859984 ],
[ 1.39582273],
[24.40583102]]), array([[ 6.46588828],
[ 2.60047913],
[32.12003692]]), array([[ 6.14115517],
[ 2.47552243],
[34.92117132]]), array([[ 4.03888009],
[ 2.61196364],
[30.70286705]]), array([[ 3.55441623],
[ 4.7111428 ],
[43.76192256]]), array([[ 5.03379415],
[ 5.04138057],
[41.53930474]]), array([[ 6.46588828],
[ 2.60047913],
[32.12003692]]), array([[ 5.72289451],
[ 7.05744565],
[62.22621202]]), array([[ 5.82450601],
[-3.68982478],
[35.4065732 ]]), array([[ 3.96191928],
[ 3.48737091],
[42.65504337]]), array([[ 1.61328369],
[ 2.6626999 ],
[31.39045421]])]
total error: 0.3688376760710377#反投影误差输出```