当前位置: 首页 > news >正文

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

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

相机标定是计算机视觉中的基础技术,简单来说,就是确定相机从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空间相关的复杂问题。

http://www.lryc.cn/news/574218.html

相关文章:

  • springboot开发项目 SLF4J+Logback日志框架集成【最终篇】
  • 用 EXCEL/WPS 实现聚类分析:赋能智能客服场景的最佳实践
  • Linux笔记---线程控制
  • 用安卓手机,怎样远程管理孩子iPhone屏幕使用时间?
  • 新高考需求之一
  • uniapp+vue3做小程序,获取容器高度
  • 世赛背景下,高职物联网应用开发赛项实训解决方案
  • 2025年小程序地图打车的5大技术革新:实时路况预测与智能调度升级
  • 【Docker基础】Docker容器管理:docker pause详解
  • 【文件】Linux 内核优化实战 - fs.inotify.max_user_watches
  • 用DeepSeek完成实际生产编程完整项目
  • 树莓派超全系列教程文档--(66)rpicam-apps可用选项介绍之视频选项
  • [论文阅读] 人工智能 + 软件工程 | AI 驱动工具在软件质量保证中的革新:挑战与未来之路
  • 物联网的全球布局与未来趋势
  • 【Golang玩转MCP】-实现一个加减乘除MCP服务
  • 1 Studying《Systems.Performance》7-13
  • 数据赋能(313)——合作共享——跨界融合
  • Modbus TCP转Profibus DP网关与JF - 600MT称重变送器在STEP 7快速配置
  • 计算机操作系统(十六)进程同步
  • DMDRS部署实施手册(ORACLE=》DM)
  • Long类型返回给前端精度丢失问题(解决方案)
  • AWS S3服务器访问日志启用
  • RAG实战基础篇/windows电脑快速部署qwen3:14B
  • Python Arrow 库详解:更智能的日期时间处理
  • 制造业B端页面个性化设计案例:生产流程监控的专属布局打造
  • 探秘 Java 安全利器 ——JVMTI
  • 揭开 Git 裸仓库的神秘面纱:`git clone --mirror` 详解与使用指南
  • idea 报错:java: 非法字符: ‘\ufeff‘
  • Node.js特训专栏-实战进阶:7.Express模板引擎选型与使用
  • 大数据时代UI前端的变革:从静态展示到动态交互