☰
计算机视觉考点因果链:从图像滤波到目标检测的41条核心解析
2026/11/2 5:31:01 网站建设 项目流程

简介:面向备考北京交通大学计算机视觉课程的学生,这份汇总围绕课程必考与高频问答梳理了40余个知识点,覆盖图像滤波的相关、卷积与内积定义,模板匹配、图像增强和特征提取三类用途,以及高斯核参数、高斯偏导核、Canny双阈值与非极大值抑制等经典内容。资料进一步延伸到Harris角点、自动尺度选择、SIFT关键点方向与描述子、视觉词典和纹理基元,并串联NB、KNN、线性分类、交叉熵与铰链损失、梯度更新、批块与动量、隐藏层、CNN参数与感受野、AlexNet、Dropout、1×1卷积和残差模块。检测部分还整理了R-CNN到Fast/Faster R-CNN的演进、RPN、NMS、PR图与AP计算,以及全卷积网络和转置卷积的上采样思路。资源包共1个PDF文件,约9.69MB,已有667人学习,适合按考点快速查阅、期末复盘与查漏补缺。

1. 41 条考点不是清单,是一条从滤波到检测的因果链

期末前一周,A 同学把一份 41 条的计算机视觉课程资源打印出来,从图像滤波一路背到 Faster R-CNN,合上书却答不出「为什么 kernel size 翻倍、σ 不变,结果几乎一样」。问题不在知识点密度,而在这份汇总按题型排序,没按因果链排序:滤波给出「怎么看局部」,特征给出「哪里值得看」,线性分类给出「怎么打分」,CNN 给出「怎么自动学模板」,检测给出「怎么在图上找多个目标」。这份资源是某高校计算机视觉课的考点清单加参考答案,覆盖相关/卷积/内积三种定义、Canny 双阈值、Harris 二次型、SIFT 双塔的四重简化、W 的代数与几何双视角、AlexNet 五件套,一路到 RPN 与 NMS。适合两类人:临考要抓重点的,以及已经跑过框架、却说不清 RoI Pooling 和锚点到底在干什么的从业者。下面按能动手复现的顺序拆。

2. 图像滤波三种定义:相关、卷积、内积在同一块 patch 上怎么对齐

滤波这块最容易被当成背诵题,实际上它是全篇唯一能用手算验证的地方。三种定义说的几乎是同一件事,差别只在「模板翻不翻转」和「结果当成标量还是当成图」。把这三个概念对齐之后,后面高斯偏导核、Canny、LoG 的推导才不会变成背公式。

2.1 三种定义的手算对照与验证脚本

相关(cross-correlation):模板盖住图像某块区域,对应位置元素逐一相乘再累加,结果放在模板中心对应的那个像素上。卷积:把同样的模板旋转 180° 再做相关。内积:把模板和它盖住的那块 patch 都按行(或按列)扫描成两个等长向量,逐元素相乘再求和,得到一个标量。

三者的关系可以一句话说死:内积就是相关在「单点」上的取值形式,卷积就是相关加一次核翻转。写代码验证比背定义可靠得多:

import numpy as np def corr(patch, k): """相关:模板不翻转,逐元素相乘求和""" return float(np.sum(patch * k)) def conv(patch, k): """卷积:模板旋转 180° 后再做相关""" return float(np.sum(patch * np.rot90(k, 2))) patch = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) k = np.array([[1., 0., -1.], [2., 0., -2.], [1., 0., -1.]]) # 类 Sobel 的竖直边缘核,非对称 print("corr =", corr(patch, k)) print("conv =", conv(patch, k)) print("dot =", float(np.dot(patch.ravel(), k.ravel()))) # 行扫描内积,等于相关

跑完会发现corr和dot数值完全相同,conv是另一个数。这里的关键参数是核的对称性:高斯核、拉普拉斯核都是中心对称的,翻转前后一模一样,所以「相关还是卷积」在工程上无所谓;而 Sobel、高斯偏导核这类非对称核,翻不翻转直接决定边缘的正负号方向。深度学习框架里的nn.Conv2d实现的是相关而不是数学卷积,因为核是学出来的,翻转与否只是把学到的核取了个镜像,不影响表达能力和最终损失。手推公式时按数学卷积写、跑代码时按相关跑,两边对不上就是这么来的。

