计算机视觉(传统篇)

前言:这篇blog的最初写作目的是为了把cv初步的方法和其代码一同学习,但是后期发现特征提取部分的方法还是过于复杂的,一篇文章写完不太现实。所以决定这篇文章就按照大纲形式收集下cv各个模块的常用方法以及初步代码(不保真),以做后续查找使用。 PS:主要是机器学习部分的学习,没有涉及深度的内容,深度学习的处理之后会继续写。

图像和视频

图像的取样和量化

图像的取样与量化是数字图像处理的基础,用于将连续的现实世界图像转换为离散的数字形式。

取样(Sampling):

  • 定义:将连续的空间信号(如现实世界的图像)分解为离散像素点网络
  • 过程:通过在空间上以固定间隔(分辨率)采集图像的强度值,将连续图像分割为像素网格。例如,一个 1920x1080 的图像表示在水平和垂直方向上分别采样了 1920 和 1080 次。 #### 量化(Quantization):
  • 定义:将连续的像素强度值(通常是光强或颜色值)映射到离散的数值范围。
  • 过程:将像素的强度值(通常为浮点数)离散化为有限的整数级别。例如,8位灰度图像将强度值量化为 0 到 255 的 256 个级别。
  • 影响:量化级别越高,图像的灰度或颜色表现越细腻,但存储需求也增加。过低的量化级别会导致伪轮廓(False Contouring),即图像出现明显的颜色或灰度分层。
  • 数学表示:假设原始强度值范围为\([I_{\text{min}}, I_{\text{max}}]\),量化为 \(L\) 级,量化公式为:\[I_{\text{quantized}} = \text{round}\left(\frac{I - I_{\text{min}}}{I_{\text{max}} - I_{\text{min}}} \cdot (L-1)\right)\]
    1
    2
    3
    img = cv2.imread('image.jpg',cv2.IMREAD_GRAYSCALE)
    levels=16 # 减少灰度级别到16级
    quantized_img = np.floor_divide(img, 256 // levels) * (256 // levels) # np.floor_divide 取整 // 除法取整

滤波

滤波是图像处理中用于增强、平滑或提取特征的操作,通过对图像像素值进行加权运算来实现。

  • 定义:滤波通过一个卷积核(滤波器)对图像进行处理,改变像素值以实现平滑、锐化、边缘检测等效果。
  • 类型:
    • 低通滤波(Low-pass Filtering):平滑图像,减少噪声。比如说中值滤波、均值滤波、高斯滤波。
    • 高通滤波(High-pass Filtering):突出图像的高频部分,如边缘或细节,常用于边缘检测(如 Sobel 滤波器)。
    • 带通滤波:保留特定频率范围,常用于特征提取。
  • 实现:通过卷积操作(详见“卷积”部分)将滤波核应用于图像。
1
2
3
4
5
6
7
8
blur=cv2.blur(img,(3*3)) # 均值滤波
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
# cv2.Sobel(src, ddepth, dx, dy, ksize): 计算 Sobel 边缘。
# - src: 输入图像。
# - ddepth: 输出图像深度(如 cv2.CV_64F 表示 64 位浮点数以保留负值)。
# - dx, dy: x 和 y 方向的导数阶数(dx=1, dy=0 表示水平边缘)。
# - ksize: 核大小(奇数,如 3)。
# - 返回: 边缘强度图像。

直方图

直方图是统计图像像素强度分布的工具,用于分析图像的亮度或颜色特性。

  • 定义:直方图表示图像中每个强度级别(或颜色通道值)的像素数量。

    • 对于灰度图像,直方图通常表示 0 到 255 的灰度级别的像素分布。
    • 对于彩色图像,可以分别对 R、G、B 通道生成直方图。
  • 用途

    • 分析图像的亮度分布(例如,判断图像是否过暗或过亮)。
    • 作为特征用于图像检索或分类。
    • 指导直方图均衡化等增强算法。
  • 计算:统计每个强度级别的像素数量,绘制为柱状图(或曲线)。

    • 数学表示:对于灰度值 iii(0 到 255),直方图 h(i)h(i)h(i) 表示强度值为 iii 的像素数。
      1
      2
      # 计算直方图
      hist = cv2.calcHist([img], [0], None, [256], [0, 256])
      ### 上采样
  • 定义:增加图像的分辨率(像素数量),通常用于图像放大。

  • 方法

    • 插值法:如最近邻插值、双线性插值或双三次插值(见“插值”部分)。
    • 超分辨率:使用机器学习模型(如深度卷积网络)预测高分辨率细节。
  • 应用

    • 图像放大(例如,显示器适配)。
    • 增强图像细节(例如,超分辨率重建)。
  • 挑战:上采样可能引入模糊或伪影,需选择合适的插值或模型。

    1
    2
    3
    4
    5
    6
    7
    8
    # 上采样(放大 2 倍)
    upsampled = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)
    # cv2.resize(src, dsize, fx, fy, interpolation): 调整图像大小。
    # - src: 输入图像。
    # - dsize: 输出图像尺寸(若为 None,则由 fx, fy 决定)。
    # - fx, fy: x 和 y 方向的缩放因子(如 fx=2 表示宽度放大 2 倍)。
    # - interpolation: 插值方法,如 cv2.INTER_LINEAR(双线性插值)。
    # - 返回: 调整大小后的图像。
    ### 下采样

  • 定义:减少图像的分辨率(像素数量),通常用于图像缩小或减少计算量。

  • 方法

    • 直接抽样:每隔固定间隔取一个像素(可能导致混叠)。
    • 平滑后抽样:先应用低通滤波(如高斯模糊)去除高频分量,再抽样以避免混叠。
  • 应用

    • 图像压缩(减少存储需求)。
    • 构建图像金字塔(用于多尺度分析)。
  • 挑战:下采样会丢失细节,需平衡分辨率与信息保留。

    1
    2
    3
    4
    5
    6
    7
    8
    # 先高斯模糊以避免混叠
    blurred = cv2.GaussianBlur(img, (5, 5), 0)
    # 下采样(缩小 0.5 倍)
    downsampled = cv2.resize(blurred, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_LINEAR)
    # cv2.GaussianBlur(src, ksize, sigmaX): 应用高斯模糊。
    # - src: 输入图像。
    # - ksize: 核大小,元组 (width, height),必须为奇数(如 (5, 5))。
    # - sigmaX: x 方向高斯核标准差,0 表示自动计算。

