相机标定与3D重建技术通俗讲解

发布于:2025-06-24 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、什么是相机标定?能解决什么问题?

相机标定是计算机视觉中的基础技术,简单来说,就是确定相机从3D世界拍摄到2D图像时的"转换规则"。具体解决两个核心问题:

  • 相机内部属性:如焦距(决定图像缩放)、主点位置(图像中心偏移)
  • 镜头畸变:真实镜头会导致图像变形,比如手机拍摄的广角照片边缘会出现桶形畸变
二、针孔相机模型:3D到2D的投影原理

想象相机是一个暗箱,光线通过小孔在底片上成像,这就是针孔模型的直观理解。数学上,这个过程通过两个矩阵完成:

  1. 内参矩阵(Camera Intrinsics):相机的"固有属性"

    • 形式: [ f x 0 c x 0 f y c y 0 0 1 ] \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} fx000fy0cxcy1
    • 含义: f x , f y f_x, f_y fx,fy是焦距(单位为像素), c x , c y c_x, c_y cx,cy是主点坐标(理想情况下是图像中心)
  2. 外参矩阵(Camera Extrinsics):相机的"位置与朝向"

    • 由旋转矩阵 R R R(3×3)和平移向量 t t t(3×1)组成
    • 作用:将3D世界坐标转换为相机坐标
  3. 投影过程

    • 3D点 P w P_w Pw→相机坐标 P c P_c Pc→图像像素点 p p p
    • 公式: s ⋅ p = A [ R ∣ t ] P w s \cdot p = A[R|t]P_w sp=A[Rt]Pw,其中 s s s是缩放因子
三、镜头畸变:为什么照片会变形?

真实镜头存在多种畸变,OpenCV支持以下模型:

  1. 径向畸变:越靠近图像边缘变形越明显

    • 桶形畸变(如鱼眼镜头):线条向外弯曲
    • 枕形畸变(如长焦镜头):线条向内收缩
    • 参数: k 1 , k 2 , k 3 , k 4 , k 5 , k 6 k_1, k_2, k_3, k_4, k_5, k_6 k1,k2,k3,k4,k5,k6
  2. 切向畸变:镜头安装倾斜导致的变形

    • 参数: p 1 , p 2 p_1, p_2 p1,p2
  3. 薄棱镜畸变:更复杂的光学误差

    • 参数: s 1 , s 2 , s 3 , s 4 s_1, s_2, s_3, s_4 s1,s2,s3,s4
四、3D重建:从多张照片到立体模型

3D重建的核心逻辑:

  1. 单目相机:通过运动恢复结构(SfM),利用相机在不同位置拍摄的图像计算深度
  2. 双目相机:利用左右眼视差计算深度(类似人眼感知距离)
  3. 关键技术
    • 立体校正:让左右相机的光轴平行,便于计算视差
    • 三角测量:通过同一物体在不同视角的投影计算3D坐标
五、Python实战:相机标定与图像校正

下面通过一个完整的Python示例,展示如何使用OpenCV进行相机标定和图像畸变校正:

import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt

# 1. 准备标定数据:拍摄棋盘格多角度图像
# 假设已拍摄一组棋盘格图像并保存到calibration_images文件夹
images = glob.glob('calibration_images/*.jpg')

# 2. 定义棋盘格尺寸(内角点行列数)
pattern_size = (9, 6)  # 9列6行的内角点

# 3. 存储3D点(世界坐标,假设棋盘格在Z=0平面)
obj_points = []  # 3D点
img_points = []  # 2D图像点

# 创建3D点模板(棋盘格每个角点的世界坐标)
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)

# 4. 遍历图像,检测棋盘格角点
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 检测棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
    
    if ret:
        obj_points.append(objp)
        img_points.append(corners)
        
        # 绘制角点并显示(可选)
        cv2.drawChessboardCorners(img, pattern_size, corners, ret)
        cv2.imshow('Corners Detected', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

# 5. 执行相机标定
img_size = (gray.shape[1], gray.shape[0])
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    obj_points, img_points, img_size, None, None)

# 6. 打印标定结果
print("内参矩阵:\n", mtx)
print("畸变参数:\n", dist)

# 7. 畸变校正:读取一张图像进行验证
img = cv2.imread(images[0])
h, w = img.shape[:2]

# 获取校正映射
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(
    mtx, dist, (w, h), 1, (w, h))
mapx, mapy = cv2.initUndistortRectifyMap(
    mtx, dist, None, newcameramtx, (w, h), 5)

# 应用映射进行校正
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# 裁剪校正后的图像(去除黑边)
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]

# 8. 显示原始图像与校正后图像对比
plt.figure(figsize=(12, 6))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.title('原始图像'), plt.axis('off')
plt.subplot(122), plt.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB))
plt.title('校正后图像'), plt.axis('off')
plt.tight_layout()
plt.show()

# 9. 保存标定结果(便于后续使用)
np.savez('camera_calibration.npz', mtx=mtx, dist=dist)
六、代码解析:标定流程拆解
  1. 数据准备:拍摄至少10张不同角度的棋盘格图像,越多越准确
  2. 角点检测findChessboardCorners函数自动识别棋盘格内角点
  3. 相机标定calibrateCamera函数计算内参与畸变参数
    • 内参矩阵包含焦距和主点
    • 畸变参数包含径向和切向畸变系数
  4. 图像校正
    • getOptimalNewCameraMatrix计算最优校正矩阵
    • initUndistortRectifyMap生成映射表
    • remap应用映射完成图像校正
七、扩展应用:双目相机立体视觉

如果需要3D重建,可以扩展到双目相机标定:

