单目相机部分参考于:单目相机标定实现--张正友标定法_张正友标定法 opencv-CSDN博客
单目相机标定
1.为什么单目相机需要进行相机标定
单目相机需要相机标定是因为相机在拍摄图像时会引入畸变,这会影响到图像的几何形状和尺度,使得图像中的物体位置和大小不能直接反映真实世界中的情况。相机标定的目的是通过对已知几何形状的图像进行分析,来推导相机的内部参数(如焦距、主点位置)和畸变参数,从而纠正图像中的畸变,使得图像能够更准确地反映真实场景的几何信息。
具体来说,单目相机的畸变主要包括径向畸变和切向畸变。径向畸变是由于相机的透镜不是完全理想的,导致图像中离光心较远的点在图像中表现为弧形弯曲;切向畸变则是由于相机的透镜和成像平面不完全平行引起的,使得图像中的线条不是水平或垂直的。
可以通过相机标定,得到相机的内参矩阵和畸变系数,然后利用这些参数可以对图像进行畸变校正,从而得到更准确的图像信息,方便后续的图像处理和计算。
2.单目相机标定的流程原理
现实世界中有一个P点和一个相机(光心),描述这个P点的空间坐标首先得有一个坐标系,那么以光心为原点O建一个坐标系,叫相机坐标系。
那么就可以在相机坐标系下,设 P 坐 标 ( X , Y , Z ) P坐标(X,Y,Z) P坐标(X,Y,Z)和P的投影点 P ′ ( x ′ , y ′ , z ′ ) P'(x',y',z') P′(x′,y′,z′)。值得一提的是, P ′ ( x ′ , y ′ , z ′ ) P'(x',y',z') P′(x′,y′,z′)坐落在物理成像平面和像素平面。
物理成像平面,像素平面是二维的,他们的坐标系并不一样:
物理成像平面在 O ′ ( x ′ , y ′ ) O'(x',y') O′(x′,y′)平面上;
像素平面的原点在那个黑灰色图的左上角(图片的左上角),横轴向右称为 u u u轴,纵轴向下称为 v v v轴。
这样就得到了 P ′ P' P′的像素坐标 P ( u , v ) P(u,v) P(u,v),称为 P u v Puv Puv。
归一化的成像平面,就是将三维空间点的坐标都除以Z,在相机坐标系下,P有X, Y, Z 三个量,如果把它们投影到归一化平面 Z = 1 上,就会得到P的归一化坐标P(X/Z, Y/Z, 1)。
其中外参 T T T是平移向量 ( t 1 , t 2 , t 3 ) T (t1,t2,t3)^T (t1,t2,t3)T.
R R R旋转矩阵如下图:
3.个人工具代码
单目相机标定测试阶段
一.
- 用手机先拍摄10~15张棋盘格图片(正对、倾斜等角度)
- 棋盘格尺寸为角点数量,比如我的棋盘格横有九个格子,则记8
- 输出图片方便查看
import cv2
import glob
import os
import numpy as np
# 棋盘格尺寸
pattern_size = (8, 5)
# 获取目录中所有图片的路径
image_paths = glob.glob('/home/lxy/Desktop/camera_calibration/picture/*.jpg')
for image_path in image_paths:
# 读取图像
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
# 如果找到角点,绘制角点
if ret == True:
# 亚像素级别的角点优化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
cv2.drawChessboardCorners(img, pattern_size, corners, ret)
# 获取匹配点对
srcPoints = corners.squeeze()
dstPoints = np.array([[j * 80, i * 80] for i in range(5) for j in range(8)], np.float32)
# 计算单应矩阵H
H, _ = cv2.findHomography(srcPoints, dstPoints)
# 使用单应矩阵H进行后续操作
# ...
# 保存图像至指定路径
save_path = os.path.join('/home/lxy/Desktop/camera_calibration/output', os.path.basename(image_path))
cv2.imwrite(save_path, img)
print(f"Saved image to: {save_path}")
# 关闭窗口
cv2.destroyAllWindows()
input:
output:
二.
配合第一个代码使用,将打印无畸变前提条件下的内参、外参
import cv2
import numpy as np
import glob
# 定义棋盘格尺寸
pattern_size = (8, 5)
# 创建棋盘格角点的世界坐标
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
# 用于保存所有棋盘格角点的世界坐标和图像坐标
objpoints = [] # 世界坐标系中的棋盘格角点
imgpoints = [] # 图像坐标系中的棋盘格角点
# 获取目录中所有图片的路径
image_paths = glob.glob('/home/lxy/Desktop/camera_calibration/output/*.jpg')
for image_path in image_paths:
# 读取图像
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
# 如果找到角点,将世界坐标和图像坐标保存起来
if ret == True:
objpoints.append(objp)
imgpoints.append(corners)
# 进行相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 打印内参和畸变系数
# 无畸变下的内参、外参
print("Camera matrix:")
print(mtx)
print("Distortion coefficients:")
print(dist)
output:
三.
将以上代码统合,输入原始图片,输出打印内参、外参、畸变系数,保存矫正后的图像
import cv2
import numpy as np
import glob
import os
# 定义棋盘格尺寸
pattern_size = (8, 5)
# 创建棋盘格角点的世界坐标
objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2)
# 用于保存所有棋盘格角点的世界坐标和图像坐标
objpoints = [] # 世界坐标系中的棋盘格角点
imgpoints = [] # 图像坐标系中的棋盘格角点
# 获取目录中所有图片的路径
image_paths = glob.glob('/home/lxy/Desktop/camera_calibration/picture/*.jpg')
for image_path in image_paths:
# 读取图像
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
# 如果找到角点,将世界坐标和图像坐标保存起来
if ret == True:
objpoints.append(objp)
imgpoints.append(corners)
# 绘制棋盘格角点
cv2.drawChessboardCorners(img, pattern_size, corners, ret)
# 保存图像至指定路径
save_path = os.path.join('/home/lxy/Desktop/camera_calibration/output', os.path.basename(image_path))
cv2.imwrite(save_path, img)
print(f"Saved image to: {save_path}")
# 进行相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
# 打印内参、畸变系数
print("Camera matrix:")
print(mtx)
print("Distortion coefficients:")
print(dist)
# 打印外参
for i in range(len(objpoints)):
retval, rvec, tvec = cv2.solvePnP(objpoints[i], imgpoints[i], mtx, dist)
print(f"Image {i+1}:")
print("Rotation vector:")
print(rvec)
print("Translation vector:")
print(tvec)
# 矫正图像畸变并输出
for image_path in image_paths:
img = cv2.imread(image_path)
# 矫正图像畸变
undistorted_img = cv2.undistort(img, mtx, dist)
# 保存矫正后的图像
undistorted_save_path = os.path.join('/home/lxy/Desktop/camera_calibration/undistorted_output', os.path.basename(image_path))
cv2.imwrite(undistorted_save_path, undistorted_img)
print(f"Saved undistorted image to: {undistorted_save_path}")
四.利用得到的内参和畸变系数,对非棋盘格图片进行去畸变操作。结束
import cv2
import numpy as np
# 内参矩阵
camera_matrix = np.array([[287.45316998, 0.0, 266.30516443],
[0.0, 287.74766998, 313.24311295],
[0.0, 0.0, 1.0]])
# 畸变系数
dist_coeffs = np.array([-0.04538883, -0.03414269, -0.00016384, -0.00078098, 0.00900785])
# 读取待校正的图像
image_path = "/home/lxy/Desktop/camera_calibration/rosbag_picture/2/image_1710390491359887524.jpg"
image = cv2.imread(image_path)
# 校正图像
undistorted_image = cv2.undistort(image, camera_matrix, dist_coeffs)
# 将原始图像和校正后的图像拼接在一起
concatenated_image = np.hstack((image, undistorted_image))
# 显示拼接后的图像
cv2.imshow("Concatenated Images", concatenated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()