卷积

卷积是图像处理和机器学习中的核心操作,用于特征提取和滤波。

  • 定义:通过一个小的滤波核(Kernel 或 Filter)在图像上滑动,计算核与图像区域的加权和,生成新的像素值。
  • 数学表示: 对于图像\(I(x, y)\) 和核 \(K(m, n)\),卷积结果为: \(O(x, y) = \sum_{m} \sum_{n} I(x+m, y+n) \cdot K(m, n)\) 其中 \(O(x, y)\) 是输出图像的像素值。
  • 应用
    • 图像平滑(均值滤波、高斯滤波)。
    • 边缘检测(Sobel、Prewitt 算子)。
    • 特征提取(卷积神经网络 CNN 的核心操作)。
  • 注意
    • 核大小通常为奇数(如 3x3、5x5),以确保中心像素对称。
    • 边界处理:常用填充(Padding)方式,如零填充或镜像填充。
1
2
3
4
5
6
7
8
9
10
11
# 定义 3x3 锐化核
kernel = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]], dtype=np.float32)
# 应用卷积
convolved = cv2.filter2D(img, -1, kernel)
# cv2.filter2D(src, ddepth, kernel): 应用自定义卷积核。
# - src: 输入图像。
# - ddepth: 输出图像深度,-1 表示与输入相同。
# - kernel: 卷积核(NumPy 数组)。
# - 返回: 卷积后的图像。

直方图均衡化算法

直方图均衡化是一种增强图像对比度的方法,通过重新分配像素强度值使直方图更均匀。

  • 原理
    • 计算图像的灰度直方图。
    • 计算累积分布函数(CDF,Cumulative Distribution Function): \[CDF(i) = \sum_{k=0}^{i} h(k)\] 其中 \(h(k)\) 是直方图值,\(i\)是灰度级别。
    • 归一化 CDF 并映射到目标范围(通常为 0 到 255): \[I_{\text{new}}(x, y) = \text{round}\left(\frac{CDF(I(x, y)) - CDF_{\text{min}}}{N - 1} \cdot (L-1)\right)\]其中 \(N\) 是像素总数,\(L\)是量化级别数(如 256)。
  • 步骤
    1. 计算直方图 \(h(i)\)
    2. 计算 CDF。
    3. 归一化并映射像素值。
  • 效果
    • 增强图像对比度,尤其对低对比度图像效果显著。
    • 可能放大噪声或导致不自然的对比度增强。
      1
      2
      # 直方图均衡化
      equalized = cv2.equalizeHist(img)

最近邻差值(Nearest Neighbor Interpolation)

  • 定义:一种简单的插值方法,用于上采样或下采样,选取距离目标点最近的像素值作为新像素值。
  • 特点
    • 计算简单,速度快。
    • 结果可能出现块状伪影(“马赛克”效应),不平滑。
  • 应用:适合对像素级精度要求高的场景(如像素艺术放大)。
    1
    2
    # 最近邻插值(放大 2 倍)
    nearest = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_NEAREST)
    ### 单/双线性差值(Bilinear Interpolation)
  • 单线性插值
    • 定义:在单一维度上使用线性插值,基于相邻两个点的距离加权平均。
    • 公式:对于点 \(x\)\(x_0\)\(x_1\) 之间,值 \(f(x)\) 为: \[ f(x) = f(x_0) + \frac{x - x_0}{x_1 - x_0} \cdot (f(x_1) - f(x_0)) \]
  • 双线性插值
    • 定义:在二维空间上扩展单线性插值,使用四个邻近像素的加权平均。
    • 公式:对目标点 \((x, y)\),基于四个角点 \((x_0, y_0), (x_0, y_1), (x_1, y_0), (x_1, y_1)\),计算: \[ f(x, y) = (1-w_x)(1-w_y)f(x_0, y_0) + w_x(1-w_y)f(x_1, y_0) + (1-w_x)w_y f(x_0, y_1) + w_x w_y f(x_1, y_1) \] 其中 \(w_x = \frac{x - x_0}{x_1 - x_0}\)\(w_y = \frac{y - y_0}{y_1 - y_0}\)
  • 特点
    • 比最近邻插值更平滑,结果更自然。
    • 计算复杂度适中,广泛用于图像缩放。
  • 应用
    • 图像放大/缩小(如 OpenCV 的 cv2.resize)。
    • 纹理映射(如 3D 渲染)。
      1
      2
      # 双线性插值(放大 2 倍)
      bilinear = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)

特征选择与特征提取

特征选择与特征提取概述

特征选择 (Feature Selection)

  • 定义: 从原始特征集合中选择一个子集,保留最相关、最具区分度的特征,去除冗余或无关特征。
  • 目标: 降低维度、减少计算量、避免过拟合、提高模型可解释性。
  • 方法分类:
    • 过滤法 (Filter Methods): 基于统计指标(如方差、相关系数)选择特征,独立于模型。
    • 包装法 (Wrapper Methods): 通过模型性能(如交叉验证)评估特征子集。
    • 嵌入法 (Embedded Methods): 在模型训练过程中进行特征选择(如 LASSO 回归)。
  • 优点: 保留原始特征的物理意义,计算效率较高。
  • 缺点: 可能忽略特征之间的复杂关系。

特征提取 (Feature Extraction)

  • 定义: 将原始高维数据通过变换(如线性或非线性映射)转换为新的低维特征空间,生成新的特征表示。
  • 目标: 提取更有代表性的特征,捕捉数据中的关键模式或结构。
  • 方法分类:
    • 线性方法: 如主成分分析 (PCA)、线性判别分析 (LDA)。
    • 非线性方法: 如 t-SNE、核 PCA、深度学习特征提取。
  • 优点: 能捕捉复杂模式,适用于高维数据。
  • 缺点: 新特征可能丧失物理意义,计算复杂度较高。