一个常被忽略的点:内积视角的价值不在计算,而在解释。一旦把滤波看成「patch 向量和模板向量的内积」,就自然能解释为什么模板匹配里响应越大越匹配(夹角越小),也能顺势接到第 4 章 W 的几何视角——那就是把整张图的每个位置都和一个模板做内积。

2.2 高斯核的两个参数:σ 定形状,size=6σ 定截断

高斯核的连续形式是 G(x,y) = 1/(2πσ²)·exp(-(x²+y²)/(2σ²))。这里 σ 是标准差,不是方差——这个措辞在不少手写笔记里被写反,后面算核宽就会全错。

「kernel size 不同、σ 相同,结果类似」的原因在截断误差。二维高斯在半径 r 处的权重与 exp(-r²/(2σ²)) 同阶,取 r = 3σ 时指数项约为 e^(-4.5) ≈ 0.011,已经被 1 的量级盖住;再往外扩到 4σ、5σ,新增的权重接近 0。从积分视角看,核在某个像素上的输出是高斯函数在该窗口上的定积分,窗口半径超过 3σ 之后积分值基本不再增长,所以把 size 从 6σ 加到 12σ,画面看起来几乎没区别,只是白算了一倍。

参数设置的经验规则是:ksize = 2 * ceil(3σ) + 1,取奇数保证有唯一的中心像素。σ 本身控制平滑强度,σ 越大保留的低频越多、细节丢得越狠;做边缘检测前的去噪一般取 1.0~2.0,做多尺度分析就按金字塔层数按比例递增。

import numpy as np def gaussian_kernel(sigma, ksize=None): if ksize is None: ksize = 2 * int(np.ceil(3 * sigma)) + 1 # 3σ 截断,强制奇数 ax = np.arange(ksize) - ksize // 2 xx, yy = np.meshgrid(ax, ax) g = np.exp(-(xx**2 + yy**2) / (2 * sigma**2)) return g / g.sum() # 归一化,保证亮度不变 for s in (1.0, 2.0): k1 = gaussian_kernel(s) k2 = gaussian_kernel(s, ksize=k1.shape[0] * 2 + 1) # 人为把窗口放大一倍 print(s, k1.shape, k2.shape, "中心权重比 =", round(k2[k2.shape[0]//2, k2.shape[1]//2] / k1[k1.shape[0]//2, k1.shape[1]//2], 4))

第二个参数ksize传 None 时按 3σ 自动推;手动传一个更大的奇数只是把外围接近 0 的权重也存进来,中心权重会被归一化摊薄,所以上面打印的比值略小于 1 但很接近。这就是「结果类似但不完全相等」的量化解释,答题时把「截断误差 + 归一化」两点写上就够。

2.3 高斯偏导核:把平滑和求梯度合并成一次卷积

高斯偏导核解决的是一个很实际的问题:先高斯平滑再求梯度,要做两次卷积;而微分算子和卷积可交换,∂/∂x(G * I) = (∂G/∂x) * I,于是可以先把导数作用在高斯核上,得到 G_x,再和原图卷一次就完事。计算量减半,数值上也更稳——先平滑再差分等于对噪声做了两次处理,容易在强边缘附近产生偏移。

设计上,G_x = -x/(2πσ⁴)·exp(-(x²+y²)/(2σ²)),离散化后按窗口采样。这里有个必须记住的性质:高斯核的元素和是 1(保亮度),高斯偏导核的元素和是 0(它是导数,常数区域响应必须为 0)。很多人手写核对不上,就是忘了偏导核不该归一化到 1。

用途上它一身兼两职:平滑抑制噪声,一阶偏导提取边缘。Canny 的第二步用的就是它,输出的两个通道分别对应水平、竖直方向的梯度,再合成幅值和方向。

2.4 滤波器组、Canny 的细边缘与双阈值

滤波器组这块,答「有哪些滤波器」时最好配一张对照表,把图像处理用途、视觉任务用途、数学解释三列填满,比罗列名词有说服力:

滤波器图像处理中的用途视觉任务中的用途数学解释
高斯核图像平滑、去噪斑点检测对图像做加权积分
LoG(高斯拉普拉斯)图像锐化斑点(Blob)检测先高斯平滑再求二阶偏导
高斯一阶偏导核边缘检测检测边和条先对高斯求一阶偏导再卷积
高斯二阶偏导核边缘检测检测脊/细条先对高斯求二阶偏导再卷积
Gabor 核纹理增强方向性纹理分析高斯窗调制的复正弦

Canny 拿到「细边缘」靠的是非极大值抑制。算出每个像素的梯度幅值和方向后,沿梯度方向取前后两个邻居比较:当前点幅值若不是三者最大就置零。麻烦在于梯度方向一般不是 0/45/90/135 这四个格点方向,两个邻居落在像素之间,处理方式有两种——角度量化,把方向近似到最近的 45° 倍数;或者线性插值,按方向的小数部分对两个真实邻居加权。前者快、后者准,考卷上两种都写出来最稳。

双阈值的设计思路是「强边定骨架,弱边补断点」。设 maxVal 和 minVal,梯度大于 maxVal 的直接标记为边缘,小于 minVal 的直接丢弃,落在中间的像素只有当它能通过八连通路径接到某个强边缘上时才保留。这样既压掉了噪声引起的孤立弱响应,又不会把一条真实边缘从中间截断。阈值本身由梯度图的幅值分布决定,实践中常按高分段的分位数取,maxVal 取 minVal 的 2~3 倍。

3. 从 Harris 到 SIFT:尺度、方向与 128 维描述子怎么一步步长出来

特征这一段的难点不在单个算子,而在几个算子之间的逻辑接力:Harris 告诉你「这个点独特」,但没告诉你「在多远处看它才独特」;尺度选择补上这一环;SIFT 再把尺度、方向、描述子打包成可匹配的向量。把这条线捋顺,十来个考点其实是一个故事。

3.1 Harris:泰勒展开之后为什么盯住 M 的两个特征值

Harris 的起点是灰度变化量 E(u,v) = Σ w(x,y)·[I(x+u, y+v) − I(x,y)]²,w 是窗口权重。把 I(x+u, y+v) 在 (x,y) 处一阶泰勒展开,得到 I + I_x·u + I_y·v,代回去就把「平移量 (u,v)」和「图像坐标 (x,y)」解耦了:

E(u,v) ≈ [u v]·M·[u v]ᵀ,其中 M = Σ w · [[I_x², I_xI_y], [I_xI_y, I_y²]]

M 是实对称半正定矩阵,可以做相似对角化,特征向量 U 给出变化最快和最慢的两个方向,特征值 λ1、λ2 给出这两个方向上的变化强度。判据就三句话:两个特征值都大 → 任意方向移动灰度都剧烈变化 → 角点;一大一小 → 只有一个方向变化 → 边缘;都小 → 平坦区域。

工程上不用真的做特征值分解,因为 2×2 矩阵的 det 和 trace 是闭式的,而 det = λ1λ2、trace = λ1+λ2。用 R = det(M) − k·trace(M)²(k 常取 0.04~0.06)一个标量就能判:R 大且为正 → 角点;R 为负 → 边缘;|R| 很小 → 平坦。这个替换的意义是把每像素一次的特征值分解降成几次乘加,响应图能直接拿去做非极大值抑制。

3.2 自动尺度选择:什么样的 f 才能在 σ 方向出极大值

自动尺度选择要回答的是「这个特征在多大的窗口下最突出」。做法是构造尺度空间——一系列从小到大 σ 的滤波器分别和图像卷积,得到 f(x, y, σ) 的三维响应体,然后在每个空间位置上沿 σ 方向找局部极大值,把取到极大的 σ 记为该特征的尺度。

这里有个隐含条件,也是这道题真正的考点:f 必须在 σ 方向存在局部极大值。直接用 LoG 的原始响应不行,因为高斯的二阶导幅值随 σ 增大按 1/σ² 衰减,响应曲线单调下滑,argmax 永远落在最小的 σ 上。所以要做尺度归一化,用 σ²∇²G,把衰减因子抵消掉,曲线才会出现真正的峰。这个坑在第 5 章还会再提一次,它是尺度和 SIFT 那几道题的总开关。

