计算机视觉学习笔记

一份完整的计算机视觉(CV)学习笔记。学多少记多少,重点讲"为什么这么设计"(费曼式 📌 讲解),覆盖从图像基础到深度学习的完整知识体系。

怎么用这份笔记

  1. 学习/复习 → 顺序看正文,重点看 📌 类比和"为什么"
  2. 查术语 → 翻 附录 A 速查表
  3. 检验 → 做 附录 B 自测题,盖住答案自答
  4. 落地 → 跟 附录 C 实战项目做一遍,把知识串起来

1 · 计算机视觉概述

1.1 什么是计算机视觉

计算机视觉(Computer Vision, CV) 是让计算机"看懂"图像和视频的学科——从像素中提取语义信息(这是什么、在哪里、怎么运动)。

层级 任务 例子
低层视觉 像素级处理 滤波、边缘检测、色彩转换
中层视觉 特征与几何 特征点、光流、立体匹配
高层视觉 语义理解 分类、检测、分割、识别
TIP

📌 打个比方:低层视觉是"眼睛视网膜"——处理光信号;中层视觉是"视觉皮层"——提取边缘和运动;高层视觉是"大脑认知"——认出这是猫还是狗。CV 的难度在于:人类觉得"看一眼就懂"的事,对计算机来说是海量像素到语义的巨大鸿沟。

1.2 CV 的发展历程

1960s 边缘检测理论(Roberts) → 让计算机"认"简单形状 1990s 特征工程时代(SIFT/HOG/SVM) → 人工设计特征 + 机器学习分类 2012 AlexNet(深度学习革命) → CNN 自动学习特征,性能暴涨 2015 ResNet(残差连接) → 网络可以做到 100+ 层 2017 YOLO / Transformer 出现 → 实时检测 / 序列建模 2020 ViT(Vision Transformer) → 图像也用注意力机制 2023 SAM / DINOv2(大模型时代) → 通用视觉基础模型
TIP

📌 为什么 2012 年是分水岭:2012 年 AlexNet 在 ImageNet 竞赛上把错误率从 26% 降到 15%,碾压传统方法。关键不是算法多新颖(CNN 1998 年就有了),而是 GPU 算力 + 大数据(ImageNet 1400 万张图)+ ReLU 激活函数 三者同时成熟。深度学习的成功是"量变引起质变"——足够大的网络 + 足够多的数据,就能自动学到人工设计不出来的特征。

1.3 CV 的核心任务全景

图 1 \xb7 CV 核心任务全景


2 · 图像基础

2.1 图像的数字表示

一张彩色图像在计算机中是一个 三维张量:H × W × C(高 × 宽 × 通道数)。

概念 说明 典型值
分辨率 图像的像素尺寸 H × W 1920×1080
通道数 颜色通道数 灰度=1, RGB=3, RGBA=4
位深 每个像素值的比特数 8bit=0~255, 16bit=0~65535
数据类型 像素值的存储类型 uint8, float32
# OpenCV 读取图像
import cv2
img = cv2.imread('test.jpg')       # BGR 格式, shape=(H, W, 3), dtype=uint8
gray = cv2.imread('test.jpg', 0)   # 灰度图, shape=(H, W)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # BGR → RGB
TIP

📌 OpenCV 为什么用 BGR 而不是 RGB:历史原因——早期 OpenCV 主要处理 BMP 格式,BMP 文件存储顺序就是 BGR。这个"历史包袱"一直延续至今,是 CV 工程师最常见的坑之一。用 matplotlib 显示 OpenCV 图像时颜色会反,必须先 cv2.cvtColor 转成 RGB。

2.2 色彩空间

色彩空间 通道 用途
RGB R, G, B 显示器、通用
BGR B, G, R OpenCV 默认
GRAY 亮度 边缘检测、特征提取
HSV H(色调), S(饱和度), V(亮度) 颜色分割、跟踪
LAB L(亮度), a(红绿), b(蓝黄) 感知均匀色彩差
YCrCb Y(亮度), Cr, Cb 视频压缩(JPEG/MPEG)
TIP

📌 HSV 为什么适合颜色检测:在 RGB 空间中,"红色"分布在 R 高、G/B 低——受光照影响大(暗红和亮红的 RGB 值差异巨大)。HSV 把"什么颜色"(H)和"多亮"(V)分开,检测红色只需固定 H 范围,不受亮度变化干扰。这就是为什么目标跟踪、交通灯识别常用 HSV。

2.3 采样与量化

连续图像 → 采样(Spatial Sampling)→ 离散像素网格 → 量化(Quantization)→ 离散像素值 采样:连续空间 → 离散网格(决定空间分辨率) 量化:连续灰度 → 离散等级(决定灰度分辨率)
操作 影响 过低的结果
采样率↓ 空间分辨率↓ 锯齿、丢失细节
量化级数↓ 灰度分辨率↓ 色带(posterization)
TIP

📌 奈奎斯特采样定理在图像中的体现:采样频率必须 ≥ 2 倍最高空间频率,否则会产生摩尔纹(Moiré)——拍摄屏幕时常见的波纹。这就是为什么拍显示器照片总有奇怪条纹——屏幕像素网格和相机传感器网格"打架"了。

2.4 直方图与均衡化

# 直方图均衡化——拉伸对比度
gray = cv2.imread('dark.jpg', 0)
eq = cv2.equalizeHist(gray)      # 全局均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))  # 自适应均衡化
eq_adaptive = clahe.apply(gray)  # 局部均衡化,效果更自然
TIP

