跳转至

通用视频分类产线使用教程

1. 通用视频分类产线介绍

视频分类是一种将视频片段分配到预定义类别的技术。它广泛应用于动作识别、事件检测和内容推荐等领域。视频分类可以识别各种动态事件和场景,如体育活动、自然现象、交通状况等,并根据其特征将其归类。通过使用深度学习模型,尤其是卷积神经网络(CNN)和循环神经网络(RNN)的结合,视频分类能够自动提取视频中的时空特征并进行准确分类。这种技术在视频监控、媒体检索和个性化推荐系统中具有重要应用.

通用视频分类产线用于解决视频分类任务,提取视频中的主题和类别信息以标签形式输出,本产线是一个集成了业界知名的 PP-TSM 和 PP-TSMv2的视频分类系统,支持400种视频类别的识别。基于本产线,可实现视频内容的精准分类,使用场景覆盖媒体、安防、教育、交通等各个领域。本产线同时提供了灵活的服务化部署方式,支持在多种硬件上使用多种编程语言调用。不仅如此,本产线也提供了二次开发的能力,您可以基于本产线在您自己的数据集上训练调优,训练后的模型也可以无缝集成。

通用视频分类产线中包含了视频分类模块,如您更考虑模型精度,请选择精度较高的模型,如您更考虑模型推理速度,请选择推理速度较快的模型,如您更考虑模型存储大小,请选择存储大小较小的模型

模型模型下载链接 Top1 Acc(%) 模型存储大小 (M) 介绍
PP-TSM-R50_8frames_uniform推理模型/训练模型 74.36 93.4 M PP-TSM是一种百度飞桨视觉团队自研的视频分类模型。该模型基于ResNet-50骨干网络进行优化,从数据增强、网络结构微调、训练策略、BN层优化、预训练模型选择、模型蒸馏等6个方面进行模型调优,在中心采样评估方式下,Kinetics-400上精度较原论文实现提升3.95个点
PP-TSMv2-LCNetV2_8frames_uniform推理模型/训练模型 71.71 22.5 M PP-TSMv2是轻量化的视频分类模型,基于CPU端模型PP-LCNetV2进行优化,从骨干网络与预训练模型选择、数据增强、tsm模块调优、输入帧数优化、解码速度优化、DML蒸馏、LTA模块等7个方面进行模型调优,在中心采样评估方式下,精度达到75.16%,输入10s视频在CPU端的推理速度仅需456ms。
PP-TSMv2-LCNetV2_16frames_uniform推理模型/训练模型 73.11 22.5 M

测试环境说明:

  • 性能测试环境
  • 测试数据集K400 验证集。
  • 硬件配置

    • GPU:NVIDIA Tesla T4
    • CPU:Intel Xeon Gold 6271C @ 2.60GHz
    • 其他环境:Ubuntu 20.04 / cuDNN 8.6 / TensorRT 8.5.2.2
  • 推理模式说明

模式 GPU配置 CPU配置 加速技术组合
常规模式 FP32精度 / 无TRT加速 FP32精度 / 8线程 PaddleInference
高性能模式 选择先验精度类型和加速策略的最优组合 FP32精度 / 8线程 选择先验最优后端(Paddle/OpenVINO/TRT等)

2. 快速开始

PaddleX 支持在本地使用命令行或 Python 体验产线的效果。

在本地使用通用视频分类产线前,请确保您已经按照PaddleX本地安装教程完成了PaddleX的wheel包安装。

2.1 命令行方式体验

一行命令即可快速体验视频分类产线效果,使用 测试文件,并将 --input 替换为本地路径,进行预测

paddlex --pipeline video_classification \
    --input general_video_classification_001.mp4 \
    --topk 5 \
    --save_path ./output \
    --device gpu:0
相关的参数说明可以参考2.2 Python脚本方式集成中的参数说明。

运行后,会将结果打印到终端上,结果如下:

{'res': {'input_path': 'general_video_classification_001.mp4', 'class_ids': array([  0, ..., 162], dtype=int32), 'scores': [0.91997, 0.07052, 0.00237, 0.00214, 0.00158], 'label_names': ['abseiling', 'rock_climbing', 'climbing_tree', 'riding_mule', 'ice_climbing']}}
运行结果参数说明可以参考2.2 Python脚本方式集成中的结果解释。

可视化结果保存在save_path下,其中视频分类的可视化结果如下:

2.2 Python脚本方式集成

  • 上述命令行是为了快速体验查看效果,一般来说,在项目中,往往需要通过代码集成,您可以通过几行代码即可完成产线的快速推理,推理代码如下:
