相机标定与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.pswp.cn/diannao/88446.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88446.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek-Reasoner推理模型示例

《DEEPSEEK原生应用与智能体开发实践 王晓华 书籍 图书》【摘要 书评 试读】- 京东图书 在之前讲解的示例中(指这个示例:通过Prompt提示构建思维链-CSDN博客),无论是进行日常对话还是调用特定工具,我们所依赖的底层技…

常说的电源芯片到底指什么?

电源芯片是电子系统中用于管理、转换和分配电能的集成电路,根据功能和应用场景的不同,主要分为以下几类: 一、线性稳压器(LDO, Low Dropout Regulator) LDO内部的基本电路情况如下: LDO内部主要分为四大部…

【大模型学习】项目练习:套壳DeepSeek

这里是阿川的博客,祝您变得更强 ✨ 个人主页:在线OJ的阿川 💖文章专栏:AI入门到进阶 🌏代码仓库: 写在开头 现在您看到的是我的结论或想法,但在这背后凝结了大量的思考、经验和讨论 &#x1f4…

笔记03:布线-过孔的调用与添加

布线-过孔的调用与添加 (1)在进行PCB设计时,都必须使用到过孔,对走线进行换层处理。在走线进行打过孔之前,必须先要添加过孔,这样在PCB布线时才可以使用过孔。 (2)需要使用pad des…

在vscode中,Python程序的内置对象、关键字、自定义函数名/类名、字符串进行着色,说明分别是什么颜色?

在 VS Code 中,Python 代码的着色完全取决于你当前使用的主题。不同主题(如 Dark, Monokai, Solarized Dark, Light, Quiet Light 等)对不同类型的代码元素会使用不同的颜色。 一、Default Dark(默认的深色主题) impo…

Visual Studio 中使用 AddressSanitizer 指南

Visual Studio 中使用 AddressSanitizer 指南 基于 Microsoft Visual Studio 2022,支持 MSVC 和 Clang 编译器链,本文详细说明如何在 VS 中配置和使用 AddressSanitizer,用于检测内存误用,如消息释放后访问、超界读写等类型错误。…

Flink Sink函数深度解析:从原理到实践的全流程探索

在Flink的数据流处理体系中,Sink函数作为数据处理的最终出口,肩负着将处理后的数据写入外部存储引擎的关键使命。它如同数据旅程的终点站,决定着数据的最终归宿与应用价值。深入理解Sink函数的工作原理、核心概念及实现方式,对构建…

Codex+ 自建中转 API 部署教程(Windows 版)

📌 一、前置环境准备 安装 Node.js 和 Codex CLI: npm install -g openai/codex准备 OpenAI API Key 确保你已有的中转接口兼容 OpenAI 格式, 📌 二、设置 PowerShell 环境变量 # 设置你的 API Key(使用哪家的看你的…

Centos 7离线部署Nginx 高效省时

给脚本执行权限:chmod x install_nginx.sh以root用户运行:sudo ./install_nginx.sh 脚本如下: #!/bin/bash # Nginx一键化部署脚本(修复版本开机自启) # 需要以root权限运行set -e # 任何命令失败时立即退出脚本# 定…

P7915 [CSP-S 2021] 回文

题目描述 给定正整数 n n n 和整数序列 a 1 , a 2 , … , a 2 n a_1, a_2, \ldots, a_{2 n} a1​,a2​,…,a2n​,在这 2 n 2 n 2n 个数中, 1 , 2 , … , n 1, 2, \ldots, n 1,2,…,n 分别各出现恰好 2 2 2 次。现在进行 2 n 2 n 2n 次操作&#xf…

小智AI -- ESP32-S3 DIY面包板WIFI-LCD彩屏

DIY 所需硬件 开发板:ESP32-S3-DevKitC-1(选择 WROOM N16R8 模组) Goouuu ESP32-S3-N16R8开发板数字麦克风:INMP441 INMP441全向麦克风模块功放:MAX98357A MAX98357 I2S 音频放大器模块腔体喇叭:8Ω 2~3W 或…

家用网络进行DNS优选

家用网络进行DNS优选的好处主要体现在以下几个方面: 提升网络访问速度: DNS优选通过选择响应时间更快的DNS服务器,减少域名解析的延迟,从而加快网页加载和应用访问速度。尤其在访问国内外网站时,选择合适的DNS服务器可…

刷题 | 牛客 - js中等题-下 (更ing)45/54知识点解答

JS45 数组去重 描述 为 Array 对象添加一个去除重复项的方法 示例1 输入: [false, true, undefined, null, NaN, 0, 1, {}, {}, a, a, NaN] 复制输出: [false, true, undefined, null, NaN, 0, 1, {}, {}, a] Array.prototype.uniq function () …

vue3使用krpano1.22

官方文档链接 https://krpano.com/docu/js/#top 例子 https://krpano.com/releases/1.22/viewer/examples/javascript-interface/js-api-examples.html https://krpano.com/viewsource.html?releases/1.22/viewer/examples/javascript-interface/js-api-examples.html 注…

2025年AI面试推荐榜单,数字化招聘转型优选

一、AI面试为何成为2025招聘标配? 2025年企业对AI面试的需求从“效率工具”升级为“战略级招聘伙伴”。数据显示,超7成企业计划年内全面引入AI面试,其中技术岗、全球化招聘及蓝领用工场景需求增速显著。以下以综合技术实力、行业口碑及落地能…

人机协作新篇章:艾利特按摩机器人如何重塑健康生活

引言:按摩机器人的需求爆发 在快节奏的现代生活中,亚健康人群比例持续攀升。据《全球健康产业白皮书》显示: 85%的都市人群存在肌肉劳损问题专业理疗师供需缺口达1:3200精准按摩服务成本年均增长18% 这一背景下,按摩…

从代码学习深度学习 - 情感分析:使用循环神经网络 PyTorch版

文章目录 前言1. 加载与预处理数据集数据读取与词元化构建词汇表截断、填充与数据迭代器2. 构建循环神经网络模型双向RNN模型(BiRNN)详解权重初始化3. 加载预训练词向量构建词向量加载器将预训练向量注入模型4. 训练与评估模型定义训练函数可视化训练过程5. 模型预测编写预测…

化于无形的 lambda 语法

针对数据集合的每个成员进行计算是很常见的任务,用循环语句当然能实现,但比较麻烦,算个简单的求和都要写很多句代码。 编程语言经常把这些运算封装成函数,比如 Python 的 sum 函数,求订单价格总和是这样写的&#xff…

day42

1. 回调函数:把一个函数当成“任务清单”交给另一个函数,等后者干完活,就按清单执行这个函数。比如点外卖后留电话,骑手送到了就打电话(执行回调)通知你。 2. lambda函数:临时写的超短函数&…

百度日志中台前端重构实践

日志中台是百度内部针对打点数据的全生命周期管理平台,作为公司日志数据的唯一入口,承担以下核心职能:1.功能覆盖:提供从数据采集、传输、存储到查询分析的一站式服务,支持产品运营分析、研发性能监控、运维管理等多元…