计算机视觉——基于使用 OpenCV 与 Python 实现相机标定畸变校正

news2025/5/16 14:49:59

概述

相机标定是一种旨在通过确定相机的内参(焦距、光学中心、畸变系数)和外参(相机的位置和方向),提高图像在现实世界中的几何精度的过程。该过程可以纠正相机拍摄的图像中的畸变,使相机能够准确感知现实世界中的距离、角度和物体。一个很好的例子是纠正鱼眼相机拍摄的图像。

一、什么是相机标定

相机通过将其投影到二维平面上来捕捉现实世界。然而,由于光学元件和镜头的结构特性,这些图像可能会出现误差。最常见的误差是畸变和透视误差。相机标定通过计算相机的内参和外参来纠正这些误差,从而实现更准确的测量和几何计算。

关键参数

  1. 内参

    • 焦距:根据镜头的焦距确定图像的大小。
    • 光学中心(主点):相机镜头的中心点。
    • 畸变系数:用于纠正镜头畸变,如桶形畸变和枕形畸变。(你可以在图 1 中清楚地看到这一点。)

    注意:术语“畸变”指的是镜头引起的误差,如变形或弯曲。

    图 1. 枕形畸变和桶形畸变

  2. 外参

    • 相机位置:相机相对于世界的位置(x、y、z 坐标)。
    • 相机方向:相机相对于世界的视角(旋转角度)。

    图 2. 收集标定用的视觉数据

二、如何进行相机标定

通常使用已知的几何图案(如棋盘格)进行相机标定。该图案的已知尺寸和位置用作参考,以检测相机图像中的畸变。 标定过程包括以下步骤:

  1. 图像采集:要进行相机标定,你需要一组至少 15 张从不同角度拍摄的棋盘格图案的图像。该图案的角点有助于检测图像中的畸变。
  2. 角点检测:在每张图像中检测棋盘格图案的角点。正确检测这些角点对于准确标定至关重要。
  3. 内参和外参的计算:根据相机图像中的角点与它们的真实世界坐标之间的差异,优化并计算相机的内参和外参。通常使用 AI 算法或数学优化技术进行此计算。
  4. 畸变纠正:使用计算出的参数纠正图像中的畸变,去除非线性镜头畸变。
  5. 验证:为了测试标定的准确性,用相机拍摄一张新图像,并应用标定参数来纠正图像。然后观察纠正的准确性。

图 3. 添加桶形畸变
图 4. 标定结果示例

三、使用 OpenCV 在 Python 中进行相机标定

OpenCV 是 Python 中用于相机标定最常用的库之一。OpenCV 提供了相机标定和畸变纠正所需的函数。以下是简单的标定示例:

import cv2
import numpy as np
import glob

# 棋盘格的尺寸(内部角点的数量)
grid_size = (9, 6)

# 每个正方形的实际尺寸(2 厘米)
square_size = 2  # 厘米

# 棋盘格的 3D 世界坐标
obj_points = np.zeros((grid_size[0] * grid_size[1], 3), np.float32)
obj_points[:, :2] = np.mgrid[0:grid_size[0], 0:grid_size[1]].T.reshape(-1, 2) * square_size

# 用于存储标定所需点的列表
object_points = []  # 3D 世界坐标
image_points = []  # 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, grid_size, None)

    if ret:
        object_points.append(obj_points)
        image_points.append(corners)

        # 可视化角点
        cv2.drawChessboardCorners(img, grid_size, corners, ret)
        cv2.imshow('Chessboard Corners', img)
        cv2.waitKey(500)

cv2.destroyAllWindows()

# 执行标定
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(
    object_points, image_points, gray.shape[::-1], None, None
)

# 保存相机矩阵和畸变系数
np.savez('calibration_data.npz', camera_matrix=camera_matrix, dist_coeffs=dist_coeffs)

# 打印标定结果
print("Camera Matrix:\n", camera_matrix)
print("Distortion Coefficients:\n", dist_coeffs)

四、代码解释

import cv2
import numpy as np
import glob
  • cv2(OpenCV):我们导入 OpenCV,这是一个用于图像处理的库。该库提供了许多用于相机标定和图像处理的函数。
  • numpy(np):我们导入 NumPy,以便轻松进行数值运算和处理数组。
  • glob:用于组织文件路径并列出文件夹中的文件。在这里,它用于获取包含标定图像的文件夹中的所有图像。
