导读:本期聚焦于越南程序员创作的《遮挡场景下人头计数不准怎么办?密度图估计优化方法详解》,敬请观看详情。为什么密集人群中一旦出现遮挡,人头计数结果就会明显偏离真实值?密度图估计是解决人群计数的核心思路,但传统固定高斯核生成的密度图在遮挡区域无法区分重叠目标,回归出的密度峰值互相干扰,最终导致漏检和重复计数。本文从密度图生成、损失函数设计、网络结构三个层面展开,介绍几何自适应高斯核、DM-Count损失、多尺度空洞卷积和注意力机制等优化手段。结合代码示例,分析每种方法如何缓解遮挡引起的密度图模糊问题。通过改进密度图监督信号和特征提取能力,模型能够在人头相互遮挡的复杂场景中更准确地估计局部密度,从而提升整体计数精度。文章给出可直接落地的训练策略和调参建议,适合研究人群计数或安防场景落地的开发者参考。

人群计数在安防监控、公共安全预警和商业客流分析等场景中有着重要应用。密度图估计是当前解决人群计数问题的主流方法,它不直接输出一个总人数,而是回归一张与输入图像同尺寸的密度图,密度图上每个像素的值表示该位置出现人头的概率密度。对密度图求和即可得到总人数。这种方法相比直接回归一个标量人数,保留了空间分布信息,便于定位密集区域。但在实际场景中,人与人之间经常发生遮挡,尤其在视角倾斜、人群密集的情况下,后方人头的很大一部分被前方人体挡住。遮挡导致标注点附近的密度分布出现异常重叠,模型很难区分相邻的两个目标,容易把多个人头合并成一个高密度峰值,或者忽略被遮挡的微弱响应。因此,围绕遮挡情况优化密度图估计,是提升人群计数准确率的关键方向。

遮挡场景下人头计数不准怎么办?密度图估计优化方法详解

一、遮挡对密度图估计的影响在哪里

密度图估计的第一步是将稀疏的人头标注点转换为连续的密度图作为回归目标。常见做法是对每个标注点放置一个二维高斯核,把所有高斯核叠加起来形成密度图。最朴素的方式是使用固定标准差的高斯核,例如在ShanghaiTech、UCF-QNRF等数据集中,很多基线方法会采用固定σ生成密度图。这种固定σ在人群分布均匀、人头大小一致的理想场景下表现尚可,但遇到遮挡和透视变化就会暴露出明显问题。

在遮挡严重区域,多人头标注点之间的像素距离非常近。如果高斯核的σ较大,两个相邻高斯核会高度重叠,叠加后的密度图在该区域呈现一个宽而扁的峰,峰值位置甚至可能偏移到两个人头的中间。模型在训练时以这张模糊的密度图作为监督信号,自然学会把两个目标合并成一个目标,导致计数偏低。反过来,如果σ设置得很小,虽然峰值更尖锐,但被遮挡的人头由于标注点可能只落在可见的一小部分区域,响应范围过小,模型又容易忽略这些弱目标,导致漏检。因此,固定σ的高斯核在遮挡场景下无论取大还是取小都难以兼顾。

另一个影响来自密度图求和与真实人数之间的不一致。密度图回归常用的MSE损失只关心逐像素误差,不直接约束求和后的总人数。当遮挡导致密度图局部峰值模糊时,MSE损失可能很小,但求和结果却与真实人数相差较大。也就是说,模型输出的密度图在视觉上可能接近模糊的监督图,但总数并不准确。要解决这个问题,需要从密度图生成和损失函数两方面共同优化。

二、优化密度图生成:几何自适应高斯核与透视校正

几何自适应高斯核的核心思想是根据每个头标注点周围的人群密度动态调整σ。具体做法是计算当前标注点到其k个最近邻标注点的平均距离,然后用该平均距离乘以一个系数作为高斯核的σ。这样在人群密集、遮挡严重的区域,点到邻居的距离更小,σ相应变小,生成的高斯核更窄,峰值更加突出,相邻目标之间的密度峰不易合并。而在人群稀疏区域,σ变大,密度图更平滑,符合视觉感知。

下面的代码展示了如何根据标注点坐标生成几何自适应密度图。代码中使用了KDTree快速查找最近邻,并对每个点计算自适应σ。注意代码中的比较运算符已经做了转义处理,以便在网页中正确显示。

import numpy as np
from scipy.spatial import KDTree

