PyVideoProc

基于 PyVideoProc 的多路视频流分析实现

PyVideoProc 提供基于 CUDA 加速优化的高性能 Python SDK,可高效实现多路、多卡、多模型的视频解码、AI 推理与编码,显著降低开发复杂度并提升吞吐性能

output_2x2

⭐ 多进程绕过 GIL 限制,提升 Python 并发性能

⭐ 减少 Host-Device 数据传输,降低 GPU 显存冗余拷贝,提升推理速度

⭐ 尽可能在 GPU 上计算,以降低 CPU 计算负担

⭐ 开箱即用,简单易懂,扩展性强,适合中小型项目快速部署

Open Source Learning Curve Developer Friendliness Performance Architecture Design
DeepStream High Low High Single-process, multi-threaded
VideoPipe medium(requires cpp knowledge) Medium(requires cpp knowledge) Medium Single-process, multi-threaded
Our ≈ 0 High Medium Multi-process, single-threaded

项目地址:https://github.com/lmk123568/PyVideoProc
项目讲解视频:

第一步_检查

首先你需要检查本地服务器环境是否满足三个条件

  1. Docker 版本要大于 24.0
docker --version  # 查看版本

在这里插入图片描述

  1. NVIDIA 驱动版本要大于 590
nvidia-smi

在这里插入图片描述

注意,作者没试过驱动小于 590 的情况,为了保险起见建议大于 590

  1. NVIDIA Container Toolkit 版本要大于 1.13.0
nvidia-ctk --version

在这里插入图片描述

第二步_运行

当本地环境满足以上条件后,按照以下步骤运行

这里强烈建议生成镜像容器运行,不建议自己头铁装环境,因为你装环境花的时间足够你研究明白 docker 了

  1. 生成镜像

clone 本项目,生成包含完整开发环境的镜像

git clone https://github.com/lmk123568/PyVideoProc.git
cd PyVideoProc/docker
docker build -t pyvideoproc:cuda12.8 .

镜像生成后,进入容器,不报错即成功

docker run -it \
  --gpus all \
  -e NVIDIA_DRIVER_CAPABILITIES=all \
  -v {your_path}/PyVideoProc:/workspace \
  pyvideoproc:cuda12.8 \
  bash

后续示例代码默认在容器内/workspace运行

⚠️ 不推荐自己本地装环境,如果一定要自己装,请参考 Dockerfile

  1. 编译加速包
python scripts/setup.py install

这里面包含了硬件编解码、YOLO26 推理优化的 C++ 实现,并通过 Pybind11 给 Python 调用

  1. 训练模型权重转换