grid_size = (9, 6)
  • grid_size:指定棋盘格上内部角点的数量。在此示例中,棋盘格图案有 9 列和 6 行角点(9 列 × 6 行 = 54 个角点)。此值应与实际使用的棋盘格匹配。
square_size = 2  # cm
  • square_size:棋盘格上的每个正方形的实际边长设置为 2 厘米。
obj_points = np.zeros((grid_size[0] * grid_size[1], 3), np.float32)
obj_points[:, :2] = np.mgrid[0:grid_size[0], 0:grid_size[1]].T.reshape(-1, 2) * square_size
  • obj_points:我们创建棋盘格角点在真实世界坐标中的 3D 位置。每个角点的 Z 轴值设置为 0。
  • np.zeros((grid_size[0] * grid_size[1], 3), np.float32):创建一个 3D 空矩阵,为每个角点包含(x、y、z)坐标。
  • np.mgrid[0:grid_size[0], 0:grid_size[1]]:生成棋盘格上角点的(x、y)坐标。

注意mgrid 是 NumPy 的一个函数,用于创建多维网格结构,其语法为 mgrid[start:end:step]

例如:

# 二维网格(网格)
x, y = np.mgrid[0:3, 0:3]
print("X:\n", x)
print("Y:\n", y)

输出:

X:
 [[0 0 0]
  [1 1 1]
  [2 2 2]]
Y:
 [[0 1 2]
  [0 1 2]
  [0 1 2]]
  • T.reshape(-1, 2):将二维角点转换为单个矩阵。
object_points = []  # 3D 世界坐标
image_points = []  # 2D 图像坐标
  • object_points:一个列表,用于存储每张图像的 3D 真实世界棋盘格角点。
  • image_points:一个列表,用于存储每张图像的 2D 图像角点。
images = glob.glob('calibration_images/*.jpg')
  • images:查找 calibration_images 文件夹中的所有 .jpg 文件,并将它们存储在一个列表中。这些是用于标定的棋盘格图案图像。
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  • 此循环处理每张图像:
    • cv2.imread(fname):读取由 fname 指定的图像。
    • cv2.cvtColor(img, cv2.COLOR_BGR2GRAY):将读取的图像从彩色(BGR)转换为灰度。在灰度图像上检测棋盘格图案更容易。
ret, corners = cv2.findChessboardCorners(gray, grid_size, None)
  • cv2.findChessboardCorners:尝试在灰度图像中查找棋盘格的角点。
  • ret:一个标志(True/False),指示是否成功找到棋盘格的角点。
  • corners:检测到的图像中角点的 2D 坐标。
if ret:
    object_points.append(obj_points)
    image_points.append(corners)
  • 如果 retTrue,即成功找到棋盘格的角点:
    • object_points.append(obj_points):将 3D 真实世界坐标添加到列表中。
    • image_points.append(corners):将 2D 图像坐标添加到列表中。
cv2.drawChessboardCorners(img, grid_size, corners, ret)
cv2.imshow('Chessboard Corners', img)
cv2.waitKey(500)
  • cv2.drawChessboardCorners:通过在图像上绘制线条来可视化检测到的棋盘格角点。
  • cv2.imshow:在一个新窗口中显示带有检测到的角点的图像。
  • cv2.waitKey(500):显示图像 500 毫秒(0.5 秒)。
cv2.destroyAllWindows()
  • cv2.destroyAllWindows:关闭所有打开的窗口。
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(
    object_points, image_points, gray.shape[::-1], None, None
)
  • camera_matrix:一个包含相机内参(焦距、光学中心等)的矩阵。
  • dist_coeffs:镜头畸变系数。
  • rvecs:旋转向量。
  • tvecs:平移向量。
  • cv2.calibrateCamera:执行相机标定。该函数根据真实世界 3D 点与其在图像中的对应 2D 点之间的关系计算相机参数。
  • object_points:3D 真实世界坐标。
  • image_points:图像中的 2D 投影坐标。
  • gray.shape[::-1]:图像分辨率(宽度和高度)。
  • camera_matrix(默认值:None)—— calibrateCamera 的参数:这是一个 3×3 矩阵,表示相机的内参(焦距、主点等)。如果提供为 None,这些参数将由函数计算。如果你有一个已知的相机矩阵,你可以在这里提供它。
  • dist_coeffs(默认值:None)—— calibrateCamera 的参数:这是一个表示畸变系数(畸变)的向量。如果提供为 None,畸变系数将由函数计算。

