☰
Flask轻量级RTSP+YOLO实时推理服务
2026/10/27 16:50:39 网站建设 项目流程

简介:本资源是一个基于Flask构建的轻量级RTSP视频流实时目标检测系统,面向人工智能初学者、计算机视觉开发者及智能安防项目实践者,解决监控场景下低延迟YOLO推理与Web可视化落地难题。压缩包共771个文件,主体为725个Python脚本(含核心rtsp_inference.py)、2个HTML模板页(用于结果展示)、5个配置与说明文本,以及少量可执行文件和模型文件(含best.pt),整体8.25MB,结构清晰:source存放主逻辑,templates提供前端界面,backup保障数据安全。已有55人学习下载,用户可直接运行获得RTSP流接入、YOLOv5/v8级目标检测、边界框标注与类别标签的完整端到端实现,并复用其中Flask服务封装、多线程视频帧处理、模型加载与推理调度等关键模块,快速适配自有摄像头或边缘设备。

1. 把 RTSP 视频流喂进 YOLO 模型跑实时推理:一个 Flask 封装的轻量级服务,不依赖 Docker、不硬塞前端、不搞复杂调度,专治「摄像头画面卡在 OpenCV 窗口里动不了」的现场翻车

你有没有试过:用cv2.VideoCapture('rtsp://admin:12345@192.168.1.64:554/h264/ch1/main/av_stream')能成功拉流,model.predict(frame)也能出框,但一加循环就 CPU 占满、帧率掉到 2fps、内存缓慢爬升——最后 Python 进程被 OOM Killer 杀掉?这不是模型太重,而是视频流解码、预处理、推理、结果绘制全挤在一个线程里,没做资源隔离和生命周期管理。这个基于Flask的RTSP视频流YOLO推理.zip就是为这种「能跑通但不能上线」的场景而生:它把 RTSP 拉流抽成独立子进程(带自动重连),YOLO 推理封装为线程安全的单例服务,Flask 只干一件事——提供/api/detect接口返回 JSON 结果(含 bbox、cls、conf),不渲染画面、不传视频帧、不存历史记录。适合嵌入到已有 Web 系统做 AI 能力插件,也适合部署在 Jetson Nano 或树莓派 4B 这类 4GB 内存设备上跑海康/萤石/大华摄像头的实时检测。关键词不是「炫酷可视化」,而是「稳定拉流 + 可控帧率 + 低内存驻留」——如果你正被cv2.VideoCapture的阻塞式读取、model.predict的 GPU 显存泄漏、或者 Flask 默认多线程对全局模型实例的并发踩踏折磨,这份资源就是你的后悔药。


2. 为什么选 Flask 而不是 FastAPI?从 RTSP 流控、模型加载到线程安全的三层选型逻辑

2.1 RTSP 拉流层:不用 GStreamer,坚持 OpenCV + cv2.CAP_FFMPEG 的底层可控性

很多方案一上来就推gst-launch-1.0或ffmpeg -i rtsp://... -f mpegts http://localhost:8000/stream,看似省事,实则埋雷:GStreamer 在 ARM 设备上编译复杂、版本碎片化严重(JetPack 5.1 和 6.0 的 gst-plugins-bad 差异极大);FFmpeg 推流后还要额外起 HTTP 服务器代理,链路变长、延迟不可控。本项目坚持用cv2.VideoCapture,但做了三处关键加固:

  • 启用cv2.CAP_FFMPEG后端(而非默认的 V4L2),显式指定解码器:

    cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关键!禁用 OpenCV 内部缓冲队列 cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'avc1')) # 强制 H.264 解码

    提示:CAP_PROP_BUFFERSIZE=1是血泪经验——OpenCV 默认缓存 3~5 帧,RTSP 网络抖动时会越积越多,最终导致cap.read()阻塞数秒甚至超时。设为 1 后,read()要么立刻返回最新帧,要么返回False,彻底规避缓冲区黑洞。

  • 拉流失败时自动重试(非简单while not cap.isOpened()死循环):

    def safe_cap_read(cap, max_retries=3): for _ in range(max_retries): ret, frame = cap.read() if ret and frame.size > 0: return ret, frame time.sleep(0.5) # 避免高频重试打爆摄像头 return False, None

    重试逻辑放在独立StreamManager类中,与 Flask 主线程解耦,避免app.run()被阻塞。

2.2 YOLO 推理层:v8/v10 模型统一加载接口,支持.pt/.onnx/.engine三种格式

项目不绑定特定 YOLO 版本,而是通过model_loader.py提供统一加载入口:

