(2024年4月编写)
github地址
https://github.com/sherlockchou86/video_pipe_c
作者微信
zhzhi78
(备注 videopipe),拉群交流(1000人群),及时获取代码更新。
网站介绍
配套教程
http://www.videopipe.cool/index.php/2024/09/11/videopipetutorials/
演示Sample
http://www.videopipe.cool/index.php/2024/08/21/videopipesamples/
0、一个引子
计算机视觉(Computer Vision,后简称CV)是人工智能领域中的一个细分场景。随着深度学习技术的飞速发展,基于神经网络的图像算法已经慢慢取代了传统图像算法,也越来越多地落地到实际应用场景中。下图列举了常见的神经网络图像算法:
图1 常见神经网络图像算法
神经网络的训练、调优离不开算法工程师,近几年在众多IT技术招聘岗位中,无论是薪资待遇还是招聘数量上,算法工程师岗位都是名列前茅。那么在这个调参(炼丹)火热的时代(公司保安大爷都知道什么叫“监督学习”),如何将训练好的神经网络图像算法模型快速部署落地到实际应用场景中呢?VideoPipe视频分析框架满足这一需求,它适用于大部分视频结构化应用场景,可以快速集成训练好的图像算法模型,包括但不限于:
图像分类算法
。给定的图片是小猫还是小狗?是白毛还是黑毛?目标检测算法
。给定的图片中包含哪些目标(车、人、文字...)?目标坐标、尺寸是多少?图像分割算法
。为给定的图片做像素级分割,不同区域显示不同颜色。图像特征编码算法
。为给定的图片提取“指纹级”特征,可用于图片识别、对比、检索。图像生成算法
。随机生成指定风格的图片,或为图片转换风格(比如将手机拍摄的人物照片转成卡通风格)。
VideoPipe视频分析框架可广泛应用于智慧交通、智能安防等领域,可快速构建人脸识别
、车牌识别
、交通事件检测
、以图搜图
、OCR文字识别
、AI换脸
等软件系统。下图是使用VideoPipe实现口算练习检查
(仿作业帮APP)的功能截图:
图2 口算练习检查
图中绿色代表正确
,红色代表错误
(并在括号中给出了正确答案),橙色代表格式不对
(比如表达式中左括号没有对应的右括号)。另外,图中上半部分的管道
代表口算练习检查
的核心环节。下面整理了作业帮APP中口算练习检查
功能的完整流程,我们按照该流程可以完整复现作业帮APP中的功能:
- 用户打开作业帮APP,点击
口算检查
按钮,APP弹出相机界面准备拍照 - 用户将手机对准口算作业本,保持相机画面正对口算题目,点击
拍照
按钮 - 作业帮APP抓拍一张照片,通过http协议上传到作业帮服务器
- 服务器收到图片数据(编码数据),对其进行解码,生成图片
- 服务器调用OCR文字识别算法,对图片中的口算题目进行检测和识别,得到String类型字符串,类似
1+1=2
- 服务器调用数学表达式解析库(一种可以对数学表达式进行解析和计算的工具库),计算
=
号左边的结果(这里结果是2),然后将左边结果和=
号右边的值(学生答题结果,这里也是2)进行比较,给出对错结论 - 按照步骤6的方式检查整张图片中的口算表达式,最后计算总得分
- 服务器将检查结果(对、错、无效)用不同颜色叠加到原上传图片上
- 服务器将最终结果(含叠加结果的图片)返回给作业帮APP,APP呈现给用户
我们可以看到,口算练习检查
整个流程包含了多个处理环节和技术栈,我用图片直观描述如下:
图3 口算练习检查流程
图中虚线
部分全部可以借助VideoPipe来实现,图片接收和解码
、字符检测和识别
、数学表达式解析和计算
、检查结果叠加
,这些环节在VideoPipe中有节点类型一一与之对应:
vp_image_src_node
节点:完成接收图片和解码工作vp_ppocr_text_detector_node
节点:完成字符检测和识别vp_expr_check_node
节点:完成数学表达式解析和计算vp_expr_osd_node
节点:完成检查结果叠加
VideoPipe可以帮助你实现口算练习检查
的核心环节,当然VideoPipe能做的远不止于此,接下来我来对它做详细介绍。
1、VideoPipe介绍
一个用于视频(含图片,下同)分析的图像算法集成框架。它可以处理复杂任务,如流读取(从本地或网络)、解码、基于深度学习模型推理(分类/检测/特征提取/...)、跟踪、行为分析(逻辑处理)、数据叠加(OSD)、数据转发(如Kafka/Socket)、编码和流推送(RTMP或本地文件)。整个框架采用面向插件的编程风格,我们可以使用各种不同类型的插件,即框架中的Node
类型,来构建不同类型的视频分析管道。
图4 VideoPipe工作管道(示意)
VideoPipe类似于英伟达的DeepStream和华为的mxVision,但它更易于使用,更具备可移植性,并且对于像GStreamer这样难以学习(编程风格或调试方面)的第三方模块依赖较少。该框架纯粹由原生C++ STL编写,只依赖主流的第三方库(如OpenCV),因此代码更易于在不同平台上移植。
注意:
VideoPipe是一个让计算机视觉领域中算法模型集成更加简单的框架,它并不是TensorFlow、TensorRT类似的深度学习框架。
2、主要功能
VideoPipe包含以下主要功能:
- 流读取。支持主流的视频流协议,如udp(视频或图像)、rtsp、rtmp、文件(视频或图像)。
- 视频解码。支持基于OpenCV/GStreamer的视频(图片)解码(支持硬件加速)。
- 基于深度学习的算法推理。支持基于深度学习算法的多级推理,例如目标检测、图像分类、特征提取。你只需准备好模型并了解如何解析其输出即可。推理可以基于不同的后端实现,如
OpenCV::DNN(默认)
、TensorRT
、PaddleInference
、ONNXRuntime
等,任何你喜欢的都可以。 - 屏幕显示(OSD)。支持可视化,如将模型输出结果绘制到帧上。
- 数据代理。支持将结构化数据(json/xml/自定义格式)以kafka/Sokcet等方式推送到云端、文件或其他第三方平台。
- 目标跟踪。支持目标追踪,例如IOU、SORT跟踪算法等。
- 行为分析(BA)。支持基于跟踪的行为分析,例如越线、停车、违章等交通判断。
- 录制。支持特定时间段的视频录制,特定帧的截图。
- 视频编码。支持基于OpenCV/GStreamer的视频(图片)编码(支持硬件加速)。
- 流推送。支持通过rtmp、rtsp(无需专门的rtsp服务器)、文件(视频/图像)、udp(仅限图像)、屏幕显示(GUI)进行流推送或结果展示。
3、主要特点
VideoPipe具备以下特点:
- 可视化管道,对程序调试非常有帮助。管道的运行状态会自动在屏幕上刷新,包括管道中每个连接点的fps、缓存大小、延迟等信息,你可以根据这些运行信息快速确定管道的瓶颈所在。
- 节点之间通过智能指针传递数据,默认情况下是浅拷贝,当数据在整个管道中流动时无需进行内容拷贝操作。当然,如果需要,你可以指定深拷贝,例如当管道具有多个分支时,你需要分别在两个不同的内容拷贝上进行操作。
- 你可以构建不同类型的管道,支持单通道或多通道的管道,管道中的各通道是独立的。每个通道可以拆分成多个分支并行处理不同的任务,之后进行数据同步,再合并回一个分支进行输出。
- 管道支持钩子(回调),你可以向管道注册回调以获取状态通知(参见第1项特点),例如实时获取某个连接点的fps。
- VideoPipe中已经内置了丰富的节点(Node)类型,所有节点都可以由用户重新实现,也可以根据你的实际需求实现更多节点类型。
- 支持动态操作管道,支持多线程并行操作,支持
热插拔
操作模式(管道无需先暂停,即插即用)。 - 整个框架主要由原生C++编写,可在所有平台上移植。
4、如何使用
4.1 快速开始
下面是一个如何构建Pipeline然后运行的Sample(请先修改代码中的相关文件路径):
#include "../nodes/vp_file_src_node.h" #include "../nodes/infers/vp_yunet_face_detector_node.h" #include "../nodes/infers/vp_sface_feature_encoder_node.h" #include "../nodes/osd/vp_face_osd_node_v2.h" #include "../nodes/vp_screen_des_node.h" #include "../nodes/vp_rtmp_des_node.h" #include "../utils/analysis_board/vp_analysis_board.h" /* * ## 1-1-N sample ## * 1个视频输入,1个视频分析任务(人脸检测和识别),2个输出(屏幕输出/RTMP推流输出) */ int main() { VP_SET_LOG_INCLUDE_CODE_LOCATION(false); VP_SET_LOG_INCLUDE_THREAD_ID(false); VP_LOGGER_INIT(); // 创建节点 auto file_src_0 = std::make_shared<vp_nodes::vp_file_src_node>("file_src_0", 0, "./test_video/10.mp4", 0.6); auto yunet_face_detector_0 = std::make_shared<vp_nodes::vp_yunet_face_detector_node>("yunet_face_detector_0", "./models/face/face_detection_yunet_2022mar.onnx"); auto sface_face_encoder_0 = std::make_shared<vp_nodes::vp_sface_feature_encoder_node>("sface_face_encoder_0", "./models/face/face_recognition_sface_2021dec.onnx"); auto osd_0 = std::make_shared<vp_nodes::vp_face_osd_node_v2>("osd_0"); auto screen_des_0 = std::make_shared<vp_nodes::vp_screen_des_node>("screen_des_0", 0); auto rtmp_des_0 = std::make_shared<vp_nodes::vp_rtmp_des_node>("rtmp_des_0", 0, "rtmp://192.168.77.60/live/10000"); // 构建管道 yunet_face_detector_0->attach_to({file_src_0}); sface_face_encoder_0->attach_to({yunet_face_detector_0}); osd_0->attach_to({sface_face_encoder_0}); // 管道自动拆分 screen_des_0->attach_to({osd_0}); rtmp_des_0->attach_to({osd_0}); // 启动管道 file_src_0->start(); // 可视化管道 vp_utils::vp_analysis_board board({file_src_0}); board.display(); }
上面代码运行后,会出现3个画面:
- 管道的运行状态图,状态自动刷新
- 屏幕显示结果(GUI)
- 播放器显示结果(RTMP)
图5 1-1-N_sample运行截图
在上面Sample中,VideoPipe从本地读取视频文件,然后检测其中的人脸、对每个人脸提取特征、比较2张人脸的相似度、将结果叠加到视频上,最后在屏幕中显示,并以RTMP的形式将视频推到服务器上供互联网其他用户播放。整个流程完全由VideoPipe帮忙完成,插件式
编程风格,直观、灵活。
4.2 更多案例
github仓库中有接近40个Sample,涵盖人脸识别
、车辆检测分类
、以图搜图
、目标跟踪
、交通事件检测
、车牌识别
、动态管道
、图像分割
等等。下面是拥堵检测
和口算练习检查
的截图:
车牌识别
拥堵检测
更多案例请访问github仓库。
5、原理细节
下面给大家详细介绍框架实现原理和技术细节,对该部分感兴趣的小伙伴可以加我微信交流(见文章末尾)。
5.1 视频结构化应用的核心环节
视频结构化
是将非结构化数据(视频/图片)转换为结构化数据的过程。非结构化数据通常包括:
- 视频
- 图像
- 音频
- 自然语言文本
而结构化数据主要包括诸如 JSON、XML或数据库中的数据表等,这些数据可以直接由机器(程序)处理。具体到视频(含图片,下同)方面,结构化的过程主要涉及以下核心部分:
读取流
。从网络或本地机器获取视频流。解码
。将字节流解码为帧,因为算法只能作用于图像。推理
。对图像进行深度学习推理,如检测、分类或特征提取。跟踪
。跟踪视频中的目标。行为分析/逻辑处理
。分析目标的轨迹、属性。OSD
。在图像上显示结果,用于调试或得到直观效果。消息代理
。将结构化数据推送到外部,供业务平台使用。编码
。对包含结果的帧进行编码,以便传输、存储。推送流
。将字节流推送到外部或直接保存。
上述每个环节对应VideoPipe
中的一种插件类型,即代码中的Node
。
5.2 VideoPipe中的Node
VideoPipe中的每个Node负责一种任务(严格遵循单一职责原则
),例如解码或推理。我们可以将许多节点串在一起构建成管道,并让视频数据流经整个管道。每个Node内部都有两个队列,一个用于缓存上游节点推送的数据,另一个用于缓存等待被推送到下游节点的数据。我们可以在两个队列之间编写逻辑代码,这是典型的生产者-消费者
模式。
默认情况下,生产者和消费者在节点内部使用单个线程工作,因此在处理复杂任务时(例如,在 vp_message_broker_node
中推送数据是一个耗时的操作),我们需要编写异步代码来避免阻塞管道。
VideoPipe中有三种类型的节点,分别是:
SRC节点
:源节点,数据被创建的地方(内部只有一个队列,用于缓存被推送到下游节点的数据)。MID节点
:中间节点,数据将在此处理。DES节点
:目标节点,数据消失的地方(内部只有一个队列,用于缓存来自上游节点的数据)。
每个节点本身具有合并多个上游节点和拆分成多个下游节点的能力。注意,默认情况下节点在将数据从一个节点传输到另一个节点时使用浅拷贝和等值拷贝。如果您需要深拷贝或希望按通道索引传输数据(希望数据不混淆),则在分裂点添加一个vp_split_node
类型节点。
5.3 VideoPipe中的数据流
视频是一种重量级数据,因此频繁进行深拷贝会降低管道的性能。实际上,VideoPipe中两个节点之间传递的数据默认使用智能指针,一旦数据由源节点创建,数据内容在整个管道中大多数时间不会被复制(但如果需要,我们可以指定深度拷贝模式,例如使用vp_split_node
)。
视频由连续的帧组成,VideoPipe 逐帧处理这些帧,因此帧元数据中的帧索引也会连续增加。
5.4 VideoPipe中的钩子
钩子是一种机制,让主体在发生某些事件时通知检测者,VideoPipe也支持钩子。管道触发回调函数(std::function
)与外部代码通信,例如实时推送管道自身的 fps
、延迟
和其他状态信息
。我们在编写回调函数内部代码时,不允许有阻塞出现,否则影响整个管道性能。
钩子有助于调试我们的应用程序,并快速找出整个管道中的瓶颈,VideoPipe框架中自带的可视化工具vp_analysis_board
就依赖于钩子机制。
5.5 在VideoPipe中实现新的Node类型
vp_node
是VideoPipe中所有节点的基类。我们可以定义一个从vp_node
派生的新节点类,并重写一些虚函数,如handle_frame_meta
和handle_control_meta
。
handle_frame_meta
:处理流经当前节点的帧数据。handle_control_meta
:处理流经当前节点的控制指令数据。
帧数据指的是VideoPipe中的 vp_frame_meta
,其中包含与帧相关的数据,如帧索引
、数据缓冲区
、原始宽度
等等。控制指令数据指的是VideoPipe中的 vp_control_meta
,其中包含与命令相关的数据,例如记录视频
、记录图像
等。
注意,并非所有流经当前节点的数据都应该被处理,我们只需要处理我们感兴趣的内容。
六、相似框架对比
VideoPipe主要用途与DeepStream/mxVision相似,用于快速构建视频分析应用系统。但三者之间存在一些差异:
序号 | 名称 | 是否开源 | 学习门槛 | 适用平台 | 性能 | 三方依赖 |
---|---|---|---|---|---|---|
1 | DeepStream | 否 | 高 | 仅限英伟达 | 高 | 多 |
2 | mxVision | 否 | 高 | 仅限华为 | 高 | 多 |
3 | VideoPipe | 是 | 低 | 不限平台 | 中 | 少 |
DeepStream和mxVision(包括其他AI硬件厂家推出的类似框架)只可用于自家硬件平台,性能相对较高,VideoPipe由于要兼容各种不同硬件,数据共享方面弱于前者。VideoPipe学习门槛更低,核心代码完全开源,编码风格和代码调试方便均要优于其他类似框架(VideoPipe没有GObject相关内容)。下图显示视频分析过程中数据共享程度
对整个处理管道性能的影响:
上图左侧数据来回在CPU和GPU之间传递,影响整个管道处理速度;右侧大部分操作全部在GPU侧完成,减少数据来回传递次数,数据共享程度更高,有利于管道处理性能。