拓冰建站拓冰建站
首页 / 资讯中心 / 正文

实时视频AI项目实战:YOLO模型与SmartMediaKit流媒体集成全指南

前阵子做了个挺典型的实时视频AI项目需求不复杂把厂区里几十路摄像头接进来实时识别安全帽、反光衣、区域闯入再把结果叠加到视频流上给值班室看。听起来就是“YOLO检测一下就行”但实际上手才发现光是把几十路RTSP流稳定拉进来、解码、分配、推理、编码输出就是个比训练模型更磨人的活。真正把整套东西跑顺靠的是两样东西的组合一个是YOLO系模型负责“看懂画面”另一个是流媒体服务框架SmartMediaKit负责“搞定视频”。这篇就把我踩过的坑、趟出来的集成思路和实测数据整理一遍给正准备做类似“实时视频AI”项目的朋友一个参考。1. 视频AI项目的第一个卡点不是模型是视频帧从哪来1.1 从“跑通单张图片”到“跑通实时多路视频”的差距大多数人入门YOLO时的流程是读一张图片或者一段视频文件预处理后丢给模型推理画框展示结果。换成真实项目输入源变成了网络摄像头、NVR、GB28181平台常见协议是RTSP、RTMP有的还带认证、带时间戳、带音频流。这时候“读一帧”这件事本身就变成了一个需要认真处理的工程问题。我自己一开始也偷懒过在Python里对每路摄像头开一个cv2.VideoCapture线程循环read()拿到帧就丢给YOLO推理。小规模测试三四路没什么问题但接到二十路以上的时候状况频出——RTSP断流后线程阻塞、解码延迟越积越高、CPU被打满最后整个程序卡死。这不是YOLO的问题是视频接入层没做好。摄像头的RTSP流本质上是个“不保证稳定”的数据源弱网、设备重启、码率波动都会导致流中断。业务逻辑里最不该做的就是让AI推理代码直接去管这些传输层的破事。所以需要有一个独立的、健壮的组件来把“拉流—解码—缓冲—重连”这些事情全部接管。SmartMediaKit在方案里扮演的就是这个角色。1.2 自己拼还是用现成框架SmartMediaKit的角色定位SmartMediaKit是一个开源的流媒体服务框架底层基于C11支持RTSP推拉流、RTMP、HTTP-FLV、WebRTC等常见协议解码走FFmpeg内部实现了完整的会话管理、音视频同步、缓冲和断线重连机制。在这些能力之上它更像一个“视频总线”各路设备把流推给它它统一管理解码后的帧数据然后分发给下游模块。在集成YOLO时SmartMediaKit不做推理也不关心检测结果它只负责两件事把视频流稳定地接入进来把解码后的视频帧以可控的方式交给外部模块。这样就把“AI推理”和“视频接入”彻底解耦了。有人可能会问那我直接写个FFmpeg命令行拉流转发不行吗命令行转发确实能做但它解决不了“把某一帧交给AI处理”的问题。FFmpeg命令的输出是编码后的码流AI需要的是解码后的原始帧中间还得再做一次拉流和解码。SmartMediaKit作为库嵌入到项目里可以在解码后、编码前的链路当中把原始帧回调给推理模块这一下就把整条链路打通了。说句实在话用现成框架的意义不是“少写代码”而是把容易出问题的网络传输和协议细节交给一个经历过大量场景验证的组件。我们自己写拉流重连逻辑大概率没有SmartMediaKit处理得好这就是选它的核心理由。2. YOLO版本那么多选型时先问部署平台再问精度2.1 不同版本在视频场景里的真实差异“YOLO目前到几了”这个问题我隔三差五就在技术群里看到。从YOLOv5的工程化成熟度到YOLOv8加入实例分割和姿态估计再到YOLO11在COCO上的精度进一步提升版本迭代确实快。但对做视频AI项目的人来说版本号本身不重要重要的是你计划部署的平台和你的业务真正需要什么能力。模型版本检测头设计是否自带实例分割边缘设备部署成熟度社区资料丰富度YOLOv5Anchor-Based无需额外方案很高极高YOLOv8Anchor-Free有YOLOv8-seg高高YOLO11Anchor-Free有YOLO11-seg中等持续完善中中高从视频AI集成的角度我更看重的是模型导出的便捷性和部署生态。YOLOv5和YOLOv8在ONNX、TensorRT、RKNN各个平台的转换工具链都已非常成熟踩坑解决案例多。YOLO11作为较新的版本精度更高尤其在小目标上优势明显但相应的在部分边缘平台的算子支持和量化工具链上还需要更多适配工作。之前做一个工地场景的项目需求是检测远处塔吊区域的工人是否佩戴安全帽属于典型的小目标检测。实测下来YOLO11的检测率确实比YOLOv8高一些但模型转换到RK3588平台时遇到了算子兼容问题最后只能回退到YOLOv8加图像分块处理。所以选型的时候不能只盯着精度榜单还得把“能不能跑在目标硬件上”放在最前面。2.2 模型大小、精度与延迟的平衡数据YOLO系列同一版本下还有n/s/m/l/x不同规格我在实际项目里一般按这个原则选边缘小盒子用nano或small带独立显卡的服务器用medium或large不到万不得已不用extra-large做实时视频。拿YOLOv8在安全帽检测场景的实测数据举例输入尺寸640×640NVIDIA RTX 3060TensorRT FP16模型规格平均推理耗时毫秒实测mAP0.5显存占用YOLOv8n约2.50.87约1GBYOLOv8s约4.00.91约1.5GBYOLOv8m约7.50.94约2.5GBYOLOv8l约12.00.95约3.5GB这个数据有点反直觉n和l的精度差距不到10个百分点但延迟差距接近5倍。实时视频场景下延迟意味着价值。你检测一个违规行为早一秒钟发现和晚一秒钟发现处理方式完全不同。所以我的建议是如果业务对精度要求不是极其苛刻尽量选小模型加合理的后处理逻辑给整条视频管线留出余量。2.3 预训练权重还是自训模型COCO覆盖不了的业务边界COCO数据集的80类覆盖了人、车、动物等通用目标但真实业务里大部分检测对象它都不认识。安全帽、反光衣、吸烟动作、积水区域、消防设施这些都属于“COCO之外”的需求。这种情况下预训练权重只能作为迁移学习的起点。我的做法是下载在COCO上预训练的权重冻结骨干网络的前若干层用自己的标注数据微调。这样做有两个好处一是收敛速度快小数据集训练几百个epoch就能达到可用状态二是模型对通用特征的提取能力有保障在光线变化、视角变化等场景下更稳。借助完整的图片标注、数据集管理、模型训练和模型导出功能开源平台来做这件事会省力很多——数据标注完直接能划分数据集、配置训练参数、输出部署格式。我后来复盘项目里省下大量“环境配置”和“格式转换”时间就是因为在训练链路工具上选对了。3. 从原始视频到训练集标注、格式转换与难例循环3.1 标注工具选型与YOLO格式的“坑”训练自己数据集的第一步是标注热词里“yolo标注训练工具”“yolo数据集标注”这类搜索量一直很大说明这是很多人的拦路虎。主流的标注工具我基本都用过LabelImg是老牌选择胜在轻量、能直接导YOLO格式Label Studio功能全面支持多人协同和自动标注辅助x-AnyLabeling则在交互式分割上做得更好。不管用哪个工具YOLO格式的坑是共通的。YOLO标签就是一行五个数字class cx cy w h其中cx、cy是归一化后的中心点坐标w、h是归一化后的宽高全部是0到1之间的浮点数。很多人第一次标注完训练时发现loss异常高或者检测框偏移多半是格式出了问题。常见的几个格式相关错误我都遇到过类别编号错位标注工具里类的顺序和训练配置文件里的顺序不一致导致模型把安全帽认成了人。坐标未归一化比如标注工具导出的是像素坐标直接拿来训练框全跑到画面外。空标签文件某些标注工具在“跳过”或者“漏标”时会生成空TXT训练时如果没过滤掉会保报错或者影响loss统计。做标注的第一步不是画框而是先定义一个固定不变的类别清单并且让所有参与标注的人严格遵守这个清单的顺序。中途加类别会带来数据管理上的麻烦最好在一开始就规划好。3.2 KITTI等标注数据转YOLO格式的脚本思路很多公开数据集用的不是YOLO格式而是COCO、VOC或者KITTI格式。比如热词里的“kitti标注转yolo”就是把KITTI的标注转成YOLO训练能用的格式。KITTI格式的TXT文件每一行大致是class truncated occluded alpha x1 y1 x2 y2 x3 y3 x4 y4 z其中x1 y1 x2 y2是矩形框的两个顶点像素坐标。转成YOLO格式时核心是把顶点坐标转成中心点加宽高的归一化格式import os def kitti_to_yolo(txt_path, img_width, img_height): yolo_lines [] with open(txt_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_name parts[0] # KITTI的框坐标在第5和第6个字段之后 x1, y1 float(parts[4]), float(parts[5]) x2, y2 float(parts[6]), float(parts[7]) x1, x2 min(x1, x2), max(x1, x2) y1, y2 min(y1, y2), max(y1, y2) # 边界裁剪防止越界 x1 max(0, min(x1, img_width)) x2 max(0, min(x2, img_width)) y1 max(0, min(y1, img_height)) y2 max(0, min(y2, img_height)) w x2 - x1 h y2 - y1 if w 0 or h 0: continue cx (x1 x2) / 2 / img_width cy (y1 y2) / 2 / img_height nw w / img_width nh h / img_height yolo_lines.append(f{class_index_map.get(cls_name, 0)} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) return yolo_lines这个脚本看起来简单但有三个细节容易被忽略一是必须保证图片尺寸参数正确不同数据集的图片分辨率可能不一致不能用一个固定值硬套二是坐标可能超出图片边界需要裁剪三是类别名称到ID的映射必须全局统一。这类转换脚本是一次性写的但一定要留档。因为下次做新项目、再找一批公开数据集时脚本稍微改改就能复用。我见过太多次“上次那个转换脚本没了”的窘境重新写一遍也就半小时但心情会非常糟糕。3.3 数据集划分、类别不均衡与难例挖掘标注完成后需要把数据划分成训练集、验证集和测试集。划分比例我习惯用8:1:1但有一个硬性要求来自同一段视频的帧必须划到同一个集合里否则验证集会泄漏到训练集指标虚高。类别不均衡是真实业务数据里几乎必然出现的问题。做一个厂区场景的数据集安全帽样本可能有几万框反光衣样本可能只有几千框特殊工装可能只有几百框。这种情况下模型很容易偏向样本多的类别。我的处理手段是组合拳对少数类别做复制增强、图像旋转、亮度调整等操作同时可以在训练时给少数类别设置更高的loss权重。视频数据相对图片集有一个独特优势可以做难例挖掘。模型训练完第一版后把所有帧再跑一遍把漏检、误检的帧自动挑出来人工确认补充标注再放进训练集。因为视频是连续的漏检的目标在相邻帧里大概率还能找到用跟踪器甚至可以半自动补框。几轮下来模型在真实场景的表现会明显上台阶——这个技巧是纯图片数据集给不了的。4. SmartMediaKit的集成位置把流媒体当插座把YOLO当插头4.1 视频帧获取方式对比回调、拉流与抽帧策略集成SmartMediaKit时首先要确定视频帧获取方式。SmartMediaKit的核心能力是管理各种协议的流接入和分发接入的流可以来自RTSP摄像头、RTMP推流端或者本地文件。解码后的原始视频帧可以通过注册帧回调的机制交给我们注册的消费者。实际项目里我们采用的是“中心节点模式”每一路摄像头以RTSP源的形式接入SmartMediaKit由它统一解码帧数据同时分发给两条下游链路——一条是编码输出给值班室查看另一条是给YOLO推理模块做检测。这样设计的好处是摄像头和AI模块之间不直接耦合摄像头只需要关心推流AI只需要关心拿帧。帧率控制是集成中非常重要的一个设计点。一路摄像头25帧/秒如果每帧都做检测几十路加起来算力开销太大而且大多数业务根本不需要这么高的检测频率。我的做法是加一个抽帧策略默认每3帧检测1帧即大约8帧/秒的检测频率对安全帽、区域闯入这类场景完全够用。如果某些摄像头覆盖的是快速移动区域可以单独提高该路的检测频率。4.2 BGR转换和缩放策略别忽视的耗时点摄像头解码出来的帧原始格式大多是YUV420或者NV12而YOLO模型输入一般是RGB或者BGR的640×640。这个转换和缩放的耗时比很多人想象的大得多。如果直接在整帧上做格式转换、再缩放然后再做归一化每一路每秒25帧这个开销会严重挤占推理资源。我的做法是把“格式转换缩放归一化”合并成一次操作完成而不是分三步走。SmartMediaKit底层有FFmpeg的滤镜能力可以直接在解码后挂scale滤镜先把帧缩放到模型输入尺寸再一次性地把YUV变成RGB这样后面推理模块拿到的已经是可以直接输入模型的数据。伪代码大致是解码回调 - 获取原始帧(编码格式YUV420) - 通过FFmpeg滤镜链: scale640:640, formatrgb24 - 拷贝到推理buffer或零拷贝 - 转换为模型tensor归一化 - 排队进入推理引擎这里还要提醒一个细节GPU推理和CPU解码之间的数据搬运。如果解码做在CPU侧推理做在GPU侧每一帧都需要通过PCIe拷贝到显存这个拷贝耗时在小模型推理时甚至比推理本身还高。解决办法要么是做异步拷贝要么想办法复用显存buffer避免重复分配。前者容易实现后者对性能提升非常明显。4.3 把推理塞进流媒体管线的正确位置很多第一次集成的人容易犯一个错误把推理放在流媒体分发的关键路径上也就是“解码—推理—编码”串行执行。这会导致整个管线的帧率被推理速度拖累一路卡所有路都卡。正确的是把推理放在旁路位置。SmartMediaKit在解码出原始帧后一帧数据同时复制给“编码分发”和“AI推理”两个独立分支。编码分支保持原有的流畅转发推理分支只在需要的时候消费帧数据做检测检测结果异步回到上层应用再通过叠加方式渲染到显示流上。这样的架构下即使AI推理偶尔出现性能抖动也不会影响视频观看的连续性。值班室看到的监控画面永远是实时流畅的检测框偶尔慢个一两帧人眼基本感知不到。有一点必须强调AI推理和视频编码如果都依赖GPU需要做好显存和算力的分配。不要把显存全部留给模型要给编码器留出足够的空间。在NVIDIA平台上可以用“GPU显存上限”参数约束推理框架的占用比如TensorRT的setMaxWorkspaceSize给视频编码让路。5. 推理管线的后处理与性能调优不止看模型速度5.1 NMS后处理没那么神圣也没那么便宜模型推理输出的原始tensor是一堆框坐标、置信度和类别概率必须经过后处理才能得到最终检测结果。YOLO后处理流程里最关键的一步是NMS非极大值抑制用来解决同一个目标被多个框重复框住的问题。NMS的耗时在GPU上看起来不显眼但在CPU上或者低功耗边缘设备上开销会非常扎眼。在树莓派、RK3588这类设备上做推理NMS的耗时甚至可能超过模型前向推理本身。优化思路有几种第一种先按置信度阈值粗过滤低于阈值的框直接丢弃再做完整NMS可以显著减少参与计算框的数量。第二种按类别分组并行做抑制不同类别之间天然不需要互相抑制。第三种在检测框数量极少的场景下可以降低NMS的IoU阈值甚至只保留置信度最高的框——但这个方法我建议只用在目标单一、重叠率极低的业务里贸然使用会漏检。5.2 多路视频批量推理与显存管理单路视频逐帧推理是最浪费的做法尤其当模型输入尺寸是640×640时GPU的并行能力完全没有被充分利用。我的方案是把多路帧打包成一个batch同时推理。流程是管理一个帧调度器维护各路视频的帧队列攒够batch数比如4路或8路后统一拷贝到推理输入buffer一次前向推理输出拆解后对应各路的结果。实测下来在RTX 3060上YOLOv8n单路推理耗时约2.5ms而8路batch推理总耗时约8ms平均每路仅1ms吞吐提升明显。但这种方式有一个工程上的复杂度不同路视频的帧到达时间是不对齐的需要一个缓冲超时机制比如等50ms没攒够batch就把已有帧先送进去否则延迟会无限累积。这个超时参数需要根据实际帧率和模型速度调优设大了延迟高设小了batch利用率低。显存管理上核心原则是复用而不是重新分配。推理框架的输入输出buffer在启动时就分配好整个生命周期内重复使用而不是每帧都申请新的显存。我遇到过连续运行一两天后显存缓慢增长的情况排查下来就是某个模块每次推理都创建新的CUDA context或者tensor积累下来就炸了。5.3 端到端延迟实测延迟到底花在哪儿做视频AI项目客户最关心的就是“延迟多少”。这里说的延迟是端到端延迟从摄像头画面发生的那一瞬间到值班室屏幕上看到检测结果画面的时间差。实测数据大概是这样环节耗时毫秒备注摄像头采集与编码约50-80取决于摄像头硬件网络传输局域网约5-20有线较稳WiFi波动大流媒体解码约10-20硬解更快软解看CPUAI推理约3-12取决于模型与硬件画框叠加与编码约15-30低延迟参数可优化播放端显示缓冲约30-80播放器缓冲越少延迟越低合计算下来局域网环境下端到端延迟在200到350毫秒之间是比较正常的。如果远超这个范围优先检查播放端的缓冲设置和编码器的GOP大小。GOP越大关键帧间隔越长播放端需要等待关键帧才能开始解码延迟就越大。建议把GOP设成帧率的2到4倍比如25帧/秒的流GOP设为50到100。6. 部署到不同硬件平台RKNN、TensorRT与长时间运行的那些坑6.1 三种典型平台的部署差异同一个YOLO模型在不同硬件平台上的落地难度完全不同。我把实际用过的三种平台放到一起对比一下平台典型推理框架模型格式是否支持动态尺寸部署难度典型功耗单路推理耗时YOLOv8sx86 NVIDIA GPUTensorRTONNX - TensorRT Engine支持有限建议固定低高约4msNVIDIA JetsonTensorRTONNX - TensorRT Engine同上中中约5-8msRK3588RKNN-Toolkit2ONNX - RKNN大多要求固定高很低约15-25msx86加NVIDIA是回退方案开发调试方便遇到问题能找到的参考资料最多。Jetson系列在嵌入式场景中综合体验最好工具链和桌面端一致很多模型可以直接迁移。RK3588的优势是功耗低、价格友好但模型转换和算子适配是真正的硬骨头尤其当你用的YOLO版本更新、算子更复杂的时候。6.2 模型转换过程中的“经典老坑”模型转换的坑每个平台都有自己的脾气。ONNX导出时常见的问题是某些算子在ONNX中不支持。比如早期YOLOv5用的Focus模块部分导出版本会变成一堆切片拼接操作在TensorRT转引擎时还会报维度错误。我的经验是优先使用官方配套的导出脚本并且固定PyTorch和ONNX的版本随便升级依赖往往是踩坑的开始。TensorRT转换时动态尺寸和静态尺寸的选择也很有讲究。TensorRT的动态shape模式在性能上通常不如静态shape而且一旦开启动态尺寸很多算子融合优化无法生效。我们的做法是统一将模型输入固定为640×640好处是推理速度和显存占用都可预期坏处是没法处理超大分辨率画面——但可以通过保持宽高比加padding的方式解决。RKNN转换则是另一套玩法。RK3588上跑YOLO必须把模型转成RKNN格式而且一般要做量化INT8才能发挥NPU性能。量化校准数据集的选择很关键如果校准集和真实场景分布差异大量化后精度会掉得很难看。我的做法是从真实场景录制的视频里抽几百帧作为校准集而不是用公开数据集的图片。还有个容易被忽略的操作RKNN-Toolkit2的版本要和板子上的RKNN Runtime版本严格对应否则模型加载时会报版本不匹配的错误。这个报错信息通常很不直观我当时排查了很久才怀疑到版本头上。6.3 长时间运行的稳定性内存泄漏与断线重连视频AI系统和普通模型服务最大的不同在于它不是跑几秒钟就结束的实验而是要7×24小时不间断运行。长时间运行暴露出来的问题和功能正确性几乎无关全是稳定性问题。内存泄漏是我遇到最多的敌人。显存不涨但系统内存缓慢上涨大概率是解码器每次创建新对象没有释放或者推理buffer在持续重新分配。排查手段比较朴素用nvidia-smi盯显存用top盯内存如果观察到单调递增的趋势就用valgrind或者ASAN逐模块排查。最可恨的是那种“跑三四天才涨一点”的泄漏肉眼很难发现我的建议是新功能上线后至少要连续观察一周的内存曲线。摄像头断线重连也是一个逃不掉的问题。局域网摄像头虽然相对稳定但设备重启、网线松动、电源波动都会导致RTSP流中断。SmartMediaKit自身有断线重连机制但重连期间的策略需要仔细斟酌是马上重连还是退避一段时间如果摄像头在升级固件疯狂重连只会加大设备的压力。我用的策略是设置递增的重连间隔从3秒到30秒超过一定次数后转为低频探测由上层应用决定是否需要告警。还有一种隐蔽的坑解码线程数随时间增长。原因是每次断线重连都会创建一个新的解码上下文如果旧上下文没有完全释放虽然单个内存不大但几十次重连累积下来就是可观的内存占用。我最终通过复用解码器、限制解码上下文创建次数的方式解决了这个问题。另外还有一个小建议视频AI服务的日志系统最好独立出来记录每一路流的连接状态、断开原因、推理耗时和异常检测事件。上线初期这些日志会帮助你快速定位问题“跑着跑着某个摄像头不出结果了”这种情况有日志和没日志的排查效率完全不一样。整套系统做下来我最大的体会是实时视频AI项目的难点通常不在AI本身而在AI和视频基础设施的衔接处。把SmartMediaKit当插座、把YOLO当插头两者解耦后后续换模型、加算法、扩路数都变得轻量很多。如果你正在规划类似的系统先从视频接入和帧管理上下功夫大概率能少走很多弯路。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门