CANN Samples(七):视频与流媒体:RTSP与多路输入实战

在前面的章节中,我们已经掌握了模型推理的基础,并了解了如何通过DVPP和AIPP等技术优化数据预处理流程。然而,在许多真实世界的AI应用场景中,我们处理的不再是静态的图片,而是连续的视频流,例如来自监控摄像头的RTSP流。更复杂的场景甚至需要同时处理多路视频输入,并进行高效的并行推理。

这篇文章将带你进入视频处理的世界,我们将探讨两个核心主题:

  1. 单路RTSP视频流处理:如何从一个网络摄像头或视频源获取RTSP流,并利用昇腾AI硬件进行实时推理。
  2. 多路输入的并行处理与多卡调度:当需要同时处理多个视频源时,如何构建一个高效的并行处理流水线,并利用多张昇腾AI处理器来分担计算压力。

我们将通过分析两个具体的示例------sampleResnetRtspsampleYOLOV7MultiInput------来深入理解这些技术的实现细节。

1. 单路RTSP视频流处理:sampleResnetRtsp 示例解析

想象一下,你需要对一个网络摄像头的实时画面进行物体识别。这通常涉及到从RTSP(Real-Time Streaming Protocol)流中拉取视频帧,然后一帧一帧地送入AI模型进行推理。sampleResnetRtsp示例完美地展示了这一流程。

这个示例的核心在于利用AclLiteVideoProc这个强大的工具类来简化视频处理。它封装了视频解码的复杂过程,让我们可以像读取本地视频文件一样,轻松地从RTSP流中获取图像帧。

1.1 AclLiteVideoProc:RTSP流处理的利器

AclLiteVideoProc是AclLite库中的一个组件,专门用于处理视频输入。它的设计非常巧妙,无论是处理本地视频文件(如MP4)还是网络RTSP流,它都提供了统一的接口。

