资讯动态

PaddlePaddle人流密度检测实战:从密度图回归到模型调优

发布时间:2026/10/14 8:47:05 来源:尧图企业网站定制
PaddlePaddle人流密度检测比赛已经结束有一阵子了结果也早就出来了。我们队伍最后拿了复赛第15名不算特别亮眼的成绩但整个过程踩过的坑、总结出来的经验我觉得比名次本身值钱得多。下面这些内容是我从初赛到复赛完整走下来的实战笔记里面有方案选型的纠结过程、数据处理的细节、模型训练的参数设置还有十几个真实踩过的坑希望能给后面参加类似比赛或者准备做人群计数方向项目的同学一点参考。先说清楚这虽然是一个密度检测比赛但本质上解决的是**人群计数Crowd Counting**问题。比赛提供了多个监控场景的视频和图像数据要求对每一帧画面预测出人流密度分布并给出区域内的人头数量。评估指标用的是准确率和召回率综合计算这一点和学术界的MAE/MSE不太一样后面会细说。1. 任务拆解与基线方案选择1.1 如何理解“人流密度检测”比赛的原始数据是监控摄像头拍下的俯视或斜俯视画面场景包含商场出入口、站台、景区步道等。目标是输出两个东西一是每个像素点附近的人头密集程度也就是所谓的密度图二是整张图片上的总人数。如果只看总数其实就是一个回归任务但如果还要结合视频空间位置密度图就变成了一张和原图尺寸对应的二维热度图。模型要做的事就是把输入图片映射成这张热度图热度越高表示这个位置越可能有人头。为什么比赛不直接让目标检测模型来数人因为监控画面里人挤人的情况太常见了行人框互相遮挡严重检测框覆盖率极低。用目标检测的思路做就面临从NMS非极大值抑制的框筛选到小目标漏检的一串麻烦事。密度回归方案的天然优势是把“数人”转成“积分问题”对密度图求和就得到人数估计天然避开了框重叠的困扰也更贴近实际业务中只关心人流量级别、不关心具体人框的需求。1.2 技术演进路线与选型背后的取舍人群计数方向近十年的主流方案大致经历了一个清晰的演变过程第一阶段是“检测为主”的时期。早期百度的相关项目里也尝试过用YOLO或者Faster R-CNN一类检测器去做人头识别但真实监控场景里人头饱和度很高重叠区域太多检测性能很容易触顶。第二阶段是“密度图回归”时期。典型代表是MCNN多列卷积网络和CSRNet。这类模型把输入图像映射成密度图再对密度图积分得到人数核心思想是让网络学习位置信息和人群分布的全局规律。MCNN用不同感受野的列并联来捕捉尺度变化CSRNet更大胆地用空洞卷积扩大感受野在保持分辨率的同时提高了密度图质量。第三阶段是“Transformer与点监督”时期。近两年出现了一些基于Transformer的密度估计模型以及更极端的单点标注监督方案。但这类方案普遍需要更庞大的数据量和更复杂的训练策略比赛周期短团队人力有限性价比不高。我们最终选择了CSRNet结构 HRNet主干特征提取的组合理由有三个方面第一CSRNet结构简单训练稳定比赛这种短平快的场景里不需要冒险尝试新奇模型第二HRNet能同时保留高分辨率和多尺度语义信息对监控视角下人头大小差异很大的场景效果更好第三PaddlePaddle框架里这两个组件都有现成实现不需要自己从头搭。1.3 方案设计的总览图整个系统的数据流向是这样的原始图片先做归一化和尺寸调整送入主干网络HRNet提取多尺度特征然后通过空洞卷积堆叠的头部网络生成密度图最后对密度图按区域求和得到人数预测。训练阶段用真实标注生成的高斯核密度图作为监督信号用欧氏距离损失加上SSIM结构一致性损失来做优化。音频特征这里设计了一个关键点验证阶段不直接用人数误差来选模型而是用比赛官方给出的“准确率和召回率”指标来切模型效果会好很多。这也是我们后期分数提升的一个隐含原因。2. 数据预处理与增强的实操细节2.1 标注数据的清洗与密度图生成初赛给的数据大概有两千多张图官方提供了图像中每个人头中心点的坐标标注。训练的第一步是把点坐标转成密度图。这一步有个容易犯的错误直接用固定高斯核在原分辨率的黑色背景上画点然后缩放到模型输入尺寸。如果原图是1080p模型输入是512x512直接缩放会导致密度图里的人数不等于原始人数。正确做法是先缩放到目标分辨率再在目标分辨率上以每个点为中心做高斯分布或者用尺度归一化的方式处理。点标注转密度图的核心代码不复杂import numpy as np import cv2 def points_to_densitymap(size, points, sigma4): size: (h, w) 目标密度图的尺寸 points: [[x1,y1],[x2,y2],...] 标注点坐标列表 sigma: 高斯核标准差 h, w size density np.zeros((h, w), dtypenp.float32) for x, y in points: if x 0 or y 0 or x w or y h: continue # 生成以(x,y)为中心的高斯核 gauss_x np.arange(0, w, 1, dtypenp.float32) - x gauss_y np.arange(0, h, 1, dtypenp.float32) - y gauss_2d np.exp(-(gauss_x[None, :] ** 2 gauss_y[:, None] ** 2) / (2 * sigma * sigma)) density gauss_2d return densitysigma的选择直接影响训练质量。过小会变成稀疏的点阵网络学到的梯度不稳定过大会让人头之间的密度重叠得面目全非计数偏差大。实际操作中我根据场景做了缩放人头小的远场景用sigma34近场景人头大用sigma58。比赛数据里摄像头离人群远近不一最稳妥的是按人头距离的平均间距动态设置sigma而不是全局写死。2.2 训练集与验证集的划分策略数据划分上不能随机打乱。原因很简单很多连续帧来自同一个监控视频直接随机切分会导致同一场景的内容同时出现在训练和验证里模型没有真正见过的新场景但验证指标会虚高给后面复赛带来风险。我们的划分方式是基于场景ID来分把每个视频或场景的所有帧归为一组以场景为单位按8:2切分。保证训练集和验证集没有共同的场景这样验证分数才更能反映真实的新场景泛化能力。划分完之后我又手动审计了验证集合每一张图片的人数分布确保验证数据里既有人流稀疏的时段小于10人也有密集时段超过100人避免验证集本身偏向一个极端。这个步骤虽然耗时一两个小时但能避免后续模型调参时被片面指标误导。2.3 数据增强策略与PaddlePaddle实现监控视频截图的特点是场景相对固定但光照变化大人群密度分布差异大。训练时我用了三组增强手段第一组是几何增强包括随机水平翻转、随机裁剪、多尺度缩放。特别说明一下随机裁剪非常关键因为交通枢纽这种场景里不同区域的人群密度差异很大裁剪能让模型看到更多局部拥挤的样例。通常先resize到原图的1.01.5倍再随机裁剪到目标尺寸相当于做了随机尺度增强。第二组是光照与噪声增强。监控画面有夜间红外、强背光、窗户反光等复杂光照使用随机亮度扰动、gamma变换、高斯噪声让模型不要依赖绝对亮度来判断是否有人的特征。第三组是场景拼贴增强。从训练集里抽取其他场景的小图块随机粘贴到当前图片的空白区域这样等于变相增加了高密度区域的样本量。比较有意思的是粘贴的图块要带有来自不同场景的成像噪声和色调否则模型会学习到“拼接边界”这种伪特征。在PaddlePaddle里面这些增强都可以通过paddle.vision.transforms配合自定义函数实现import random import numpy as np import paddle from paddle.vision.transforms import Compose, RandomHorizontalFlip class RandomCropResize: def __init__(self, target_size512, scale_range(0.8, 1.5)): self.target_size target_size self.scale_range scale_range def __call__(self, img, density): h, w img.shape[:2] scale random.uniform(*self.scale_range) nh, nw int(h * scale), int(w * scale) img cv2.resize(img, (nw, nh), interpolationcv2.INTER_LINEAR) density cv2.resize(density, (nw, nh), interpolationcv2.INTER_LINEAR) # 注意密度图resize后必须按缩放比例反向补偿保证积分值不变 density * (scale * scale) # 随机裁剪到目标尺寸 if nh self.target_size and nw self.target_size: y random.randint(0, nh - self.target_size) x random.randint(0, nw - self.target_size) img img[y:y self.target_size, x:x self.target_size, :] density density[y:y self.target_size, x:x self.target_size] return img, density上面代码里“密度图resize后必须按缩放比例补偿”这一句是重点。因为密度图的积分代表人数直接resize会把人数也缩放掉不乘回比例系数的话模型的学习目标就出错了。我当时第一次没加这个补偿训练loss迟迟降不下去排查了半天才发现是这个问题。2.4 数据集加载器性能优化两千多张图逐张读入并做增强如果不做优化一个epoch就要跑很久。我们用内存缓存加异步数据加载的方式来解决。具体做法是把所有训练图片read进内存放在RAM里训练每个epoch时直接从内存读取而不是反复IO。这一批数据加起来大概十几个G比赛要求使用带GPU的服务器内存通常比较充足实际可用。换成SSD随机读取后数据加载瓶颈反而从IO变成了图像处理和增强计算此时再配合Dataloader的num_workers多进程预取速度就能提上去。PaddlePaddle的DataLoader里面设置num_workers参数就可以了train_loader paddle.io.DataLoader( train_dataset, batch_size16, shuffleTrue, num_workers8, drop_lastTrue, use_shared_memoryTrue )2.5 数据增强的“度”如何把握增强不是越多越好。增强过强会引入失真噪声模型学不到真实场景的结构特征增强过弱又容易过拟合。经过十几轮实验我总结了几条判断标准密度图的增强必须跟随图像增强做同样的变换人和密度图之间是像素级对齐关系剪裁区域、翻转方向、缩放比例都必须一致这一点容易忽略。高频增强要慎重。高斯噪声、色彩抖动这类强度不宜过大否则会让模型在低纹理区域产生虚假密度响应把墙面错判成人头。每个epoch里的增强强度可以动态调整。前50个epoch用较弱增强让模型学基础特征后面逐步增强提高鲁棒性。这样训练比全程用强增强更平滑也更容易收敛。3. 模型选型与训练调优全过程3.1 主干网络与head结构的选型分析我们对比过三个方案VGG16作为backbone的CSRNet、ResNet50加Dilated Backbone、以及HRNet-W32加CSRNet头。训练速度和验证分数的数据表明HRNet-W32的方案前期略占优尤其在密集场景下它保留的高分辨率细节能明显增加密度图的峰值清晰度。CSRNet的head部分用的是连续的dilated卷积层组合。dilated rate从2、4、8、4、2这样一组配置最终输出的密度图和输入分辨率相同或1/2尺寸。用空洞卷积而不是普通下采样卷积是因为下采样会把密度图的空间分辨率降低返回积分的时候精度损失较大。head部分代码import paddle import paddle.nn as nn import paddle.nn.functional as F class DilatedHead(nn.Layer): def __init__(self, in_channels720, mid_channels256): super(DilatedHead, self).__init__() self.conv1 nn.Conv2D(in_channels, mid_channels, 3, padding2, dilation2) self.conv2 nn.Conv2D(mid_channels, mid_channels, 3, padding4, dilation4) self.conv3 nn.Conv2D(mid_channels, mid_channels, 3, padding8, dilation8) self.conv4 nn.Conv2D(mid_channels, mid_channels, 3, padding4, dilation4) self.conv5 nn.Conv2D(mid_channels, mid_channels, 3, padding2, dilation2) self.conv_out nn.Conv2D(mid_channels, 1, 1) self._init_weights() def forward(self, x): x F.relu(self.conv1(x)) x F.relu(self.conv2(x)) x F.relu(self.conv3(x)) x F.relu(self.conv4(x)) x F.relu(self.conv5(x)) out F.relu(self.conv_out(x)) return out def _init_weights(self): for m in self.sublayers(): if isinstance(m, nn.Conv2D): k m.weight.shape[2] * m.weight.shape[3] m.weight.set_value( paddle.normal(mean0, std1.0 / (k ** 0.5), shapem.weight.shape) )3.2 损失函数的设计与调参实验密度估计的常用损失函数是欧氏距离损失L2 Loss。L2损失单独使用的问题在于它假设各像素独立但实际密度图是强相关的空间结构人头的峰值和周围的高斯分布有内在关联。模型完全靠L2损失训练出来的密度图峰值往往偏低背景区域却又会出现雾状伪密度。我们最终使用了三个损失的加权组合第一项L2损失监督全局密度值回归量 第二项SSIM损失结构化相似损失感知密度图的空间结构相似性SSIM能迫使模型输出的密度图在局部区域与人头位置的空间关系保持一致性 第三项局部一致性损失将密度图划分成小patch约束每个patch内的密度积分与真实值的误差。权重比例经过几轮回调最终确定为L2权重为10SSIM权重为0.1局部一致性权重为0.05。SSIM权重过大时模型会把整张图的纹理趋势抓得很准但人数总和反而不太准L2权重过大时SSIM起不到作用密度图容易出现高频噪声。损失函数实现class DensityLoss(nn.Layer): def __init__(self, l2_weight10.0, ssim_weight0.1, patch_weight0.05): super(DensityLoss, self).__init__() self.l2_weight l2_weight self.ssim_weight ssim_weight self.patch_weight patch_weight def forward(self, pred, target): # 欧氏距离损失 l2_loss F.mse_loss(pred, target) # SSIM损失用paddle的ssim接口 ssim_value paddle.nn.functional.ssim( pred, target, data_range1.0 ) ssim_loss 1.0 - ssim_value # 局部patch一致性损失 pred_patches pred.reshape([pred.shape[0], 1, -1, 16, 16]).sum(axis[2, 3, 4]) target_patches target.reshape([target.shape[0], 1, -1, 16, 16]).sum(axis[2, 3, 4]) patch_loss F.l1_loss(pred_patches, target_patches) return self.l2_weight * l2_loss self.ssim_weight * ssim_loss self.patch_weight * patch_loss3.3 优化器、学习率与训练时长配置优化器选用Adam初始学习率定为1e-4权重衰减1e-5。为什么不用SGD密度回归对初始学习率很敏感SGD配大学习率容易飞掉配小学习率收敛太慢。Adam的适应性学习率机制在这个场景下更省心。学习率策略上用的是PaddlePaddle的CosineAnnealingDecay最小学习率设为1e-6预热了5个epoch。预热阶段让统计信息先稳定避免开局就冲向loss的震荡区。配置文件scheduler paddle.optimizer.lr.CosineAnnealingDecay( learning_rate1e-4, T_max120, eta_min1e-6, last_epoch-1, verboseFalse ) optimizer paddle.optimizer.Adam( parametersmodel.parameters(), learning_ratescheduler, weight_decay1e-5 )训练轮次上总共训练了120个epoch。实际观察曲线前40个epochloss下降非常快60~100个epoch在验证集上逐步稳定100~120个epoch验证指标略微震荡但此时保存的是历史最优模型不用太担心过拟合的问题。3.4 训练过程中的显存优化技巧HRNet-W32加上膨胀卷积head单卡batch_size16确实可以跑但在512x512输入分辨率下显存占用接近12G。如果显存只有11G那就要做一些调整有几个技巧第一是梯度累积。用batch_size4的小批次梯度累积4步再更新一次参数等效于batch_size16的效果。内存占用从12G降到6G以下。accum_steps 4 optimizer.clear_grad() for step, (images, targets) in enumerate(train_loader): outputs model(images) loss criterion(outputs, targets) loss loss / accum_steps loss.backward() if (step 1) % accum_steps 0: optimizer.step() optimizer.clear_grad() scheduler.step()第二是用混合精度训练。在PaddlePaddle里开启AMP自动混合精度非常简单只需要配置amp_init大部分计算用float16执行显存直接减半而且在这类任务上精度损失几乎可以忽略。model paddle.amp.decorate( modelsmodel, levelO2, save_dtypefloat32 ) scaler paddle.amp.GradScaler(init_loss_scaling1024.0)第三是把输入尺寸先减到384x384来跑通基线再提升到512x512对比精度。很多人一上来就追求高分辨率但384和512的精度差距在0.5%以内的时候优先选择384训练速度快接近一倍能多跑很多实验版本。3.5 不同场景下的模型表现差异比赛中我发现模型在不同类型场景的表现差异很大。平均准确率有91%左右但把结果按场景分类统计之后商场景点的准确率明显高于交通枢纽场景。分析原因有二商场画面的视角更平直人头大小相对一致密度分布均匀交通枢纽的俯视视角下人头小而密集交错遮挡严重。这个差异提醒我一件事要针对性地做场景自适应。我们后面在粗剪数据阶段把交通枢纽的视频片段单独取出做了更多尺度的增强、更大范围的随机裁剪才把这个场景的分数追上来。这类“按场景拆分组”的评测方式在比赛里很常见早一点做分组统计能提前发现模型的偏科问题比最后看一个平均分数有用得多。4. 推理优化与后处理逻辑4.1 多尺度推理的实际效果推理阶段我们使用了三尺度测试0.8x、1.0x、1.2x。多尺度推理的原理是不同尺度下模型对人群密集程度的响应不同把多个尺度的密度图对齐回原分辨率并求平均能减少单一尺度下的预测偏差。对齐时要注意尺寸取整问题。每个尺度的密度图resize回原图尺寸人头的积分值会被缩放需要除以缩放系数的平方来补偿人数。比如0.8x推理的密度图resize回原图时要除以0.64。多尺度推理的收益在我们实测中约提升0.5~1%的准确率。代价是推理时间乘了三倍。如果是线上实时场景可以用flip测试水平翻转推理再加权平均效果接近但代价只有两倍。4.2 从密度图到最终人数的转译策略去掉多尺度推理之后整合密度图有两种常见方式直接对整张密度图求和得到总人数。这是最直觉的做法适用于评估阶段只要求总人数的任务。按区域求和再相加。如果监控场景有固定的物理分区闸机口、扶梯口等可以对每个区域独立求积分。这里有个容易踩的坑对于人群极度密集、密度图峰值互相重叠的区域直接整图求和会略微少估人数。因为多个相邻人头的密度峰值叠加后会被模型压到一个范围里积分就不等于实际总数。我们的解决方案是引入一个后处理调制系数用验证集统计真实人数和预测人数的比值把这个比值作为一个全局校正因子。虽然看起来很朴素但确实能提升最终赛果0.5%左右。逻辑上是因为模型对密集区域的系统性低估乘以一个大于1的系数能部分补偿这个偏差。4.3 推理时的预处理细节推理阶段的预处理比训练阶段简单很多但有几个细节值得注意色域归一化要和训练一致。如果训练时用的是PaddleX或PaddleDetection自带的Normalize推理阶段也必须走完全相同的均值和标准差。很多人在这一步用错了顺序导致输入分布和训练分布不一致。推理图片长宽比不同时可以等比缩放后做padding而不是直接拉伸。直接拉伸会扭曲人头形状密度图的峰值位置会受到形变的干扰。预测密度图低于某个阈值的峰值要直接置零。密度图在背景区域会有轻微响应如果不对峰值做筛选那些低幅度的噪声也会被加进总数里。阈值选择通过验证集搜索确定我们最终用的是密度图上所有像素最大值的10%作为过滤阈值。推理代码示例def inference_one_image(model, img, scales[0.8, 1.0, 1.2]): model.eval() h, w img.shape[:2] density_sum np.zeros((h, w), dtypenp.float32) for scale in scales: nh, nw int(h * scale), int(w * scale) resized cv2.resize(img, (nw, nh)) inp transform(resized).unsqueeze(0) # shape: 1,3,N,H,W inp paddle.to_tensor(inp) with paddle.no_grad(): pred model(inp)[0, 0].numpy() # resize回原分辨率时需要补偿缩放 pred cv2.resize(pred, (w, h)) if scale ! 1.0: pred pred / (scale * scale) density_sum pred density_avg density_sum / len(scales) total_count density_avg.sum() return density_avg, total_count4.4 输出格式与官方评估口径的适配比赛要求提交的结果是每张图片对应的总人数但官方排行榜的计分方式并不是对人数误差直接排序而是综合了准确率和召回率。具体来说预测人数和真实人数分别作为正样本和检出的置信度来算指标也就是说要让最终评分的准确率尽量高模型的预测就应该在某些情况下有偏置。这里要特别警惕如果只追求准确率那预测人数应当尽量接近真实人数但官方指标里还有召回率说明他们更关心“检出的目标占真实目标的比例”一味压低预测值反而可能伤到召回率。我们最后做的调优策略是训练时以损失函数优化为主推理时用验证集上综合F1最大化的阈值来确定全局人数校正系数而不是简单地让总人数无偏估计。这一步调整在排行榜上提升了0.3个百分点左右。5. 比赛中的问题排查经验实录5.1 训练loss正常但验证指标极差这是比赛中段最让人头疼的问题。训练集上的loss一直在降但验证集上准确率死活上不去有时还往下掉。排查思路先检查是否过拟合。如果验证loss和训练loss差距过大说明模型记住了训练集特殊场景。检查数据划分是否按场景隔离。按场景隔离后如果验证指标仍然差那就是增强强度不够模型学到了场景的色调分布等背景特征。最终在我们的项目里找到的原因不是过拟合而是训练集图片的裁剪范围太小。很多训练图裁出来都是单调的地板纹理模型学到的是“地板上没有人”的特征。换成全局场景局部抽查的组合增强后验证分数提升非常明显。5.2 人群高度密集区域预测偏少预测偏少几乎是所有人流密度模型都会遇到的问题原因在于密度图在极度拥挤区域的峰值重叠严重模型建模能力饱和。处理方法有三个对输入图片的高密度区域进行切块推理。把原图切成2x2的小块分别预测再合并。注意切块边缘会有“边缘效应”即靠近切块边界的人群被低估。解决方式是切块时保留10~15%的重叠区域合并时取重叠区域的平均值。在训练时增加局部区域的高斯增强让模型对密集小目标更敏感。对极端密集样本做密度图峰值裁剪设定密度值上限避免网络为了优化几个极端像素而牺牲整体分布。5.3 GPU显存不足导致训练中断比赛过程中遇到过约三次OOM显存溢出都在将batch_size调到16以上时。解决方式将输入尺寸调整为384x384而不是512x512开启AMP混合精度使用梯度累积。如果你在比赛环境里遇到OOM建议先查这四个地方输入图片尺寸是否过大、batch_size是否过高、是否启用了混合精度、backbone通道数是否太大。通常调整前两个就能解决问题。5.4 模型在不同摄像头视角下的泛化问题初赛和复赛的数据分布差距很大初赛多为俯视视角复赛出现了大量的平视和低角度监控画面。很多模型的初赛成绩很好复赛却翻车了。实际上人群计数模型对视角高度极其敏感。训练数据里俯视视角样本多导致模型的密度分布学习偏向于小尺度人头特征。平视视角下人头的表观特征完全不同需要模型去适应。一个有效的对策是在训练数据里做视角增强变换对图像做随机透视变换perspective transform模拟不同摄像头高度和角度下的视角变化。这个增强方式强度不宜过大否则会产生畸变的假样本。5.5 比赛提交后评分与本地验证不一致这个问题几乎是比赛必踩的坑。我们在本地验证集上有93%的准确率但提交给官方初赛榜单只有88%。分析出来有两个原因第一本地验证集的场景和初赛测试集场景重叠过少评分波动大。解决办法是切出一部分训练数据作为本地评测确保场景多样性。第二官方评测用的可能不是同一套阈值或者后处理逻辑。后来发现我们本地做过调制的全局人数校正系数在官方口径下并不起反作用但sigmoid输出密度图的阈值设置对官方计分有较大影响。6. 比赛成绩提升的经验总结与碎碎念6.1 提分最容易的五个方向按投入产出比排序我个人认为提分最快的方向依次是保证数据划分和增强策略的正确性这个投入小收益巨大把损失函数从纯L2升级为L2SSIM局部一致性组合多尺度推理根据视角做定向增强后处理阈值搜索与全局人数校正。这里面第一条看起来最简单但很多人一开始就犯了划分错误导致后面所有调参都建立在一个扭曲的评价指标上。如果一开始就把这些打牢后面会省下大量时间。6.2 关于PaddlePaddle框架的几个使用看法比赛中实际使用的PaddlePaddle版本动态图带来不少便利在数据集迭代和模型调试阶段尤其好用打印中间层输出、动态修改网络结构都很顺手。PaddleDetection和PaddleSeg里预训练模型机制很完善直接加载ImageNet预训练权重很简单。但框架适配比赛时有个实际问题不少第三方修改过的模型实现采用的是TensorFlow或PyTorch代码的习惯迁移到PaddlePaddle时要改动的地方不少这可能才是大家用起来最耗时的地方。建议比赛周期内尽量少魔改模型结构不要试图在框架差异上浪费精力。除非有明确必须改的原因否则沿用官方实现最稳。6.3 如果再来一次我会怎么做如果现在再参加一次同等任务我会把更多的精力提前投入到场景视角的分析上。很多队伍包括我们自己一开始都在模型结构上花了太多时间不断换backbone、调head结构但真正拉开差距的其实是数据理解和后处理的细致程度。对密集人群场景backbone的差异远不如训练数据的场景丰富度重要。后处理的细节如区域自适应阈值、高密度区域切块、多尺度对齐策略提升效果比换一个模型明显得多。人群密度检测这个方向比赛的终点往往不是模型的准确率比拼而是工程细节和数据集理解的竞争。希望这份记录能帮后续参赛的同学少走点弯路。最后分享一个小操作习惯建议每次实验的配置、代码、得分都记录在表格里。后期对比模型的时候这能帮你少跑很多无效实验。赛后回看的时候也会更有迹可循。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