📌 CLAHE 为什么比全局均衡化好:全局均衡化把整张图的直方图拉平,但暗区域里可能有亮的小物体——全局均衡化后亮物体被"淹没"。CLAHE(限制对比度自适应直方图均衡化)把图像分小块,每块独立均衡化,再限制对比度增益防止放大噪声。医学图像几乎都用 CLAHE。


3 · 图像处理基础

3.1 卷积操作

卷积 是 CV 最基础的操作——一个小的权重矩阵(卷积核/滤波器)在图像上滑动,每到一个位置做加权求和。

图 2 \xb7 卷积操作原理

输出像素 = Σ(卷积核 × 对应邻域像素) 例:3×3 均值滤波 [1/9, 1/9, 1/9] [1/9, 1/9, 1/9] → 中心像素 = 邻域 9 个像素的平均值 [1/9, 1/9, 1/9]
TIP

📌 卷积为什么是 CV 的核心:卷积的数学本质是"加权求和"——权重决定了你在提取什么信息。把权重设成均值 → 平滑去噪;设成差分 → 边缘检测;设成高斯 → 高斯模糊。CNN 更进一步——让网络自己学权重,自动学出"什么特征有用"。理解卷积,就理解了从图像处理到深度学习的主线。

3.2 常用滤波器

滤波器 卷积核示例 作用
均值滤波 全 1/9 去噪(简单粗暴)
高斯滤波 高斯分布权重 去噪(保留边缘)
中值滤波 取中值(非线性) 去椒盐噪声
Sobel [-1 0 1; -2 0 2; -1 0 1] 水平/垂直边缘
Laplacian [0 1 0; 1 -4 1; 0 1 0] 二阶导数,各方向边缘
锐化 [0 -1 0; -1 5 -1; 0 -1 0] 增强边缘
# 常用滤波操作
blur = cv2.GaussianBlur(img, (5,5), 0)        # 高斯模糊
median = cv2.medianBlur(img, 5)               # 中值滤波(去椒盐噪声)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)  # X 方向边缘
laplacian = cv2.Laplacian(gray, cv2.CV_64F)   # Laplacian 边缘
TIP

📌 高斯滤波 vs 均值滤波:均值滤波对邻域所有像素一视同仁,边缘也被"平均"掉了——图像变模糊。高斯滤波给中心像素更高权重、远处像素更低权重,在去噪的同时更好地保留边缘。这就是"加权"的力量——不是所有邻居都一样重要。

📌 中值滤波为什么去椒盐噪声特别有效:椒盐噪声是随机出现的极亮/极暗像素。均值滤波会把噪声"摊"到周围,反而扩大影响。中值滤波取中位数——如果 9 个像素中有 1 个是噪声(极值),中位数不受影响。对脉冲噪声,中值滤波是"降维打击"。

3.3 边缘检测

Canny 边缘检测 是最经典的边缘检测算法,包含 5 个步骤:

1. 高斯模糊 → 去噪 2. Sobel 计算梯度 → 得到边缘强度和方向 3. 非极大值抑制 (NMS) → 沿梯度方向只保留最强像素 → 细边 4. 双阈值检测 → 高阈值确定真边缘,低阈值连接断裂 5. 连通性分析 → 去掉孤立弱边缘
edges = cv2.Canny(gray, threshold1=100, threshold2=200)
TIP

📌 为什么需要非极大值抑制:Sobel 算完梯度后,边缘是"粗"的——好几个像素宽。NMS 的思路:沿着梯度方向(垂直于边缘),只保留梯度最大的那个像素,其余置零。这样边缘从"3 像素宽"变成"1 像素宽"——又细又准。NMS 这个思想后来也被目标检测借用——去掉重复的检测框。

3.4 形态学操作

操作 效果 用途
腐蚀 (Erosion) 白色区域缩小 去除小噪声
膨胀 (Dilation) 白色区域扩大 填充小空洞
开运算 先腐蚀后膨胀 去噪(不改变大小)
闭运算 先膨胀后腐蚀 填孔(不改变大小)
梯度 膨胀 - 腐蚀 提取边缘
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
eroded = cv2.erode(binary, kernel)
dilated = cv2.dilate(binary, kernel)
opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

4 · 特征提取与匹配

4.1 什么是特征点

特征点(Keypoint) 是图像中"独特"的像素——在另一张图中能被重新找到的点。

好的特征点应具备:

  • 可重复性:不同图像中同一物理点能被检测到
  • 独特性:邻域信息足够区分,不会混淆
  • 局部性:只依赖小邻域,对遮挡鲁棒
  • 数量足够:但不能太多
TIP

📌 为什么不能直接用像素匹配:如果拿一个像素的灰度值去另一张图里找相同值——满图都是亮度相近的像素,根本不知道哪个才是对应点。特征点的思路是:先找到"与众不同"的位置(角点、斑点),再计算该位置的"描述子"(邻域编码),用描述子做匹配。

4.2 角点检测

Harris 角点检测 的核心思想:在角点处,无论往哪个方向移动,像素值都会剧烈变化。

窗口位置 水平移动 垂直移动 结论
平坦区域 几乎不变 几乎不变 不是特征
边缘 不变 剧变 只一个方向有变化
角点 剧变 剧变 两个方向都变 → 特征点
gray = np.float32(gray)
corners = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
img[corners > 0.01 * corners.max()] = [0, 0, 255]  # 标记角点

4.3 SIFT / SURF / ORB 对比

