
简介完整提供YOLOv8模型与RTSP视频流结合的实时目标检测项目面向具备Python和深度学习基础的开发者可应用于视频监控、智能交通管理、工业自动化、安防巡检等实时视觉场景。资源共467个文件压缩包169.25MB包含145个Python源码、215个编译后的pyc文件、80个YAML模型配置文件、6个pt预训练权重以及网页端展示所需的HTML、CSS、JS组件与README说明另有测试图片和演示视频便于快速启动与验证效果。已有220人学习浏览该资源。项目已将模型推理、视频流接入和前端展示串联为完整流程部署者可用内置权重直接运行参照YAML配置调整检测参数结合标注样例图片核对检测结果可直接作为基于RTSP的实时目标检测系统的工程参考与二次开发起点。1. 从“读摄像头”到做检测YOLOv8接RTSP流为什么不能照抄单张图片用YOLOv8对RTSP视频流做目标检测听起来就是把“读取摄像头画面”和“模型推理”两件事接在一起实际做起来却是一条非常容易翻车的链路。刚开始我习惯把每一帧当成单张图片循环调用一次model.predict()在测试视频上效果不错但一接到海康或者大华的实时流上花屏、延迟堆积、断流后程序僵死、误报频出问题一个接一个。RTSPReal Time Streaming Protocol解决的是“视频帧怎么从摄像头稳定到达你的程序”YOLOv8解决的是“帧里有什么”。两者的关注点完全不同而你需要在中间做缓冲、丢帧、超时重连和前后处理。这篇文章按“拉流 → 选模型 → 双线程推理 → 断流排查 → 边缘设备部署”的顺序把我在项目里实际采用的方案拆开讲适合正在做安防巡检、路口车检、安全帽检测和工业视觉这类实时业务的开发者。新手可以照着把第一版跑通熟手可以重点看我放在第五、六章的踩坑记录和调参边界。2. RTSP地址里的门道主码流、子码流与第一个能跑通的拉流命令2.1 海康、大华、萤石的RTSP取流地址格式做RTSP检测之前第一步永远是先把摄像头的取流地址拿到手。不同厂商的路径规则差异很大最常被问到的是海康威视。典型的取流地址长这样rtsp://用户名:密码摄像头IP:554/Streaming/Channels/101其中路径最后一位最有讲究101代表通道1的主码流102代表通道1的子码流201代表通道2的主码流202代表通道2的子码流。如果你在浏览器里能看到画面但程序连不上八成是密码里带了特殊字符没有转义或者是把101写成了1。大华的地址格式完全不同rtsp://用户名:密码摄像头IP:554/cam/realmonitor?channel1subtype0subtype0是主码流subtype1是子码流。萤石摄像头的取流地址形态更多常见的是rtsp://admin:验证码IP:554/h264/ch1/main/av_stream具体以设备型号为准。我的习惯是拿到新设备后先查厂商协议文档里的“取流地址”一节把完整地址存成环境变量而不是硬编码到脚本里后面换测试流、换设备都会省事。2.2 用ffprobe和OpenCV把链路跑通再写业务逻辑刚拿到地址时我一般先用ffprobe验证链路是否通、分辨率是多少这比直接写Python去cap.read()更容易定位问题。下面这个命令会输出视频流的宽度、高度和编码格式ffprobe -rtsp_transport tcp -v error \ -select_streams v:0 \ -show_entries streamwidth,height,codec_name,avg_frame_rate \ -of csvp0 \ rtsp://username:password192.168.1.64:554/Streaming/Channels/101-rtsp_transport tcp是这里最重要的参数。RTSP底层可以走UDP或TCPUDP延迟低但容易丢包TCP更稳。我只要连的是局域网摄像头一律先指定TCP避免后面排查时把网络抖动误判成算法问题。-select_streams v:0只取视频流-of csvp0把输出改成简洁的CSV格式方便直接看结果。链路通了之后再用OpenCV拉一帧画面确认程序侧能收到解码后的BGR图像import cv2 url rtsp://username:password192.168.1.64:554/Streaming/Channels/101 cap cv2.VideoCapture(url, cv2.CAP_FFMPEG) print(isOpened:, cap.isOpened()) ok, frame cap.read() if ok: print(frame shape:, frame.shape) else: print(read failed) cap.release()cv2.VideoCapture的第二个参数cv2.CAP_FFMPEG明确告诉OpenCV使用FFmpeg后端解码对H.264/H.265的兼容性比默认后端好很多。frame.shape输出形如(1080, 1920, 3)分别是高度、宽度、通道数这个数值直接决定后面YOLOv8输入尺寸怎么设。2.3 主码流还是子码流按检测目标的大小来选择很多新手默认选主码流觉得分辨率高一定更好。实际不是。主码流通常是1080P甚至4K带宽和解码压力大RTSP场景下的帧率还不一定比子码流高子码流一般是640×360或704×576画质差但对小目标检测非常不友好。我的选型标准很简单目标在画面里占的像素大就直接用子码流降低解码压力目标小比如安全帽、鸟、远处车辆就必须用主码流否则YOLOv8的640×640输入在缩放时会把小目标直接抹掉。另外注意用主码流时不要把imgsz设成1280去“迁就”分辨率那样推理耗时翻倍收益却很小。更合理的做法是保持imgsz640把镜头对准场景里目标最可能出现的中近景区域让目标在画面里占的像素面积尽量大。检测算法解决不了摄像机安装位置带来的根本问题RTSP拉流也一样。3. YOLOv8模型选型与推理参数先跑对再谈跑快3.1 最小推理调用从COCO权重到视频帧用Ultralytics的YOLOv8对一帧图像做推理代码非常短from ultralytics import YOLO model YOLO(yolov8n.pt) results model.predict( sourceframe_bgr, imgsz640, conf0.25, iou0.45, device0, verboseFalse )model.predict返回一个Results对象列表每个元素对应一张输入图。在视频场景里常用的是results[0].boxes它的.xyxy、.conf、.cls三个属性分别给出检测框坐标、置信度和类别索引results[0].names是类别名列表。想要快速看到效果直接调用results[0].plot()就能得到画好框的BGR图像适合放到cv2.imshow里做可视化。第一个参数device0指第一块GPU机器没有CUDA时改成devicecpu。这个调用看起来简单但我见过不少人把predict的结果转成JSON、再组装成业务对象最后才拿去做显示白白浪费几十毫秒。RTSP检测里能少一次拷贝就少一次直接使用plot()和boxes的底层数据后面的性能压力会小很多。3.2 n/s/m/l的参数规模差异先看计算量和目标大小YOLOv8官方提供n、s、m、l、x五个规格我按常见的公开参数量整理了一个选型表模型参数量计算量典型使用场景yolov8n3.2M8.7 GFLOPsRK3588、CPU、低功耗边缘盒子单路RTSPyolov8s11.2M28.6 GFLOPs普通GPU、精度要求稍高的单路场景yolov8m25.9M78.9 GFLOPs中大规模GPU小目标多、需要更高mAPyolov8l43.7M165.2 GFLOPs离线分析吞吐量要求不高的RTSP任务yolov8x68.2M257.8 GFLOPs追求极致精度通常不用于实时视频流RTSP场景里我更愿意推荐从yolov8n或yolov8s起步。原因不是精度差而是视频流有帧率要求模型推理时间直接决定延迟。一个常见误用是拿yolov8x去做实时检测GPU利用率看似很高实际每秒只能处理几帧检测框比现场实际画面延迟好几秒违背了实时监控的初衷。先跑通n再看召回率是否达标不够再升级到s或m这是最省时间的路径。3.3 针对摄像头场景训练自有数据集安全帽、鸟类、车辆都一样如果RTSP摄像头要检测的不是COCO里的80类而是安全帽、鸟类、占道物品这类专属目标直接用yolov8.pt权重基本不可用。常见做法是在自己的数据集上做微调数据标注格式是Ultralytics标准的YOLO txt格式每行一个目标类别 cx cy w h坐标是归一化到0到1的浮点数。训练命令很直接yolo detect train \ datasafety_helmet.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0data指向一个YAML文件里面至少写清楚path、train和names三个字段。batch16要按显存调整12GB以上显存的卡一般没问题epochs不是越大越好我自己习惯先跑100轮看验证集mAP如果后面曲线不再上升就提前停止。训练时还需要注意数据集里的图片最好从摄像头实际抓帧抽出来而不是从网上下载一堆风格完全不同的图否则推理时画质、亮度、逆光表现都会和训练集脱节。4. 双线程拉流推理RTSP目标检测最小可复现脚本4.1 为什么把拉流和推理拆成两个线程我早期犯过的最大错误是把cap.read()和model.predict()放进同一个while循环里。表面看逻辑最简单实际上暗藏一个生产者-消费者问题摄像头以25帧的速度产生帧而YOLOv8单帧推理可能耗时100毫秒甚至更多。推理期间FFmpeg解码器还在继续收帧cap.read()下一次读到的是被积压了好几秒的旧帧。这就是延迟持续增长的根源画面越来越“拖”最后达到数秒时检测结果已经完全失去实时意义。把它想成一条流水线摄像头是上游工位模型是下游工位如果中间没有缓冲并且上游不停工下游永远在加工旧物料。解法是给两个线程之间加一个有界队列拉流线程只负责把新帧放进队列推理线程只负责从队列取帧检测。队列满了就丢最旧的一帧保证下游永远拿到的接近最新数据。4.2 完整脚本RTSP拉流线程与YOLOv8推理线程下面是我实际用于单路RTSP检测的最小脚本可以保存为rtsp_yolov8_demo.py直接运行import threading import queue import time import cv2 from ultralytics import YOLO STOP threading.Event() RTSP_URL rtsp://username:password192.168.1.64:554/Streaming/Channels/101 MODEL_PATH yolov8n.pt # 有界队列容量故意设小防止积压旧帧 frame_q queue.Queue(maxsize4) def rtsp_reader(q: queue.Queue, url: str, stop_evt: threading.Event): 只负责拉流和解码把新帧放进队列。 cap cv2.VideoCapture(url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while not stop_evt.is_set(): ok, frame cap.read() if not ok: # 断流后不能硬撑着释放资源等几秒再重建 cap.release() time.sleep(3) cap cv2.VideoCapture(url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) continue if q.full(): try: q.get_nowait() # 队列满了丢最旧的一帧 except queue.Empty: pass q.put(frame) cap.release() def detection_worker(q: queue.Queue, model_path: str, stop_evt: threading.Event): 只负责模型推理和结果可视化。 model YOLO(model_path) prev_time time.time() while not stop_evt.is_set(): try: frame q.get(timeout0.5) except queue.Empty: continue results model.predict( sourceframe, imgsz640, conf0.25, iou0.45, device0, verboseFalse, ) annotated results[0].plot() now time.time() fps 1.0 / max(now - prev_time, 1e-6) prev_time now cv2.putText( annotated, ffps: {fps:.1f}, (12, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2 ) cv2.imshow(rtsp-yolov8, annotated) if cv2.waitKey(1) 0xFF ord(q): stop_evt.set() if __name__ __main__: q queue.Queue(maxsize4) t1 threading.Thread(targetrtsp_reader, args(q, RTSP_URL, STOP), daemonTrue) t2 threading.Thread(targetdetection_worker, args(q, MODEL_PATH, STOP), daemonTrue) t1.start() t2.start() try: while not STOP.is_set(): time.sleep(1) except KeyboardInterrupt: STOP.set()逻辑说明rtsp_reader是生产者循环执行cap.read()并put进队列detection_worker是消费者从队列get一帧处理一帧。两个线程由同一个STOP事件控制退出按CtrlC时主线程stop_evt.set()两个子线程都会在下一个循环退出。daemonTrue保证主程序退出后不会残留线程。关键参数有两个。queue.Queue(maxsize4)控制最大缓冲长度设得越小延迟越低但太小容易丢帧4到6是实践中比较平衡的值。cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)用来限制FFmpeg解码器内部的帧缓存很多RTSP卡顿问题都是因为这层默认缓存被积满。q.get(timeout0.5)是为了让推理线程在队列空时能定期醒来查看停止标志而不是永远阻塞。4.3 没有真实摄像头时先拿本地视频推一个RTSP测试流在没有现场设备的时候我习惯先做一个本地RTSP测试流来复现问题。常见做法是用开源的RTSP服务端比如mediamtx再用FFmpeg把一段MP4循环推上去ffmpeg -re -stream_loop -1 -i test_video.mp4 \ -c copy -f rtsp rtsp://127.0.0.1:8554/demo-re让FFmpeg按视频原始帧率真实速度读取输入模拟摄像头实时推流-stream_loop -1是无限循环-c copy不做转码直接复用原始编码数据推流对CPU几乎零开销。这样你就能得到一个rtsp://127.0.0.1:8554/demo的标准RTSP测试流后续断流重连、延迟测试都可以在上面做不受外网带宽影响。还有一点值得注意别用公网上的所谓“RTSP测试流”来做性能基准。公网传输带来的抖动、丢包、码率波动会把算法本身的耗时掩盖掉最后你会分不清是模型慢还是网络慢。本地推流最大的优势是可控我在做基准测试时只认本地源。4.4 RTSP场景下的关键参数速查参数推荐值作用CAP_PROP_BUFFERSIZE1降低FFmpeg解码缓冲减少延迟frame_q.maxsize4~6平衡延迟与丢帧率conf0.25~0.4太低误报多太高漏检多iou0.45~0.5控制重叠框合并强度imgsz640主流可用性追求小目标可试800device0 或 cpu按实际硬件选择当并发路数增加时不要只靠起更多线程更好的做法是每个RTSP地址一个拉流线程但所有路数共享同一个推理模型实例。YOLO(model_path)创建一次就够了每路把帧丢进各自的队列推理线程轮询每个队列。这样显存不会随路数线性增长GPU利用率也更高。5. RTSP断流重连与性能排查按现象定位的五种改法5.1 五个典型坑位现象、原因、解决排查RTSP检测问题最怕的是脚本报错信息太少读卡时瓜子壳都抓不到。我把项目里多次出现的五个坑按“现象→原因→解决”整理出来建议直接对照保存。坑位一程序开着画面却再也不刷新现象cap.read()不报错但拿到的帧一直是最后一帧检测画面像是被按了暂停。原因RTSP连接已经断开但OpenCV的isOpened()仍然返回Trueread()在内部等RTP包一直等不到新帧也不会返回False。解决给拉流线程加一个看门狗记录每次成功读取的时间。如果超过5秒没新帧就主动把cap.release()销毁重建连接。不要相信isOpened()它只表示连接对象还活着不表示数据还在流动。坑位二检测画面延迟越跑越大现象刚开始延迟几百毫秒运行几分钟后延迟达到两三秒画面里的目标位置和实际现场明显对不上。原因拉流和推理在同一个线程里模型推理速度跟不上摄像头帧率解码器积压了大量旧帧。解决按第四章拆双线程并用有界队列。在队列满时直接丢最旧帧保证下游拿到的是接近实时的画面而不是历史回放。坑位三摄像头重启后程序再也恢复不了现象设备断电重启后程序一直黑屏或停在最后一帧手动重启脚本才能恢复。原因部分IPC在重启后不会主动恢复原有RTSP会话旧连接已经失效但代码没有释放旧句柄就开始频繁重连造成连接握手越拆越乱。解决重连前先release()等待2到3秒再重新创建VideoCapture。等待时间不要太短有些摄像头开机加网络就绪需要数秒立刻重连只会反复失败。坑位四画面花屏或绿屏现象画面周期性花屏有时整帧变绿但检测程序不报错。原因RTSP底层走UDP传输时丢包RTP序号错乱解码器拿到不完整的数据就输出残帧。解决把RTSP传输方式改成TCP。在OpenCV里可以这样强制设置:cap cv2.VideoCapture(url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_RTSP_TRANSPORT, 2) # 2表示TCP也可以在URL末尾拼?tcp实际效果因OpenCV编译版本而异。改成TCP后延迟会略有增加但花屏基本消失这个代价值得。坑位五检测框全屏乱跳或者一个框都没有现象置信度阈值设到0.01时满屏都是小框设到0.7时目标全部漏检。原因阈值偏离正常区间或者模型和摄像头场景不匹配。解决先固定conf0.25、iou0.45跑一遍基准确认框可靠后再微调。画面里目标像素小优先换大的imgsz或用主码流而不是调低置信度。阈值是用来过滤噪声的不是用来救小目标的。5.2 快速分流先判断是网络问题还是算法问题排查RTSP检测性能问题时最重要的动作是分流不然很容易把锅扣在模型头上。先用FFmpeg直接拉流10秒看它能否以稳定帧率推进ffmpeg -rtsp_transport tcp \ -i rtsp://username:password192.168.1.64:554/Streaming/Channels/101 \ -t 10 -f null -这条命令会丢弃解码后的画面只看拉流和解码过程中是否丢帧。如果输出的time时间戳匀速变化说明RTSP链路稳定问题在推理侧如果大量丢包、卡顿就要先解决摄像头到服务器的网络质量再去调YOLOv8。推理侧的判断更简单把results[0].speed打印到日志里它会返回preprocess、inference、postprocess三段耗时哪一段最长就处理哪一段。我遇到的情况里前处理慢基本是imgsz太大或输入图像做了多余拷贝推理慢则要考虑换更小模型或降分辨率。6. 部署到RK3588ONNX导出、NPU加速与上线验证6.1 导出与转换三步必须固定RTSP检测的最终形态往往不是跑在服务器上而是跑在摄像头旁边的边缘设备。RK3588是这类部署里的常见选择它自带的NPU约6TOPS算力跑yolov8n量化的RTSP单路检测完全够用。很多初学者直接在板子上装PyTorch这是最不可取的路径正确做法是先导出ONNX再转成RKNN格式。先用Ultralytics导出ONNXyolo export modelyolov8n.pt formatonnx opset12 imgsz640opset12和imgsz640这两个值在后续转换RKNN时要保持一致。拿到ONNX后在PC上安装RKNN-Toolkit2加载ONNX并配置量化数据集。量化数据集建议从摄像头实际场景抽100到200张图覆盖白天、夜晚和逆光情况而不是随便拿COCO图片凑数。量化校准集和实际场景差异越大掉点越明显。6.2 板端验证先跑通NPU输出再谈RTSP在线时长RKNN模型编译后板端推理的输出通常是一个类似[1, 84, 8400]的张量其中8400是特征图铺开的候选框数。这个格式和PyTorch里的Results对象不一样需要自己写解码前4个值是中心坐标和宽高后面80个是类别得分。常见工程做法是用OpenCV在板端解析替换掉原来的NMS再绘制检测框。这块工作省不了也是转换部署里最容易被忽略的一环。上线前的验证顺序我建议固定为先在PC上对比ONNX和RKNN输出允许1到2个像素的数值差异再在板端跑单路RTSP连续一小时记录断流重连次数最后看fps和延迟是否满足现场要求。我个人的习惯是跑一版n模型先满足实时性再根据误检率决定要不要换s模型。部署完毕后把重启策略和看门狗脚本一并加到开机服务里避免现场断电后进程一直卡在重连循环。这套做法帮我在多个RK3588项目里平滑上线希望也能帮到你。本文还有配套的精品资源点击获取