from paddlex import create_pipeline

pipeline = create_pipeline(pipeline="video_classification")

output = pipeline.predict("general_video_classification_001.mp4", topk=5)
for res in output:
    res.print()
    res.save_to_video(save_path="./output/")
    res.save_to_json(save_path="./output/")

在上述 Python 脚本中,执行了如下几个步骤:

(1)通过 create_pipeline() 实例化视频分类产线对象,具体参数说明如下:

参数 参数说明 参数类型 默认值
pipeline 产线名称或是产线配置文件路径。如为产线名称,则必须为 PaddleX 所支持的产线。 str None
config 产线具体的配置信息(如果和pipeline同时设置,优先级高于pipeline,且要求产线名和pipeline一致)。 dict[str, Any] None
device 产线推理设备。支持指定GPU具体卡号,如“gpu:0”,其他硬件具体卡号,如“npu:0”,CPU如“cpu”。 str gpu:0
use_hpip 是否启用高性能推理,仅当该产线支持高性能推理时可用。 bool False

(2)调用 通用视频分类产线对象的 predict() 方法进行推理预测。该方法将返回一个 generator。以下是 predict() 方法的参数及其说明:

参数 参数说明 参数类型 可选项 默认值
input 待预测数据,支持多种输入类型,必填 str|list
  • str:如视频文件的本地路径:/root/data/video.mp4如URL链接,如视频文件的网络URL:示例如本地目录,该目录下需包含待预测视频,如本地路径:/root/data/
  • List:列表元素需为上述类型数据,如 [\"/root/data/video1.mp4\", \"/root/data/video2.mp4\"][\"/root/data1\", \"/root/data2\"]
None
device 产线推理设备 str|None
  • CPU:如 cpu 表示使用 CPU 进行推理;
  • GPU:如 gpu:0 表示使用第 1 块 GPU 进行推理;
  • NPU:如 npu:0 表示使用第 1 块 NPU 进行推理;
  • XPU:如 xpu:0 表示使用第 1 块 XPU 进行推理;
  • MLU:如 mlu:0 表示使用第 1 块 MLU 进行推理;
  • DCU:如 dcu:0 表示使用第 1 块 DCU 进行推理;
  • None:如果设置为 None, 将默认使用产线初始化的该参数值,初始化时,会优先使用本地的 GPU 0号设备,如果没有,则使用 CPU 设备;
None
topk 预测结果的前 topk 个类别和对应的分类概率 int|None
  • int:大于 0 的任意整数
  • None:如果设置为 None, 将默认使用产线初始化的该参数值 1
None

(3)对预测结果进行处理,每个样本的预测结果均为对应的Result对象,且支持打印、保存为图片、保存为 json 文件的操作:

方法 方法说明 参数 参数类型 参数说明 默认值
print() 打印结果到终端 format_json bool 是否对输出内容进行使用 JSON 缩进格式化 True
indent int 指定缩进级别,以美化输出的 JSON 数据,使其更具可读性,仅当 format_jsonTrue 时有效 4
ensure_ascii bool 控制是否将非 ASCII 字符转义为 Unicode。设置为 True 时,所有非 ASCII 字符将被转义;False 则保留原始字符,仅当format_jsonTrue时有效 False
save_to_json() 将结果保存为json格式的文件 save_path str 保存的文件路径,当为目录时,保存文件命名与输入文件类型命名一致
indent int 指定缩进级别,以美化输出的 JSON 数据,使其更具可读性,仅当 format_jsonTrue 时有效 4
ensure_ascii bool 控制是否将非 ASCII 字符转义为 Unicode。设置为 True 时,所有非 ASCII 字符将被转义;False 则保留原始字符,仅当format_jsonTrue时有效 False
save_to_video() 将结果保存为视频格式的文件 save_path str 保存的文件路径,支持目录或文件路径
  • 调用print() 方法会将结果打印到终端,打印到终端的内容解释如下:

    • input_path: (str) 待预测视频的输入路径
    • class_ids: (numpy.ndarray) 视频分类的id 列表
    • scores: (List[float]) 视频分类的置信度分数列表
    • label_names: (List[str]) 视频分类的类别列表
  • 调用save_to_json() 方法会将上述内容保存到指定的save_path中,如果指定为目录,则保存的路径为save_path/{your_video_basename}_res.json,如果指定为文件,则直接保存到该文件中。由于json文件不支持保存numpy数组,因此会将其中的numpy.array类型转换为列表形式。

  • 调用save_to_video() 方法会将可视化结果保存到指定的save_path中,如果指定为目录,则保存的路径为save_path/{your_video_basename}_res.{your_video_extension},如果指定为文件,则直接保存到该文件中。(产线通常包含较多结果视频,不建议直接指定为具体的文件路径,否则多个视频会被覆盖,仅保留最后一个视频)

  • 此外,也支持通过属性获取带结果的可视化视频和预测结果,具体如下:

属性 属性说明
json 获取预测的 json 格式的结果
video 获取格式为 dict 的可视化视频
  • json 属性获取的预测结果为dict类型的数据,相关内容与调用 save_to_json() 方法保存的内容一致。
  • video 属性返回的预测结果是一个字典类型的数据。其中,键为 res,对应的值是一个元组,元组的第一个值是可视化视频数组,维度是(视频帧数,视频高度,视频宽度,视频通道数);第二个值是帧率。

此外,您可以获取视频分类产线配置文件,并加载配置文件进行预测。可执行如下命令将结果保存在 my_path 中:

paddlex --get_pipeline_config video_classification --save_path ./my_path

若您获取了配置文件,即可对视频分类产线各项配置进行自定义,只需要修改 create_pipeline 方法中的 pipeline 参数值为产线配置文件路径即可。示例如下:

from paddlex import create_pipeline

pipeline = create_pipeline(pipeline="./my_path/video_classification.yaml")

output = pipeline.predict("general_video_classification_001.mp4", topk=5)
for res in output:
    res.print()
    res.save_to_video(save_path="./output/")
    res.save_to_json(save_path="./output/")
注: 配置文件中的参数为产线初始化参数,如果希望更改通用视频分类产线初始化参数,可以直接修改配置文件中的参数,并加载配置文件进行预测。同时,CLI 预测也支持传入配置文件,--pipeline 指定配置文件的路径即可。

3. 开发集成/部署

如果产线可以达到您对产线推理速度和精度的要求,您可以直接进行开发集成/部署。

若您需要将产线直接应用在您的Python项目中,可以参考 2.2 Python脚本方式中的示例代码。

此外,PaddleX 也提供了其他三种部署方式,详细说明如下:

🚀 高性能推理:在实际生产环境中,许多应用对部署策略的性能指标(尤其是响应速度)有着较严苛的标准,以确保系统的高效运行与用户体验的流畅性。为此,PaddleX 提供高性能推理插件,旨在对模型推理及前后处理进行深度性能优化,实现端到端流程的显著提速,详细的高性能推理流程请参考PaddleX高性能推理指南

☁️ 服务化部署:服务化部署是实际生产环境中常见的一种部署形式。通过将推理功能封装为服务,客户端可以通过网络请求来访问这些服务,以获取推理结果。PaddleX 支持多种产线服务化部署方案,详细的产线服务化部署流程请参考PaddleX服务化部署指南

以下是基础服务化部署的API参考与多语言服务调用示例:

API参考

对于服务提供的主要操作:

  • HTTP请求方法为POST。
  • 请求体和响应体均为JSON数据(JSON对象)。
  • 当请求处理成功时,响应状态码为200,响应体的属性如下:
名称 类型 含义
errorCode integer 错误码。固定为0
errorMsg string 错误说明。固定为"Success"

响应体还可能有result属性,类型为object,其中存储操作结果信息。

  • 当请求处理未成功时,响应体的属性如下:
名称 类型 含义
errorCode integer 错误码。与响应状态码相同。
errorMsg string 错误说明。

服务提供的主要操作如下:

  • infer

对视频进行分类。

POST /video-classification

  • 请求体的属性如下:
  • 请求处理成功时,响应体的result具有如下属性:
名称 类型 含义 是否必填
video string 服务器可访问的视频文件的URL或视频文件内容的Base64编码结果。
topk integer | null 参见产线 predict 方法中的 topk 参数说明。
名称 类型 含义
categories array 视频类别信息。

categories中的每个元素为一个object,具有如下属性:

名称 类型 含义
id integer 类别ID。
name string 类别名称。
score number 类别得分。

result示例如下:

{
"categories": [
{
"id": 5,
"name": "兔子",
"score": 0.93
}
],
"video": "xxxxxx"
}
多语言调用服务示例
Python
import base64
import requests

API_URL = "http://localhost:8080/video-classification" # 服务URL
video_path = "./demo.mp4"
output_video_path = "./out.mp4"

# 对本地视频进行Base64编码
with open(video_path, "rb") as file:
    video_bytes = file.read()
    video_data = base64.b64encode(video_bytes).decode("ascii")

payload = {"video": video_data}  # Base64编码的文件内容或者视频URL

# 调用API
response = requests.post(API_URL, json=payload)

# 处理接口返回数据
assert response.status_code == 200
result = response.json()["result"]
print("Categories:")
print(result["categories"])
C++
#include <iostream>
#include "cpp-httplib/httplib.h" // https://github.com/Huiyicc/cpp-httplib
#include "nlohmann/json.hpp" // https://github.com/nlohmann/json
#include "base64.hpp" // https://github.com/tobiaslocker/base64

int main() {
    httplib::Client client("localhost:8080");
    const std::string videoPath = "./demo.mp4";

    httplib::Headers headers = {
        {"Content-Type", "application/json"}
    };

    // 对本地视频进行Base64编码
    std::ifstream file(videoPath, std::ios::binary | std::ios::ate);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<char> buffer(size);
    if (!file.read(buffer.data(), size)) {
        std::cerr << "Error reading file." << std::endl;
        return 1;
    }
    std::string bufferStr(reinterpret_cast<const char*>(buffer.data()), buffer.size());
    std::string encodedImage = base64::to_base64(bufferStr);

    nlohmann::json jsonObj;
    jsonObj["video"] = encodedImage;
    std::string body = jsonObj.dump();

    // 调用API
    auto response = client.Post("/video-classification", headers, body, "application/json");
    // 处理接口返回数据
    if (response && response->status == 200) {
        nlohmann::json jsonResponse = nlohmann::json::parse(response->body);
        auto result = jsonResponse["result"];
        auto categories = result["categories"];
        std::cout << "\nCategories:" << std::endl;
        for (const auto& category : categories) {
            std::cout << category << std::endl;
        }
    } else {
        std::cout << "Failed to send HTTP request." << std::endl;
        return 1;
    }

    return 0;
}
Java
import okhttp3.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;

public class Main {
    public static void main(String[] args) throws IOException {
        String API_URL = "http://localhost:8080/video-classification"; // 服务URL
        String videoPath = "./demo.mp4"; // 本地视频

        // 对本地视频进行Base64编码
        File file = new File(videoPath);
        byte[] fileContent = java.nio.file.Files.readAllBytes(file.toPath());
        String videoData = Base64.getEncoder().encodeToString(fileContent);

        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode params = objectMapper.createObjectNode();
        params.put("video", videoData); // Base64编码的文件内容或者视频URL

        // 创建 OkHttpClient 实例
        OkHttpClient client = new OkHttpClient();
        MediaType JSON = MediaType.Companion.get("application/json; charset=utf-8");
        RequestBody body = RequestBody.Companion.create(params.toString(), JSON);
        Request request = new Request.Builder()
                .url(API_URL)
                .post(body)
                .build();

        // 调用API并处理接口返回数据
        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                String responseBody = response.body().string();
                JsonNode resultNode = objectMapper.readTree(responseBody);
                JsonNode result = resultNode.get("result");
                JsonNode categories = result.get("categories");
                System.out.println("\nCategories: " + categories.toString());
            } else {
                System.err.println("Request failed with code: " + response.code());
            }
        }
    }
}
Go
package main