特征 尺度不变 旋转不变 专利 速度 描述子维度
SIFT 是 是 已过期 慢 128
SURF 是 是 已过期 中 64
ORB 是 是 免费 快 32(二进制)
TIP

📌 SIFT 为什么是里程碑:SIFT(2004)解决了 CV 的两个核心难题——尺度不变和旋转不变。尺度不变靠"高斯金字塔 + 差分金字塔"自动找最佳尺度;旋转不变靠计算主方向并归一化。SIFT 的设计者 Lowe 花了 10 年打磨这个算法,它的思想后来直接影响了深度学习中的特征金字塔网络(FPN)。

📌 ORB 为什么成为主流:ORB = FAST 关键点 + BRIEF 描述子 + 旋转归一化。关键优势是速度快 + 无专利限制。在 SLAM(如 ORB-SLAM)和 AR 应用中,ORB 几乎是默认选择。SIFT 虽然精度更高,但速度差一个数量级。

# 特征提取与匹配
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:20], None)

4.4 特征匹配策略

方法 原理 适用场景
暴力匹配 (BF) 逐一计算描述子距离 小规模、精确
FLANN 基于 KD-tree 的近似最近邻 大规模、快速
比率测试 最近/次近距离比 < 0.7 去除歧义匹配
RANSAC 随机采样一致性 去除外点、估计变换
TIP

📌 比率测试为什么有效:如果一个特征点的最佳匹配(最近邻)和次佳匹配(次近邻)距离差不多,说明这个匹配有歧义——两个候选都差不多好。Lowe 的经验法则:最近/次近距离比 < 0.7 时,匹配大概率正确。这一个简单规则能过滤掉 90% 的错误匹配。


5 · 相机模型与几何

5.1 针孔相机模型

图 3 \xb7 针孔相机模型

针孔模型把相机简化为一个点(光心),光线穿过小孔在像平面上成像。

世界坐标 (X_w, Y_w, Z_w) ↓ 外参 [R|t](旋转+平移) 相机坐标 (X_c, Y_c, Z_c) ↓ 透视投影 像素坐标 (u, v) = 投影矩阵 × 世界坐标

5.2 内参与外参

参数 含义 矩阵
内参 K 相机自身属性(焦距、主点、畸变) 3×3
外参 [R|t] 相机在世界坐标系中的位姿 3×4
投影矩阵 P 完整映射:世界 → 像素 3×4 = K × [R|t]
内参矩阵 K: ┌ fx 0 cx ┐ │ 0 fy cy │ fx, fy = 焦距(像素单位) cx, cy = 主点(图像中心) └ 0 0 1 ┘
TIP

📌 焦距的两种单位:物理焦距 f(毫米)和像素焦距 fx=f/dx(像素),dx 是单个像素的物理尺寸。标定得到的 fx 是像素单位——因为计算机处理的是像素,不是毫米。这就是为什么同样镜头在不同分辨率传感器上 fx 不同。

📌 为什么要标定:制造工艺有误差——镜头不是理想针孔,有径向畸变(桶形/枕形)和切向畸变。标定就是拍棋盘格,用已知图案反算 K 和畸变系数。不标定的后果:直线变弯、测量不准、3D 重建扭曲。

# 相机标定(棋盘格法)
ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objpoints, imgpoints, gray.shape[::-1], None, None)
# 去畸变
undistorted = cv2.undistort(img, mtx, dist, None, mtx)

5.3 对极几何

两个相机从不同角度看同一场景,它们之间的几何关系由对极几何描述。

图 4 \xb7 对极几何

概念 定义
基线 (Baseline) 两个光心连线
对极平面 基线 + 一个3D点确定的平面
对极线 对极平面与像平面的交线
本质矩阵 E 相机坐标系下的对极约束,E = t^R
基础矩阵 F 像素坐标系下的对极约束,F = K⁻ᵀ E K⁻¹
TIP

📌 对极约束的意义:已知左图中一个点,它在右图中的对应点不必满图搜索——一定在对极线上。这把搜索空间从 2D 降到 1D,是立体匹配和 SLAM 的理论基础。本质矩阵 E 编码了两个相机之间的相对位姿(旋转 R + 平移 t),从 2D 匹配点恢复 3D 结构(SfM)的核心就是估计 E。

5.4 单应矩阵

单应矩阵 H 描述平面在不同视角下的映射关系(3×3 矩阵,8 自由度)。

p₂ = H × p₁ (平面场景或纯旋转相机)

应用:全景拼接、文档矫正、AR 标记定位。


6 · 立体视觉

6.1 立体视觉原理

图 5 \xb7 立体视觉原理

深度 Z = (f × B) / disparity f = 焦距, B = 基线长度, disparity = 视差(左右图像中同一点的横向偏移)
TIP

📌 视差与深度的关系:近处物体左右眼看到的差异大(视差大),远处物体差异小(视差小)。这就是人眼测距的原理——双目视差。Z = fB/d 告诉我们:基线 B 越大、焦距 f 越长,测距越远越准。但基线太大会导致近距离物体视差过大,难以匹配。所以立体相机要在"测远"和"测近"之间取舍。

6.2 立体匹配算法

类别 方法 特点
局部法 BM、SGBM 窗口匹配,速度快
全局法 Graph Cut、Belief Propagation 能量最小化,精度高但慢
深度学习 PSNet、GA-Net 端到端学习视差
# SGBM 半全局匹配
stereo = cv2.StereoSGBM_create(
    minDisparity=0, numDisparities=128, blockSize=5,
    P1=8*3*5*5, P2=32*3*5*5, disp12MaxDiff=1, uniquenessRatio=10)