注意提供 **camera_matrix=None** **dist_coeffs=None** 的原因是你希望计算这些参数并获得标定的结果。 在你的代码中,你试图从特定图像中推导出相机的内参和镜头畸变系数(camera_matrix 和 dist_coeffs)。

  • flags(可选):这些是用于在标定期间指定某些选项的标志。例如:
    • cv2.CALIB_USE_INTRINSIC_GUESS:启用内参(相机矩阵和畸变系数)的初始猜测。
    • cv2.CALIB_FIX_PRINCIPAL_POINT:固定主点。
    • cv2.CALIB_FIX_ASPECT_RATIO:固定纵横比。
  • criteria(可选):指定迭代标准。这是优化过程的停止条件,通常与 cv2.TERM_CRITERIA_MAX_ITERcv2.TERM_CRITERIA_EPS 等选项一起使用。
np.savez('calibration_data.npz', camera_matrix=camera_matrix, dist_coeffs=dist_coeffs)
  • np.savez:将计算出的相机矩阵和畸变系数保存到名为 calibration_data.npz 的文件中。此文件允许你稍后重用标定参数。
print("Camera Matrix:\n", camera_matrix)
print("Distortion Coefficients:\n", dist_coeffs)
  • print:在屏幕上显示 camera_matrix(内参)和 dist_coeffs

此代码使用 OpenCV 进行相机标定,并保存结果。相机标定对于纠正图像中的畸变以及进行准确测量至关重要。

五、标定结果中你将获得的值

在这一过程结束时,camera_matrixdist_coeffs 将由函数计算并返回:

camera_matrix

该矩阵包含相机的内参(焦距、主点等)。它用于了解相机镜头的特性以及透视变换。它是一个 3×3 矩阵,可能如下所示:

camera_matrix 示例

在这里,f_xf_y 是相机沿水平和垂直方向的焦距(以像素为单位),c_xc_y 是图像平面上主点的像素坐标(通常是图像的中心)。

什么是主点? 主点表示图像的光学中心,并定义了相机镜头与图像平面之间的关系。如果相机的光轴偏离中心,这种偏移可以通过 c_xc_y 坐标检测到。

示例
假设你的相机分辨率为 1920×1080 像素,标定后你获得以下 camera_matrix:

camera_matrix 示例 2

  • f_x = 1200f_y = 1200:沿水平和垂直方向的焦距。
  • c_x = 960c_y = 540:主点的坐标,位于图像平面的中心(960 和 540 表示中心点,因为你的分辨率为 1920×1080)。

dist_coeffs

该向量包含镜头的畸变系数。这些系数用于纠正镜头的几何畸变(例如桶形畸变或枕形畸变)。

通常,该向量包含以下系数:

dist_coeffs 示例

这些系数:

  1. k_1、k_2、k_3:径向畸变系数。这些系数用于纠正桶形或枕形畸变。如果图像中的畸变随着距离中心的增加而变得更加明显,这就是径向畸变,k_1k_2k_3 用于纠正这种畸变。k_1 纠正靠近图像中心的畸变。k_2 纠正向图像边缘的较大畸变。k_3 对远离中心的点(尤其是边缘)进行微调,特别是对于边缘处的畸变。

    桶形畸变:一种向边缘膨胀的畸变。

    枕形畸变:一种向边缘收缩的畸变。

  2. p_1、p_2:切向畸变系数。当镜头与传感器未完美对齐时,会发生这种畸变。如果镜头未完美居中或存在轴向偏移,图像倾向于向边缘偏移。p_1p_2 用于纠正这种倾斜。p_1 纠正沿 x 轴(水平平面)的偏移或畸变。p_2 纠正沿 y 轴(垂直平面)的偏移或畸变。

    什么是径向畸变和切向畸变?

    径向畸变:这些畸变导致图像中的直线随着距离中心的增加而弯曲。k_1k_2k_3 用于纠正这些曲线。

    切向畸变:当镜头与传感器不完全垂直时发生,导致图像向边缘偏移。p_1p_2 用于纠正这些偏移。

  3. k_4、k_5、k_6(可选):高阶径向畸变系数。这些参数用于纠正更复杂的畸变,例如来自超广角镜头的畸变。它们通常不用于标准标定,但在需要更精确的校正时可以应用。