import (
    "bytes"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    API_URL := "http://localhost:8080/video-classification"
    videoPath := "./demo.mp4"

    // 对本地视频进行Base64编码
    videoBytes, err := ioutil.ReadFile(videoPath)
    if err != nil {
        fmt.Println("Error reading video file:", err)
        return
    }
    videoData := base64.StdEncoding.EncodeToString(videoBytes)

    payload := map[string]string{"video": videoData} // Base64编码的文件内容或者视频URL
    payloadBytes, err := json.Marshal(payload)
    if err != nil {
        fmt.Println("Error marshaling payload:", err)
        return
    }

    // 调用API
    client := &http.Client{}
    req, err := http.NewRequest("POST", API_URL, bytes.NewBuffer(payloadBytes))
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }

    res, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer res.Body.Close()

    // 处理接口返回数据
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }
    type Response struct {
        Result struct {
            Categories []map[string]interface{} `json:"categories"`
        } `json:"result"`
    }
    var respData Response
    err = json.Unmarshal([]byte(string(body)), &respData)
    if err != nil {
        fmt.Println("Error unmarshaling response body:", err)
        return
    }

    fmt.Println("\nCategories:")
    for _, category := range respData.Result.Categories {
        fmt.Println(category)
    }
}
C#
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

class Program
{
    static readonly string API_URL = "http://localhost:8080/video-classification";
    static readonly string videoPath = "./demo.mp4";