disparity = stereo.compute(imgL, imgR)

6.3 深度传感器

技术 原理 代表产品 范围
双目立体 两个相机三角测量 ZED, RealSense D435 0.5~20m
结构光 投射已知图案 + 三角测量 Kinect v1, RealSense F200 0.3~3m
ToF 光飞行时间测距 Kinect Azure, L5 激光雷达 0.25~5m+
LiDAR 激光脉冲测距 Velodyne, Livox 100m+
TIP

📌 结构光为什么怕室外强光:结构光相机投射红外图案到物体上,再用相机拍图案的变形。室外太阳光含大量红外线——把投射的图案"淹没"了,相机看不到自己的图案。这就是为什么 Kinect v1 只能在室内用。ToF 和 LiDAR 用主动激光脉冲,功率远高于环境光,所以能在室外工作。


7 · 光流与运动估计

7.1 光流是什么

光流(Optical Flow) 是图像中像素的表观运动——连续两帧之间,每个像素"移动"到了哪里。

光流假设: 1. 亮度恒定:同一像素在两帧中亮度不变 2. 运动小:帧间位移小 3. 空间一致:邻域像素运动相似 Lucas-Kanade 方程: I_x · u + I_y · v + I_t = 0 (一个方程,两个未知数 u, v) → 假设邻域运动一致 → 用最小二乘求解
TIP

📌 光流的不适定性:一个像素从 A 移到 B,但计算机只看到"A 处像素消失了、B 处像素出现了"——它怎么知道这是平移、还是旋转、还是光照变化?这就是"孔径问题"(Aperture Problem)——通过小孔看一个运动的条纹,你只能感知垂直于条纹的运动。Lucas-Kanade 的解法是假设邻域像素一起运动,用多个方程解两个未知数。

7.2 光流方法对比

方法 类型 密度 精度 速度
Lucas-Kanade 稀疏 稀疏(角点) 中 快
Farneback 稠密 稠密(全图) 中 中
RAFT 深度学习 稠密 高 GPU
FlowNet 深度学习 稠密 高 GPU
# Lucas-Kanade 稀疏光流
lk = cv2.optflow.createOptFlow_SparseToDense()
flow = lk.calc(prev_gray, curr_gray, None)

# Farneback 稠密光流
flow = cv2.calcOpticalFlowFarneback(prev_gray, curr_gray, None,
    0.5, 3, 15, 3, 5, 1.2, 0)

8 · CNN 与图像分类

8.1 CNN 基本结构

图 6 \xb7 CNN 基本结构

层 作用 参数
卷积层 (Conv) 提取局部特征 卷积核大小、数量、步长
激活函数 引入非线性 ReLU、SiLU、GELU
池化层 (Pool) 降采样、减少参数 Max/Avg Pooling
全连接层 (FC) 分类输出 输出维度=类别数
BN 层 归一化加速训练 γ, β
TIP

📌 CNN 为什么适合图像:全连接网络处理 224×224 图像需要 150,528 个输入神经元,第一层如果有 1024 个神经元就是 1.5 亿参数——参数爆炸。CNN 的三个关键设计:

  1. 局部连接:每个神经元只看一个小邻域(如 3×3),参数从 150K 降到 9
  2. 权值共享:同一个卷积核在整张图上滑动,参数量与图像大小无关
  3. 层次抽象:浅层学边缘 → 中层学纹理 → 深层学语义

这三个设计让 CNN 参数量可控,且具有平移不变性——物体在图像中移动位置不影响识别。

8.2 经典 CNN 架构演进

网络 年份 创新 Top-5 错误率
LeNet 1998 第一个 CNN —
AlexNet 2012 ReLU + GPU + Dropout 15.3%
VGG 2014 小卷积核堆叠 (3×3) 7.3%
GoogLeNet 2014 Inception 多尺度并行 6.7%
ResNet 2015 残差连接(跳跃连接) 3.6%
DenseNet 2017 密集连接 3.4%
EfficientNet 2019 复合缩放策略 2.9%
ViT 2020 Transformer 替代 CNN 1.8%
TIP

📌 ResNet 为什么能做 152 层:网络加深后出现"退化问题"——不是过拟合,而是训练误差反而上升。ResNet 的解法极其简单:加一条"跳跃连接"(skip connection),让梯度可以直接"跳"过中间层反传。数学上,残差块学的是 H(x) = F(x) + x,如果某层是多余的,F(x) 学成 0 即可——网络"不会变差"。这个看似简单的创新让网络深度从 22 层跃升到 152 层,是深度学习最重要的架构创新之一。

8.3 Vision Transformer (ViT)

ViT 把图像切成固定大小的 patch(如 16×16),每个 patch 当作一个"词",用 Transformer 的自注意力机制处理。

图像 224×224 → 切成 14×14=196 个 patch (16×16) 每个 patch → 线性投影 → 196 个 token 加位置编码 → Transformer Encoder → 分类
TIP

📌 ViT vs CNN 的本质区别:CNN 的归纳偏置(inductive bias)很强——局部性 + 平移不变性 + 层次性。这些先验知识让 CNN 在小数据集上表现好。ViT 几乎没有归纳偏置——自注意力是全局的,不假设局部性。这意味着 ViT 需要更多数据才能学到 CNN "天生就有"的先验,但数据足够多时,ViT 的上限更高——因为它不被"局部性"这个假设束缚。这就是为什么 ViT 在大数据集上超越 CNN,但小数据集上不如 CNN。