示例:

dist_coeffs = [0.1, -0.25, 0.001, 0.002, 0.03]
  • k_1 = 0.1k_2 = -0.25:用于纠正径向畸变的系数。
  • p_1 = 0.001p_2 = 0.002:用于纠正切向畸变的系数。
  • k_3 = 0.03:一个高阶径向畸变系数。

KP 的值越大,校正效果越强。随着它们的减小,校正效果减弱。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2337983.html

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

相关文章

Python作业4 文本词云统计,生成词云

编写程序,统计两会政府工作报告热词频率,并生成词云。 2025两会政府工作报告 import jieba import wordcloud from collections import Counter import re# 读取文件 with open("gov.txt", "r", encoding"gbk") as f:t …

Jenkins 2.492.2 LTS 重置管理员密码

文章目录 1. Jenkins 关闭用户认证2. jenkins 修改密码 如果忘记了 Jenkins 的管理员密码的话,也不用担心,只要你有权限访问 Jenkins 的根目录,就可以轻松地重置密码。 1. Jenkins 关闭用户认证 // 查看 jenkins 家目录(使用 doc…

【裁判文书网DES3数据解密】逆向分析

点击翻页,出现请求,可以看到请求参数有个ciphertext密文,响应数据也是密文 打上断点,点击翻页,断住 可以看到postData里面的ciphertext已经生成 往前跟栈,可以发现是var ciphertext cipher(); funct…

花园灌溉问题

#include <bits/stdc.h> using namespace std;// 设置最大行列数&#xff08;题目限制 n, m ≤ 100&#xff09; const int N 104;// 标记某个格子是否已经被水浇灌 bool used[N][N];// 队列&#xff0c;用于 BFS&#xff0c;存储当前水源的位置 queue<pair<int,i…

《AI大模型应知应会100篇》第22篇:系统提示词(System Prompt)设计与优化

第22篇&#xff1a;系统提示词(System Prompt)设计与优化 摘要 在大语言模型&#xff08;LLM&#xff09;应用中&#xff0c;系统提示词&#xff08;System Prompt&#xff09;是控制模型行为的核心工具之一。它不仅定义了模型的身份、角色和行为规范&#xff0c;还直接影响输…

Jsp技术入门指南【六】jsp脚本原理及隐式对象

Jsp技术入门指南【六】jsp脚本原理及隐式对象 前言一、JSP 脚本元素1.1 声明1.2 表达式1.3 脚本标签 二、JSP 的隐式对象是什么三、隐式对象详解outrequestsessionapplicationconfigexception 前言 在之前的博客中&#xff0c;我们已经介绍了JSP的环境搭建、编译文件查找以及生…

Jsp技术入门指南【五】详细讲解jsp结构页面

Jsp技术入门指南【五】详细讲解jsp结构页面 前言一、JSP页面的结构二、JSP页面的部件1. 指令&#xff08;核心控制部件&#xff09;2. 动作&#xff08;页面交互部件&#xff0c;了解即可&#xff09;3. 脚本&#xff08;Java逻辑嵌入部件&#xff09; 三、JSP指令详解1.1 JSP指…

Beyond Compare 30天评估到期 解决方法

Beyond Compare 30天评估到期 解决方法 一、问题二、解决办法2.1 第一步&#xff1a;打开注册表2.2 第二步&#xff1a;删除cacheID 三、效果 一、问题 Beyond Compare提示评估到期&#xff0c;重装也无效&#xff0c;只需简单两步&#xff0c;轻轻松松出困境。 二、解决办法…

Arduino无线体感机器手——问题汇总

文章不介绍具体参数&#xff0c;有需求可去网上搜索。 特别声明&#xff1a;不论年龄&#xff0c;不看学历。既然你对这个领域的东西感兴趣&#xff0c;就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题&#xff1a;提出问题时&#xff0c;应说明是哪款产品&a…

学习设计模式《一》——简单工厂