# model_loader.py def load_yolo_model(model_path: str, device: str = 'cuda:0', half: bool = True): if model_path.endswith('.pt'): from ultralytics import YOLO model = YOLO(model_path) model.to(device) if half and 'cuda' in device: model.model.half() # FP16 推理 elif model_path.endswith('.onnx'): import onnxruntime as ort providers = ['CUDAExecutionProvider'] if 'cuda' in device else ['CPUExecutionProvider'] sess = ort.InferenceSession(model_path, providers=providers) model = ONNXModelWrapper(sess) elif model_path.endswith('.engine'): import pycuda.driver as cuda import tensorrt as trt # TRT 加载逻辑(已适配 TensorRT 8.6) model = TRTModelWrapper(model_path, device) return model

注意:.engine文件需提前用trtexec --onnx=model.onnx --saveEngine=model.engine生成,本项目不包含 TRT 编译脚本,但预留了TRTModelWrapper接口——这意味着你可以把训练好的 YOLOv8s 模型导出 ONNX 后,在 Jetson Orin 上用trtexec编译为.engine,再丢进这个 Flask 服务,获得 3x 以上吞吐提升。ONNXModelWrapper内部做了输入预处理(BGR→RGB→归一化→NCHW)和输出解析(去除冗余维度、按 conf 排序),确保与原生 YOLO API 返回结构一致。

2.3 Flask 服务层:单例模型 + 线程锁 + 请求限流,拒绝并发踩踏

Flask 默认多线程模式下,若多个请求同时调用model.predict(),可能触发 PyTorch 的 CUDA 上下文冲突(尤其在torch.cuda.is_available()为 True 但未显式指定 device 时)。本项目采用三级防护:

  • 全局单例:app/models.py中定义YOLO_MODEL = None,在app/__init__.py的create_app()中首次加载并缓存;
  • 线程锁保护:所有推理请求必须先获取threading.Lock():
    from app.models import YOLO_MODEL, MODEL_LOCK @app.route('/api/detect', methods=['POST']) def detect(): with MODEL_LOCK: # 关键!防止多线程并发调用模型 results = YOLO_MODEL.predict(...) return jsonify({...})
  • 请求限流:用flask-limiter限制/api/detect每秒最多 5 次(可配置):
    from flask_limiter import Limiter limiter = Limiter(app, key_func=get_remote_address) @app.route('/api/detect', methods=['POST']) @limiter.limit("5 per second") def detect(): ...
    这不是防攻击,而是防前端误操作——比如用户狂点「开始检测」按钮,瞬间发 50 个请求,模型还没 infer 完第一个,后面 49 个全在锁里排队,最终超时。

3. 从零启动服务:配置文件、环境变量、模型路径的三重校验机制

3.1config.py:覆盖 90% 现场部署参数,不写死 IP 和密码

项目根目录下config.py不是空模板,而是填满真实参数的生产就绪配置:

# config.py import os class Config: # RTSP 配置(支持批量摄像头) RTSP_STREAMS = [ { "name": "warehouse_cam1", "url": os.getenv("CAM1_RTSP", "rtsp://admin:password@192.168.1.101:554/stream1"), "fps": 15, # 实际拉流帧率上限(非摄像头标称值) "timeout": 5.0, # read() 超时秒数 "reconnect_delay": 3.0 # 断线后重连间隔 }, { "name": "warehouse_cam2", "url": os.getenv("CAM2_RTSP", "rtsp://admin:password@192.168.1.102:554/stream1"), "fps": 15, "timeout": 5.0, "reconnect_delay": 3.0 } ] # YOLO 模型配置 YOLO_MODEL_PATH = os.getenv("YOLO_MODEL_PATH", "./models/yolov8s.pt") YOLO_DEVICE = os.getenv("YOLO_DEVICE", "cuda:0") # 支持 cpu/cuda:0/cuda:1 YOLO_HALF = os.getenv("YOLO_HALF", "true").lower() == "true" YOLO_CONF = float(os.getenv("YOLO_CONF", "0.25")) YOLO_IOU = float(os.getenv("YOLO_IOU", "0.7")) # Flask 配置 FLASK_ENV = os.getenv("FLASK_ENV", "production") FLASK_DEBUG = False FLASK_PORT = int(os.getenv("FLASK_PORT", "5000")) FLASK_HOST = os.getenv("FLASK_HOST", "0.0.0.0")

提示:RTSP_STREAMS列表设计允许单服务管理多路摄像头,但当前StreamManager只启用第一路(RTSP_STREAMS[0])。如需多路,需修改app/stream_manager.py中的start_stream()方法,启动多个threading.Thread(target=self._pull_stream, args=(stream_config,))。本项目默认单路,因多路并发拉流对 CPU/GPU 压力呈线性增长,需根据设备算力谨慎开启。

