资讯动态

边缘设备行人检测与密度分析:TensorRT INT8量化与RTSP推流实战

发布时间:2026/10/18 14:20:36 来源:尧图企业网站定制
1. 为什么做这个项目边缘设备上的行人检测与密度分析说实话行人检测和密度分析本身不是什么新鲜话题。真正让我下决心写这篇文章的是最近帮一个园区安防项目做边缘端视频分析时遇到的情况客户要求在现有的摄像头和一台边缘设备上同时完成实时行人检测、区域内人流密度统计还要把分析结果画面通过网络视频流推送到监控平台。硬件资源有限GPU算力不算宽裕模型不能太大延迟必须压到可接受范围。几轮尝试下来最终锁定了 TensorRT 的 INT8 量化方案配合 RTSP 推流完成了整套闭环。整个项目做下来最有价值的部分不是“跑通了”而是理解了每一层优化背后的原因为什么选 INT8 而不是 FP16为什么校准集要这样挑为什么推流参数会影响端到端延迟为什么密度分析不能直接套用检测结果。这些经验我想完整记录下来给后面同样要在嵌入式侧或边缘设备上做视频分析的朋友一份可参考的路线图。先交代一下项目的基本形态。输入是海康风格的 RTSP 摄像头流经过解码后喂给 TensorRT 加速的目标检测模型检测结果一方面用于绘制行人框叠加到原画面上另一方面聚合为密度统计信息——比如某区域当前的瞬时人数、单位时间通过人数、热点拥堵区域热力图。输出端再编码成 H.264/H.265 并推到内网 RTSP 服务供 Web 端或客户端展示。整体架构看起来简单但每一条链路里都有坑。2. 整体方案选型检测模型、密度分析策略与部署框架的取舍逻辑2.1 为什么选 TensorRT 而不是 OpenVINO、ONNX Runtime先聊推理框架选型。OpenVINO 在 CPU 上确实有优势但到了 GPU 上TensorRT 的延迟和吞吐表现通常会更好。尤其在 INT8 量化方面TensorRT 提供了一整套校准和层融合机制是当前边缘 GPU 部署的主流选择。ONNX Runtime 的 GPU 执行提供程序在部分模型上也能接近 TensorRT但它的算子融合深度和显存管理机制没有 TensorRT 成熟。做视频流实时推理的时候TensorRT 的 engine 序列化、多 stream 并发、动态 shape 优化模式都能直接拿过来用工程成本低很多。所以我最终定了 TensorRT版本选 8.x这个版本的 INT8 量化接口和动态 shape 支持都比较完善。2.2 行人检测模型选型小目标和大密度场景是关键矛盾行人检测模型的候选方案我对比过 YOLO 系列、SSD、以及基于 Transformer 的检测器。工程角度YOLO 系是最顺手的。推理速度好部署生态成熟ONNX 导出工具链完整。Transformer 类模型在行人这种中等尺寸目标上精度优势不明显反而会增加延迟而且 INT8 量化的稳定性更差所以直接排除。但直接拿标准 YOLO 的速度版本还不够。行人检测有两个特殊难点一是行人经常互相遮挡密集场景下小目标多二是摄像头视角通常是俯视或斜俯视行人的形态变化大。标准 COCO 预训练权重直接迁移过来的效果一般我需要微调并且针对小目标检测增加感知层。我最后用的是一个 YOLO 架构的变体主干网络采用了 lightweight 结构来降低计算量同时在特征金字塔部分加了融合增强模块针对近处小目标做了 anchor 策略调整。为了追求极致的推流延迟我在满足精度要求的情况下尽量收窄了输入分辨率最终用 640 输入而不是 1280。后面会详细说分辨率和精度、延迟之间的权衡。2.3 密度分析方案检测计数和密度图估计怎么选行人密度分析通常有两类做法。第一种纯粹基于检测结果框的数量统计第二种是用密度回归网络比如 CSRNet 那类输出密度图再进行积分求和。两者各有适用范围。单纯检测计数的优点是简单、与检测框直接对应但问题在于严重遮挡场景下漏检率高而且无法体现“人群聚集程度”的语义。密度图方案的优点是能自然处理拥挤场景输出平滑的热度分布但模型更重推理开销更大而且作为前向任务在网络中是一个独立的头增加了工程量。最终我采用了“检测为主、密度分析为辅”的混合策略正常密度场景下直接基于检测框统计人数和区域人数当检测帧中的行人数超过阈值、判定进入拥挤状态时再启用轻量密度估计头进行热力分析。这样在算力占用和业务效果之间取得了平衡。实现上密度估计部分可以复用检测模型特征图的一部分添加一个轻量回归分支而不是独立跑一个全图网络这样额外算力只增加约 20% 左右。2.4 系统架构从解码到推理到推流的完整链路整个系统跑在一块有 8GB 显存的专业显卡上镜头路数为 4 路。为了不互相阻塞我拆成多个进程解码进程负责处理 RTSP 拉流和解码把 YUV 帧共享到显存推理进程持有 TensorRT engine按队列消费帧推流进程从推理端拿叠加了检测框的 RGB 帧编码推流。进程间共享数据我用了 SharedMemory 和 CUDA 的显存零拷贝机制避免 CPU 和 GPU 之间的重复拷贝。每一路分析综合算下来GPU 利用率维持在 60% 到 70% 左右端到端延迟从摄像头画面产生到推流端看到检测框控制在 400 毫秒以内。这个指标直接决定了后面调优的方向。3. INT8 量化核心细节校准、精度评估和层敏感度处理3.1 先搞清楚 FP16 和 INT8 差距在哪FP16 与 FP32 相比速度提升有限显存减半。INT8 能带来额外的 2 到 4 倍加速显存再减半。代价是精度损失和量化敏感层的问题。TensorRT 做 INT8 推理时并不是把整个模型简单地扔到 INT8 空间。它内部会根据校准结果为每个张量计算动态范围然后用量化参数映射到 INT8 表示。部分不适宜量化的层会保留 FP16 甚至 FP32 计算精度。这个“部分量化”设计非常关键意味着你无法用简单的“把权重变成 INT8”的思维去理解效果。3.2 校准集选择这个坑我踩得最深INT8 量化流程里校准集的质量直接决定最终精度的下限。我用过两种校准集一种是从训练集随机抽 1000 张另一种是从实际摄像头场景截取 2000 帧覆盖不同光照和密度。实测结论是实际场景帧的校准效果远比随机训练样本好尤其在项目场景偏暗、光照不均的情况下校准集里如果缺少暗光样本量化后 mAP 会掉 3 到 5 个点。建议校准集挑选要覆盖这些维度不同光照条件晴天、阴天、夜间红外、不同人群密度空场景、几人、拥挤、不同距离近景、远景、不同视角仰角、俯角。数量上TensorRT 官方建议至少 500 张以上实际经验是 1500 到 3000 张覆盖度高的图比少量图更稳但也不要盲目堆数量太多反而拉长校准时间且收益递减。3.3 校准算法选择默认熵校准还是其他选项TensorRT 提供三种校准方式直方图校准、熵校准、以及基于 MinMax 的校准。对于行人检测模型我测试下来熵校准是整体精度表现最稳定的。它的原理是用 KL 散度寻找最优截断阈值以保证量化前后信息损失最小。但有一个具体细节很多文章不会提熵校准适用于分布相对集中的激活值而行人检测模型浅层部分存在比较明显的长尾分布此时 MinMax 校准反而在某些层上更合适。因此实际操作中我不建议只用单一校准策略跑全模型而是可以先全部用熵校准生成第一个 engine然后分析各层量化误差把误差大的层手动指定为 FP16 或使用 MinMax 校准重新生成。TensorRT 支持逐层配置精度这个能力要善用。3.4 精度评估不只看 mAP还要看漏检率模型量化的精度验证不能只看整体 mAP。行人检测场景下漏检率的敏感度远高于误检。我评估时会把测试集划分为三个子集正常场景、密集遮挡场景、光照异常场景分别报告 mAP、recall 和漏检数。实测 INT8 量化后密集遮挡场景的 recall 下降最明显甚至比正常场景 mAP 下降值高一倍。这也从侧面验证了校准集中必须加入遮挡密集样本的重要性。3.5 敏感层分析与混合精度选几个典型层做量化敏感度实验是有必要的。方法不复杂惊喜的是检测输出头的某些卷积层对 INT8 极度敏感量化后直接导致小目标漏检但主干网络的大部分层则表现得非常宽容。定位这些敏感层的经验法则是输出 stride 越小的层、特征金字塔融合层、以及检测头最后的回归分支优先保持 FP16。我最终生成的 engine 中大约 78% 的层运行在 INT812% 运行在 FP1610% 运行在 FP32。最终模型大小从 FP16 的约 24MB 压缩到 INT8 的约 10MB推理延迟从 FP16 的 7.8ms 降到 4.2ms640 输入在这块显卡上。精度 mAP 只掉了约 1.1%满足项目要求。4. 行人检测模型训练与轻量化调优实录4.1 数据集处理与标注项目场景是园区内部道路和出入口我基于公开行人数据集做了场景迁移又自己标注了约 8000 帧真实场景框类别只有“person”一类。标注时要特别注意两点一是俯视角度下的行人框要尽量贴合头肩部不要沿用平视数据集的全身框标准否则小目标尺寸差异会被放大影响匹配二是遮挡目标必须单独标注同时在小训练时引入部分遮挡逻辑否则模型对遮挡的鲁棒性很难提升。数据增强方面Mosaic 和 MixUp 对提升遮挡鲁棒性和小目标性能帮助明显。我额外加入了随机亮度和对比度扰动模拟不同曝光条件。这个增强策略和最后部署时校准集的构建逻辑要保持一致。4.2 模型轻量化技巧结构改造和剪枝取舍直接使用标准 YOLO 的速度版在推理速度和精度平衡上已经有不错表现但我还是做了一些瘦身主干网络里部分 3x3 标准卷积替换为深度可分离卷积特征金字塔里对深层特征通道数做了裁剪注意力模块使用轻量的坐标注意力而不是计算量更大的 SE 或 CBAM。需要提醒大家的一点是不要盲目裁剪检测头相关结构的通道数。这些部分对检测精度的影响极大我试过把检测头从 256 通道裁剪到 128 通道检测精度掉了约 4 个 mAP 点怎么调都救不回来。后来只在主干和特征融合部分做压缩精度没损失推理速度反而明显提升了。小目标检测方面我的做法是在特征金字塔上增加了一层高分辨率特征图保留更细节的空间信息。这层特征会增加一点计算量但对密度稍高的远景行人检测提升明显这个取舍是值得的。4.3 训练策略与损失函数训练过程分两阶段。第一阶段冻结主干只训练检测头和融合模块用较大学习率快速收敛。第二阶段全部解冻使用较低学习率微调。这一阶段需要特别小心学习率太大容易破坏已经训练好的主干特征太小则检测头的收敛会很慢。损失函数我采用了标准的分类损失、定位损失和目标置信度损失加权组合。行人检测场景下分类相对单一因此我把定位损失的权重调高了一点让回归分支更准确地贴合行人框。回归分支对行人的位置精度影响很大尤其在密集场景下框与框之间只是一两个像素的差异定位不准会直接降低后续密度统计的可靠性。4.4 ONNX 导出与 TensorRT 转换的注意事项PyTorch 模型转 ONNX 再转 TensorRT 是常规路径。但有几个细节容易被忽略。第一动态轴设置。如果模型要支持动态输入尺寸ONNX 导出时必须显式声明动态轴TensorRT 侧也要配置 optimization profile。这个项目里我固定了推理分辨率 640所以动态轴问题不涉及。但如果你需要支持多分辨率建议在导出前想清楚TensorRT 的动态 shape 配置会影响性能和显存。第二算子兼容性。ONNX 导出时注意选 opset 版本和 TensorRT 支持范围的匹配。某些低版本 opset 会把一些算子展开成子图导致 TensorRT 优化融合失败推理性能会掉一截。我实测中遇到过导出时默认开了一些不必要算子导致转 TensorRT 时层融合覆盖率明显下降的情况。解决办法是导出后用 ONNX 简化库做一次模型清理。第三TensorRT builder 配置。这阶段关键参数有工作空间大小、批大小、精度模式、动态 shape profile。我把工作空间设成了 2GB批大小固定为 1因为推流分析是一帧一帧处理的。如果你要追求吞吐可以设置动态批大小但边缘设备上通常延迟优先批为 1 是最直接的。构造函数里我加了setMemoryPoolLimit设置显存上限避免 builder 阶段显存爆炸。5. 密度分析模块设计从计数到热力分布的实施路径5.1 检测计数如何聚合为密度分析仅靠检测框数量反馈密度在场景简单时够用。但场景内有多个区域时需要定义区域与行的映射逻辑。我在画面中预设了多个多边形区域通过点在多边形内的判断方法把检测结果归属到对应区域。还有一个关键逻辑如何计算“单位时间通过人数”。这不是简单地统计每个区域内框的数量变化而是需要做跨帧的目标关联。我在显著性检测的基础上提取目标中心点的跟踪轨迹维护一个轻量级的跟踪器。当目标中心点第一次进入区域时计数加一离开时不计重复进入同一个区域只计一次。这样统计出来的通过人数比直接框数累加准确得多也避免了目标在区域边缘来回抖动导致的重复计数。5.2 轻量密度估计头的实现当单帧检测人数超过阈值我设定为 25 人时进入拥挤状态启用密度估计分支。这个分支从检测模型特征金字塔的中层特征接入额外经过三个上采样层和两个卷积层输出一个尺度和原图相同的密度热力图。训练这个分支时我用了高斯核平滑的密度图作为回归目标损失函数是像素级 MSE 加一点结构相似性约束让热力分布更平滑。这个分支在推理时可以按需启用拥挤状态时才计算平时完全不走节省资源。实测下来密度热力图的价值不仅在人数统计精度上更体现在“热点展示”这个业务功能上。监控平台的热力图层叠加效果能直观呈现哪个区域是拥堵重灾区比纯数字列表可读性高很多。5.3 光线突变和雨雪天气对密度分析的影响还有一个实际运维中的问题光线突变。摄像头在夜间切换到红外模式时画面亮度和色彩会有跳变模型推理在短时间内会输出不一致的检测结果。我在前处理环节加了一个自适应预处理逻辑根据帧均值和灰度直方图动态调整输入图像的对比度在切换瞬间平稳过渡检测结果。这个细节虽然不影响稳定场景但在实际项目的可靠性评估中是加分项。雨雪天气的影响主要体现在目标框被遮挡和模糊。应对策略是降级判定当检测置信度整体下降、检测数突减时密度模块自动降低置信度阈值。这个策略简单但有效在连续几天的雨天测试中密度统计误差从 23% 降到了 12% 以内。6. RTSP 推流实现从解码到编码的完整工程化方案6.1 拉流解码CPU 软解还是 GPU 硬解RTSP 输入源来自摄像头通常为 H.264 或 H.265 编码。解码环节我推荐在 CPU 上做软解原因在于硬解虽然占用 GPU 资源少但在多路场景下会有解码器绑定限制和内存对齐问题软解在现代多核 CPU 上成熟稳定配合显存共享效率可控。我用 FFmpeg 的 API 拉流和解码解码输出为 YUV 格式。需要注意网络超时处理摄像头断流后FFmpeg 的阻塞读可能让进程卡死。解决方案是设置超时参数并通过独立线程监控心跳。断流后自动重连恢复后继续分析。实测重连耗时约 1 到 2 秒业务可接受。6.2 帧格式转换BGR、RGB 与 YUV 的踩坑记录解码器输出的 YUV 帧在送入 TensorRT 前需要转换为 RGB 平面格式。我在这个环节踩过一个大坑FFmpeg 解码帧的 strides 和宽度并不总是相同有对齐 padding。直接拿原始指针按宽度计算地址会导致画面倾斜或条纹。正确做法是用swscale做格式和分辨率转换输出连续的 RGB 缓冲。这个转换在 CPU 上做640 分辨率单帧耗时约 1.2ms四路并发时可接受。如果追求更极限的性能可以考虑 CUDA 上的转换方案但那需要单独写核函数工程成本较高先不推荐。6.3 TensorRT 推理的异步处理与批策略推理进程采用异步流水线解码线程把帧放入环形缓冲区推理线程从缓冲区取出帧并执行。TensorRT 的enqueueV2是非阻塞接口调用后会立即返回实际推理在 GPU 上异步执行。但要注意在执行下一帧推理之前必须确保 GPU 里的结果已拷贝回 CPU否则会覆盖上一帧的推理结果。我维护了一组固定大小的 CUDA buffer 池轮换使用输入和输出 buffer避免每次推理都分配显存。实测这种方式比每次重新分配输入输出的方案减少了约 25% 的延迟抖动。批处理策略方面在四路视频同时推理时我把四路帧组合成一个 batch 输入TensorRT 一次执行四帧。虽然单帧延迟比批 1 模式略有增加但整体吞吐翻倍GPU 利用率更高。这个权衡在边缘多路场景非常关键。6.4 编码推流H.264 还是 H.265码率怎么设推流端我用 FFmpeg 显卡编码器来做编码硬编码延迟低CPU 占用几乎为零。H.264 和 H.265 的选择需要考虑兼容性H.265 在相同码率下画质更好但某些 Web 播放器不支持硬解会产生卡顿。最终我选择了 H.264用更高的码率换取兼容性和低延迟。码率控制我用了 CBR 模式目标码率设置在 4Mbps。这个值在 1080p 下足够清晰叠加检测框和热力图后也不会有太多质量损失。关键是 GOP 大小设置为了降低播放端首帧延迟GOP 设为 2 秒即帧率 25fps 时 50 帧一个关键帧。同时开启repeat-head参数确保关键帧间隔稳定。还有一个容易被忽视的参数tunezerolatency。这个参数会牺牲一些编码压缩率换取极低的编码延迟在实时推流场景下是必须的。实测开启后编码延迟从约 120 毫秒降到了 20 毫秒以内画质损失几乎肉眼不可见。6.5 RTSP 服务器选择与延迟优化RTSP 服务器可以用现成的开源方案也可以自己用媒体服务器框架实现。考虑到项目规模不大我选了轻量级的开源 RTSP 服务端方案它支持简单鉴权、多客户端拉流系统资源占用很低。推流延迟集中在三处采集端编码缓冲延迟、网络传输缓冲延迟、播放器缓冲延迟。前面两个通过推流端参数基本可控播放端的缓冲延迟属于客户端播放器的解码缓冲策略只能通过关键帧间隔和封装格式来间接影响。实测局域网环境下从画面产生到播放器看到检测框叠加视图的端到端延迟稳定在 280 到 380 毫秒。7. 性能调优实录延迟、吞吐和显存占用的平衡7.1 TensorRT 的层融合优化带来的实际加速TensorRT 在转换模型时做了大量层融合将卷积、偏置、激活函数合并为单个算子减少内核启动和显存读写的次数。这解释了为什么同一个 ONNX 模型在 TensorRT 上的速度通常快于普通 PyTorch GPU 推理。我启动 TensorRT 的createInferBuilder后开启了所有可选的融合和优化选项实际推理速度对比 FP16 PyTorch 快约 2.3 倍。INT8 模式下再快约 1.8 倍。层融合虽然不能直接调整但我们可以通过模型结构来影响融合率避免使用 TensorRT 不支持的算子尽量用标准卷积和激活组合会给融合创造更好的条件。7.2 动态 shape 和固定分辨率的权衡固定输入分辨率 640x640 是一个非常有效的简化。动态 shape 虽然提升了灵活性但会让 TensorRT 在运行时做更多的内存规划和 kernel 选择带来额外开销。如果是单一布控场景强烈建议固定分辨率。多场景需要切换时再考虑多个固定分辨率的 optimization profile。7.3 GPU 显存管理多大才算够8GB 显存在实验中运行 4 路视频流绰绰有余。TensorRT engine 加载后占用约 500MB 显存加上输入输出缓冲池和 CUDA 上下文总占用不超过 2GB。真正的显存压力来自解码和编码环节如果使用 GPU 硬解需要额外预留显存如果在 TensorRT 之外再跑其他 CUDA 任务还要考虑上下文切换开销。官方明确支持多进程共享同一份 engine 的显存实际工程中可以让多个推理进程同时加载同一个 engine 文件共用一组权重。但要注意进程之间共享 engine 需要采用只读方式如果每个进程都创建自己的 CUDA context显存占用就会成倍增长需要注意分配策略。7.4 从 30 FPS 到 25 FPS 的稳定性选择推理速度完全能超过 30 FPS但我将推流和分析整体限制在 25 FPS。原因在于监控平台的播放端和业务流程都按 25 FPS 设计超过这个帧率并不会让用户感觉到流畅度提升反而会增加编码和解码的负载。而且系统保持一定的帧率余量可以在 CPU 负载升高时用丢帧方式保住关键帧的推理质量。具体帧率控制逻辑是这样做的在解码进程拿到新帧后检查时间戳与上一帧处理时间戳的间隔不足 40 毫秒就丢弃该帧。这种丢帧策略保证了推理和推流的节奏稳定避免因网络突发造成的帧积累延迟。8. 常见问题与排查技巧实录实战中踩过的坑8.1 TensorRT engine 加载失败或崩溃遇到最多的问题是 engine 文件与当前 TensorRT 版本或 GPU 架构不匹配。TensorRT 生成的 engine 是绑定到具体 GPU 架构和 TensorRT 版本的跨设备复制 engine 文件需要重新生成。排查方法是在 builder 阶段打印出设备属性和库版本在加载阶段打印错误日志对齐后再跑。另一个容易忽视的问题是环境变量CUDA_MODULE_LOADING的设定。在某些系统上如果模块加载顺序不对engine 加载会崩溃。设置成LAZY模式通常可以避免这个问题但性能可能有一点轻微损失。这个需要在项目环境里实测对比。8.2 INT8 校准精度损失严重怎么办如果校准后 mAP 下降超过 2 个点首先检查校准集看是否覆盖了足够的复杂场景。接着检查敏感层在生成 engine 时打开setCalibrationProfile人工观察哪些层的动态范围异常把它们排除在量化范围之外。我的经验之谈校准集数量宁多勿少且时间跨度要够长——不同时间段的光照变化对行人检测的影响远大于训练集分布差异。部分极端场景下行人的细节纹理信息在 INT8 下会丢失这是量化的天然代价无法完全消除只能通过保持敏感层为 FP16 来缓解。8.3 RTSP 推流花屏、黑屏和延迟漂移花屏通常来自编码参数设置不当或网络丢包。我会在推流端设置flagslow_delay和max_delay0降低缓冲同时在服务端设置基于 TCP 的 RTSP 传输避免 UDP 丢包。黑屏通常发生在断流重连时需检查解码线程在重连后是否重新初始化了编码器上下文。延迟漂移的根源是处理速度跟不上帧速导致帧在缓冲队列里越积越多。排查时监控推理进程的队列长度如果长期大于 5说明处理能力不足需要检查 GPU 利用率或裁剪处理链路。实测中把批处理从 1 改为 2 路组合后队列长度从 12 降到了稳定在 1 到 2延迟恢复正常。8.4 密度统计过拟合和误差分析密度统计误差来源主要有两类漏检和重复计数。漏检在遮挡和光照异常场景中不可避免重复计数则源于跟踪器的 ID 切换和跨帧抖动。误差分析时要按时间段分片评估而不是只看平均值。我做过一组对比实验不启用跟踪器、仅基于检测框统计时密集时段误差约 18% 到 25%启用简单跟踪器后误差降到 8% 到 12%接上密度分支后在极拥挤场景的误差控制在 5% 以内。这个数据说明了混合策略的设计价值。9. 一些留给后来人的经验和扩展思路项目最后整体交付时客户关心的不是某个技术点跑得多快而是“系统稳定不稳定、误报多不多、操作员看不看得懂”。这几句话实际倒逼我在工程层面做了不少收尾工作可视化界面上加了区域划分图层、检测置信度可调开关、断流告警和自动恢复日志以及按小时导出密度统计报表。这些业务侧的完善和算法性能一样重要。从后续扩展的角度看有三个方向值得考虑。一是把检测模型替换为面向边缘优化的新轻量结构当前模型的骨干部分其实还有进一步压缩空间可以让移动端的部署成为可能。二是把 INT8 量化模式从离线阶段进一步推进到在线自适应让系统能针对光线骤变自动重校准。三是把 RTSP 推流升级到支持多客户端负载均衡的架构在大规模监控平台场景下保证稳定性。这个项目做完我个人最核心的体会是TensorRT 的部署优化不是“把模型转换完就结束”而是一个从数据分布、模型结构、量化策略到推理调度、视频链路全栈都需要综合考虑的系统工程。任何一环掉链子最终的体验和性能都会大打折扣。希望这篇文章能把我在项目里踩过的坑和总结的经验完整传达到位帮你少走几条弯路。

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

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

免费获取报价 →
↑