差异

  • 输入输出: 特征选择从原始特征中挑选子集,输出是原始特征的子集;特征提取生成新的特征表示。
  • 计算复杂度: 特征选择通常比特征提取简单。
  • 应用场景: 特征选择适合解释性要求高的场景;特征提取适合高维数据或需要捕捉复杂模式的场景。

特征选择与特征提取原理

特征选择原理

特征选择的目的是从原始特征集合中挑选一个子集,保留最具区分度、最相关的特征,减少冗余和噪声。以下是其核心原理和数学基础:

  • 目标: 最大化模型性能(如分类精度),最小化特征数量以降低计算复杂度和过拟合风险。

  • 数学表示: - 给定特征集 \(X = \{x_1, x_2, \dots, x_n\}\)(每个 \(x_i\) 是特征向量),目标是选择子集\(S\subseteq \{1, 2, \dots, n\}\),使得目标函数 \(J(S)\)(如分类准确率)最大化: \[ S^* = \arg\max_{S} J(S), \quad \text{s.t.} \ |S| \leq k \] 其中 \(k\) 是期望的特征数量。

  • 方法分类: - 过滤法 (Filter Methods): 使用统计指标(如方差、相关系数、互信息)评估特征的重要性,独立于模型。例如,方差阈值方法移除方差低于某一值的特征: \[ \text{Var}(x_i) = \frac{1}{m} \sum_{j=1}^m (x_{i,j} - \mu_i)^2 \] 其中 \(\mu_i\)是特征 \(x_i\)的均值,\(m\)是样本数。 - 包装法 (Wrapper Methods): 通过模型性能(如交叉验证得分)迭代评估特征子集。例如,递归特征消除 (RFE) 通过模型权重或重要性排序特征,逐步移除不重要特征。 - 嵌入法 (Embedded Methods): 在模型训练过程中进行特征选择。例如,L1 正则化 (LASSO) 通过惩罚项使不重要特征的权重趋于零: \[ \min_w \left( \frac{1}{m} \sum_{i=1}^m (y_i - w^T x_i)^2 + \lambda \|w\|_1 \right) \] 其中 \(\lambda\)控制正则化强度。

特征提取原理

特征提取通过变换将原始高维数据映射到低维空间,生成新的特征表示,捕捉数据的关键模式。

  • 目标: 提取更具代表性的特征,降低维度,同时保留或增强数据区分能力。
  • 数学表示:
    • 给定数据矩阵 \(X \in \mathbb{R}^{m \times n}\)\(m\) 个样本,\(n\) 个特征),特征提取寻找变换函数 \(f: \mathbb{R}^n \to \mathbb{R}^k\)\(k < n\)),使得新特征 \(Z = f(X)\) 更适合后续任务。
    • 线性方法(如 PCA)通过矩阵分解或投影实现: \[ Z = X \cdot W \] 其中 \(W \in \mathbb{R}^{n \times k}\) 是变换矩阵。
    • 非线性方法(如 t-SNE)通过优化复杂目标函数(如 KL 散度)实现降维。
  • 方法分类:
    • 线性方法: 主成分分析 (PCA)、线性判别分析 (LDA)。
    • 非线性方法: t-SNE、核 PCA、深度学习特征提取。
    • 局部特征提取: 如 SIFT、ORB、HOG,专注于图像局部不变特征。

扩展的特征选择方法及代码

以下介绍三种特征选择方法:方差阈值(过滤法)、递归特征消除 (RFE, 包装法)、L1 正则化 (嵌入法)。

方差阈值(过滤法)

  • 原理: 移除方差低于阈值的特征,因为低方差特征变化小,区分能力弱。
  • 数学: 计算每个特征的方差,选择方差大于阈值 \(\theta\) 的特征: \[ \text{Var}(x_i) \geq \theta \]
1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
from sklearn.feature_selection import VarianceThreshold

# 读取图像并转换为灰度
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 将图像展平为一维特征向量(每像素作为一个特征)
features = img.reshape(-1, img.shape[0] * img.shape[1])

# 应用方差阈值选择
selector = VarianceThreshold(threshold=10.0)
selected_features = selector.fit_transform(features)

说明: 代码将图像像素作为特征,使用 VarianceThreshold 移除方差低于 10 的特征。


递归特征消除 (RFE, 包装法)

  • 原理: 使用模型(如 SVM)评估特征重要性,递归移除最不重要的特征,直到达到指定数量。
  • 数学: 对于模型权重 \(w\),特征重要性基于 \(|w_i|\),移除最小值后重新训练。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2
import numpy as np
from sklearn.feature_selection import RFE
from sklearn.svm import SVC

# 模拟数据:读取图像并提取简单特征
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
features = np.array([
[np.mean(img), np.std(img), np.max(img)], # 特征:均值、标准差、最大值
[np.mean(img[::2, ::2]), np.std(img[::2, ::2]), np.max(img[::2, ::2])], # 隔1行下采样
[np.mean(img[::4, ::4]), np.std(img[::4, ::4]), np.max(img[::4, ::4])], # 隔3行下采样
])
labels = np.array([0, 1, 0]) # 模拟标签

# 使用 SVM 和 RFE 选择特征
estimator = SVC(kernel="linear")
selector = RFE(estimator, n_features_to_select=2)
selector = selector.fit(features, labels)

说明: 代码提取图像均值、标准差、最大值作为特征,使用 RFE 选择 2 个最重要特征。需更多样本和标签以实际应用。


L1 正则化(嵌入法)

  • 原理: 通过 L1 正则化(如 LASSO)使不重要特征的权重趋于零,自动选择特征。
  • 数学: 优化目标: \[ \min_w \left( \frac{1}{m} \sum_{i=1}^m (y_i - w^T x_i)^2 + \lambda \|w\|_1 \right) \]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy as np
from sklearn.linear_model import Lasso