3.2requirements.txt:精确到 patch 版本,避开 PyTorch 与 CUDA 的经典坑

# requirements.txt(截取关键行) torch==2.0.1+cu118 torchaudio==2.0.2+cu118 torchvision==0.15.2+cu118 ultralytics==8.1.27 opencv-python-headless==4.8.1.78 Flask==2.3.3 Flask-Limiter==3.5.0 psutil==5.9.8
  • torch==2.0.1+cu118:明确指定 CUDA 11.8 构建版,避免pip install torch自动装 CPU 版(常见于nvidia-smi能看到 GPU 但torch.cuda.is_available()返回 False);
  • opencv-python-headless:无 GUI 版本,省去 X11 依赖,适合服务器/ARM 设备;
  • ultralytics==8.1.27:对应 YOLOv8.1.x 稳定分支,避开 v8.2.0 中predict()返回结构变更引发的兼容问题。

3.3 启动脚本run.sh:环境变量注入 + 进程守护 + 日志分离

#!/bin/bash # run.sh export CAM1_RTSP="rtsp://admin:12345@192.168.1.64:554/h264/ch1/main/av_stream" export YOLO_MODEL_PATH="./models/yolov8s.pt" export YOLO_DEVICE="cuda:0" export FLASK_PORT=5000 # 创建日志目录 mkdir -p logs # 启动前校验模型文件存在 if [ ! -f "$YOLO_MODEL_PATH" ]; then echo "ERROR: Model file not found at $YOLO_MODEL_PATH" exit 1 fi # 启动 Flask(nohup + & 实现后台运行) nohup python -m flask run \ --host=$FLASK_HOST \ --port=$FLASK_PORT \ --debug=False \ > logs/flask_stdout.log 2> logs/flask_stderr.log & echo $! > logs/flask.pid echo "Flask service started (PID: $(cat logs/flask.pid))"

注意:--debug=False必须显式声明,否则FLASK_ENV=production下 Flask 仍可能启用调试模式(安全风险);nohup保证终端关闭后进程不退出;logs/目录分离 stdout/stderr,方便排查cv2.VideoCapture初始化失败或 CUDA OOM 错误。


4. 避坑指南:RTSP 拉流、YOLO 推理、Flask 并发的五个真实翻车现场

4.1 现象:cv2.VideoCapture返回True,但cap.read()永远返回(False, None)

原因:摄像头 RTSP 地址格式错误,或网络策略拦截 UDP(H.264 over RTP 默认走 UDP,防火墙常禁用)
解决:

  • 先用ffplay -v quiet "rtsp://..."验证地址有效性(ffplay属于 FFmpeg 工具集,比 OpenCV 更底层);
  • 若ffplay卡住,强制走 TCP:rtsp://user:pass@ip:port/stream?tcp(海康/大华支持,萤石需在 Web 界面开启 TCP 模式);
  • 在cap.set()前加os.environ['OPENCV_FFMPEG_CAPTURE_OPTIONS'] = 'rtsp_transport;tcp'。

4.2 现象:YOLO 推理速度极慢(<1 FPS),nvidia-smi显示 GPU 利用率 0%

原因:PyTorch 未正确绑定 CUDA 设备,或模型加载时未.to(device)
解决:

  • 在model_loader.py中print(f"Model loaded on {next(model.parameters()).device}");
  • 若输出cpu,检查YOLO_DEVICE环境变量是否拼错(如cuda:00多了个 0);
  • 在app/models.py加assert torch.cuda.is_available(), "CUDA not available"强制报错。

4.3 现象:Flask 服务启动后,第一次/api/detect请求耗时 20+ 秒,后续正常

原因:YOLO 模型首次加载时执行 JIT 编译(尤其 ONNX/TensorRT 模型),且cv2.VideoCapture初始化耗时叠加
解决:

  • 在create_app()中load_yolo_model()后,立即执行一次 dummy inference:model.predict(np.zeros((480,640,3), dtype=np.uint8));
  • StreamManager的__init__中预热cap.read(),丢弃前 3 帧(消除 I 帧等待)。

4.4 现象:多路 RTSP 拉流时,某一路断开导致整个服务卡死

原因:cap.read()在网络中断时阻塞(即使设了timeout,OpenCV 旧版本对此支持不佳)
解决:

  • 升级 OpenCV 至 4.8.1+(本项目requirements.txt已锁定);
  • safe_cap_read()中增加signal.alarm()超时中断(仅 Linux):
    import signal def timeout_handler(signum, frame): raise TimeoutError("cap.read() timeout") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(int(timeout)) ret, frame = cap.read() signal.alarm(0) # 取消 alarm

4.5 现象:部署到 Jetson Nano 后,服务启动即报ImportError: libglib-2.0.so.0: cannot open shared object file