特征尺度的物理意义,用信号与滤波器的关系解释最清楚:把图像看成信号,把 LoG 看成带通滤波器,卷积结果的幅值等于信号在滤波器通带内的能量。当信号的结构尺寸(比如一个亮斑的半径)与 LoG 的零交叉半径重合时,正负瓣刚好把亮斑夹住,内积取到最大。尺度 σ 因此不是抽象的编号,它直接对应「这个特征有多大」。

3.3 SIFT 双塔:四处简化到底省了什么

SIFT 的「双塔」指高斯金字塔和差分金字塔(DoG)。高斯金字塔按 Octave(组)和 Scale(层)组织:每组图像尺寸减半,组内用按 k 倍递增的 σ 逐层平滑。这模仿了人类视觉近大远小、近清晰远模糊的特点,也为后面的尺度不变性打底。DoG 则是同组相邻两层相减,近似等于 (k−1)σ²∇²G 与图像的卷积,也就是 LoG 的廉价替身。

四处简化计算,答的时候要一条条对上是省了哪一步:

  1. 层内递推。两个高斯核卷积仍是高斯核,方差相加。所以 kσ 尺度的结果,可以由已经算好的 σ 尺度结果再卷一个 σ' = σ·√(k²−1) 得到,不必从原图重算大核。
  2. 跨组下采样。组与组之间图像尺寸减半,用同样大小的小核就能覆盖两倍的尺度范围,避免构造超大核。
  3. 跨组复用。Octave2 里的 2σ 层,可以直接由 Octave1 倒数第三层的卷积结果下采样一倍得到,省掉一次完整的平滑。
  4. DoG 替代 LoG。卷积换成逐元素减法,代价从 O(F²) 降到 O(1),这是四重简化里收益最大的一条。

再补一句常被漏掉的:极值检测是在 DoG 金字塔的三维邻域里做的,每个点要和同层的 8 个邻居、上下层各 9 个邻居共 26 个点比较,同时得到空间位置和尺度。

3.4 方向投票与 4×4×8 描述子

关键点定下来之后要先定方向,才能拿到旋转不变性。以关键点为圆心、1.5σ 为半径取圆形邻域,用一个高斯窗给邻域内的像素加权(离中心越近权重越大),统计每点的梯度方向,把 0~360° 划成 36 个 bin(每 10° 一个),投票权重 = 梯度幅值 × 高斯权重。直方图的峰值方向就是主方向;如果还有别的 bin 超过主峰 80%,就再生成一个辅方向的关键点,同一位置出多个方向,能显著提高匹配的稳定性。

描述子生成,假设关键点位置、尺度、方向都已确定:

  1. 把邻域按主方向旋转,消除旋转差异。
  2. 取 16×16 的窗口,分成 4×4 共 16 个子块,每块 4×4 像素。
  3. 每个子块内统计 8 个方向的梯度直方图,得到 8 维向量。
  4. 16 个子块串起来,得到 4×4×8 = 128 维描述子。
  5. 归一化:除以向量模长去掉光照线性变化,再把大于 0.2 的分量截断后重新归一化,抑制非线性光照和饱和。

原稿里用「m 个 n 维向量」描述这一步,方向是对的,落到具体数就是 m=16、n=8、总维 128。手写时把 16×16、4×4、8 这三个数写清楚,比写符号有用。

3.5 视觉词典与纹理基元:BoW 那条支线

视觉词典是把 SIFT 从「点匹配」推到「图检索」的关键一步。流程是:对训练集每张图提 SIFT,得到一堆 128 维描述子;用 K-means 聚成 K 个簇(K 通常取几百到几千);每个簇心就是一个视觉词,所有簇心构成词典。之后一张图就被表示成「每个视觉词出现多少次」的直方图,配合 tf-idf 加权和倒排表,就能做大规模检索。

纹理基元(Texton)是同一套思路换到像素级。每个像素用滤波器组的响应拼成一个向量(比如 48 维),对所有像素向量聚类得到 K 个 texton;再用滑动窗口统计窗口内各 texton 的出现次数,得到直方图;两个窗口的直方图接近,就认为它们属于同一种纹理区域。和 BoW 的差别只在于描述单元是「关键点」还是「像素」,聚类、量化、直方图这三步完全同构。理解这一点,第 14、15 两道题可以合并记忆。