# 读取图像并提取简单特征
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
features = np.array([
[np.mean(img), np.std(img), np.max(img)],
[np.mean(img[::2, ::2]), np.std(img[::2, ::2]), np.max(img[::2, ::2])],
[np.mean(img[::4, ::4]), np.std(img[::4, ::4]), np.max(img[::4, ::4])],
])
labels = np.array([0, 1, 0]) # 模拟标签

# 使用 LASSO 进行特征选择
lasso = Lasso(alpha=0.1) # alpha 控制正则化强度
lasso.fit(features, labels)

说明: 代码使用 LASSO 回归选择特征,非零权重的特征被选中。

扩展的特征提取方法及代码

以下介绍五种特征提取方法:主成分分析 (PCA)、线性判别分析 (LDA)、SIFT、ORB 和 HOG。

主成分分析 (PCA)

  • 原理: 通过协方差矩阵的特征分解,将数据投影到方差最大的方向,降低维度,无监督。
  • 数学:
    1. 标准化数据:\(X_{\text{norm}} = (X - \mu)/\sigma\)
    2. 计算协方差矩阵:\(C = \frac{1}{m} X_{\text{norm}}^T X_{\text{norm}}\)
    3. 特征分解:\(C = W \Lambda W^T\),选择前 \(k\) 个特征向量 \(W_k\)
    4. 投影:\(Z = X_{\text{norm}} \cdot W_k\).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import cv2
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 读取图像并转换为灰度
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
raise ValueError("Image not found or invalid path")

# 提取 8x8 图像块作为样本
patch_size = 8
patches = []
for i in range(0, img.shape[0] - patch_size + 1, patch_size):
for j in range(0, img.shape[1] - patch_size + 1, patch_size):
patch = img[i:i+patch_size, j:j+patch_size].flatten()
patches.append(patch)
features = np.array(patches) # 形状: (n_patches, patch_size * patch_size)

# 动态选择 n_components
n_components = min(3, min(features.shape)) # 最多 3 维
pca = PCA(n_components=n_components)
reduced_features = pca.fit_transform(features)

# 重构图像(仅用于展示)
reconstructed = pca.inverse_transform(reduced_features)
reconstructed_patches = reconstructed.reshape(-1, patch_size, patch_size)
# 将 patches 重新拼接为图像(近似)
h, w = img.shape
n_patches_h = (h - patch_size + 1) // patch_size
n_patches_w = (w - patch_size + 1) // patch_size
reconstructed_img = np.zeros((h, w))
count = 0
for i in range(n_patches_h):
for j in range(n_patches_w):
reconstructed_img[i*patch_size:(i+1)*patch_size, j*patch_size:(j+1)*patch_size] = reconstructed_patches[count]
count += 1

# 保存结果
cv2.imwrite('pca_reconstructed.jpg', reconstructed_img)

# 显示结果
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(122), plt.imshow(reconstructed_img, cmap='gray'), plt.title(f'PCA Reconstructed (n_components={n_components})')
plt.show()

# 打印方差解释率
print("Features shape:", features.shape)
print("Explained variance ratio:", pca.explained_variance_ratio_)
print("Cumulative variance ratio:", pca.explained_variance_ratio_.cumsum())

说明: 代码将图像像素降维到 2 维并重构,展示 PCA 效果。


线性判别分析 (LDA)

  • 原理: 最大化类间方差、最小化类内方差,适用于监督学习,一般需要多张图片,因为单一张图片标签难以提取。
  • 数学:
    1. 计算类内散布矩阵 \(S_W\) 和类间散布矩阵 \(S_B\)\[ S_W = \sum_{c} \sum_{i \in c} (x_i - \mu_c)(x_i - \mu_c)^T, \quad S_B = \sum_{c} n_c (\mu_c - \mu)(\mu_c - \mu)^T \]
    2. 求解广义特征值问题:\(S_W^{-1} S_B w = \lambda w\),取前 \(k\) 个特征向量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import cv2
import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
import matplotlib.pyplot as plt

# 读取两张图像并转换为灰度
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)
if img1 is None or img2 is None:
raise ValueError("Image not found or invalid path")

# 提取 8x8 图像块作为样本
patch_size = 8
patches = []
labels = [] # 监督类别
for img, label in [(img1, 0), (img2, 1)]: # 图像1: 类别0, 图像2: 类别1
for i in range(0, img.shape[0] - patch_size + 1, patch_size):
for j in range(0, img.shape[1] - patch_size + 1, patch_size):
patch = img[i:i+patch_size, j:j+patch_size].flatten()
patches.append(patch)
labels.append(label)
features = np.array(patches) # 形状: (n_patches, 64)
labels = np.array(labels) # 形状: (n_patches,)

# 动态选择 n_components(LDA 最多 C-1 维,C 为类别数)
n_classes = len(np.unique(labels)) # 类别数
n_components = min(1, n_classes - 1, min(features.shape)) # 2 类时最多 1 维
lda = LinearDiscriminantAnalysis(n_components=n_components)
reduced_features = lda.fit_transform(features, labels)

# 打印结果
print("Features shape:", features.shape)
print("Reduced features shape:", reduced_features.shape)
print("Explained variance ratio:", lda.explained_variance_ratio_)

说明: 代码模拟两张图像(需 image1.jpgimage2.jpg),使用 LDA 降维到 1 维。实际应用需更多样本。

SIFT 特征提取

基本概念

  • 目标:检测图像中的关键点(角点等),并为每个关键点生成描述子,用于图像匹配、目标识别等任务。
  • 特性
    • 尺度不变:通过尺度空间检测,适应不同大小的目标。
    • 旋转不变:通过分配主方向,描述子相对于方向归一化。
    • 光照鲁棒:基于梯度信息,对亮度变化不敏感。
  • 应用:图像匹配、物体识别、3D重建。