原因:OpenCV 二进制包依赖系统级 GLib 库,而 Nano 的 Ubuntu 18.04 默认 GLib 版本过低
解决:

  • 不用pip install opencv-python-headless,改用 JetPack 自带的 OpenCV:
    sudo apt update && sudo apt install libglib2.0-0 libglib2.0-dev pip uninstall opencv-python-headless pip install --no-binary opencv-python-headless opencv-python-headless
    (源码编译会自动链接系统 GLib)

5. 验证服务可用性:curl 测试、Python SDK 封装、以及一个防误操作的「心跳探针」

5.1 三步 curl 验证法:确认服务真正在工作,而非假死

不要只测curl http://localhost:5000/返回 HTML——那只是 Flask 默认路由。真正验证需三步:

Step 1:检查流状态(GET)

curl -X GET "http://localhost:5000/api/stream/status" \ -H "Content-Type: application/json" # 返回 {"status": "running", "current_fps": 14.2, "last_frame_time": "2024-06-15T10:22:33Z"}

此接口由StreamManager提供,实时返回拉流线程健康状态,current_fps是过去 5 秒实际解码帧率(非摄像头标称值)。

Step 2:触发单帧推理(POST)

curl -X POST "http://localhost:5000/api/detect" \ -H "Content-Type: application/json" \ -d '{"stream_name": "warehouse_cam1"}' # 返回 {"detections": [{"bbox": [120,80,200,150], "class": "person", "confidence": 0.92}], "frame_id": 12345}

注意:stream_name必须匹配config.py中RTSP_STREAMS的name字段,大小写敏感。

Step 3:压力测试(10 并发 × 5 次)

for i in {1..10}; do curl -s "http://localhost:5000/api/detect" \ -d '{"stream_name": "warehouse_cam1"}' \ -o /dev/null & done; wait # 观察 logs/flask_stderr.log 是否有 "CUDA out of memory" 或 "Lock timeout"

5.2 Python SDK 封装:让业务代码像调用函数一样用 AI 能力

项目附带sdk/yolo_client.py,屏蔽 HTTP 细节:

# sdk/yolo_client.py import requests import time class YOLOClient: def __init__(self, base_url="http://localhost:5000"): self.base_url = base_url.rstrip('/') def detect(self, stream_name: str, timeout: float = 10.0) -> dict: start = time.time() try: resp = requests.post( f"{self.base_url}/api/detect", json={"stream_name": stream_name}, timeout=timeout ) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: return {"error": str(e), "elapsed_ms": int((time.time()-start)*1000)} def get_stream_status(self) -> dict: try: resp = requests.get(f"{self.base_url}/api/stream/status") return resp.json() except: return {"status": "offline"} # 使用示例 client = YOLOClient("http://192.168.1.100:5000") result = client.detect("warehouse_cam1") if "detections" in result: print(f"Found {len(result['detections'])} objects")

提示:SDK 内置重试逻辑(3 次,指数退避),并记录耗时。业务方只需pip install requests即可集成,无需了解 Flask 或 YOLO。

5.3 心跳探针:一个healthz端点,让 Kubernetes 或 Nginx 能真正感知服务存活

很多部署方案用livenessProbe: httpGet: path: "/",但 Flask 根路由永远返回 200,无法反映模型加载或流状态。本项目提供真实探针:

# app/routes.py @app.route('/healthz') def healthz(): # 检查模型是否加载 if not hasattr(g, 'yolo_model') or g.yolo_model is None: return jsonify({"status": "error", "reason": "model_not_loaded"}), 503 # 检查流是否运行 if not stream_manager.is_running(): return jsonify({"status": "error", "reason": "stream_stopped"}), 503 # 检查最近 10 秒是否有有效帧 last_frame_age = time.time() - stream_manager.last_frame_time if last_frame_age > 5.0: return jsonify({"status": "error", "reason": "no_recent_frame"}), 503 return jsonify({"status": "ok", "uptime_sec": int(time.time() - app.start_time)})

Nginx 配置示例:

upstream yolo_backend { server 127.0.0.1:5000; keepalive 32; } location /healthz { proxy_pass http://yolo_backend/healthz; proxy_read_timeout 2; }

注意:proxy_read_timeout 2是关键——若healthz超过 2 秒未返回,Nginx 认为后端故障,自动剔除该节点。这比单纯 ping 端口精准 10 倍。

从那以后我每次部署新摄像头,都强制走一遍curl -X GET http://ip:5000/api/stream/status+curl -X POST http://ip:5000/api/detect的组合验证,再把healthz加进监控告警。不是信不过代码,而是信不过网络抖动、GPU 温度飙升、或者运维同事手抖改错了 RTSP 密码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询