一、基础概念 1.1、接口 简单的说&#xff1a;接口是【用来实现类的行为定义、约束类的行为】&#xff08;即&#xff1a;定义可以做什么&#xff09;&#xff1b;接口可以包含【实例方法】、【属性】、【事件】、【索引器】或这四种成员类型的任意组合。 接口的优点&#xff1…

010数论——算法备赛

数论 模运算 一般求余都是对正整数的操作&#xff0c;如果对负数&#xff0c;不同编程语言结果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正数&#xff1a;&#xff08;ab&#xff09;%m((a%m)(b%m))%m~正数&#xff…

NAT、代理服务、内网穿透

NAT、代理服务、内网穿透 1、NAT1.1、NAT过程1.2、NAPT2、内网穿透3、内网打洞3、代理服务器3.1、正向代理3.2、反向代理1、NAT 1.1、NAT过程 之前我们讨论了IPv4协议中IP地址数量不充足的问题。NAT技术是当前解决IP地址不够用的主要手段,是路由器的一个重要功能。 NAT能够将…

【unity实战】Unity动画层级(Animation Layer)的Sync同步和Timing定时参数使用介绍,同步动画层制作角色的受伤状态

文章目录 前言方案一&#xff1a;复制粘贴原有层级的状态机1、实现2、问题 方法二&#xff1a;勾选Sync同步动画层1、简单实现同步2、同步blend tree的问题3、动画状态的播放时长4、下层状态覆盖了上层状态 专栏推荐完结 前言 如何制作角色的受伤状态&#xff1f; 玩家角色在…

Uniapp调用native.js使用经典蓝牙串口通讯方法及问题解决

本人尝试在uniapp环境下开发一款安卓应用&#xff0c;需要与使用经典蓝牙协议的设备进行串口通讯&#xff0c;而uniapp官方给出的蓝牙操作接口目前只支持BLE&#xff08;低功耗蓝牙&#xff09;&#xff0c;用该接口无法正常获取到我想要连接的设备。 通过大量搜索&#xff0c…

C++23 新特性:行拼接前去除空白符 (P2223R2)

文章目录 1\. 什么是行拼接前去除空白符2\. 为什么需要这一特性3\. 示例代码输出结果 4\. 编译器支持5\. 优势与应用场景5.1 提高代码可读性5.2 减少潜在错误5.3 适用于多行字符串 6\. 其他相关特性7\. 总结 C 语言一直在不断进化&#xff0c;以满足现代软件开发的需求。C23 标…

【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法】

【通过Zadig给鼠标适配器安装驱动后,鼠标动不了,无法恢复的解决办法 问题产生缘由感谢这位大佬提供的解决办法解决办法 问题产生缘由 通过Zadig给鼠标适配器安装USB GAMING MOUSE这个驱动后,鼠标动不了,无法恢复(重启电脑,卸载鼠标驱动再重装也不可以), 不过还好,我用的是笔记…

GoogleCodeUtil.java

Google动态验证码实现 GoogleCodeUtil.java package zwf;import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom;/** https://mvnrepository.com/artifact/commons-codec/…

Maven 简介(图文)

Maven 简介 Maven 是一个Java 项目管理和构建的工具。可以定义项目结构、项目依赖&#xff0c;并使用统一的方式进行自动化构建&#xff0c;是Java 项目不可缺少的工具。 Maven 的作用 提供标准化的项目结构&#xff1a;以前不同的开发工具创建的项目结构是不一样的&#xf…

2025Github介绍与注册(有图片讲解,保姆级)

为什么要注册Github账号 利于团队协作&#xff0c;特别是打比赛的队友 版本控制强大&#xff0c;代码安全 开源项目多&#xff0c;方便个人模仿或抄袭 方便托管&#xff0c;形成自动化工具链 教育福利&#xff0c;教育参与者暂时免费 讲解完了优势&#xff0c;下面讲注册 Gith…

RUI电视桌面中文版:下载安装教程及桌面固件包获取全攻略

在智能电视的使用过程中&#xff0c;一款出色的桌面系统能极大提升用户体验&#xff0c;RUI电视桌面中文版就是这样一个不错的选择。下面为大家详细介绍RUI电视桌面中文版的下载安装教程以及桌面固件包的获取方法。 一、桌面固件包获取 首先是获取桌面固件包。可以通过RUI官方…