8.4 数据增强

方法 操作 效果
翻转 水平/垂直翻转 增加样本量
裁剪 随机裁剪+缩放 位置鲁棒
色彩抖动 亮度/对比度/饱和度随机 光照鲁棒
MixUp 两图线性混合+标签混合 正则化
CutMix 两图区域拼接+标签按比例 定位能力
AutoAugment 自动搜索增强策略 最优增强
Mosaic 4 图拼接(YOLO) 小目标检测
TIP

📌 为什么数据增强有效:深度网络参数多、容易过拟合。数据增强的本质是"人工扩大数据集"——让网络看到更多变体,学到不变特征。比如猫无论翻转、变色、裁剪都是猫——增强让网络学到"猫的本质"而非"这张特定照片"。在数据量有限时,数据增强是最有效的正则化手段。


9 · 目标检测

9.1 检测任务概述

图 7 \xb7 目标检测方法演进

类型 代表 思路 速度 精度
两阶段 R-CNN, Faster R-CNN 先找区域再分类 慢 高
单阶段 YOLO, SSD, RetinaNet 直接回归框+类别 快 中高
Transformer DETR 集合预测 中 高

9.2 YOLO 系列详解

YOLO(You Only Look Once)是实时目标检测的代表算法。

YOLO 核心思路: 1. 图像 → CNN backbone 提特征 2. 特征图分成 S×S 网格 3. 每个网格预测 B 个框 + C 个类别概率 4. 输出:S × S × (B×5 + C) 张量 5. NMS 去除重复框
版本 年份 创新 mAP FPS
YOLOv1 2016 首个单阶段实时检测 63.4 45
YOLOv3 2018 多尺度检测 (FPN) 55.3 35
YOLOv5 2020 工程优化 + Mosaic 67.2 140
YOLOv8 2023 Anchor-free + 解耦头 53.9 280
YOLOv10 2024 NMS-free 54.4 500+
TIP

📌 YOLO 为什么快:两阶段检测(Faster R-CNN)先用 RPN 生成 ~2000 个候选区域,再逐个分类——串行处理。YOLO 把检测变成"一次回归"——整张图一次前向传播就输出所有框的位置和类别。从"先找再认"变成"边找边认",速度提升 10 倍以上。代价是对小物体和密集物体精度稍低——因为网格划分太粗。

📌 Anchor-free 为什么是趋势:传统 YOLO 用预设的 anchor box(不同大小和比例的先验框)来匹配物体。但 anchor 的尺寸需要人工设定,且和数据集相关。YOLOv8/X 等新方法去掉 anchor,直接预测框的中心点和宽高——减少超参数、更通用。

9.3 评估指标

指标 含义 计算
IoU 交并比 = 交集面积 / 并集面积 衡量框的重合度
Precision 精确率 = TP / (TP + FP) 预测中有多少是对的
Recall 召回率 = TP / (TP + FN) 真实目标有多少被找到
AP PR 曲线下面积 单类别精度
mAP 所有类别 AP 的平均 整体精度
mAP@0.5 IoU 阈值=0.5 的 mAP 宽松标准
mAP@0.5:0.95 IoU 从 0.5 到 0.95 每隔 0.05 取平均 严格标准
TIP

📌 为什么用 mAP 而不是 Accuracy:目标检测中"负样本"(背景区域)远多于"正样本"(目标)。如果用 Accuracy,模型把所有区域都预测为"背景"也能得到 99% 的 Accuracy——毫无意义。mAP 只关注"真正检测到的目标"的质量(PR 曲线下面积),不受背景干扰。

9.4 NMS(非极大值抑制)

NMS 流程: 1. 按置信度排序所有检测框 2. 取最高分框,移除与它 IoU > 阈值(如0.5)的所有框 3. 对剩余框重复步骤 2 4. 剩下的就是最终检测结果
TIP

📌 NMS 的缺陷与改进:NMS 假设重叠的框是"重复检测"——但如果两个物体真的重叠(如人群),NMS 会把第二个框误删。Soft-NMS 的解法:不直接删除,而是降低重叠框的分数(分数 × (1-IoU)),让密集场景下保留更多框。YOLOv10 更进一步——用一致性匹配指标(CMO)完全去掉 NMS,端到端训练。


10 · 图像分割

10.1 分割任务分类

图 8 \xb7 图像分割任务分类

任务 输出 例子
语义分割 每像素一个类别 道路/建筑/天空
实例分割 每像素一个实例 ID 这只猫 vs 那只猫
全景分割 语义 + 实例 所有像素都有标签

10.2 经典分割模型

模型 年份 创新
FCN 2015 全卷积网络,首个端到端分割
U-Net 2015 编码器-解码器 + 跳跃连接
DeepLab v3+ 2018 空洞卷积 + ASPP 多尺度
Mask R-CNN 2017 在 Faster R-CNN 上加 mask 分支
SAM 2023 通用分割大模型,可提示
TIP

📌 U-Net 为什么在医学图像中称王:医学图像分割有两个特点:数据少、需要精确定位。U-Net 的"对称编解码 + 跳跃连接"设计完美匹配——编码器提取语义特征,解码器恢复空间分辨率,跳跃连接把浅层的高分辨率细节直接传给深层。这样即使数据少,也能精确定位器官边界。U-Net 发表于 2015 年,至今仍是医学图像分割的 baseline。