    static async Task Main(string[] args)
    {
        var httpClient = new HttpClient();

        // 对本地视频进行Base64编码
        byte[] videoBytes = File.ReadAllBytes(videoPath);
        string video_data = Convert.ToBase64String(videoBytes);

        var payload = new JObject{ { "video", video_data } }; // Base64编码的文件内容或者视频URL
        var content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json");

        // 调用API
        HttpResponseMessage response = await httpClient.PostAsync(API_URL, content);
        response.EnsureSuccessStatusCode();

        // 处理接口返回数据
        string responseBody = await response.Content.ReadAsStringAsync();
        JObject jsonResponse = JObject.Parse(responseBody);

        Console.WriteLine("\nCategories:");
        Console.WriteLine(jsonResponse["result"]["categories"].ToString());
    }
}
Node.js
const axios = require('axios');
const fs = require('fs');

const API_URL = 'http://localhost:8080/video-classification'
const videoPath = './demo.mp4'

let config = {
   method: 'POST',
   maxBodyLength: Infinity,
   url: API_URL,
   data: JSON.stringify({
    'video': encodeImageToBase64(videoPath)  // Base64编码的文件内容或者视频URL
  })
};

// 对本地视频进行Base64编码
function encodeImageToBase64(filePath) {
  const bitmap = fs.readFileSync(filePath);
  return Buffer.from(bitmap).toString('base64');
}