SIFT算法步骤

  1. 尺度空间极值检测
    • 构建高斯金字塔(不同尺度和模糊程度的图像)。
    • 计算相邻尺度的高斯差分(DoG,Difference of Gaussian): \[D(x, y, \sigma) = L(x, y, k\sigma) - L(x, y, \sigma) \]其中\(L(x, y, \sigma) = G(x, y, \sigma) * I(x, y)\)是高斯模糊图像。其实本质是求二次偏导,其函数形式与DoG十分类似,故使用DoG函数。
    • 在DoG中寻找局部极值点(3D空间:x, y, 尺度)。
  2. 关键点定位
    • 过滤低对比度或边缘点,确保关键点稳定。
    • 使用泰勒展开精确定位极值点。
  3. 方向分配
    • 计算关键点邻域的梯度方向直方图,分配主方向(峰值方向)。
    • 使描述子对旋转不变。
  4. 描述子生成
    • 在关键点周围取16x16邻域,分成4x4子块。
    • 每个子块计算8个方向的梯度直方图,生成128维描述子(4x4x8=128)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import cv2
import matplotlib.pyplot as plt

# 读取图像并转换为灰度
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 初始化 SIFT 检测器
sift = cv2.SIFT_create()

# 检测关键点和计算描述子
keypoints, descriptors = sift.detectAndCompute(img, None)

# 绘制关键点
img_with_keypoints = cv2.drawKeypoints(img, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 保存结果
cv2.imwrite('sift_keypoints.jpg', img_with_keypoints)

# 显示结果
plt.imshow(img_with_keypoints, cmap='gray')
plt.title('SIFT Keypoints')
plt.show()

# 打印关键点数量和描述子形状
print(f"Number of keypoints: {len(keypoints)}")
print(f"Descriptor shape: {descriptors.shape}")
# cv2.SIFT_create(): 创建 SIFT 检测器。
# - 返回: SIFT 对象。
# cv2.SIFT.detectAndCompute(image, mask): 检测关键点并计算描述子。
# - image: 输入灰度图像。
# - mask: 可选掩码,None 表示使用整个图像。
# - 返回: 关键点列表 (keypoints) 和描述子数组 (descriptors)。

说明: 代码提取 SIFT 关键点和描述子,需 opencv-contrib-python


ORB 特征提取

  • 原理: 方向快速和旋转 BRIEF (ORB) 是一种高效的局部特征检测算法,结合 FAST 关键点检测和 BRIEF 描述子,速度快且对旋转鲁棒。但是对尺度的检测并不十分敏感。算法主要是求速度。
  • 步骤:
    1. FAST 检测关键点。
    2. 计算关键点方向。
    3. 生成 BRIEF 描述子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cv2
import matplotlib.pyplot as plt

# 读取图像并转换为灰度
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 初始化 ORB 检测器
orb = cv2.ORB_create()

# 检测关键点和计算描述子
keypoints, descriptors = orb.detectAndCompute(img, None)

# 绘制关键点
img_with_keypoints = cv2.drawKeypoints(img, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# 保存结果
cv2.imwrite('orb_keypoints.jpg', img_with_keypoints)

# 显示结果
plt.imshow(img_with_keypoints, cmap='gray')
plt.title('ORB Keypoints')
plt.show()

# 打印关键点数量和描述子形状
print(f"Number of keypoints: {len(keypoints)}")
print(f"Descriptor shape: {descriptors.shape}")

说明: 代码使用 ORB 检测关键点,速度比 SIFT 快,适合实时应用。


HOG 特征提取

  • 原理: 方向梯度直方图 (HOG) 通过计算图像局部区域的梯度方向直方图,生成特征描述子,常用于行人检测。
  • 步骤:
    1. 计算图像梯度(x 和 y 方向)。
    2. 将图像分成小单元 (cell,一般8×8),计算每个单元的梯度方向直方图。
    3. 将单元组成块 (block,一般2×2cell),归一化直方图。
    4. 连接所有块的特征向量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2
import numpy as np
from skimage.feature import hog
import matplotlib.pyplot as plt

# 读取图像并转换为灰度
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 调整图像大小以适合 HOG
img = cv2.resize(img, (128, 64))

# 计算 HOG 特征
hog_features, hog_image = hog(img, pixels_per_cell=(8, 8), cells_per_block=(2, 2), visualize=True)

# 保存 HOG 可视化图像
cv2.imwrite('hog_image.jpg', hog_image * 255)

# 显示结果
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original')
plt.subplot(122), plt.imshow(hog_image, cmap='gray'), plt.title('HOG Visualization')
plt.show()

# 打印 HOG 特征长度
print(f"HOG feature length: {len(hog_features)}")

说明: 代码使用 skimage.feature.hog 计算 HOG 特征,需安装 scikit-image (pip install scikit-image)。


总结

  • 特征选择:
    • 方差阈值: 简单高效,适合高维数据筛选。
    • RFE: 结合模型性能,精准但计算成本高。
    • L1 正则化: 自动选择特征,适合回归任务。
  • 特征提取:
    • PCA: 线性降维,适合图像压缩。
    • LDA: 监督降维,适合分类任务。
    • SIFT: 尺度不变特征,适合图像匹配。
    • ORB: 高效局部特征,适合实时应用。
    • HOG: 梯度方向特征,适合行人检测。
    • 除此之外还有SURF(基于SIFT的快速算法)、LBP(人脸识别)等 [[../TrDentityCo.png]]
  • OpenCV 应用:
    • 特征选择常结合 sklearn,特征提取可直接使用 OpenCV 的 SIFT、ORB 或外部库的 HOG。
    • 这些方法在图像分类、目标检测、图像检索中广泛应用。

边缘提取

边缘提取是计算机视觉的基础任务,用于检测图像中亮度或颜色发生显著变化的区域,通常对应于物体的边界。本文记录以下常用方法CannyRobertsSobelPrewittHessian特征Haar特征LaplacianScharr

1. Canny 边缘检测

原理

Canny 边缘检测是一种多阶段算法,旨在检测强边缘并抑制噪声。它包括以下步骤: 1. 噪声抑制:使用高斯模糊平滑图像,减少噪声影响。 2. 梯度计算:使用 Sobel 算子计算图像的强度梯度和方向。 3. 非极大值抑制:在梯度方向上,仅保留局部最大值,抑制非边缘点。 4. 双阈值检测:使用高低阈值将边缘分为强边缘、弱边缘和非边缘。 5. 边缘跟踪:通过连通性分析,将弱边缘与强边缘连接,剔除孤立点。

优缺点

  • 优点
    • 检测精度高,边缘定位准确。
    • 能有效抑制噪声,减少误检。
    • 提供连通性强的边缘。
  • 缺点
    • 计算复杂度较高,实时性较差。
    • 对阈值选择敏感,需手动调整。
    • 对复杂背景的边缘检测可能不理想。

适用条件

  • 适用于需要高质量边缘检测的场景,如图像分割、物体检测。
  • 适合噪声较少的图像,或经过预处理的图像。

Python + OpenCV 代码

1
2
# Canny 边缘检测
edges = cv2.Canny(img, threshold1=100, threshold2=200)

2. Roberts 边缘检测

原理

Roberts 算子是一种简单的边缘检测方法,使用 2x2 的卷积核计算图像的梯度。它通过以下两个核检测对角方向的边缘: - \(G_x = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}\) - \(G_y = \begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix}\)