4. 线性分类器、卷积核与 R-CNN 三代:参数到底怎么数

这一章是全篇计算量最集中的地方。W 的两种视角决定了你怎么理解分类,损失函数决定了怎么评价 W,卷积尺寸公式决定了网络能不能搭起来,检测三代演进决定了这些积木怎么拼成一个系统。每个点都能落到可验证的数字上。

4.1 W 的两个视角与两个损失函数

得分函数 s = f(x, W) = Wx,其中 x 是展平后的图像向量(CIFAR 量级是 3072 维),W 是 C×D 的矩阵,C 是类别数。代数视角:W 的每一行是一个模板,和 x 做内积,内积越大说明图像与该模板越像,所以训练出来的 W 每一行可视化出来就是该类别的「平均长相」。几何视角:W 的每一行是超平面的法向量,w_i·x = 0 是高维空间里的一张超平面,得分正负表示样本落在法向的哪一侧,得分绝对值正比于到平面的距离。加上偏置 b 就是把超平面从过原点平移到不过原点。

这两种视角是同一件事的两种说法,答题时都要写,因为后面接隐藏层时用的是代数视角(模板组合),接 SVM 损失时用的是几何视角(间隔)。

Softmax + 交叉熵:先做 p_i = e^{s_i} / Σ_j e^{s_j} 把得分变成概率,损失 L = −log p_y。数学解释是最小化预测分布与真实分布(one-hot)之间的交叉熵,等价于最小化两者的 KL 散度,也等价于对正确类别做极大似然估计。梯度形式极简:∂L/∂s = p − y,预测概率减真实标签,所以反向传播时这一层的代码往往只有一行。数值实现上要先减去最大得分再取指数,否则 e^{s} 容易溢出。

铰链损失(SVM):L = Σ_{j≠y} max(0, s_j − s_y + 1)。含义是只要正确类别的得分比错误类别高出至少 1 分的间隔,就不产生损失;一旦违反间隔,就对每个违约项线性计罚。和交叉熵的差别在于它对「已经分对且有余量」的样本完全不关心,梯度是常数而不是随概率衰减,因此对噪声和困难样本的敏感度不一样。

损失出发点梯度特性典型场景
Softmax + 交叉熵概率分布匹配、极大似然p − y,随置信度自适应多分类主流选择
铰链损失(SVM)最大间隔违约项梯度恒为 1,非违约项为 0小样本、需要间隔裕度时

参数更新方向就是梯度的反方向:W ← W − lr·∂L/∂W,前提是这一步里其他变量固定不动(比如学习率、数据批次),否则「沿负梯度走一定下降」这个前提不成立。

4.2 隐藏层:从特征变换到分布式表示

单层线性分类器的问题是只能画直线(高维里是超平面),异或这种线性不可分的数据直接卡死。加一个隐藏层做的第一件事是特征变换:把输入从原始空间映射到另一个空间,在新空间里原本纠缠的样本变得线性可分。这是「能」的部分。

代数视角再深一层:假设 10 分类,单层线性模型每个类别只能学出一个模板 w_i,一共 10 个模板,如果某个类别本身长得就很杂,一个模板根本表达不了。单隐层 100 个单元相当于学了 100 个可共享的模板,10 个类别共用这 100 个模板,每个类别可以由其中若干模板组合而成,这就是分布式表示。代价是模板多了以后,逐层手推梯度变得不现实,反向传播才成为必需品——它是让这条路走通的计算工具,不是另一个独立的技巧。

顺带说清楚几个和训练相关的判断。批块不是越小越快:小到一定程度,单步时间被算子启动开销和访存主导,吞吐不再提升,反而因为梯度噪声太大收敛变差。小批量之所以有助于训练,是因为全批量在梯度接近 0 的平坦区会停住,而每个小批量的损失曲面略有差异,噪声有机会把参数推離停滞点。Momentum 则是给更新加惯性:v ← μv − lr·g,W ← W + v,μ 常取 0.9,沿一致方向累积加速,在震荡方向上相互抵消,还能帮助冲过鞍点。