def generate_adaptive_density_map(points, img_h, img_w, k=3, beta=0.3):
    density_map = np.zeros((img_h, img_w), dtype=np.float32)
    if len(points) == 0:
        return density_map
    tree = KDTree(points)
    distances, _ = tree.query(points, k=k+1)  # 第0个是自身
    avg_dist = distances[:, 1:].mean(axis=1)
    # 最小距离保护,避免σ为0
    avg_dist = np.clip(avg_dist, 1e-5, None)
    sigmas = beta * avg_dist
    # 生成高斯核
    for (x, y), sigma in zip(points, sigmas):
        x = int(round(x))
        y = int(round(y))
        if x < 0 or x >= img_w or y < 0 or y >= img_h:
            continue
        radius = max(1, int(3 * sigma))
        x_min = max(0, x - radius)
        x_max = min(img_w, x + radius + 1)
        y_min = max(0, y - radius)
        y_max = min(img_h, y + radius + 1)
        xx, yy = np.meshgrid(np.arange(x_min, x_max), np.arange(y_min, y_max))
        gaussian = np.exp(-((xx - x) ** 2 + (yy - y) ** 2) / (2 * sigma ** 2))
        density_map[y_min:y_max, x_min:x_max] += gaussian.astype(np.float32)
    return density_map

这种自适应σ虽然比固定σ有所改善,但仍未显式建模透视关系。透视校正需要结合场景中人的尺度变化。一种做法是先估计图像中不同位置的透视权重,例如利用行人高度或人头大小估计尺度因子,再根据尺度因子调整σ。对于遮挡严重且成像较小的远处区域,σ应该更小,密度峰更集中;对于近处尺度较大的目标,σ可以稍大。透视信息通常借助额外的标注或预训练尺度估计模型获得。实际工程中,如果无法获取透视先验,几何自适应高斯核已经能够带来明显提升。

三、损失函数改进:从逐像素回归到计数一致性

密度图回归最常用的损失是MSE,即逐像素计算预测密度图与真实密度图的平方差。MSE倾向于让模型输出模糊的密度图,因为模糊预测可以在多个位置都获得较小的像素误差。在遮挡场景中,模糊预测意味着相邻目标的峰值被抹平,计数误差增大。为此,研究者提出了多种损失函数来强化计数一致性。

DM-Count损失是一种基于最优传输的损失函数。它不再逐像素比较,而是把预测密度图和真实密度图分别归一化为概率分布,然后计算两个分布之间的最优传输代价。这样即使预测密度图的峰值位置与真实峰值有一定的空间偏移,只要整体质量分布接近,损失就不会被过度惩罚。这有助于模型在遮挡区域学习到更准确的目标数量,而不是追求每个像素完全对齐。DM-Count损失通常使用Sinkhorn算法近似计算,其核心代码如下。

import torch
import torch.nn.functional as F

def sinkhorn_distance(pred_density, gt_density, reg=1.0, num_iters=50):
    batch_size = pred_density.shape[0]
    pred_flat = pred_density.view(batch_size, -1)
    gt_flat = gt_density.view(batch_size, -1)
    # 归一化为概率分布
    pred_prob = pred_flat / (pred_flat.sum(dim=1, keepdim=True) + 1e-6)
    gt_prob = gt_flat / (gt_flat.sum(dim=1, keepdim=True) + 1e-6)
    # 计算代价矩阵,使用L2距离的平方
    n = pred_prob.shape[1]
    grid = torch.arange(n, dtype=torch.float32, device=pred_density.device)
    cost = (grid.unsqueeze(0) - grid.unsqueeze(1)) ** 2
    cost = cost / (n ** 2)
    # Sinkhorn迭代
    K = torch.exp(-cost / reg)
    u = torch.ones_like(pred_prob)
    for _ in range(num_iters):
        v = gt_prob / (torch.matmul(K, u.unsqueeze(-1)).squeeze(-1) + 1e-6)
        u = pred_prob / (torch.matmul(K.transpose(0, 1), v.unsqueeze(-1)).squeeze(-1) + 1e-6)
    transport_plan = u.unsqueeze(-1) * K * v.unsqueeze(1)
    optimal_cost = (transport_plan * cost).sum(dim=1)
    return optimal_cost.mean()

贝叶斯损失是另一种常用改进。它针对每个头标注点定义一个伯努利分布,将预测密度图在标注点邻域内的积分作为该点存在目标的概率,通过极大似然估计优化模型。贝叶斯损失直接约束每个标注点的期望计数为1,从而在遮挡区域也能保持每个目标的独立性。实际训练时,可以将MSE损失与贝叶斯损失或DM-Count损失加权组合,兼顾像素级精度和计数一致性。