梯度幅值计算为:\(G = \sqrt{G_x^2 + G_y^2}\)

优缺点

  • 优点
    • 计算简单,速度快。
    • 对对角边缘敏感。
  • 缺点
    • 对噪声非常敏感,容易产生伪边缘。
    • 仅检测对角方向边缘,对水平和垂直边缘效果较差。
    • 边缘定位不精确。

适用条件

  • 适用于噪声极低的简单图像。
  • 适合对计算速度要求高的实时应用。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
# 定义 Roberts 算子
roberts_x = np.array([[1, 0], [0, -1]], dtype=np.float32)
roberts_y = np.array([[0, 1], [-1, 0]], dtype=np.float32)

# 卷积操作
edge_x = cv2.filter2D(img, -1, roberts_x)
edge_y = cv2.filter2D(img, -1, roberts_y)
edges = np.sqrt(np.square(edge_x) + np.square(edge_y)).astype(np.uint8)

3. Sobel 边缘检测

原理

Sobel 算子通过 3x3 的卷积核计算图像在水平和垂直方向的梯度,强调边缘区域。它使用以下核: - \(G_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix}\) - \(G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}\)

梯度幅值为:\(G = \sqrt{G_x^2 + G_y^2}\),方向为:\(\theta = \arctan(G_y / G_x)\)

优缺点

  • 优点
    • 对水平和垂直边缘检测效果较好。
    • 计算简单,适合实时处理。
    • 比 Roberts 更抗噪。
  • 缺点
    • 对对角边缘检测较弱。
    • 边缘可能较粗糙,定位不够精确。
    • 对高噪声图像效果较差。

适用条件

  • 适用于检测水平和垂直边缘的场景,如道路线检测。
  • 适合噪声适中的图像。

Python + OpenCV 代码

1
2
3
4
# Sobel 边缘检测
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
edges = np.sqrt(np.square(sobel_x) + np.square(sobel_y)).astype(np.uint8) # uint8是灰度格式,这里是转化成灰度图

4. Prewitt 边缘检测

原理

Prewitt 算子与 Sobel 类似,也是 3x3 卷积核,用于计算水平和垂直方向的梯度,但权重更均匀: - \(G_x = \begin{bmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{bmatrix}\) - \(G_y = \begin{bmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{bmatrix}\)

梯度幅值和方向计算同 Sobel。

优缺点

  • 优点
    • 计算简单,速度快。
    • 对水平和垂直边缘有较好响应。
  • 缺点
    • 比 Sobel 更易受噪声影响。
    • 边缘定位精度较低。
    • 对复杂纹理图像效果较差。

适用条件

  • 适用于简单图像的边缘检测。
  • 适合对噪声要求不高的场景。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
# 定义 Prewitt 算子
prewitt_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32)
prewitt_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32)

# 卷积操作
edge_x = cv2.filter2D(img, -1, prewitt_x)
edge_y = cv2.filter2D(img, -1, prewitt_y)
edges = np.sqrt(np.square(edge_x) + np.square(edge_y)).astype(np.uint8)

5. Hessian 特征

原理

Hessian 特征基于图像的二阶导数,通过 Hessian 矩阵检测图像中的显著点(如角点或边缘)。Hessian 矩阵定义为: \[ H = \begin{bmatrix} I_{xx} & I_{xy} \\ I_{xy} & I_{yy} \end{bmatrix} \] 其中 \(I_{xx}, I_{xy}, I_{yy}\) 是图像的二阶偏导数。特征点通过矩阵的行列式或特征值分析确定。

在边缘检测中,Hessian 常用于检测细长结构(如血管),通过分析行列式 \(\det(H)\) 或特征值。

优缺点

  • 优点
    • 对细长边缘或线状结构检测效果好。
    • 能捕捉图像的局部几何特征。
  • 缺点
    • 计算复杂度高,需计算二阶导数。
    • 对噪声敏感,需预处理。
    • 不适合检测宽边缘或复杂纹理。

适用条件

  • 适用于医疗图像(如血管分割)或线状结构检测。
  • 适合噪声较低或经过平滑处理的图像。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import gaussian_laplace

# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE).astype(float)

# 计算 Hessian 矩阵的行列式(近似)
sigma = 2.0
img_xx = cv2.Sobel(cv2.Sobel(img, cv2.CV_64F, 2, 0, ksize=3), cv2.CV_64F, 2, 0, ksize=3)
img_yy = cv2.Sobel(cv2.Sobel(img, cv2.CV_64F, 0, 2, ksize=3), cv2.CV_64F, 0, 2, ksize=3)
img_xy = cv2.Sobel(cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3), cv2.CV_64F, 0, 1, ksize=3)
det_H = img_xx * img_yy - img_xy ** 2