# 双目相机标定示例(简化流程)
ret, mtx1, dist1, mtx2, dist2, R, T, E, F = cv2.stereoCalibrate(
    obj_points, img_points1, img_points2, mtx1, dist1, mtx2, dist2, img_size)

# 立体校正
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
    mtx1, dist1, mtx2, dist2, img_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY)

# 生成校正映射
mapx1, mapy1 = cv2.initUndistortRectifyMap(mtx1, dist1, R1, P1, img_size, 5)
mapx2, mapy2 = cv2.initUndistortRectifyMap(mtx2, dist2, R2, P2, img_size, 5)
八、常见问题与注意事项
  1. 标定精度

    • 棋盘格尺寸应已知(如每个格子30mm×30mm)
    • 图像应覆盖相机的全视野范围
    • 拍摄角度应多样化(包括倾斜、远近)
  2. 畸变校正效果

    • 校正后图像边缘可能出现黑边,需裁剪(代码中已处理)
    • 鱼眼镜头需使用专门的fisheye模块
  3. 3D重建基础

    • 单目重建存在尺度不确定性(需额外信息)
    • 双目重建精度取决于基线长度(两相机距离越远,深度越准确)

通过上述技术,相机标定为计算机视觉应用奠定了基础,从自动驾驶的环境感知到AR游戏的虚实融合,再到工业质检的尺寸测量,都离不开精准的相机标定技术。OpenCV的calib3d模块主要用于相机标定和三维重建,是计算机视觉中处理3D空间与2D图像映射关系的核心工具。这个模块提供了从基础几何变换到复杂场景重建的一系列功能。

核心概念

  1. 相机标定
    相机标定是确定相机内部参数(如焦距、主点)和外部参数(位置、姿态)的过程。真实世界中的3D点通过相机投影到2D图像上时会产生畸变(如径向畸变、切向畸变),标定可以校正这些畸变。

  2. 坐标系统

    • 世界坐标系:真实世界中的3D坐标。
    • 相机坐标系:以相机为原点的3D坐标。
    • 图像坐标系:2D像素坐标。
  3. 基础矩阵(Fundamental Matrix)与本质矩阵(Essential Matrix)

    • 本质矩阵(E):描述两个相机坐标系之间的关系,包含旋转和平移信息。
    • 基础矩阵(F):描述两个图像平面上点的对应关系,是本质矩阵的扩展,包含相机内参。

关键函数

  1. 相机标定

    • cv2.findChessboardCorners():检测棋盘格角点。
    • cv2.calibrateCamera():计算相机内参和畸变系数。
    • cv2.undistort():校正图像畸变。
  2. 立体视觉

    • cv2.stereoCalibrate():双目相机联合标定。
    • cv2.stereoRectify():计算校正变换矩阵。
    • cv2.StereoBM_create()/cv2.StereoSGBM_create():计算视差图。
  3. 姿态估计

    • cv2.solvePnP():已知3D点和对应2D点,求解相机位姿。
    • cv2.findEssentialMat()/cv2.findFundamentalMat():计算本质矩阵和基础矩阵。

Python示例:相机标定与畸变校正

下面是一个使用棋盘格进行相机标定的完整示例:

import cv2
import numpy as np
import glob

# 设置棋盘格参数
pattern_size = (9, 6)  # 棋盘格内角点数(横向和纵向)
square_size = 25.0     # 棋盘格方块的实际尺寸(毫米)

# 准备对象点,如 (0,0,0), (1,0,0), (2,0,0) ..., (8,5,0)
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) * square_size

# 存储对象点和图像点的数组
objpoints = []  # 3D点(世界坐标系)
imgpoints = []  # 2D点(图像平面)

# 获取所有棋盘格图像
images = glob.glob('calibration_images/*.jpg')

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 查找棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)

    # 如果找到,添加对象点和图像点
    if ret:
        objpoints.append(objp)
        
        # 亚像素级角点检测,提高精度
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
                                   (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
        imgpoints.append(corners2)

        # 绘制并显示角点
        cv2.drawChessboardCorners(img, pattern_size, corners2, ret)
        cv2.imshow('img', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

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

# 打印标定结果
print("相机内参矩阵:")
print(mtx)
print("\n畸变系数:")
print(dist)

# 畸变校正示例
img = cv2.imread('calibration_images/left01.jpg')
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))

# 方法1:使用initUndistortRectifyMap和remap
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, np.eye(3), newcameramtx, (w, h), 5)
dst = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# 方法2:直接使用undistort
dst2 = cv2.undistort(img, mtx, dist, None, newcameramtx)

# 裁剪图像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]

# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Undistorted', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 计算重投影误差(评估标定质量)
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(f"\n总重投影误差: {mean_error/len(objpoints)}")

示例:双目相机立体匹配

下面是一个计算视差图的示例:

import cv2
import numpy as np

# 读取双目相机图像
imgL = cv2.imread('left_image.jpg', 0)
imgR = cv2.imread('right_image.jpg', 0)

# 初始化立体匹配器(StereoBM)
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)

# 计算视差图
disparity = stereo.compute(imgL, imgR)

# 归一化视差图以便显示
disparity_normalized = cv2.normalize(disparity, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

# 显示结果
cv2.imshow('Left Image', imgL)
cv2.imshow('Right Image', imgR)
cv2.imshow('Disparity Map', disparity_normalized)
cv2.waitKey(0)
cv2.destroyAllWindows()

应用场景

  • 机器人导航:通过立体视觉计算深度信息。
  • 增强现实(AR):将虚拟物体准确叠加到真实场景中。
  • 3D建模:从多角度图像重建物体的三维模型。
  • 自动驾驶:检测障碍物和估计距离。

通过掌握calib3d模块,你可以解决计算机视觉中许多与3D空间相关的复杂问题。


网站公告

今日签到

点亮在社区的每一天
去签到