一。原理
参考:
camera calibration using opencv相机标定原理、步骤opencv-python 摄像头标定
生成黑白棋盘标定图和单目相机标定相机外参估计
坐标系
坐标系名称 | 介绍 | 图形 |
世界坐标系 | world coordinate,测量坐标系,三维直角坐标系,以其为基准可以描述相机和待测物体的空间位置。世界坐标系的位置可以根据实际情况自由确定 | |
相机坐标系 | camera coordinate,三维直角坐标系,原点位于镜头光心处,x,y轴分别与相面的两边平行,z轴为镜头光轴,与像平面垂直 | |
像素坐标系 | pixel coordinate,二维直角坐标系,反映相机CCD/CMOS芯片中像素的排列情况。原点位于左上角,u,v分别于像面的两边平行,单位是像素(整数)。但是它不利于坐标转换 | |
图像坐标系 | XOY,单位通常是毫米,原点是相机光轴与相面的交点(也叫主点) |
4大坐标系理解图:
坐标系之间转换:
转换 | ||
世界坐标系-》相机坐标系 | ||
图像坐标系-》像素坐标系 | ||
相机坐标系-》图像坐标系 | ||
世界坐标系-》像素坐标系 |
过程理解:
要找到3D点在图像平面上的投影,我们首先需要使用外参(旋转和平移)将该点从世界坐标系统转换成相机坐标系,接下来,使用相机的内参将点投影到图像平面上。
相机标定3+要素(内参,外参,畸变参数等):
0.先看下总公式:
P:3x4投影矩阵
K:内参 3x3矩阵
R:旋转矩阵 3x3
t:平移矩阵 3x11.内参K
fx,fy:x,y焦距,通常值一样
cx,cy:图像平面光学中心的x,y坐标,一般近似图像中心点
gamma:是轴之间的倾斜度,一般是0
2.外参
Rt
R:旋转矩阵 3x3 但opencv标定时3x1矩阵??!!
t: 平移矩阵 3x1
3.畸变参数
分径向畸变(k1,k2,k3)和切向畸变(p1,p2)
畸变类型 | 内容 | 示例 |
径向畸变 | 来自于透镜形状,包括枕型畸变、桶型畸变,光学中心畸变为0,越往边缘移动,畸变也严重 | |
切向畸变 | 透镜制造上的缺陷使得透镜本身与图像平面不平行而产生的,包括薄透镜畸变、离心畸变 | |
二。相机标定步骤 | ||
1、打印一张棋盘格,把它贴在一个平面上,作为标定物。 | ||
2、通过调整标定物或摄像机的方向,为标定物拍摄一些不同方向的照片。 | ||
3、从照片中提取棋盘格角点。 | ||
4、估算理想无畸变的情况下,五个内参和六个外参。 | ||
5、应用最小二乘法估算实际存在径向畸变下的畸变系数。 | ||
6、极大似然法,优化估计,提升估计精度。 |
三。代码注释
- 内参、畸变参数以及外参(旋转矩阵和平移矩阵)的python脚本(get_parameter.py)求解(已验证):
import os
import cv2
import numpy as np
import glob
import pickle
path = "D:/works/distortion/standard_img/"
images = glob.glob(path+'*.jpg')
img_result_path = path +'results/'
# 找棋盘格角点
# 阈值
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# 棋盘格模板规格
w = 8 # 内角点个数,内角点是和其他格子连着的点
h = 5
# 世界坐标系中的棋盘格点,例如(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 = [] # 在图像平面的二维点
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
size = gray.shape[::-1]
# 找到棋盘格角点
# 棋盘图像(8位灰度或彩色图像) 棋盘尺寸 存放角点的位置
ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
# 如果找到足够点对,将其存储起来
if ret == True:
# 角点精确检测
# 输入图像 角点初始坐标 搜索窗口为2*winsize+1 死区 求角点的迭代终止条件
sub_corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(sub_corners)
# 将角点在图像上显示
cv2.drawChessboardCorners(img, (w, h), sub_corners, ret)
cv2.imshow('finecorners', img)
cv2.imwrite(img_result_path + 'corners' + fname.split(os.sep)[-1], img)
cv2.waitKey(1000)
cv2.destroyAllWindows()
# 标定、去畸变
# 输入:世界坐标系里的位置 像素坐标 图像的像素尺寸大小 3*3矩阵,相机内参数矩阵 畸变矩阵
# 输出:标定结果 相机的内参数矩阵 畸变系数 旋转矩阵 平移向量
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, size, None, None)
# mtx:内参数矩阵
# dist:畸变系数
# rvecs:旋转向量 (外参数)
# tvecs :平移向量 (外参数)
print(("ret:"), ret)
print(("mtx:"), mtx) # 内参数矩阵
print(("dist:"), dist) # 畸变系数 distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
print(("rvecs:"), rvecs) # 旋转向量 # 外参数
print(("tvecs:"), tvecs) # 平移向量 # 外参数
np.savetxt(r"p_mtx.txt", mtx, fmt='%f', delimiter=',')
np.savetxt(r"p_dist.txt", dist, fmt='%f', delimiter=',')
# 反投影误差
# 通过反投影误差,我们可以来评估结果的好坏。越接近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))
- 图片去畸变python脚本img_distortion.py:
import os
import cv2
import numpy as np
import glob
path = "D:/works/distortion/CapImgDir1229/"
mtx= np.loadtxt(path+'p_mtx.txt', delimiter=',')
dist= np.loadtxt(path+'p_dist.txt', delimiter=',')
img_test_path = path+'src/*.jpg'
img_result_path = path+'dst/'
img_result_roi_path = path+'dst_roi/'
# 去畸变
testimg = glob.glob(img_test_path)
for testname in testimg:
img2 = cv2.imread(testname)
h, w = img2.shape[:2]
# 我们已经得到了相机内参和畸变系数,在将图像去畸变之前,
# 我们还可以使用cv.getOptimalNewCameraMatrix()优化内参数和畸变系数,
# 通过设定自由自由比例因子alpha。当alpha设为0的时候,
# 将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;
# 当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) # 自由比例参数
# 方法1
dst = cv2.undistort(img2, mtx, dist, None, newcameramtx)
# 方法2
# mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
# dst = cv2.remap(img2, mapx, mapy, cv2.INTER_LINEAR)
cv2.imwrite(img_result_path + testname.split(os.sep)[-1], dst)
# 根据前面ROI区域裁剪图片
x, y, w, h = roi
dst2 = dst[y:y + h, x:x + w]
cv2.imwrite(img_result_roi_path + testname.split(os.sep)[-1], dst2)
- 利用PnP求距
import cv2
import numpy
import numpy as np
twod_point = "1189 254 1216 254 1242 255 1268 255 1294 256 1322 254 1347 255 1375 253 1187 279 1214 " \
"278 1243 281 1269 279 1296 280 1321 279 1346 281 1374 280"
twod_point = np.array(twod_point.split( ),dtype=np.float)
print(len(twod_point)//2)
twod_point = twod_point.reshape((16,2))
print(twod_point.shape)
threed_point = np.zeros(shape=(16,3))
for i in range(len(twod_point)):
x = twod_point[i][0]
y = twod_point[i][1]
threed_point[i][0] = (x -1189)/27 *4
threed_point[i][1] = (y - 254)/25 *4
newcameramtx=[[1.67276660e+03,0.00000000e+00,6.22925147e+02],[0.00000000e+00,1.67104004e+03,3.44706162e+02],[0.00000000e+00,0.00000000e+00,1.00000000e+00]]
dist=[[-6.11401280e-01 ,4.08011840e-01 ,-5.83043580e-05 ,-1.16998902e-03,-7.99593012e-01]]
newcameramtx = np.array(newcameramtx,dtype=np.uint8)
dist = np.array(dist,dtype=np.uint8)
_,r,t = cv2.solvePnP(threed_point,twod_point,newcameramtx,dist)
np.set_printoptions(suppress=True)
def Pix2World(point2D, rVec, tVec, cameraMat, height):
"""
Function used to convert given 2D points back to real-world 3D points
point2D : An array of 2D points
rVec : Rotation vector
tVec : Translation vector
cameraMat: Camera Matrix used in solvePnP
height : Height in real-world 3D space
Return : output_array: Output array of 3D points
"""
point3D = []
point2D = (np.array(point2D, dtype='float32')).reshape(-1, 2)
numPts = point2D.shape[0]
point2D_op = np.hstack((point2D, np.ones((numPts, 1))))
rMat = cv2.Rodrigues(rVec)[0]
# print(rMat)
rMat_inv = np.linalg.inv(rMat)
kMat_inv = np.linalg.inv(cameraMat)
for point in range(numPts):
uvPoint = point2D_op[point, :].reshape(3, 1)
tempMat = np.matmul(rMat_inv, kMat_inv)
tempMat1 = np.matmul(tempMat, uvPoint)
tempMat2 = np.matmul(rMat_inv, tVec)
s = (height + tempMat2[2]) / tempMat1[2]
p = tempMat1 * s - tempMat2
point3D.append(p)
point3D = (np.array(point3D, dtype='float32')).reshape([-1, 1, 3])
return point3D
# frame = cv2.imread("source.jpg")
frame = cv2.imread(r'D:/works/distortion/CapImgDir1229/src/000000000089.jpg')
# cap = cv2.VideoCapture("rtsp://admin:MLLaaa100848@192.168.1.64:554/h264/ch1/main/av_stream")
# ret, frame = cap.read()
drawing = False #鼠标按下为真
mode = True #如果为真,画矩形,按m切换为曲线
px,py,ix,iy=-1,-1,-1,-1
def draw_circle(event,x,y,flags,param):
global ix, iy, drawing, mode,px,py,source
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix, iy = x, y
p3d = Pix2World([ix, iy], r, t, newcameramtx, 0)
source = p3d
elif event == cv2.EVENT_LBUTTONUP:
if drawing == True:
if mode == True:
px,py = x,y
cv2.line(frame, (ix, iy), (x, y), (0, 255, 0), 10)
p3d2 = Pix2World([px, py], r, t, newcameramtx, 0)
coord = p3d2-source
length = round(np.sqrt(np.sum((p3d2-source) ** 2))*10,3)
cv2.putText(frame,"L:"+str(length)+'mm',(int((px+ix)/2)-10,int((py+iy)/2)-10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
print("-"*20,'\n',"实际长度",length,'\n','-'*20,)
cv2.imshow("frame",frame)
cv2.waitKey(0)
cv2.namedWindow('frame')
cv2.setMouseCallback("frame",draw_circle)
cv2.imshow("frame", frame)
k = cv2.waitKey(0)
# while ret:
# ret, frame = cap.read()
# cv2.imshow("frame",frame)
# k = cv2.waitKey(0)
# # if cv2.waitKey(1) & 0xFF == ord('q'):
# # break
# if k == ord('s'):
# break
cv2.destroyAllWindows()
# cap.release()
000000000089.jpg图片:
others(仅供参考):
#!/usr/bin/env python
import cv2
import numpy as np
import os
import glob
# Defining the dimensions of checkerboard
CHECKERBOARD = (6,9)
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(10)
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".format(mtx.shape))
print(mtx)
print("dist {}: \n".format(dist.shape))
print(dist)
print("rvecs {}: \n".format(np.array(rvecs).shape))
print(rvecs)
print("tvecs {}: \n".format(np.array(tvecs).shape))
print(tvecs)
# Using the derived camera parameters to undistort the image
img = cv2.imread(images[0])
# Refining the camera matrix using parameters obtained by calibration
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# Method 1 to undistort the image
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# Method 2 to undistort the image
mapx,mapy=cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
print("dst shape is ", dst.shape)
x, y, w, h = roi
cropped_frame = dst[y:y+h, x:x+w]
print("cropped_frame shape is ", cropped_frame.shape)
# Displaying the undistorted image
cv2.imshow("distorted image", img)
cv2.imshow("undistorted image", dst)
cv2.imshow("cropped image", cropped_frame)
cv2.waitKey(0)
#计算误差
mean_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)
mean_error += error
print("mean error: ", mean_error/len(objpoints))