# 归一化显示
det_H = cv2.normalize(det_H, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# 显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(det_H, cmap='gray'), plt.title('Hessian Determinant')
plt.show()

6. Haar 特征

原理

Haar 特征主要用于目标检测(如人脸检测),通过计算矩形区域的像素强度差值提取特征。Haar 特征包括: - 边缘特征:检测水平或垂直边缘。 - 线特征:检测线状结构。 - 中心-环绕特征:检测中心与周围的差异。

特征值计算为:白色区域像素和减去黑色区域像素和。Haar 特征通常结合积分图像加速计算。

优缺点

  • 优点
    • 计算速度快,适合实时应用。
    • 对简单边缘和纹理特征有效。
  • 缺点
    • 对复杂边缘或非矩形特征检测效果差。
    • 对光照变化和旋转敏感。
    • 需大量训练数据(如用于人脸检测)。

适用条件

  • 适用于目标检测(如人脸、车辆)。
  • 适合光照均匀、目标形状规则的场景。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 定义简单的 Haar 特征(水平边缘)
haar_kernel = np.array([[1, 1, -1, -1], [1, 1, -1, -1]], dtype=np.float32)

# 卷积操作
haar_edges = cv2.filter2D(img, -1, haar_kernel)

# 归一化显示
haar_edges = cv2.normalize(haar_edges, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# 显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(haar_edges, cmap='gray'), plt.title('Haar Edges')
plt.show()

7. Laplacian 边缘检测

原理

Laplacian 算子基于图像的二阶导数,检测亮度变化的区域。它使用以下核: \[ \nabla^2 I = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} \]

Laplacian 检测零交叉点作为边缘。

优缺点

  • 优点
    • 对所有方向的边缘敏感。
    • 计算简单。
  • 缺点
    • 极易受噪声影响,需预平滑。
    • 边缘可能不连续,需后处理。

适用条件

  • 适用于噪声低的图像。
  • 适合与高斯平滑结合使用。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# 高斯平滑
img_blur = cv2.GaussianBlur(img, (5, 5), 0)

# Laplacian 边缘检测
edges = cv2.Laplacian(img_blur, cv2.CV_64F)
edges = cv2.normalize(edges, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# 显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(edges, cmap='gray'), plt.title('Laplacian Edges')
plt.show()

8. Scharr 边缘检测

原理

Scharr 算子是 Sobel 的改进版,使用更精确的权重来计算梯度: - \(G_x = \begin{bmatrix} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{bmatrix}\) - \(G_y = \begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ 3 & 10 & 3 \end{bmatrix}\)

优缺点

  • 优点
    • 比 Sobel 更精确,边缘定位更好。
    • 计算速度与 Sobel 相近。
  • 缺点
    • 对噪声仍较敏感。
    • 对复杂纹理效果有限。

适用条件

  • 适用于需要高精度边缘检测的场景。
  • 适合噪声适中的图像。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)

# Scharr 边缘检测
scharr_x = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(img, cv2.CV_64F, 0, 1)
edges = np.sqrt(np.square(scharr_x) + np.square(scharr_y)).astype(np.uint8)

# 显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(edges, cmap='gray'), plt.title('Scharr Edges')
plt.show()

相机模型

相机模型描述了三维世界如何通过相机投影到二维图像平面。以下是对你列出的内容(小孔成像模型、相机模型、镜头畸变、透视变换)以及补充内容(鱼眼模型、立体视觉)的详细讲解。

1. 小孔成像模型

原理

小孔成像模型是相机成像的理想化模型,假设光线通过一个无穷小的孔(针孔)投影到成像平面,无透镜畸变。数学表达为: \[ \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \frac{f}{Z} \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} \] 其中: - \((X, Y, Z)\):世界坐标。 - \((u, v)\):图像坐标。 - \(f\):焦距。 - \(Z\):物体到针孔的深度。

优缺点

  • 优点
    • 模型简单,易于理解和计算。
    • 无畸变,适合理论分析。
  • 缺点
    • 针孔会导致光量不足,实际相机需透镜。
    • 不考虑畸变,无法描述真实相机。

适用条件

  • 适用于教学和简单几何分析。
  • 不适合需要高精度建模的实际应用。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 模拟小孔成像
def pinhole_projection(points_3d, focal_length):
points_2d = []
for point in points_3d:
X, Y, Z = point
u = (focal_length * X) / Z
v = (focal_length * Y) / Z
points_2d.append([u, v])
return np.array(points_2d)

# 定义 3D 点
points_3d = np.array([[1, 1, 5], [2, 2, 5], [3, 1, 5], [1, 3, 5]])
focal_length = 500

# 投影到 2D
points_2d = pinhole_projection(points_3d, focal_length)

2. 相机模型(针孔相机模型+内参外参)

原理

相机模型是对小孔成像的扩展,考虑内参(焦距、 principal point)和外参(旋转、平移)。投影公式为: \[ \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = K [R | t] \begin{bmatrix} X \\ Y \\ Z \\ 1 \end{bmatrix} \] 其中: - \(K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}\):内参矩阵,\(f_x, f_y\) 为焦距,\(c_x, c_y\) 为 principal point。 - \([R | t]\):外参矩阵,\(R\) 为旋转矩阵,\(t\) 为平移向量。

优缺点

  • 优点
    • 能精确描述相机成像过程。
    • 支持 3D 重建和姿态估计。
  • 缺点
    • 不考虑镜头畸变,需额外校正。
    • 标定过程复杂。

适用条件

  • 适用于 3D 重建、SLAM、增强现实。
  • 适合需要精确几何关系的场景。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 定义内参矩阵
focal_length = 500
cx, cy = 320, 240
K = np.array([[focal_length, 0, cx], [0, focal_length, cy], [0, 0, 1]])

# 定义外参(旋转矩阵和平移向量)
R = np.eye(3)
t = np.array([[0], [0], [5]])

# 定义 3D 点
points_3d = np.array([[1, 1, 0], [2, 2, 0], [3, 1, 0], [1, 3, 0]], dtype=np.float32).T
points_3d_h = np.vstack((points_3d, np.ones((1, points_3d.shape[1]))))

# 投影到 2D
Rt = np.hstack((R, t))
points_2d_h = K @ Rt @ points_3d_h
points_2d = (points_2d_h[:2] / points_2d_h[2]).T

