1. 从像素到频谱:换个角度看人群计数
如果你做过人群计数,或者对计算机视觉中的密集目标检测有所了解,大概率会和我一样,对一个问题感到头疼:人群的尺度变化。一张图里,近处的人脸清晰、个头大,远处的人则模糊、密集、个头小。这种巨大的尺度差异,让模型在训练时顾此失彼,学到的特征要么偏向大目标,要么偏向小目标,最终导致计数精度上不去。
传统的解决方案,比如多尺度特征融合、注意力机制、或者设计复杂的网络结构,本质上都是在像素域里“硬碰硬”。我们试图让网络学会同时“看”清楚远处和近处的人,这就像要求一个人既要看清百米外的蚂蚁,又要看清眼前的大象,非常困难。所以,尽管模型越来越复杂,计算量越来越大,但性能提升的边际效应也越来越明显。
去年在CVPR 2022上读到一篇论文,标题是《Crowd Counting in the Frequency Domain》,当时就眼前一亮。这个思路非常巧妙,它没有继续在像素域里卷网络结构,而是换了个战场——频域。简单来说,它把图像从我们熟悉的像素空间,转换到了频率空间去分析。这个转换,就是傅里叶变换。
为什么是频域?这里有个很直观的生活类比。想象一张人群的图片,远处密集的人群,在像素域里就是一堆高频变化的、密密麻麻的纹理和边缘;而近处清晰、稀疏的人,则包含了更多低频的、平缓的信息(比如大块的衣物颜色、皮肤区域)。在频域里,高频和低频信息是分离开的。这篇工作的核心洞见就是:人群的尺度差异,在频域里可以转化为不同频率分量的差异。处理尺度变化,就变成了处理不同频率分量的问题,这在数学上有很多成熟且高效的工具。
我花了不少时间复现和研究了这篇工作,发现它不仅仅是一个“奇技淫巧”,而是为密集场景理解提供了一个全新的、更本质的视角。它用相对轻量的网络,达到了当时SOTA的性能。更重要的是,它启发我们,对于一些在像素域里棘手的问题(如尺度、遮挡),转换到频域去思考,或许能打开一扇新的大门。这篇文章,我就来详细拆解一下这个“频域人群计数”框架,从原理到实现,再到我复现过程中的一些心得和踩过的坑。
2. 频域视角:为什么傅里叶变换能解决尺度问题?
要理解这篇工作,我们得先抛开复杂的公式,从直觉上搞懂傅里叶变换和频域分析到底在干什么。
2.1 从图像到频谱:信息的重新组织
我们平时看到的数字图像,是由一个个像素点组成的,每个点有它的亮度(灰度图)或颜色(RGB图)。这种表示方法非常直观,我们称之为空间域表示。在空间域里,图像的信息是按照位置(坐标)来组织的。
傅里叶变换,则提供了一种完全不同的视角。它告诉我们,任何一张图像,都可以看作是由无数个不同频率、不同方向、不同振幅的“正弦波”叠加而成的。这里的“频率”,指的是图像中灰度(或颜色)变化的快慢。
- 低频分量:对应图像中变化缓慢的部分,比如大面积的天空、墙壁、平静的水面。它承载了图像的主体轮廓和大致结构。
- 高频分量:对应图像中变化剧烈的部分,比如物体的边缘、纹理、细节、噪声。它承载了图像的精细细节和轮廓信息。
对一张图像做二维离散傅里叶变换(2D-DFT),我们就能得到它的频谱图。频谱图上的每一个点,代表了一个特定频率和方向的“正弦波”成分的强度(振幅)和相位。通常,我们会把零频率(直流分量,代表图像的平均亮度)移到频谱图中心,低频在中心附近,高频在四周。
2.2 人群尺度的频域解释
现在,把这个概念套用到人群图像上。
- 大尺度目标(近处、稀疏的人):在图像中占据区域较大,轮廓清晰,内部颜色/纹理相对平缓。这意味着,描述一个大尺度目标,需要更多的低频信息(勾勒主体轮廓)和相对较少的中高频信息(勾勒清晰边缘)。
- 小尺度目标(远处、密集的人):在图像中占据区域很小,可能只有几个像素,彼此紧挨。他们的轮廓和细节混合在一起,形成了一种高频变化的、密集的纹理模式。描述这些小目标,高频信息变得至关重要。
因此,人群的尺度变化问题,在频域中可以被建模为:不同尺度的人群目标,其能量(信息)主要分布在频谱的不同频带区域。大目标主导低频,小目标主导高频。传统像素域方法难以区分这些混杂在一起的信息,而频域方法天然地将它们分开了。
这篇CVPR 2022论文的核心思想,就是利用这种分离性。它不再让网络直接处理混合了所有尺度信息的原始像素,而是先通过傅里叶变换将图像转换到频域,然后在频域里设计网络模块,有针对性地处理不同频带的信息,最后再反变换回空间域,生成密度图。这个流程,相当于让网络学会了“听音辨位”——通过分析图像信号的“频率成分”来定位和计数不同尺度的人。
3. FDCNet框架详解:如何构建频域人群计数网络?
论文提出的网络叫做FDCNet。它的整体架构清晰而优雅,主要包含三个关键部分:频域转换模块、多频带表征学习模块、以及频域-空间域融合模块。下面我们逐一拆解。
3.1 整体流程与频域转换模块
整个网络的输入是一张人群图像,输出是预测的人群密度图。其核心流程可以概括为:
- 空间域 -> 频域:将输入图像通过快速傅里叶变换(FFT)转换到频域,得到频谱(包含振幅和相位)。
- 频域处理:在频域内,对频谱进行加工和处理,这是网络学习的核心部分。
- 频域 -> 空间域:将处理后的频谱通过逆傅里叶变换(IFFT)转换回空间域,得到初步的特征图。
- 空间域细化:对转换回来的特征图进行进一步的空间域卷积细化,最终生成密度图。
这里第一个技术细节就来了:我们转换的是整张图,还是特征图?论文采用的是后者,这也是更合理的做法。直接转换原始RGB图像,频谱会包含大量与任务无关的背景噪声。更常见的做法是,先用一个浅层的CNN骨干网络(如VGG的前几层或一个轻量级CNN)从原始图像中提取一个初步的、深度的特征图F ∈ R^(C×H×W)。然后对这个特征图F进行傅里叶变换。
具体操作上,对于特征图的每一个通道c,我们独立进行2D-FFT:ℱ_c = FFT2D(F_c)这样我们得到了一个复数频谱ℱ ∈ C^(C×H×W),其中包含了振幅谱A = |ℱ|和相位谱P = angle(ℱ)。振幅谱告诉我们每个频率成分的“强度”,相位谱则告诉我们这些频率成分的“位置”关系。
实操心得一:特征图尺寸与FFT效率FFT算法对输入尺寸有要求,通常处理
2^n的尺寸效率最高。在数据预处理或网络设计时,可以考虑将输入图像或特征图Resize到如256x256这样的尺寸。如果必须保持原图尺寸,需要注意FFT的计算开销。在实际编码中,PyTorch和TensorFlow都提供了torch.fft.fft2和tf.signal.fft2d等函数,使用起来非常方便。一个常见的坑是忘记处理复数结果,后续的卷积层通常需要实数输入,所以我们需要将复数频谱拆分为振幅和相位(作为两个实数通道),或者取其振幅谱作为输入。
3.2 核心创新:多频带表征学习模块
这是FDCNet的灵魂。既然不同尺度目标的信息存在于不同频带,那么我们就应该区别对待它们。论文设计了一个频带分解模块,将整个频谱ℱ划分为多个不同频率范围的子带(Sub-bands)。
如何划分频带?一种直观的方法是按照径向距离(即频率高低)划分。以频谱中心为原点,画多个同心圆环,每个圆环区域对应一个频带。例如:
- 低频带:最内圈的圆,包含最低频的成分。
- 中低频带:第一个圆环。
- 中高频带:第二个圆环。
- 高频带:最外圈的圆环,包含最高频的成分。
划分的半径可以是等间隔的,也可以根据任务自适应调整。论文中采用了可学习的频带掩码(Learnable Band Masks)。具体来说,它定义了一组可学习的参数来控制每个频带的边界,这些参数在训练中与网络其他部分一起优化,让网络自己决定如何划分频带对当前任务最有利。
分而治之的处理策略得到多个频带子谱{ℱ_1, ℱ_2, ..., ℱ_K}后,网络并不是简单地把它们扔给同一个卷积层。论文为每个频带设计了一个独立的轻量级处理子网络(通常是一两个卷积层)。这样做的理由是:
- 针对性学习:低频带子网络专注于学习与大尺度目标相关的特征(如整体形状);高频带子网络则专注于学习与小尺度、密集目标相关的特征(如精细纹理)。
- 参数效率:每个子网络只处理一小部分频率信息,因此可以设计得非常轻量(通道数少、层数浅),总体参数量增加有限,但表达能力却因专业化而大幅提升。
- 缓解混淆:避免了不同频带信息在早期就相互干扰,让梯度可以更精准地回流到负责特定尺度目标的参数上。
实操心得二:频带掩码的实现与初始化实现可学习的频带掩码时,初始化策略很重要。如果全部随机初始化,网络在训练初期可能无法有效利用频域信息。一个有效的策略是用预定义的、均匀划分的掩码作为初始值。例如,初始化4个掩码,让它们分别覆盖0-1/4、1/4-1/2、1/2-3/4、3/4-1的频带范围(归一化后的径向距离)。这样,网络从一开始就有一个合理的频带划分,然后在训练中微调边界。在PyTorch中,这可以通过定义一组可训练的
nn.Parameter来实现,并在前向传播中根据这些参数生成软掩码(soft mask)应用于频谱。
3.3 信息融合与空间域重建
各个频带子网络处理完后,我们会得到一组增强后的频带子谱{ℱ‘_1, ℱ‘_2, ..., ℱ‘_K}。接下来需要将它们融合起来,并转换回空间域。
频域融合最简单的融合方式是将所有处理后的子谱直接相加:ℱ‘_fused = Σ ℱ‘_k。但论文采用了更精细的方式,引入了一个跨频带注意力机制。这个注意力模块会计算不同频带特征之间的相关性,并生成权重,对各个频带的贡献进行加权求和。例如,对于某个空间位置,如果高频信息特别重要(说明该处小目标密集),则注意力机制会给高频子谱分配更高的权重。
逆变换与空间域细化将融合后的频谱ℱ‘_fused通过逆傅里叶变换(IFFT)转换回空间域,得到初步的特征图F‘_spatial。F‘_spatial = IFFT2D(ℱ‘_fused)注意,此时我们得到的F‘_spatial是一个复数,我们通常取其幅值(或实部)作为输出。这个特征图已经蕴含了经过频域增强的多尺度信息。
然而,逆变换回来的特征图可能丢失了一些纯粹的空间位置关系(因为相位信息在处理中可能被简化或干扰)。因此,论文在最后加入了一个空间域细化模块。这个模块通常由几层标准的卷积层组成,它的作用是:
- 恢复精确的空间定位:基于频域特征提供的丰富内容信息,在空间域进行微调,使预测的密度图人头位置更精准。
- 整合上下文信息:通过卷积的感受野,整合更大范围的上下文信息,进一步提升计数精度。
- 输出密度图:将通道数映射为1,并上采样到输入图像尺寸,生成最终的人群密度图。
4. 复现之路:代码实现关键与避坑指南
理论很美好,但把论文变成可运行的代码,中间有不少细节需要厘清。我在复现FDCNet时,主要遇到了以下几个关键问题。
4.1 数据预处理与密度图生成
人群计数是一个密集预测任务,其监督信号是密度图。密度图的生成质量直接影响模型性能。
高斯核参数的选择主流方法是使用几何自适应高斯核。对于每个人头标注点(x_i, y_i),以其与最近的k个邻居(通常k=3)的平均距离的β倍(如0.3)作为高斯核的标准差σ_i。这样,密集区域的高斯核小且重叠多,稀疏区域的高斯核大。D(x, y) = Σ_i N((x, y); (x_i, y_i), σ_i^2)这里的N是二维高斯分布。
踩坑记录一:高斯核归一化与求和一个容易忽略的细节是,每个高斯核的积分(即总和)应该是1,以确保图像总人数等于密度图积分之和。在实现时,要确保用于生成高斯核的矩阵是归一化的。另外,在非常密集的区域,多个高斯核叠加后,某些像素点的密度值可能非常大,这可能导致训练不稳定。可以考虑对最终生成的密度图进行全局归一化(如除以最大值),或者在损失函数中使用对数形式的损失(如MSELoss的变种)来缓解。
输入图像尺寸与增强人群计数数据集(如ShanghaiTech, UCF-QNRF)的图像尺寸不一。为了批处理,需要统一尺寸。常见的做法是将图像的最长边缩放到一个固定值(如512或1024),同时等比例缩放短边和标注点坐标。然后进行随机裁剪(如裁剪成512x512的patch)用于训练。这比直接Resize到固定尺寸更能保持图像中人群的尺度分布。数据增强方面,除了常规的翻转、旋转,随机缩放对于提升模型尺度鲁棒性非常有效。
4.2 频域模块的PyTorch实现细节
用PyTorch实现FFT/IFFT和频带处理需要小心处理张量的维度、数据类型和设备。
FFT/IFFT的正确使用
import torch import torch.fft def fft2d(feature_map): # feature_map: [B, C, H, W] # FFT在最后两个维度进行 fft_result = torch.fft.fft2(feature_map, norm='ortho') # 使用正交归一化 return fft_result # 输出是复数张量 [B, C, H, W] def ifft2d(spectrum): # spectrum: [B, C, H, W] (complex) spatial = torch.fft.ifft2(spectrum, norm='ortho') return spatial.real # 通常取实部作为输出注意norm='ortho'参数,它确保了FFT和IFFT是互逆且能量守恒的变换。
频带掩码的生成生成可学习的软掩码是关键。我们需要根据每个像素点到频谱中心的归一化距离r,以及可学习的边界参数,计算其属于每个频带的权重。
def create_band_masks(h, w, band_boundaries, device): """ h, w: 频谱图的高和宽 band_boundaries: 可学习参数,形状为 [K-1],表示K个频带之间的K-1个边界(归一化半径,0~1之间) """ # 创建坐标网格 y = torch.linspace(-1, 1, h, device=device) x = torch.linspace(-1, 1, w, device=device) Y, X = torch.meshgrid(y, x, indexing='ij') # 计算径向距离 (0到sqrt(2)之间) r = torch.sqrt(X**2 + Y**2) / torch.sqrt(torch.tensor(2.0)) # 归一化到[0, 1] masks = [] # 假设band_boundaries已排序,例如 [0.25, 0.5, 0.75] for K=4 boundaries = torch.cat([torch.tensor([0.0], device=device), torch.sigmoid(band_boundaries), # 用sigmoid约束在(0,1) torch.tensor([1.0], device=device)]) for i in range(len(boundaries)-1): low = boundaries[i] high = boundaries[i+1] # 使用平滑过渡(如余弦窗)生成软掩码,避免硬截断带来的频谱泄露 mask = 0.5 * (1 + torch.cos(torch.pi * (r - (low+high)/2) / (high-low))) mask = torch.clamp(mask, 0, 1) masks.append(mask) # masks: list of [H, W] tensors return torch.stack(masks, dim=0) # [K, H, W]这个实现使用了余弦窗来创建平滑过渡的软掩码,比硬阈值(0或1)更有利于梯度传播。
4.3 损失函数设计与训练技巧
人群计数常用的损失函数是像素级的均方误差(MSE Loss):L_mse = ||D_pred - D_gt||^2但MSE Loss对异常值(密度极高的区域)比较敏感。一个改进是使用贝叶斯损失或计数感知损失。FDCNet论文中结合了MSE Loss和一个基于频域特性的正则化项。
频域一致性损失这是FDCNet的一个创新点。除了在空间域约束密度图,它还提出在频域也施加约束。思想是:预测的密度图D_pred和真实密度图D_gt经过FFT后,它们的振幅谱应该相似。这可以迫使网络不仅在空间上,也在频率分布上学到正确的模式。L_freq = || |FFT(D_pred)| - |FFT(D_gt)| ||^1(使用L1损失) 总损失为:L_total = L_mse + λ * L_freq,其中λ是平衡超参数。
踩坑记录二:损失平衡与梯度爆炸
L_freq的引入需要小心。频域振幅值的动态范围可能很大(低频分量值很大),直接计算L1或L2损失可能导致梯度爆炸或不稳定。一个实用的技巧是对振幅谱进行对数压缩:log(1 + amplitude),或者在计算损失前对振幅谱进行归一化(如除以最大值)。此外,λ的值需要仔细调优,从一个小值(如0.01)开始尝试。在我的实验中,λ=0.05左右通常能取得不错的效果,既能引入频域约束,又不至于主导训练。
训练策略
- 优化器:AdamW优化器(带权重衰减)通常比Adam更稳定。
- 学习率:使用余弦退火或带热重启的余弦退火(CosineAnnealingWarmRestarts)调度器,有助于模型跳出局部最优。
- 梯度裁剪:由于频域操作可能带来数值不稳定,对梯度进行裁剪(
torch.nn.utils.clip_grad_norm_)是个好习惯。 - 验证指标:除了看训练损失,一定要在验证集上监控平均绝对误差(MAE)和均方根误差(MSE),这是人群计数的标准评估指标。
MAE = mean(|C_pred - C_gt|)反映计数准确性,MSE = sqrt(mean((C_pred - C_gt)^2))对大的误差更敏感。
5. 实验结果分析与扩展思考
按照论文的设置进行复现和训练后,我在ShanghaiTech Part_A和Part_B数据集上进行了测试,结果与论文报告的基本吻合。FDCNet在保持较低参数量(约15M)的同时,MAE和MSE指标达到了与当时最先进的纯空间域模型相当甚至更优的水平。这验证了频域方法的有效性。
5.1 频域方法的优势与局限
优势:
- 尺度鲁棒性:这是最核心的优势。通过频带分离处理,模型能更优雅地应对尺度变化,在密集小目标和稀疏大目标上表现更均衡。
- 全局信息捕获:傅里叶变换是全局操作,一个频率分量对应整张图像的某种模式。这有助于模型捕获图像的全局统计特性,对光照变化、背景杂乱等有一定抑制作用。
- 参数效率:通过频带分解和轻量子网络,可以用较少的参数实现强大的多尺度表征能力。
- 启发性:为视觉任务提供了一个新的表示空间,打开了新的研究方向。
局限与挑战:
- 计算开销:虽然FFT/IFFT算法很快(O(N log N)),但相比于纯粹的卷积操作,引入额外的变换和复数运算还是会增加一些计算成本。在移动端部署时需要权衡。
- 相位信息处理:相位谱携带了至关重要的位置信息,但直接处理复数频谱在神经网络中比较麻烦。大多数工作(包括FDCNet)主要利用振幅谱,或对振幅和相位进行简单处理,如何更有效地联合利用振幅和相位信息仍是一个开放问题。
- 频带划分的玄学:频带的数量、划分方式(径向、角度)都是超参数。虽然可以用可学习掩码,但其初始化方式和优化过程仍需经验指导。
- 对不规则目标的泛化:频域分析假设信号是平稳的或周期性的,但对于极度不规则、非周期性的目标分布,其有效性可能会打折扣。
5.2 可能的改进方向与扩展应用
基于FDCNet的思路,我们可以从几个方向进行扩展:
1. 更先进的频域特征学习器FDCNet对每个频带使用了简单的卷积块。我们可以探索更强大的架构,例如:
- 频域注意力:在频域内设计自注意力机制,让不同频率成分之间进行交互。
- 图神经网络:将不同频率分量视为图上的节点,利用GNN来学习它们之间的关系。
- 频域动态卷积:根据输入频谱的内容,动态生成卷积核的权重。
2. 多域融合的混合架构纯粹的频域方法可能丢失了空间域的某些局部细节。一个更稳健的方案是混合架构:让网络同时拥有空间域支路和频域支路,并在不同层次进行特征融合。空间域支路负责捕捉精确的局部细节和位置,频域支路负责提供全局的、尺度不变的特征。两者互补,可能产生“1+1>2”的效果。
3. 扩展到其他密集预测任务频域思想不仅适用于人群计数,任何受尺度变化困扰的密集预测任务都可能受益,例如:
- 车辆计数:交通监控场景中,车辆尺度变化同样剧烈。
- 细胞计数:生物医学图像中,细胞的聚集和分散形态。
- 显著性检测:显著物体的大小变化。
- 语义分割:特别是对于具有重复纹理或不同尺度同类物体的场景。
4. 与小样本/弱监督学习的结合频域特征可能提供了一种更紧凑、更本质的图像表示。在标注数据稀缺的情况下,利用频域先验(例如,自然图像的频谱能量通常集中在低频)来引导模型学习,或许能减少对大量标注的依赖。
复现和研究FDCNet的过程,让我深刻体会到跳出固有思维框架的重要性。当我们在一个领域(像素域)陷入瓶颈时,转换到另一个数学上等价的领域(频域),问题可能会呈现出全新的、更易处理的结构。这不仅仅是多了一个工具,更是多了一种思考问题的方式。在实际项目中,当遇到特征纠缠、尺度冲突等问题时,不妨问问自己:这个问题在频域、小波域或其他变换域里,会不会变得更简单?这种跨域的思维方式,往往能带来意想不到的突破。