4.3 输出尺寸、参数量与感受野:三个必须一次算对的式子

卷积输出尺寸、参数量、感受野是考卷上最容易算错的三样,也是最容易用脚本自检的三样:

def out_size(n, f, s=1, p=0): """n: 输入边长, f: 核边长, s: 步幅, p: 填充""" return (n + 2 * p - f) // s + 1 def n_params(f, c_in, c_out, bias=True): """卷积层参数量 = (F*F*C_in + 1) * C_out""" return (f * f * c_in + (1 if bias else 0)) * c_out def receptive_field(ks, ss): """ks/ss: 各层核大小与步幅列表,返回最终感受野""" r, jump = 1, 1 for k, s in zip(ks, ss): r += (k - 1) * jump jump *= s return r print(out_size(224, 7, 2, 3)) # 112,AlexNet 首层 print(n_params(3, 3, 64)) # 1792 print(receptive_field([3, 3, 3], [1, 1, 1])) # 7,三层 3x3 等价于一层 7x7

out_size里向下取整是因为边界不足一个核宽时直接丢弃;n_params里的 +1 是偏置,考试时若题目问「权重参数」要去掉它;receptive_field用的是累积步幅 jump,而不是简单把核大小连加,原稿里写的 R_n = R_{n−1} + (K_n − 1)·stride 是步幅全为 1 时的简化版,一旦网络里有 stride=2 的层就会算错,这是血泪经验。

有了这三个式子,「小卷积核为什么好」就能算给它看。输入输出通道都是 C,三层 3×3 堆叠的参数量是 3×(3×3×C)×C = 27C²,感受野 7×7;一层 7×7 的参数量是 49C²,感受野同样是 7×7。前者参数少了近一半,中间还夹了三次 ReLU,非线性更强,判决函数更有区分度。唯一代价是中间特征图要占显存,反向传播时内存开销更大。

1×1 卷积的价值也在同一套语言里:它不改变空间尺寸,只做通道方向的线性组合再激活,等于在通道维度上做了一次全连接,用来升维、降维、跨通道信息融合。在瓶颈结构里先用 1×1 把通道压到 1/4,做完 3×3 再升回去,参数量能掉一个量级。

4.4 感受野、权值共享与残差:网络为什么能搭深

从神经计算视角看,感受野就是某个神经元在输入图上能「看到」的那块区域;权值共享是说同一层的滤波器系数在空间上滑动时保持不变,不同颜色的连线对应不同滤波器,相同颜色的连线共用同一组权重。这两条合起来把全连接层的参数量从「像素数 × 神经元数」压到「核大小 × 通道数」,也是卷积天生具备平移等变性的来源。

残差模块要解决的不是过拟合,而是退化:层数加深后训练误差反而变大。设计思想是恒等映射,输出写成 H(x) = F(x) + x,网络只需要学残差 F(x),把 F 学成 0 比把 H 学成 x 容易得多。捷径连接带来的第二个好处是梯度通路:反向传播时梯度可以沿着 x 这条支路直接回传,避免连续穿过多个小梯度层导致的衰减。

AlexNet 的五个特点按「反梯度消失、抗过拟合、稳训练」三类记更牢:ReLU 替代 Sigmoid 抑制饱和区梯度消失;数据增广扩充样本;Dropout 取 0.5;batch size 128 配 SGD;Momentum 取 0.9 加速收敛。顺带把 Sigmoid 为什么梯度消失说清:σ' = σ(1−σ),最大值只有 0.25,落在饱和区时接近 0,反向传播连乘几次就指数衰减。数据增广对 CNN 的意义和 SIFT 不同——SIFT 靠构造尺度空间和主方向天生具备尺度、旋转不变性,CNN 没有,只能靠翻转、裁剪、颜色抖动把不变性「喂」进去。Dropout 的作用机制则有两层解释:训练时随机失活相当于训练了指数级的子网络,推理时做集成;同时迫使神经元不能依赖某个特定同伴,学到更独立的特征。

4.5 R-CNN 三代演进与 RPN、NMS、AP