📌 SAM(Segment Anything Model)的意义:Meta 2023 年发布的 SAM 用 11 亿个 mask 训练,可以分割"任何东西"——不需要针对新任务微调。用户可以用点、框、文字提示 SAM 分割目标。SAM 的意义在于把分割从"每个任务训一个模型"变成"一个通用模型解决所有任务"——CV 的大模型时代。

10.3 评估指标

指标 公式 含义
Pixel Accuracy 正确像素 / 总像素 简单但偏向大类
mIoU 平均交并比 主流指标
Dice 系数 2×交集 / (A+B) 医学常用
Boundary F1 边界质量 评估边缘精度

11 · 关键点检测与姿态估计

11.1 人体姿态估计

姿态估计 是检测人体关键点(关节)并连接成骨架。

方法 思路 代表
Top-down 先检测人 → 每人单独检测关键点 HRNet, OpenPose(单人)
Bottom-up 先检测所有关键点 → 再组合成人 OpenPose(多人), HigherHRNet
TIP

📌 Top-down vs Bottom-up 的取舍:Top-down 精度高(每人独立检测),但速度与人数成正比——10 个人就要跑 10 次关键点检测。Bottom-up 先检测所有关键点再分组,速度与人数无关——但分组(哪个肘连哪个肩)是个难题。实际应用中,人少用 Top-down(精度优先),人多用 Bottom-up(速度优先)。

11.2 人脸关键点

级别 点数 应用
5 点 眼(2) + 鼻(1) + 嘴角(2) 人脸检测辅助
68 点 轮廓 + 眉 + 眼 + 鼻 + 嘴 表情识别
106+ 点 更精细 美颜、AR
468 点 MediaPipe Face Mesh 实时 3D 人脸
# MediaPipe 人脸关键点
import mediapipe as mp
face_mesh = mp.solutions.FaceMesh(refine_landmarks=True)
results = face_mesh.process(rgb)
for landmark in results.multi_face_landmarks[0].landmark:
    x, y = int(landmark.x * W), int(landmark.y * H)

12 · 3D 视觉与点云

12.1 3D 数据表示

表示 结构 优点 缺点
点云 N×3 (xyz) 或 N×6 (xyz+rgb) 简单、无损 无序、无结构
体素网格 3D 网格 规则、卷积友好 内存大、分辨率受限
Mesh 顶点+面片 精确表面 拓扑复杂
深度图 H×W 距离值 稠密、2D 兼容 有遮挡

12.2 点云处理

import open3d as o3d

pcd = o3d.io.read_point_cloud("cloud.ply")
pcd = pcd.voxel_down_sample(voxel_size=0.05)     # 降采样
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(
    radius=0.1, max_nn=30))                       # 法线估计
cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)  # 去噪

12.3 点云配准(ICP)

ICP(Iterative Closest Point) 是最经典的点云配准算法——把两帧点云对齐。

ICP 迭代: 1. 找最近邻对应点 2. 计算最优旋转 R + 平移 t(SVD 分解) 3. 应用变换 4. 重复直到收敛
TIP

📌 ICP 的缺陷:ICP 假设"最近邻就是对应点"——但如果初始位姿偏差大,最近邻可能是错误的对应关系,导致陷入局部最优。所以 ICP 通常需要一个好的初始猜测(如特征匹配提供)。全局配准方法(如 RANSAC + FPFH 特征)用于提供初始位姿,ICP 用于精细对齐。

12.4 点云深度学习

方法 思路 代表
PointNet 逐点 MLP + 最大池化(全局特征) 2017
PointNet++ 分层局部特征提取 2017
PointTransformer 点间自注意力 2021
Sparse Conv 稀疏卷积(只算非空体素) Spconv, MinkowskiNet
TIP

📌 PointNet 的天才设计:点云是无序的——同样的点打乱顺序还是同一个点云。普通神经网络对输入顺序敏感。PointNet 的解法:用对称函数(max-pooling)——无论输入顺序如何,max 的结果不变。一个 max 操作解决了点云无序性问题,简洁而优雅。


13 · 模型训练与部署

13.1 训练流程

1. 数据准备 → 标注、清洗、增强、划分 (train/val/test) 2. 模型选择 → backbone (ResNet/EfficientNet/ViT) + head (task-specific) 3. 训练 → 前向传播 → loss → 反向传播 → 更新权重 4. 验证 → 监控 loss/mAP,调超参 5. 测试 → 在未见数据上评估 6. 部署 → 导出 → 推理优化 → 上线

13.2 损失函数

损失 公式 用途
Cross-Entropy -Σ y log(p) 分类
Focal Loss -α(1-p)^γ log(p) 正负样本不平衡
Smooth L1 0.5x² if |x|<1 else |x|-0.5 框回归
GIoU/DIoU/CIoU 改进的 IoU 损失 框回归
Dice Loss 1 - 2 A∩B
TIP

📌 Focal Loss 为什么解决类别不平衡:标准 CE 对所有样本一视同仁。但在目标检测中,背景(负样本)远多于目标(正样本)——loss 被大量简单负样本主导。Focal Loss 给置信度高的样本降权 (1-p)^γ——"已经很确定的样本就少学点",让模型把注意力放在难样本上。这一个改进让单阶段检测器精度追平了两阶段检测器。

13.3 学习率策略

策略 说明 适用
Step Decay 每 N epoch 乘 γ 简单任务
Cosine Annealing 余弦退火 主流
Warmup 前几个 epoch 线性升温 大 batch、Transformer
OneCycle 先升后降 快速收敛
TIP

