一个服务搞定视频检测、实时流推送和目标追踪
做视频 AI 的同行都知道,推理服务这个事,单图检测容易,视频处理就麻烦了。
上传视频等半天出结果算一个场景,接摄像头 RTSP 流 7×24 小时实时推又是另一个场景。市面上开源的方案大多只解决了其中一个,而且很多时候跑通容易,跑稳难——流断了怎么办?多客户端同时看怎么搞?结果怎么优雅地推出去?
这个项目把这两个场景搓到了一个服务里,顺便把目标追踪、断流重连、Docker 部署这些生产环境的刚需也一并搞定了。如果你也有类似的视频推理需求,这个项目或许能帮到你。
能干什么
一句话概括:支持两种模式的 YOLO 视频推理服务。 你可以选择:
| 模式 | 输入 | 输出 | 场景 |
|---|---|---|---|
| 验证模式 | 上传 MP4/AVI/MOV 视频文件 | JSON 检测结果,轮询获取 | 离线分析、批量处理 |
| 生产模式 | 接入 RTSP/RTMP 视频流 | WebSocket 实时推送 | 实时监控、安防、工业检测 |
再配上一个 ByteTrack 目标追踪,同一个目标在连续帧里能分配相同的 track_id,你就可以知道"这个人在画面里移动了 3 秒"。
效果展示:
架构设计
聊代码之前,先把架构讲清楚。
┌──────────────────────────────┐
│ Nginx (反向代理) │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ FastAPI 应用层 │
│ ┌─────────────────────────┐ │
│ │ 文件上传接口 (REST) │ │
│ │ 流管理接口 (REST) │ │
│ │ WebSocket 端点 │ │
│ └─────────────────────────┘ │
└──────────────┬───────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌─────────▼────────┐ ┌───────▼────────┐ ┌───────▼────────┐
│ Celery Worker │ │ Stream Manager │ │ Redis │
│ (文件异步推理) │ │ (流常驻推理) │ │ 状态/队列/缓存 │
└─────────┬────────┘ └───────┬────────┘ └────────────────┘
│ │
└──────────┬─────────┘
│
┌──────────▼──────────┐
│ YOLO 推理引擎 │
│ (单例,统一推理接口) │
└─────────────────────┘
核心思路就一条:两种模式通过同一个推理引擎单例来完成检测,但走不同的调度链路。 文件推理走 Celery 异步队列,流推理走 asyncio 协程 + WebSocket,互不干扰。
几个值得展开的技术点
1. YOLO 推理引擎:线程安全单例
模型就那么大,加载一次就够了。所以推理引擎做成线程安全的单例模式:
class InferenceEngine:
_instance = None
_lock = threading.Lock()
def __new__(cls, model_name=None):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, model_name=None):
if self._initialized:
return
self.model = YOLO(f"{model_name}.pt")
self.model.to(self.device)
self._initialized = True
无论是文件推理的 Celery Worker,还是流推理的 Stream Manager,都通过 get_engine() 拿到同一个引擎实例,不重复加载模型。
而且这个引擎集成了 ByteTrack 追踪器。调用 engine.infer(frame, enable_tracking=True, fps=25) 就会自动给检测结果加上 track_id。
2. 文件异步推理:Celery + 进度回调
上传视频之后,接口立刻返回一个 task_id,实际推理在 Celery Worker 里进行:
@celery_app.task(bind=True, name="video_detect")
def video_detect_task(self, task_id: str, file_path: str, config: dict):
cap = cv2.VideoCapture(file_path)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
for frame_idx in range(total_frames):
ret, frame = cap.read()
detections = engine.infer(frame, enable_tracking=True, fps=fps)
# 每处理 5% 更新一次进度
if frame_idx % (total_frames // 20) == 0:
self.update_state(
state="PROCESSING",
meta={"progress": progress, "processed_frames": frame_idx}
)
return {"status": "completed", "detections": all_detections, "summary": {...}}
前端或者调用方通过 GET /api/v1/video/result/{task_id} 轮询进度,处理中返回进度百分比,完成后返回完整 JSON。
3. 流推理:asyncio 协程 + WebSocket 广播
启动一个流后,Stream Manager 会创建一个常驻 asyncio 任务,持续拉流、推理、广播:
async def _run_stream(self, task: StreamTask):
engine = get_engine()
while task.status == StreamStatus.CONNECTED:
ret, frame = cap.read()
detections = engine.infer(frame, enable_tracking=True)
# 广播给所有连接的 WebSocket 客户端
await self._broadcast_detections(task, frame_count, detections)
await asyncio.sleep(0)
一个流可以有多个 WebSocket 客户端同时订阅,各自可以设置自己的过滤条件——比如客户端 A 只需要 person,客户端 B 只需要 car,互不影响。
4. 断流重连:状态机 + 指数退避
生产环境 RTSP 流断掉是家常便饭。这里实现了一个重连状态机:
CONNECTED ──断流检测──→ RECONNECTING ──成功──→ CONNECTED
│
│失败(未达上限)
└──→ 等待退避 ──→ 重试
│
│失败(达上限)
└──→ FAILED
退避时间是 2s → 4s → 8s → 16s → 30s,最多重试 5 次。断流期间新客户端连上来会收到 stream_reconnecting 状态,重连成功后再推送 stream_reconnected。
5. WebSocket 协议设计
服务端推送的消息分三类:高频的检测结果帧、低频的流状态通知、以及错误消息。客户端也可以主动发消息来控制订阅:
// 客户端发送:调整过滤条件
{"type": "subscribe", "options": {"min_confidence": 0.7, "target_classes": ["person"]}}
// 服务端推送:检测结果
{"type": "detection", "stream_id": "stream-xxx", "frame_id": 1523, "detections": [...]}
// 服务端推送:状态变更
{"type": "status", "event": "stream_reconnected", "message": "重连成功"}
性能参考
| 模型 | 单帧推理耗时 | 25fps 流(跳帧=3) | 建议最大并发流 |
|---|---|---|---|
| YOLOv8n | ~10ms | 8.3 帧/秒 | ~8 路 |
| YOLOv8s | ~20ms | ~6 帧/秒 | ~4 路 |
| YOLOv8m | ~40ms | ~4 帧/秒 | ~2 路 |
测试环境:NVIDIA T4,GPU 推理。CPU 下放个 1~2 路也能跑。
本地跑起来
# 克隆
git clone https://github.com/VaneBlien/yolo-video-service.git
cd yolo-video-service
# 虚拟环境 + 依赖
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# 启动 Redis
docker run -d --name redis -p 6379:6379 redis:7-alpine
# 终端1: FastAPI
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# 终端2: Celery Worker
celery -A app.tasks.celery_worker.celery_app worker --loglevel=info --pool=solo
浏览器打开 http://localhost:8000/docs,Swagger 文档里直接调试。
Docker 一键部署
docker-compose build
docker-compose up -d
四个容器:Nginx + FastAPI + Celery + Redis,开箱即用。
路线图(做完了的和想做的)
- 单图推理 API
- 视频文件异步推理 + 进度轮询
- RTSP 流实时推理 + WebSocket 推送
- 多客户端独立订阅过滤
- 断流指数退避重连
- ByteTrack 多目标追踪
- Docker 容器化部署
- 标注视频生成(推理结果画框合成新视频)
- 前端可视化面板
- 多 GPU 并行推理
- Prometheus + Grafana 监控
最后
这个项目从架构设计到全部代码落地,前后断断续续搞了一段时间。最花时间的其实不是推理本身,而是那些"看起来不起眼但没它真不行"的工程细节——断流重连怎么优雅、多个客户端怎么各自过滤、模型怎么不重复加载、进度怎么让前端知道——这些东西加起来才是从 Demo 到能用的关键。