当你用一个RTSP地址(例如 rtsp://username:password@ip_address:port/stream_path)来初始化AclLiteVideoProc时,它会在后台自动处理RTSP握手、拉流和解码等一系列复杂操作。你所需要做的,仅仅是调用Read()方法,就能源源不断地获取解码后的ImageData(图像数据)。

1.2 sampleResnetRtsp 的核心代码逻辑

让我们深入sampleResnetRtsp.cpp的核心代码,看看它是如何工作的。

cpp 复制代码
// main.cpp in sampleResnetRtsp
int main(int argc, char *argv[]) 
{
    // ... 初始化模型路径、宽度、高度等 ...
    std::string inputDataPath =  string(argv[1]); // 接收RTSP地址或视频文件路径

    // 1. 初始化资源
    SampleResnetRtsp sampleResnetRtsp(modelPath, modelWidth, modelHeight);
    Result ret = sampleResnetRtsp.InitResource(inputDataPath);
    if (ret) {
        ACLLITE_LOG_ERROR("Init resource failed, error %d", ret);
        return FAILED;
    }

    // 2. 创建视频处理实例
    AclLiteVideoProc cap = AclLiteVideoProc(inputDataPath, device);
    if (!cap.IsOpened()) {
        ACLLITE_LOG_ERROR("Open camera failed");
        return FAILED;
    }

    // 3. 循环读取并处理视频帧
    while (true) {
        std::vector<InferenceOutput> inferOutputs;
        ImageData image;

        // 3.1 从视频源读取一帧图像
        AclLiteError ret = cap.Read(image);
        if (ret) {
            break; // 视频结束或读取失败
        }

        // 3.2 预处理(例如,使用DVPP进行缩放)
        ret = sampleResnetRtsp.ProcessInput(image);
        if (ret) {
            ACLLITE_LOG_ERROR("Inference image failed, error %d",  ret);
            return FAILED;
        }

        // 3.3 执行模型推理
        ret = sampleResnetRtsp.Inference(inferOutputs);
        if (ret) {
            ACLLITE_LOG_ERROR("Inference image failed");
            return FAILED;        
        }

        // 3.4 后处理并获取结果
        sampleResnetRtsp.GetResult(inferOutputs);
    }

    ACLLITE_LOG_INFO("Execute sample success");
    return SUCCESS;
}

代码的逻辑非常清晰:

  1. 初始化:加载模型,初始化AscendCL资源。
  2. 打开视频源 :使用AclLiteVideoProc打开RTSP流或本地视频。
  3. 循环处理 :在一个while循环中,不断地:
    • 调用cap.Read(image)获取一帧图像。
    • 对图像进行预处理(ProcessInput),这里通常会利用DVPP硬件加速能力进行图像缩放,以匹配模型的输入尺寸。
    • 执行推理(Inference)。
    • 处理推理结果(GetResult)。

这个简单的线性流程非常适合单路视频处理的场景。但是,如果我们需要同时处理几十甚至上百路视频流,这种模式的效率就会成为瓶颈。

2. 多路输入并行处理:sampleYOLOV7MultiInput 示例解析

当面对多路视频输入的挑战时,我们必须转向并行处理架构。sampleYOLOV7MultiInput示例为我们展示了如何构建一个基于多线程的、可扩展的并行处理流水线,并且能够灵活地在多张昇腾AI处理器上进行任务调度。

这个示例的核心思想是将整个处理流程拆分成多个独立的阶段,每个阶段由一个或多个专门的线程来负责。这种"流水线"式的设计可以极大地提升系统的吞吐量。

2.1 基于线程的并行流水线架构

sampleYOLOV7MultiInput将一个典型的AI处理任务拆分成了以下几个线程:

  • DataInputThread:数据输入线程。负责从视频文件或RTSP流中读取原始数据。
  • PreprocessThread:预处理线程。接收原始数据,并进行解码、缩放等预处理操作,为模型推理做准备。
  • DetectInferenceThread:推理线程。接收预处理好的数据,执行模型推理。
  • PostprocessThread:后处理线程。对推理结果进行解析,例如,在目标检测任务中,解析出物体的位置坐标和类别。
  • DataOutputThread / RtspPushThread:输出线程。将处理结果进行可视化(例如,在图像上绘制检测框),然后保存为视频文件或推送到一个新的RTSP流中,供其他应用消费。

这些线程各司其职,通过消息队列(Message Queue)进行通信,实现了任务的解耦和并行执行。例如,当推理线程正在处理第N帧时,预处理线程可以同时处理第N+1帧,而数据输入线程则在读取第N+2帧,从而实现了高效的流水线作业。

2.2 test.json:灵活的配置与调度中心

这个并行架构的强大之处在于其灵活性,而这种灵活性很大程度上来自于test.json这个配置文件。它就像是整个系统的大脑,指挥着各个线程如何工作,以及如何在硬件资源上进行分配。

让我们看一个简化的test.json示例:

json 复制代码
{
    "device_config":[
        {
            "device_id":0,
            "model_config":[
                {
                    "infer_thread_name":"infer_thread_0",
                    "model_path":"../model/yolov7x.om",
                    "model_width":640,
                    "model_heigth":640,
                    "io_info":[
                        {
                            "input_path":"../data/car0.mp4",
                            "input_type":"video",
                            "output_path":"../out/outcar0.mp4",
                            "output_type":"video",
                            "channel_id":0
                        },
                        {
                            "input_path":"rtsp://admin:password@192.168.1.100/stream1",
                            "input_type":"rtsp",
                            "output_path":"rtsp://localhost:8554/stream_out_0",
                            "output_type":"rtsp",
                            "channel_id":1
                        }
                    ]
                }
            ]
        },
        {
            "device_id":1,
            "model_config":[
                {
                    "infer_thread_name":"infer_thread_1",
                    "model_path":"../model/yolov7_person.om",
                    // ...
                    "io_info":[
                        {
                            "input_path":"../data/person.mp4",
                            // ...
                        }
                    ]
                }
            ]
        }
    ]
}

通过这个JSON文件,我们可以实现非常复杂的配置:

  • 多路输入 :在io_info数组中,我们可以定义多个输入源。在上面的例子中,device_id: 0上的模型同时处理一个本地视频文件 (car0.mp4) 和一个RTSP流。系统会为每个输入源创建一套独立的DataInput -> Preprocess -> Postprocess -> DataOutput 线程链。
  • 多卡调度 :在device_config数组中,我们可以定义多个设备。上面的例子中,系统会在device_id: 0上加载yolov7x.om模型来处理车辆相关的视频流,同时在device_id: 1上加载yolov7_person.om模型来处理行人相关的视频流。这实现了真正的多卡并行计算。
  • 资源共享 :同一个设备上的多个输入流可以共享同一个推理线程(DetectInferenceThread),从而节省模型加载和内存占用的开销。

2.3 main.cpp:线程的创建与管理

main.cpp中的CreateALLThreadInstance函数是实现这一切魔法的核心。它负责解析test.json文件,并根据配置动态地创建所有需要的线程实例。

cpp 复制代码
// main.cpp in sampleYOLOV7MultiInput (Simplified)
void CreateALLThreadInstance(vector<AclLiteThreadParam>& threadTbl, AclLiteResource& aclDev)
{
    // ... 读取并解析 test.json ...
    if (reader.parse(srcFile, root))
    {
        // 遍历所有设备配置
        for (int i = 0; i < root["device_config"].size(); i++)
        {
            uint32_t deviceId = root["device_config"][i]["device_id"].asInt();
            aclrtContext context = aclDev.GetContextByDevice(deviceId);

            // 遍历该设备上的所有模型配置
            for (int j = 0; j < root["device_config"][i]["model_config"].size(); j++)
            {
                // 1. 创建推理线程
                AclLiteThreadParam inferParam;
                inferParam.threadInst = new DetectInferenceThread(modelPath);
                // ... 设置线程参数 ...
                threadTbl.push_back(inferParam);

                // 2. 遍历该模型的所有输入输出配置
                for (int k = 0; k < root["device_config"][i]["model_config"][j]["io_info"].size(); k++)
                {
                    // 3. 为每个IO创建一套处理线程
                    // 创建 DataInputThread
                    AclLiteThreadParam dataInputParam;
                    dataInputParam.threadInst = new DataInputThread(...);
                    threadTbl.push_back(dataInputParam);

                    // 创建 PreprocessThread, PostprocessThread, DataOutputThread ...
                    // ...
                }
            }
        }
    }
}

这段代码的逻辑清晰地反映了配置文件的结构:逐层遍历device_config -> model_config -> io_info,并依次创建推理线程和与之关联的各个处理线程。

3. 总结

从单路RTSP流的简单处理,到多路输入、多线程流水线、多卡调度的复杂并行架构,我们看到了CANN在处理真实世界视频AI应用时的强大能力和灵活性。

  • 对于简单的应用,AclLiteVideoProc提供了极其便利的接口,可以快速上手。
  • 对于追求极致性能和高吞吐量的复杂场景,sampleYOLOV7MultiInput所展示的基于配置文件的多线程并行流水线架构,提供了一个绝佳的参考实现。它将硬件能力、软件调度和应用需求完美地结合在了一起。

通过理解这两个示例,你不仅学会了如何处理视频流,更重要的是,掌握了如何根据应用需求设计不同复杂度的AI处理系统。在后续的开发中,你可以基于sampleYOLOV7MultiInput的框架,通过修改test.json或自定义新的线程类型,来快速适配更多样化的AI应用场景。

相关推荐
玖日大大1 小时前
X-AnyLabeling-实践使用AI驱动的图像
人工智能
倔强的石头1061 小时前
Rokid AI眼镜:连接现实与数字的桥梁,探索下一代智能应用开发
人工智能·ai·ar·rokid·ai眼镜
雪不下1 小时前
医用IT技术:CT(3.1)
图像处理·人工智能
咚咚王者1 小时前
人工智能之数据分析 Matplotlib:第一章 简介和安装
人工智能·数据分析·matplotlib
极客BIM工作室1 小时前
AI论文整理:Flamingo: a Visual Language Model for Few-Shot Learning
人工智能·语言模型·自然语言处理
wumingxiaoyao1 小时前
AI - AI Agent 是什么?为什么最近这么火?
人工智能·ai·chatgpt·ai agent
模型优化师1 小时前
【必收藏】AI大模型面试精选20题:从基础到高级,轻松应对大模型岗位面试
人工智能·面试·职场和发展·ai大模型·大模型学习·大模型入门·大模型教程
B站计算机毕业设计之家1 小时前
Python+Flask 电商数据分析系统(Selenium爬虫+多元线性回归)商品数据采集分析可视化系统 实时监控 淘宝数据采集 大屏可视化 (附源码)✅
大数据·爬虫·python·selenium·机器学习·flask·线性回归
Salt_07281 小时前
DAY 22 常见的特征筛选算法
人工智能·python·机器学习