📌 Warmup 为什么对 Transformer 重要:Transformer 初始权重是随机的,前几步的梯度方向噪声很大。如果一开始就用大学习率,参数会"乱跑"到不好的区域。Warmup 先用小学习率让参数稳定,再加大学习率加速收敛。CNN 不太需要 warmup(因为 BN 层和残差连接提供了稳定性),但 ViT/DETR 几乎必须用 warmup。

13.4 模型部署

阶段 工具 说明
训练框架 PyTorch 灵活、调试方便
导出 ONNX 跨平台中间格式
推理优化 TensorRT, OpenVINO, ONNX Runtime 算子融合、量化
端侧部署 NCNN, MNN, TFLite 手机/嵌入式
服务化 Triton, TorchServe GPU 服务
# PyTorch → ONNX → TensorRT
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)
# TensorRT 优化
import tensorrt as trt
builder = trt.Builder(logger)
network = builder.create_network()
parser = trt.OnnxParser(network, logger)
parser.parse_from_file("model.onnx")
engine = builder.build_cuda_engine(network)
TIP

📌 为什么要导出 ONNX 再部署:PyTorch 模型依赖 Python 运行时和 PyTorch 库——部署环境装这些很重。ONNX 是"序列化格式"——把模型存成文件,任何能读 ONNX 的运行时都能跑。TensorRT 进一步做算子融合(Conv+BN+ReLU 合成一个算子)、精度量化(FP32→FP16/INT8),推理速度提升 2~5 倍。

13.5 模型量化

精度 位宽 精度损失 加速比
FP32 32 bit 基准 1×
FP16 16 bit ~0% ~2×
INT8 8 bit 1~3% ~3~4×
INT4 4 bit 3~8% ~5~8×
TIP

📌 量化为什么能加速:GPU/TPU 的 INT8 算力通常是 FP32 的 4~8 倍(更多 ALU 并行)。量化把浮点权重映射到整数范围(如 -128~127),矩阵乘法从浮点运算变成整数运算。关键挑战是保持精度——PTQ(训练后量化)简单但可能掉精度,QAT(量化感知训练)在训练时模拟量化误差,精度更好。


14 · 前沿技术

14.1 视觉基础大模型

模型 团队 能力
SAM / SAM 2 Meta 通用分割、视频分割
DINOv2 Meta 自监督视觉特征
CLIP OpenAI 图文对齐、零样本分类
GPT-4V OpenAI 多模态理解
Florence-2 Microsoft 统一视觉任务
TIP

📌 CLIP 为什么改变了 CV:传统 CV 需要"每个任务标注数据"。CLIP 用 4 亿图文对做对比学习——让图像编码器和文本编码器在同一个空间中对齐。训练好后,给一张图和一组类别文字,计算图像和哪个文字最相似即可——零样本分类,不需要任何标注。CLIP 的图文对齐思想后来催生了 DALL-E、Stable Diffusion 等生成模型。

14.2 扩散模型与图像生成

扩散模型原理: 前向:加噪声(逐步破坏图像 → 纯噪声) 反向:去噪声(从纯噪声逐步恢复图像) 训练:学习预测噪声 生成:从随机噪声开始,逐步去噪 → 生成图像
模型 类型 特点
Stable Diffusion 潜空间扩散 开源、社区生态强
DALL-E 3 扩散 + CLIP 文本理解强
ControlNet 条件控制 用边缘/深度/姿态控制生成
DiT Transformer 扩散 Sora 的基础架构

14.3 神经辐射场 (NeRF) 与 3DGS

技术 原理 优势 劣势
NeRF 用 MLP 拟合辐射场,体渲染 质量高 训练慢、存储大
Instant-NGP 哈希网格加速 快 100× —
3D Gaussian Splatting 3D 高斯点 + 光栅化 实时渲染、质量高 内存大
TIP

📌 3DGS 为什么比 NeRF 快:NeRF 用 MLP 拟合辐射场——每个像素要从相机发出射线,在射线上采样多个点,每个点过 MLP 预测颜色和密度,最后体渲染——计算量巨大。3DGS 用大量 3D 高斯点直接表示场景,渲染时把高斯点"泼溅"(splat) 到屏幕上——类似光栅化,GPU 天然擅长。训练速度提升 10×,渲染速度提升 100×。

14.4 多模态视觉

方向 任务 代表
VLM 视觉问答、图像描述 LLaVA, GPT-4V
Video Understanding 视频问答、时序理解 VideoChat, LLaMA-VID
Visual Grounding 指代表达 Grounding DINO
Embodied AI 视觉驱动机器人 RT-2, PaLM-E

附录 A · 术语速查表

术语 英文 含义
卷积 Convolution 加权求和的局部操作
池化 Pooling 降采样
感受野 Receptive Field 一个神经元看到的输入区域
锚框 Anchor Box 预设的先验框
非极大值抑制 NMS 去除重复检测
交并比 IoU 框重合度
平均精度 mAP 检测/分割主流指标
骨干网络 Backbone 特征提取主干
轻量级 Lightweight 计算量小、适合端侧
零样本 Zero-shot 无需训练直接推理
微调 Fine-tuning 预训练模型上继续训练
蒸馏 Distillation 大模型教小模型
量化 Quantization 降低精度以加速
视差 Disparity 立体匹配中的横向偏移
本质矩阵 Essential Matrix 对极几何约束
单应矩阵 Homography 平面映射关系
光流 Optical Flow 像素表观运动
点云 Point Cloud 3D 点集
配准 Registration 点云对齐
辐射场 Radiance Field 3D 场景的神经表示