// 调用API
axios.request(config)
.then((response) => {
    // 处理接口返回数据
    const result = response.data["result"];
    console.log("\nCategories:");
    console.log(result["categories"]);
})
.catch((error) => {
  console.log(error);
});
PHP
<?php

$API_URL = "http://localhost:8080/video-classification"; // 服务URL
$video_path = "./demo.mp4";

// 对本地视频进行Base64编码
$video_data = base64_encode(file_get_contents($video_path));
$payload = array("video" => $video_data); // Base64编码的文件内容或者视频URL

// 调用API
$ch = curl_init($API_URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

// 处理接口返回数据
$result = json_decode($response, true)["result"];
echo "\nCategories:\n";
print_r($result["categories"]);
?>


📱 端侧部署:端侧部署是一种将计算和数据处理功能放在用户设备本身上的方式,设备可以直接处理数据,而不需要依赖远程的服务器。PaddleX 支持将模型部署在 Android 等端侧设备上,详细的端侧部署流程请参考PaddleX端侧部署指南。 您可以根据需要选择合适的方式部署模型产线,进而进行后续的 AI 应用集成。

4. 二次开发

如果通用视频分类产线提供的默认模型权重在您的场景中,精度或速度不满意,您可以尝试利用您自己拥有的特定领域或应用场景的数据对现有模型进行进一步的微调,以提升通用视频分类产线的在您的场景中的识别效果。

4.1 模型微调

由于通用视频分类产线仅包含视频分类模块,模型产线的效果如果不及预期。您可以对识别效果差的视频进行分析,并参考以下表格中对应的微调教程链接进行模型微调。

情形 微调模块 微调参考链接
视频分类都不准 视频分类模块 链接

4.2 模型应用

当您使用私有数据集完成微调训练后,可获得本地模型权重文件。

若您需要使用微调后的模型权重,只需对产线配置文件做修改,将微调后模型权重的本地路径替换至产线配置文件中的对应位置即可:

...
SubModules:
  VideoClassification:
    module_name: video_classification
    model_name: PP-TSMv2-LCNetV2_8frames_uniform
    model_dir: null # 替换为微调后的视频分类模型权重路径
    batch_size: 1
    topk: 1

...
随后, 参考本地体验中的命令行方式或 Python 脚本方式,加载修改后的产线配置文件即可。

5. 多硬件支持

PaddleX 支持英伟达 GPU、昆仑芯 XPU、昇腾 NPU和寒武纪 MLU 等多种主流硬件设备,仅需修改 --device参数即可完成不同硬件之间的无缝切换。

例如,您使用昇腾 NPU 进行 视频分类产线的推理,使用的 CLI 命令为:

paddlex --pipeline video_classification \
    --input general_video_classification_001.mp4 \
    --topk 5 \
    --save_path ./output \
    --device npu:0

当然,您也可以在 Python 脚本中 create_pipeline() 时或者 predict() 时指定硬件设备。

若您想在更多种类的硬件上使用通用视频分类产线,请参考PaddleX多硬件使用指南

评论