简介VisDrone2021-CC 人群计数数据集面向无人机视觉监控方向的研究者与算法工程师聚焦复杂光照、天气与密集背景下的航拍人群数量估计问题可用于目标检测、密度图回归与多尺度特征融合等实验的训练与评测。压缩包共 10854 个文件以 7236 张 jpg 航拍图像和 3616 个 xml 标注文件为主体另附 2 个 py 脚本整体约 820.44MB涵盖 RGB 与热成像DroneRGBT子集标注与图像一一对应便于直接构建监督学习流程。资源内含 data_processing 预处理与评估工具可支撑图像增强、模型训练及计数指标计算帮助读者快速复现密度估计与 CNN、U-Net 类方法的实验。目前已有 1538 人学习下载适合需要真实无人机视角数据做人群计数研究与实践的读者参考使用。1. 从 Visdrone2021_CrowdCounting.rar 说起无人机视角下的人群计数到底难在哪把一份无人机航拍人群数据集解压出来第一眼往往不是“数据真多”而是“人怎么这么小”。VisDrone2021 的人群计数子任务图像来自低空无人机平台拍摄高度从十几米到上百米不等单帧里既有几十人的稀疏场景也有上千人挤在广场、集市、车站出入口的密集场景。人头顶多占十几个像素密集处彼此粘连遮挡、透视畸变、光照突变全都叠在一起。这个标题指向的正是这样一件事拿 VisDrone2021 的人群计数数据训练一个能输出人群密度图、进而积分得到人数的模型。它适合两类人——想把计数模型真正跑起来、跑出可复现指标的算法工程师以及需要评估“无人机视角计数到底能不能落地”的技术负责人。下面按数据准备、密度图建模、训练调参、踩坑排查、验证进阶的顺序讲透。2. 把 VisDrone2021 人群数据整理成可训练格式标注解析与密度图生成2.1 先搞清楚标注长什么样再决定读法VisDrone 系列的人群计数标注常见形式是每张图对应一个标注文件里面逐行记录人头中心点坐标有的版本还会带目标宽高或置信度列。动手前必须先抽样看几行确认列数和分隔符因为不同发布批次的字段顺序可能不同。我一般会写一个极小的探查脚本统计每张图的点数分布、坐标范围是否越界、有没有空标注文件。这一步花十分钟能省掉后面训练时“loss 不降但看不出原因”的半天。核心判断是人群计数只需要点标注不需要检测框所以宽高列可以直接丢弃但坐标必须落在图像范围内越界点要在生成密度图前裁掉或剔除。import os, glob import numpy as np def peek_annotation(ann_path): pts [] with open(ann_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.replace(,, ).split() # 常见格式: x y w h 或 x y score这里只取前两列当中心点 x, y float(parts[0]), float(parts[1]) pts.append((x, y)) return np.array(pts) ann_dir ./annotations counts [] for p in glob.glob(os.path.join(ann_dir, *.txt)): pts peek_annotation(p) counts.append(len(pts)) print(图片数:, len(counts), 最多点数:, max(counts), 最少点数:, min(counts)) print(点数中位数:, int(np.median(counts)))这段代码只做一件事把标注读成点集并统计规模。parts[0]、parts[1]的取法依赖实际列顺序跑之前一定先head看两行原始文件。统计结果直接决定后面密度图的高斯核大小和裁剪策略——点数中位数上千的场景核不能太小否则密度图几乎全是零回归梯度会非常稀疏。2.2 密度图生成高斯核大小是第一个必须调对的参数人群计数主流做法是把点标注转成密度图用卷积网络回归密度图再对密度图求和得到人数。转换的关键是每个点放一个高斯核核的方差通常与该点到最近邻点的距离挂钩人越密核越小避免密度图糊成一片人越稀核可以适当放大。固定核在密集场景会严重高估在稀疏场景又会欠拟合这是血泪经验。常见做法是用几何自适应核即 σ 正比于近邻距离的均值再设一个下限防止除零。import numpy as np from scipy.ndimage import gaussian_filter def points_to_density(points, img_h, img_w, k3, min_sigma2.0): density np.zeros((img_h, img_w), dtypenp.float32) if len(points) 0: return density pts np.array(points, dtypenp.float32) # 计算每个点到最近 k 个邻居的平均距离 from scipy.spatial import cKDTree tree cKDTree(pts) dists, _ tree.query(pts, kmin(k 1, len(pts))) if dists.ndim 1: dists dists[:, None] avg_dist dists[:, 1:].mean(axis1) if dists.shape[1] 1 else np.full(len(pts), 10.0) for (x, y), d in zip(pts, avg_dist): sigma max(min_sigma, 0.3 * d) # 只在局部窗口内累加避免整图循环 r int(3 * sigma) x0, x1 max(0, int(x) - r), min(img_w, int(x) r 1) y0, y1 max(0, int(y) - r), min(img_h, int(y) r 1) if x0 x1 or y0 y1: continue yy, xx np.mgrid[y0:y1, x0:x1] g np.exp(-((xx - x) ** 2 (yy - y) ** 2) / (2 * sigma ** 2)) density[y0:y1, x0:x1] g return densityk控制近邻数量min_sigma是核方差下限0.3 * d里的系数决定核随密度收缩的力度。这三个参数没有放之四海皆准的值但有一个可操作的起点先在 20 张图上生成密度图把密度图求和与真实点数对比误差应控制在个位数百分比如果系统性偏高说明核偏大或系数偏大反之调小。注意密度图求和必须等于点数这是后续所有指标可信的前提转换脚本里最好直接断言校验。2.3 数据划分与增强别让验证集泄漏进训练集VisDrone 的序列特性很容易被忽略同一段航拍视频抽出的帧高度相似如果按帧随机划分验证集里会出现训练集的近邻帧指标虚高得离谱。正确做法是按序列或按场景划分保证验证集来自训练时没见过的拍摄片段。增强方面人群计数对水平翻转、小幅随机裁剪、亮度扰动比较耐受但不要用大角度旋转和强透视变换因为人头尺度和密度分布会被破坏模型学到的尺度先验直接失效。裁剪时要注意同步裁剪密度图且裁剪后重新积分校验人数避免边缘点被裁掉导致标签和图像不对应。3. 密度图回归模型怎么选、怎么训从骨干到损失函数的落地配置3.1 骨干网络选型轻量骨干往往比大模型更合适无人机图像分辨率高、单帧人数多推理又常要求实时或准实时骨干选型的第一约束是显存和吞吐不是参数量。常见做法是用去掉全连接阶段的轻量卷积骨干做前端接一个多尺度特征融合头最后用 1x1 卷积输出单通道密度图。多尺度很关键近处人头几十像素、远处只有几像素单尺度感受野必然顾此失彼。我一般会保留三个尺度的特征图上采样到同一分辨率后相加或拼接再回归密度图。选型时优先看“在 1080p 输入下单卡能开多大 batch”而不是榜单上的 MAE因为计数任务的指标对数据划分极其敏感跨数据集比数字意义有限。3.2 损失函数与训练循环MSE 之外为什么要加一项纯 MSE 在极稀疏区域梯度几乎为零在极密集区域又容易被大数值主导训练早期常见“背景全零、人群区域糊成一团”。实践中常用组合损失密度图逐像素 MSE 为主辅以一个人数回归损失即对预测密度图和真值密度图分别求和后取 L1 或平滑 L1。这样即使局部密度形状学得不好总人数这个标量目标也能提供稳定梯度。下面是一个最小可跑的训练片段重点看损失组织和密度图求和的位置。import torch import torch.nn as nn import torch.nn.functional as F class CountLoss(nn.Module): def __init__(self, w_density1.0, w_count0.01): super().__init__() self.w_density w_density self.w_count w_count def forward(self, pred, gt): # pred/gt: [B,1,H,W] l_density F.mse_loss(pred, gt) pred_cnt pred.sum(dim(1, 2, 3)) gt_cnt gt.sum(dim(1, 2, 3)) l_count F.l1_loss(pred_cnt, gt_cnt) return self.w_density * l_density self.w_count * l_count # 训练循环骨架 model build_model() # 轻量骨干 多尺度融合头 model.train() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) criterion CountLoss(w_density1.0, w_count0.01) for imgs, dens, _ in train_loader: imgs, dens imgs.cuda(), dens.cuda() pred model(imgs) loss criterion(pred, dens) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step()w_count不能设大否则模型会倾向于输出均匀密度来凑总数局部形状崩掉从 0.01 起步观察验证集 MAE 和密度图可视化再微调。梯度裁剪阈值 5.0 是防止密集场景早期梯度爆炸的后悔药。学习率 1e-4 配 AdamW 是稳妥起点若用 SGD 则要相应放大并加动量。训练时每个 epoch 结束在验证集上同时记录 MAE 和 RMSEMAE 看整体偏差RMSE 对大误差更敏感两者背离往往意味着某些高密度帧被系统性低估。3.3 输入分辨率与批大小的取舍无人机原图动辄 2000 像素以上直接整图训练显存吃不消。常见做法是随机裁 512×512 或 768×768 的块训练推理时整图滑窗或直接整图前向取决于显存。裁剪训练要注意密度图同步裁、裁后重新校验人数如果一块里一个人都没有不要直接丢弃保留少量纯背景样本有助于压制误检但比例别超过一成否则模型会偏向输出全零。批大小在单卡 24G 显存下512 裁剪配轻量骨干通常能到 8 到 16具体以跑通为准不要照抄别人的数字。4. 训练与推理阶段的避坑清单五个真实会翻车的点4.1 密度图求和与标注点数对不上现象训练 loss 正常下降但验证集预测人数系统性偏高或偏低十几个百分点。原因多半是密度图生成时高斯核被图像边界截断能量丢失或者标注里存在越界点没剔除。解决生成密度图后立即断言abs(density.sum() - len(points)) / len(points) 0.02超阈值就打印该图排查边界点要么镜像填充后再生成要么在损失里对边界区域降权。4.2 验证指标虚高换场景就崩现象本地验证 MAE 很漂亮换一段没见过的航拍片段误差翻倍。原因几乎都是数据划分按帧随机近邻帧泄漏。解决按序列或拍摄片段分组划分划分前把文件名里的序列号抽出来做 group用分组划分而不是随机划分。这个坑在无人机数据上极其常见指标好看不代表模型能用。4.3 显存溢出发生在验证阶段而不是训练阶段现象训练能跑一进验证就 OOM。原因是验证时忘了torch.no_grad()或者整图前向没做滑窗分辨率比训练裁剪大好几倍。解决验证包torch.no_grad()整图推理按固定步长滑窗并做重叠区平均滑窗步长取窗口的一半左右重叠太少会在拼接缝处出现密度断层。4.4 学习率稍大就输出全零密度图现象训练几十步后预测密度图全零loss 卡在一个常数。原因是密集场景真值密度图数值大早期梯度把输出压向零加上纯 MSE 在稀疏区梯度弱模型找到了“全零”这个局部极小。解决确认组合损失里人数项已开启学习率降到 1e-4 以下热启动几百步或先用稀疏场景样本预热。这个现象不是玄学是损失地形问题。4.5 推理速度不达标卡在后处理现象模型前向很快端到端却慢。原因常是密度图后处理用了逐点 Python 循环做滑窗拼接或者每帧都重新分配大数组。解决滑窗拼接用张量视图和fold类操作密度图求和用框架内置归约别在 Python 层写双重循环。无人机场景对延迟敏感后处理往往才是瓶颈。5. 验证与进阶把 MAE 拆开看才知道模型能不能用5.1 分密度区间评估比单一 MAE 有信息量单一 MAE 会把稀疏场景和密集场景的误差平均掉掩盖真实短板。我习惯把验证集按真值人数分成三档低密度少于 50 人、中密度50 到 300 人、高密度300 人以上分别算 MAE 和相对误差。常见结果是低密度档绝对误差小但相对误差大高密度档绝对误差大但相对误差可控。这个拆分直接回答“这套方案在你的目标场景能不能用”如果业务场景以高密度为主就重点看高密度档别被整体 MAE 安慰。密度档人数范围关注指标典型问题低密度 50相对误差、误检背景纹理被当成人群中密度50–300MAE、RMSE尺度变化导致漏计高密度 300相对误差、密度图形状粘连区域系统性低估5.2 用密度图可视化做定性验证数字之外一定要抽帧看密度图叠加效果。重点看三处图像边缘、强逆光区域、人群与地面纹理交界处。边缘误差大通常是滑窗拼接问题逆光区域误差大说明训练集光照分布不足需要补增强纹理交界处误检多说明模型把高频纹理当成了人头可以加纯背景负样本或提高该区域损失权重。定性验证花的时间不多但能发现指标看不出的系统性问题。5.3 一个具体技巧用人数残差做在线校准如果部署场景相对固定比如固定几个拍摄高度可以在验证集上拟合一个简单的线性校准以预测人数为自变量、真值为因变量做最小二乘得到斜率和截距推理时套用。这个技巧不改变模型只修正系统性偏差通常能把 MAE 再压几个百分点。注意校准参数必须来自与部署场景同分布的验证集跨场景套用会适得其反。我一般会把校准前后的分档指标都打出来确认每一档都没被校准搞坏再上线。这套流程走下来从解压数据到拿到可信的分档指标单卡一两天能跑通最小闭环。真正花时间的是数据划分校验和密度图参数调试模型结构反而没那么玄学。我自己踩得最深的坑永远是“指标好看就以为成了”后来养成习惯任何一次指标提升先问验证集划分有没有泄漏、密度图求和有没有对齐两个都过了再高兴。希望帮到你。本文还有配套的精品资源点击获取