R-CNN 的思路是把检测拆成「找候选框」和「判类别」两段。Selective Search 生成约 2000 个候选区域,每个裁剪缩放到固定尺寸(227×227),独立送进 CNN 提特征,再用 SVM 分类、线性回归微调边框,最后 NMS 去重。问题很致命:2000 个区域各自过一遍卷积,特征重复计算到离谱,一张图要几十秒;特征要落盘,存储爆掉;CNN、SVM、回归器分三阶段训练,流程割裂。

Fast R-CNN 的改法是把卷积提到最前面:整图只过一次 CNN 得到特征图,候选区域按比例映射到特征图上,用 RoI Pooling 把每个区域池化成固定尺寸(如 7×7),后面接全连接分成分类和回归两个分支,用多任务损失(分类交叉熵 + 平滑 L1)联合训练。计算量和存储都断崖式下降,但候选框仍然靠 Selective Search,这段 CPU 上的计算成了新瓶颈。

Faster R-CNN 直接把候选框生成也塞进网络,即 RPN。特征图上做 3×3 滑窗,每个位置铺 k 个锚点(常见 3 种尺度 × 3 种长宽比 = 9 个),每个锚点输出两样东西:2 个分类得分(目标/背景)和 4 个边框回归偏移。所以 RPN 的输出张量是 2k×H×W 和 4k×H×W,原稿例子里 k=1、特征图 20×15,于是得到 1×20×15 和 4×20×15。训练时按 IoU 划分正负样本(常取 >0.7 为正、<0.3 为负,中间丢弃),每张图采 256 个锚点、正负大致 1:1。RPN 和检测头共享同一张特征图,整个流程端到端,候选框数量和质量都优于 Selective Search。

NMS 的作用是清理重复框:把所有候选框按得分排序,取最高分那个保留,计算它和其余框的 IoU,超过阈值的删掉,然后在剩下的框里重复这一过程,直到清空。AP 的计算则是另一条线:按得分从高到低逐个把框判为正,每判一个就更新一次 precision 和 recall,以 recall 为横轴、precision 为纵轴画 PR 曲线,对曲线下的面积做插值积分就是该类 AP,各类取平均得到 mAP。

最后是全卷积与转置卷积这一对。FCN 把分类网络末端的全连接换成卷积,输出一张热力图而不是一个向量,好处是任意尺寸输入、保留空间位置;问题是多次下采样后分辨率掉得厉害,边界粗糙。转置卷积负责把特征图放大回去:它不是卷积的逆运算,而是把输入元素按步幅散布到输出网格上再补零卷积,输出尺寸 = (H−1)×S − 2P + K + output_padding。配合跳级连接把浅层的高分辨率特征和深层的语义特征相加,分割边缘才立得住。

5. 避坑:五个让答案看着对、实际错的手算细节

这一章列的五个坑,共同点是「写出来的句子没错,但数算错了」或者「公式没错,但前提漏了」。它们在任何一份参考答案里都不会显眼,却能在手算题上直接扣分,或者让你复现出来的结果和资料对不上。

5.1 滤波与特征里的三个参数陷阱

坑一:把 σ 当成方差。现象是核宽算出来差一个量级,σ=2 时按「方差为 2」推 janela 半径,得到 6×2=12 这种奇怪的偶数尺寸。原因在定义:高斯分布写成 exp(−x²/(2σ²)),σ 是标准差,方差是 σ²;3σ 原则里的 3σ 也是标准差的三倍。解决办法是统一用2*ceil(3σ)+1生成奇数核,写答案时把「σ 为标准差,窗口边长取 6σ 覆盖 99.7% 能量」这句话带上,别只写「6σ」。

坑二:以为框架的卷积层是数学卷积。现象是手推一个 Sobel 核的输出,符号和nn.Conv2d跑出来的相反,或者特征图整体上下颠倒。原因是深度学习框架实现的是相关,核不翻转;对称核看不出差别,非对称核一验就露馅。解决方式有两种:要么在手推时也按相关算,保持和代码一致;要么明确写「数学卷积需将核旋转 180°」,两个口径不要混用。做边缘检测时还要注意梯度方向的定义,G_x 取正负不同,边缘的极性就相反。