将通过 ultralytics 训练的pt模型导入到当前目录(/workspace)下(示例模型为 yolo26n.pt

python scripts/pt2trt.py  --w ./yolo26n.pt --fp16

转换过程中会与 ultralytics 官方结果进行推理对齐

💡 TensorRT 编译生成 .engine 过程中,推理尺寸默认设置为(576,1024),可以跳过letterbox降低计算开销

💡 遇到警告 requirements: Ultralytics requirement ['onnxruntime-gpu'] not found, attempting AutoUpdate... 可以 Ctrl + C 跳过

  1. 运行

开启 MPS(Multi-Process Service)

nvidia-cuda-mps-control -d
# echo quit | nvidia-cuda-mps-control  关闭 MPS

阅读理解其代码并运行

python main.py

⭐ 一些重要的细节 ⭐

  1. MPS 多进程服务

在 NVIDIA 显卡中,MPS 通常指的是 Multi-Process Service(多进程服务),全称为 NVIDIA Multi-Process Service (MPS)。多个进程可以同时向 GPU 提交任务(kernel),GPU 调度器可将它们交错执行(时间片或并行),从而更好地填满计算单元
MPS 天然适合 Python 多进程性质,利用 MPS,进一步加速多进程下模型推理速度

  1. YOLO26 检测前处理优化

通常来说,ultralytics 的 YOLO 检测算法前处理步骤通常是

Frame on CPU

LetterBox

BGR --> RGB

HWC --> NCHW

Normalize

CPU --> GPU

TensorRT

其中 LetterBox 是 YOLO 系列目标检测模型中常用的一种图像保持宽高比的缩放与填充方法,用于将任意尺寸的输入图像统一调整为目标模型所需尺寸(如 640×640),同时避免图像内容被拉伸变形,这方法主要用于训练

但是在推理阶段,我们可以不需要它,特别是在视频分析场景下,解码的每帧图像分辨率比固定为16:9,我们可以选取解码分辨率为 576x1024 尺寸的图像作为模型输入,这样好处有两点,一个是尺寸固定比为16:9,不用缩放,一个是长宽都是32的倍数,可以5次下采样,不用做填充。这样就完全跳过 LetterBox,增加推理速度

同时带来的好处是,比原来 640x640 相比,567x1024 图像带的像素信息量更多,训练时候设置size=1024,可以进一步提高场景检测精度。这其实是一种 trade off,在作者接触的业务场景下,尽管提升分辨率带来的计算开销大,但是在跳过 LetterBox 以及更高的精度配合 INT8 量化的优化下,多余的计算开销可以忽略不计

最后,在 GPU 上实现剩下的前处理,用一个大 CUDA Kernel 融合,降低计算过程中的显存碎片,最终前处理流程图如下

Frame on CPU

CPU --> GPU

CUDA Kernel Preprocess

TensorRT

值得注意的是,YOLO26 的后处理比较简单,不需要优化

  1. CPU 和 GPU 之间的数据拷贝优化

除了 DeepStream 之外,大部分多路视频流框架都是硬解码出来的帧会放在 CPU 上,在推理的过程中,CPU的帧数据会再一次拷贝到 GPU 上面,这样就有一定的资源消耗,为什么不直接硬解解码出来的帧直接在GPU上面直接给推理呢?

PyVideoProc 提供了解码 SDK,解码后直接返回在 GPU 上的 torch.Tensor,可以像 Pytorch 那样操作 GPU 的张量,后续推理分析都不影响,只要保证解码的张量在 GPU 运行即可,这样就能大幅度降低 CPU 的压力,让计算尽可能在 GPU 上

经过优化的流程图如下

Frame on GPU

CUDA Kernel Preprocess

TensorRT

解码 SDK 采用了 ffmpeg+npp,解码到后处理(nv12toBGR、尺寸缩放等)都在 GPU 上进行,代码如下

decoder = pvp.Decoder(
    self.input_url,                   # 支持 rtsp、mp4
    enable_frame_skip=False,          # 禁用跳帧,确保每一帧都被解码
    output_width=1024,                # 输出图像宽度(像素)
    output_height=576,                # 输出图像高度(像素)
    enable_auto_reconnect=True,       # 网络中断时自动重连
    reconnect_delay_ms=10000,         # 每次重连尝试前等待 10 秒
    max_reconnects=0,                 # 最大重连次数(0 表示无限重试)
    open_timeout_ms=5000,             # 打开流的超时时间(5 秒)
    read_timeout_ms=5000,             # 读取数据包的超时时间(5 秒)
    buffer_size=4 * 1024 * 1024,      # 缓冲区大小(4MB),用于容忍网络抖动
    max_delay_ms=200,                 # 允许的最大解码延迟(毫秒)
    reorder_queue_size=32,            # B帧重排序队列长度
    decoder_threads=2,                # 解码线程数量
    surfaces=2,                       # 用于缓冲的 CUDA 显存表面数量
)

frame, pts = decoder.next_frame()
# frame 是解码出来的帧,类型是 torch.Tensor,图像格式是 BGR
# pts 是当前帧时间戳
  1. 多路多卡多模型

多进程运行,每个进程表示一路,代码中用 Pipeline 表示

多卡启动的原理很简单,每个进程启动会有

class Pipeline(mp.Process):
    def __init__(self, gpu, input_url, output_url):
        super().__init__()
        os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)

巧妙利用多进程之间资源隔离特性启动多卡,有时候就是这么巧

多模型需要在 run 函数内定义,核心代码如下

class Pipeline(mp.Process):
    def __init__(self, gpu, input_url, output_url):
        super().__init__()
        os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu)

    def run(self):
        # 解码器
        decoder = pvp.Decoder('test.mp4')
        # 检测器
        det = pvp.Yolo26DetTRT('yolo26n.engine', 0.25)
        # 循环
        while 1:
            frame, _ = decoder.next_frame()
            det_results = det(frame)
          
            # 后续可以添加多个模型,需要自己实现
            # frame 是 torch.Tensor 张量,在 GPU 上
            # 尽量将预处理操作在 torch.Tensor GPU 上处理
            # 后续我补几个 ocr、pose、face、seg、cls 的例子

  1. 终端输出表示什么意思
[02/11/2026-08:06:32] Pipeline: xxx, Det: 1.35ms, Track: 1.89ms, Draw: 1.34ms, Encode: 0.35ms, Event: 0.00ms, Wait: 35.00ms
  • Det 表示平均每帧检测消耗时间
  • Track 表示平均每帧跟踪消耗时间
  • Draw 表示平均每帧绘图消耗时间
  • Encode 表示平均每帧编码消耗时间
  • Wait 表示平均等待下一帧的消耗时间

其中 Wait 很重要,25 fps 的视频,每帧间隔为 40ms,所以这一整个串行推理必须保证在 40ms 内推理完毕

实际上得益于 CPU、GPU 良好的计算性能,加上对代码的一些底层优化,很多业务场景计算都能在毫秒级别

如果你的串行 Pipeline 比较复杂,可以在解码 SDK 上开启跳帧检测,这样每帧间隔就是 80ms 了

  1. PyVideoProc 是什么意思

Py 代表用python语言开发,Video 代表处理视频流,Proc 有两层意思,一个是处理,编解码推理这种,一个是进程的意思,表示多进程运行。缩写为 PVP

参考

  1. NVIDIA 编解码矩阵:https://developer.nvidia.com/video-encode-decode-support-matrix
  2. NVIDIA Video Codec SDK:https://developer.nvidia.com/video-codec-sdk
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