四、网络结构优化:多尺度特征与注意力机制

遮挡场景中人头尺度差异很大,远处被遮挡的人头可能只有几个像素,近处完整的人头则占据较大区域。如果网络只有固定感受野,就很难同时捕捉不同尺度的目标。CSRNet使用空洞卷积构建深层网络,在不降低特征图分辨率的前提下扩大感受野。空洞卷积通过在卷积核元素之间插入空洞,使得同样层数的网络能够覆盖更大范围,适应密集人群中的小目标。

下面的代码示例定义了一个包含空洞卷积和通道注意力的模块。注意力机制帮助模型自动关注包含人头特征的通道,抑制背景通道,尤其在遮挡区域能够增强可见部分的响应。

import torch
import torch.nn as nn

class ChannelAttention(nn.Module):
    def __init__(self, in_channels, reduction=16):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(in_channels, in_channels // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(in_channels // reduction, in_channels, bias=False),
            nn.Sigmoid()
        )
    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y

class DilatedConvBlock(nn.Module):
    def __init__(self, in_ch, out_ch, dilation):
        super(DilatedConvBlock, self).__init__()
        self.conv = nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=dilation,
                              dilation=dilation)
        self.bn = nn.BatchNorm2d(out_ch)
        self.relu = nn.ReLU(inplace=True)
        self.att = ChannelAttention(out_ch)
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        x = self.att(x)
        return x

SANet则提出了尺度聚合模块,通过不同膨胀率的卷积核并行提取特征,再将多尺度特征融合。膨胀率通常设置为1、2、3、4,分别对应不同感受野。对于遮挡目标,较大的膨胀率可以看到目标周围的上下文信息,帮助判断该位置是否存在人头。不过膨胀率过大会降低对小目标的敏感度,因此需要与标准卷积配合使用。实验表明,在密集遮挡场景中,加入多尺度特征后计数误差可以降低至少10%。

五、局部密度估计与后处理校正

全局密度图回归在面对极端遮挡时仍然可能产生平滑误差。局部密度估计将图像划分为多个网格或区域,分别训练或推理每个区域的密度图,再进行拼接。这样做的好处是每个区域内的目标尺度更加一致,遮挡模式也更简单,模型可以更准确地估计局部密度。局部估计的难点在于边界目标的归属问题,通常采用滑动窗口或重叠区域融合策略。

下面的代码给出了一个简单的重叠区域融合示例。对每个局部区域预测密度图后,按照窗口位置累加,重叠部分取平均,从而避免边界效应。

import numpy as np

def fuse_local_density_maps(local_maps, positions, img_h, img_w, window_size):
    fused_map = np.zeros((img_h, img_w), dtype=np.float32)
    count_map = np.zeros((img_h, img_w), dtype=np.float32)
    for local_map, (y_start, x_start) in zip(local_maps, positions):
        h, w = local_map.shape
        fused_map[y_start:y_start+h, x_start:x_start+w] += local_map
        count_map[y_start:y_start+h, x_start:x_start+w] += 1.0
    count_map[count_map == 0] = 1.0
    return fused_map / count_map

此外,检测与回归结合也是一种有效的后处理策略。先用一个轻量级检测器定位未被遮挡的完整人头,这些检测框可以给出高置信度的计数贡献;对于检测器漏检的遮挡区域,再使用密度图回归进行补充。最终计数为检测框数量加上遮挡区域密度图求和值。这种方式将两种方法的优势结合,在遮挡严重但仍有部分可见特征的场景中表现稳定。实际部署时需要注意检测器和密度图回归模型的计算开销,可考虑共用骨干网络提取特征,分别接两个轻量头部。

综合来看,解决遮挡情况下人头计数不准,需要从密度图生成、损失函数、网络结构和后处理多个环节同时入手。几何自适应高斯核和透视校正优化了监督信号,DM-Count损失和贝叶斯损失强化了计数一致性,多尺度空洞卷积和注意力机制提升了特征鉴别能力,局部估计与检测融合则进一步缓解了全局回归的平滑误差。这些方法可以独立使用,也可以组合成统一框架。在实验评估中,使用几何自适应高斯核配合DM-Count损失,在遮挡严重的测试子集上通常能获得明显低于MSE基线的平均绝对误差。开发者可以根据实际场景的遮挡程度灵活选择优化策略,逐步提升模型在复杂人群场景下的计数精度。

人头计数密度图估计遮挡优化修改时间:2026-08-23 17:57:48

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。