坑三:尺度空间不做归一化,极值永远落在最小 σ。现象是画出的 f(σ) 曲线单调下降,argmax 永远是最小的那一层,答「自动尺度选择」时说不出所以然。原因是 LoG 的响应幅值随 σ 增大按 1/σ² 衰减,这个衰减盖住了真正的结构响应。解决办法是用归一化的 σ²∇²G,或者在 SIFT 里绕开——用 DoG 加 26 邻域比较,同时依靠金字塔的分组结构把尺度范围铺开。答题时补一句「需对尺度归一化,否则响应随 σ 单调衰减,不存在局部极大」,这句是区分背没背过的分水岭。

5.2 检测与训练里的两个工程坑

坑四:锚点坐标和特征图尺度没对齐。现象是 RPN 的回归损失怎么训练都不降,预测框系统性偏移,可视化出来所有框都缩在左上角一小块。原因是锚点定义在原图坐标系上,而特征图经过多次下采样,stride 可能是 8 或 16;把特征图上的位置直接当原图坐标用,框的位置就整体缩小了。解决办法是先算 stride = 输入边长 / 特征图边长,锚点中心坐标乘以 stride 回到原图尺度,再和 GT 算 IoU 与回归目标,回归的四个偏移量也要用同样的尺度归一化。

坑五:NMS 阈值照搬默认值。现象是密集场景(人群、货架)漏检严重,或者反过来同一目标留下好几个框。原因是 IoU 阈值 0.5 对相邻目标太狠,两个真目标挨得近就被当成重复框删掉;阈值放太松又清不干净。解决办法是按验证集调,通常在 0.3~0.7 之间扫一遍取最优点;密集场景可以改用 soft-NMS(按 IoU 给邻近框降分而不是直接删除),另外 NMS 要在类别内做而不是跨类做,否则两个不同类别的重叠目标会互相压制。

顺带补一个训练侧的观察,很多人第一次调 batch size 都会踩:把小批量当成「越小越快」。实际测下来,从 64 降到 1,单个 epoch 的总时间反而变长,因为 GPU 的并行度喂不饱,算子启动开销占了大头。判断快慢要看吞吐(样本/秒),不是看单步耗时;同时 batch 太小会让梯度噪声压过信号,收敛曲线抖得没法看。

6. 合上资料自测:用六个问题把 41 条考点串成一条线

背完 41 条不等于能答,能答不等于能推。我自己的检验方式是不看资料,按因果链口述六个问题,每个问题都必须能接上下一个:

序号自测问题答不出来的话,回去补哪块
1相关和卷积差在哪,为什么框架不翻转核第 2.1 节的验证脚本
2高斯核的 size 和 σ 是什么关系,为什么 size 翻倍结果差不多3σ 截断与归一化
3Canny 的细边缘和双阈值各自解决什么问题非极大值抑制 + 滞后连接
4尺度空间为什么必须归一化才找得到极值σ²∇²G,别只记 LoG
5三层 3×3 为什么等价于一层 7×7,参数量差多少感受野递推与 27C² vs 49C²
6Faster R-CNN 相比 Fast R-CNN 到底省掉了什么Selective Search 的瓶颈与 RPN

这六个问题能顺下来,剩下的细节(Harris 的 R 指标、SIFT 的 128 维、AlexNet 的五个特点、AP 的积分方式)都是挂在这条链上的枝节,回忆的时候有钩子。

再补一个更硬核的验证手段:把关键公式写成可执行的脚本,用随机数据跑一遍,看数量级对不对。比如算一层 3×3、输入 3 通道、输出 64 通道的卷积参数量,手算 (3×3×3+1)×64 = 1792,脚本跑出来不是这个数就说明公式记错了;再比如给一批随机得分,分别算交叉熵和铰链损失,检查交叉熵在正确类得分远高时是否趋近 0、铰链损失在间隔满足时是否严格为 0。公式能跑通,才算真的记住。

从那以后我每次复习这类汇总,都强制走一遍「口述因果链 + 脚本验算」两步,哪怕时间只够验三条公式,也比从头到尾读三遍强——读的时候大脑会骗你说懂了,只有算出来的数字不会。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询