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

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

概述

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

一、什么是相机标定

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

关键参数

  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 的值越大,校正效果越强。随着它们的减小,校正效果减弱。

相关文章:

  • C++数据收发管道:构建高效的数据传输通道
  • 【天外之物】概念区分:磅(力)与磅(质量)
  • mysql8.0 创建全文索引及mysql 8.0.32创建全文索引报错 Duplicate entry null-null的解决方案
  • 从数据质量看起,数据治理在做什么?
  • Java集合框架深度解析:HashMap、HashSet、TreeMap、TreeSet与哈希表原理详解
  • 数据可视化笔记:柱状图
  • 查看matlab函数帮助文档的方法
  • 下拉框select标签类型
  • 《操作系统真象还原》第九章(2)——线程
  • 完整的 .NET 6 分布式定时任务实现(Hangfire + Redis 分布式锁)
  • 计算机视觉中,我们经常提到到训练pipeline是什么意思
  • leetcode 2364. 统计坏数对的数目 中等
  • RT-Thread开发文档合集
  • Python大小整数池及intern机制详解
  • 模块内聚:理解和优化模块设计的关键
  • Web3架构下的数据隐私与保护
  • 【unity实战】Animator启用root motion根运动动画,实现完美的动画动作匹配
  • 基于大模型的直肠息肉诊疗全流程风险预测与方案优化研究报告
  • Ethernet/IP转ProfiNet边缘计算网关在能源管理中的应用:跨系统数据聚合与智能分析
  • SQL SERVER里面也可以插入存储过程,操作TCP,WEBSOCKET吗?数据发生改变时用于通知客户端
  • 2025年上海车展后天开幕,所有进境展品已完成通关手续
  • 高架上2名儿童从轿车天窗探出身来,驾驶员被记3分罚200元
  • 美菲开始举行年度军演,外交部:菲公然站在地区国家的对立面
  • 申花迎来中超三连胜,这一次终于零封对手了
  • 经济日报:从三个变化看外贸破局之道
  • 十四届全国人大常委会第十五次会议将于4月27日至30日举行