附录 B · 自测题

Q1: 为什么 CNN 的卷积核通常用 3×3 而不是更大的尺寸?

两个 3×3 卷积堆叠的感受野等于一个 5×5,三个 3×3 等于一个 7×7。但参数量更少(3×3×3=27 vs 7×7=49),且非线性更多(3 次 ReLU vs 1 次)。VGG 证明了这个"小卷积核堆叠"策略更优。

Q2: ResNet 的残差连接为什么能解决梯度消失?

跳跃连接给梯度一条"高速公路"——梯度可以不经过中间层直接传到浅层。即使中间层梯度接近 0,梯度仍能通过 skip connection 传回去。

Q3: SIFT 的尺度不变性是怎么实现的?

高斯金字塔:在不同 σ 的高斯模糊下检测极值点,自动找到该特征的最佳尺度。描述子在对应尺度下提取,与图像缩放无关。

Q4: 为什么 ViT 在小数据集上不如 CNN?

ViT 缺乏 CNN 的归纳偏置(局部性、平移不变性),需要更多数据来学习这些先验。数据不足时容易过拟合。

Q5: 立体视觉中,基线越长越好吗?

基线长 → 测距远(Z=fB/d,B 大则同样 d 对应更远的 Z)。但基线长 → 近距离物体视差大,匹配困难。需要在测距范围和匹配难度间取舍。

Q6: NMS 的阈值设大设小有什么影响?

阈值大 → 保留更多框(高召回、低精确);阈值小 → 删除更多框(高精确、低召回)。密集场景用大阈值,稀疏场景用小阈值。

Q7: 为什么 Focal Loss 能提升单阶段检测器精度?

单阶段检测器正负样本极度不平衡(背景远多于目标)。Focal Loss 给简单样本降权,让模型聚焦难样本,解决了类别不平衡问题。

Q8: PointNet 用什么操作解决点云无序性?

max-pooling(对称函数)——无论输入顺序如何,取最大值的结果不变。


附录 C · 实战项目

项目 1:车道线检测(经典 CV)

import cv2
import numpy as np

def detect_lanes(img):
    # 1. 灰度化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 2. 高斯模糊
    blur = cv2.GaussianBlur(gray, (5,5), 0)
    # 3. Canny 边缘
    edges = cv2.Canny(blur, 50, 150)
    # 4. ROI 裁剪(只保留下半部分)
    h, w = edges.shape
    mask = np.zeros_like(edges)
    polygon = np.array([[(0,h), (w//2, h//2), (w,h)]])
    cv2.fillPoly(mask, polygon, 255)
    masked = cv2.bitwise_and(edges, mask)
    # 5. Hough 变换检测直线
    lines = cv2.HoughLinesP(masked, 2, np.pi/180, 100,
                           minLineLength=40, maxLineGap=50)
    # 6. 绘制
    for line in lines:
        x1,y1,x2,y2 = line[0]
        cv2.line(img, (x1,y1), (x2,y2), (0,255,0), 3)
    return img

项目 2:YOLO 目标检测部署

from ultralytics import YOLO

# 训练
model = YOLO('yolov8n.pt')
model.train(data='coco128.yaml', epochs=50, imgsz=640)

# 推理
results = model('test.jpg')
results[0].show()

# 导出 ONNX
model.export(format='onnx')

# 视频检测
cap = cv2.VideoCapture('test.mp4')
while cap.isOpened():
    ret, frame = cap.read()
    if not ret: break
    results = model(frame)
    cv2.imshow('detect', results[0].plot())
    if cv2.waitKey(1) == ord('q'): break

项目 3:实时人脸关键点 + AR 滤镜

import mediapipe as mp
import cv2

mp_face = mp.solutions.face_mesh
face_mesh = mp_face.FaceMesh(refine_landmarks=True)
mp_draw = mp.solutions.drawing_utils

cap = cv2.VideoCapture(0)
while cap.isOpened():
    ret, frame = cap.read()
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = face_mesh.process(rgb)
    if results.multi_face_landmarks:
        for face_lms in results.multi_face_landmarks:
            # 468 个关键点
            for lm in face_lms.landmark:
                x, y = int(lm.x * frame.shape[1]), int(lm.y * frame.shape[0])
                cv2.circle(frame, (x, y), 1, (0, 255, 0), -1)
    cv2.imshow('AR', frame)
    if cv2.waitKey(1) == ord('q'): break

项目 4:双目立体视觉测距

import cv2
import numpy as np

# 标定参数(预先标定)
K = np.array([[700, 0, 320], [0, 700, 240], [0, 0, 1]])
baseline = 0.12  # 12cm

# SGBM 匹配
stereo = cv2.StereoSGBM_create(minDisparity=0, numDisparities=128,
                                blockSize=5, uniquenessRatio=10)

imgL = cv2.imread('left.jpg', 0)
imgR = cv2.imread('right.jpg', 0)
disparity = stereo.compute(imgL, imgR).astype(np.float32) / 16.0

# 深度图
depth = (K[0,0] * baseline) / (disparity + 1e-6)
depth[disparity <= 0] = 0  # 无效区域
cv2.applyColorMap(depth.astype(np.uint8), cv2.COLORMAP_JET)

结语:计算机视觉从"让计算机看懂世界"出发,经历了特征工程 → 深度学习 → 大模型三个时代。核心主线始终不变——从像素到语义。理解卷积、几何、深度学习这三大基石,就能快速掌握任何新方法。

本页目录