3. 镜头畸变

原理

镜头畸变是由于透镜非理想性导致的图像失真,主要包括: - 径向畸变:靠近图像中心的点移向边缘(桶形畸变)或反之(枕形畸变)。模型为: \[ x_d = x (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \] \[ y_d = y (1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \] 其中 \(r = \sqrt{x^2 + y^2}\)\(k_1, k_2, k_3\) 为径向畸变系数。 - 切向畸变:由于透镜与传感器不完全平行,模型为: \[ x_d = x + [2 p_1 x y + p_2 (r^2 + 2 x^2)] \] \[ y_d = y + [p_1 (r^2 + 2 y^2) + 2 p_2 x y] \]

优缺点

  • 优点
    • 校正畸变后可获得更精确的几何关系。
    • 适用于广角镜头或鱼眼镜头。
  • 缺点
    • 标定过程复杂,需棋盘格或其他标定物。
    • 校正可能导致图像部分区域丢失。

适用条件

  • 适用于广角镜头或高精度成像系统。
  • 适合需要校正畸变的场景,如 3D 重建、SLAM。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
# 定义内参和畸变系数(需通过标定获得)
K = np.array([[500, 0, 320], [0, 500, 240], [0, 0, 1]])
dist_coeffs = np.array([0.1, -0.01, 0.0, 0.0, 0.0]) # k1, k2, p1, p2, k3

# 校正畸变
h, w = img.shape[:2]
new_K, roi = cv2.getOptimalNewCameraMatrix(K, dist_coeffs, (w, h), 1, (w, h))
undistorted_img = cv2.undistort(img, K, dist_coeffs, None, new_K)

4. 透视变换

原理

透视变换将图像从一个视角变换到另一个视角,基于单应性矩阵(Homography)。变换公式为: \[ \begin{bmatrix} u' \\ v' \\ 1 \end{bmatrix} = H \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} \] 其中 \(H\) 是一个 3x3 矩阵,通过至少 4 对对应点计算。

优缺点

  • 优点
    • 能实现图像的视角变换,适用于校正倾斜或拼接。
    • 计算简单,效果直观。
  • 缺点
    • 要求平面场景,非平面场景会失效。
    • 对对应点精度要求高。

适用条件

  • 适用于图像拼接、文档校正、增强现实。
  • 适合平面对象的视角变换。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('image.jpg')

# 定义原始点和目标点
src_points = np.float32([[0, 0], [img.shape[1]-1, 0], [0, img.shape[0]-1], [img.shape[1]-1, img.shape[0]-1]])
dst_points = np.float32([[0, 0], [500, 0], [100, 500], [400, 500]])

# 计算单应性矩阵
H, _ = cv2.findHomography(src_points, dst_points)

# 透视变换
warped_img = cv2.warpPerspective(img, H, (500, 500))

# 显示结果
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Original Image')
plt.subplot(122), plt.imshow(cv2.cvtColor(warped_img, cv2.COLOR_BGR2RGB)), plt.title('Warped Image')
plt.show()

5. 鱼眼模型

原理

鱼眼镜头具有超广角,导致严重畸变。鱼眼模型通常使用等距投影或其他非线性投影,校正公式为: \[ r_d = f \theta \] 其中 \(\theta = \arctan(r_u / f)\)\(r_d\)\(r_u\) 分别为畸变和未畸变半径。

优缺点

  • 优点
    • 能捕获超广角视野,适合全景成像。
    • 校正后可用于 3D 重建。
  • 缺点
    • 畸变严重,校正复杂。
    • 边缘分辨率较低。

适用条件

  • 适用于全景相机或监控系统。
  • 适合需要超广角的场景。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
# 读取鱼眼图像
img = cv2.imread('fisheye_image.jpg')

# 定义内参和畸变系数
K = np.array([[500, 0, 320], [0, 500, 240], [0, 0, 1]])
dist_coeffs = np.array([0.2, 0.02, 0.0, 0.0]) # 鱼眼畸变系数

# 校正鱼眼畸变
h, w = img.shape[:2]
mapx, mapy = cv2.initUndistortRectifyMap(K, dist_coeffs, None, K, (w, h), cv2.CV_32FC1)
undistorted_img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

6. 立体视觉

原理

立体视觉通过两台相机的视差计算场景深度。基本公式为: \[ Z = \frac{f T}{d} \] 其中: - \(Z\):深度。 - \(f\):焦距。 - \(T\):两相机基线距离。 - \(d\):视差(左图和右图对应点的水平偏移)。

优缺点

  • 优点
    • 能直接计算 3D 深度信息。
    • 适用于机器人导航、3D 重建。
  • 缺点
    • 对相机标定和匹配精度要求高。
    • 计算复杂度高,实时性较差。

适用条件

  • 适用于需要深度信息的场景,如自动驾驶、机器人。
  • 适合纹理丰富的场景。

Python + OpenCV 代码

1
2
3
4
5
6
7
8
9
10
11
img_left = cv2.imread('left_image.jpg', cv2.IMREAD_GRAYSCALE)
img_right = cv2.imread('right_image.jpg', cv2.IMREAD_GRAYSCALE)

# 创建立体匹配对象
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)

# 计算视差图
disparity = stereo.compute(img_left, img_right)

# 归一化显示
disparity = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

总结

边缘提取算法对比

算法 抗噪性 边缘定位 计算复杂度 适用场景
Canny 高质量边缘检测
Roberts 简单图像
Sobel 水平/垂直边缘
Prewitt 简单图像
Hessian 线状结构
Haar 目标检测
Laplacian 简单图像
Scharr 高精度边缘

相机模型要点

  • 小孔成像:理想模型,简单但不实用。
  • 相机模型:考虑内参外参,适合精确建模。
  • 镜头畸变:校正非理想透镜,广角镜头必备。
  • 透视变换:平面视角变换,适合校正和拼接。
  • 鱼眼模型:超广角成像,需复杂校正。
  • 立体视觉:深度计算,适合 3D 重建。