引言
今天继续测试一下Milk-V DuoS开发板。这个开发板有一个sample_vi_fd示例。这个程序是一个基于嵌入式视频处理平台的实时人脸检测与 RTSP 推流应用,主要功能是从视频输入设备获取图像,通过深度学习模型检测人脸,在图像上绘制人脸框,并将处理后的图像通过 RTSP 协议实时推流。
程序原理
程序源码可以从下面地址下载:duo-tdl-examples/sample_vi_fd/sample_vi_fd.c at master · milkv-duo/duo-tdl-examples · GitHub
程序整体功能概述
程序通过多线程协作完成以下流程:
- 视频输入(VI):从摄像头传感器获取原始图像。
- 视频处理(VPSS):对原始图像进行预处理(如缩放),并分发到两个通道。
- 人脸检测(TDL):一个线程从 VPSS 通道获取图像,通过 RetinaFace 模型检测人脸,保存人脸信息。
- 图像绘制与推流:另一个线程从 VPSS 另一通道获取图像,叠加人脸框后,通过 RTSP 协议推流,供客户端查看。
- 线程同步:通过互斥锁实现人脸检测结果在两个线程间的安全传递。
- 资源管理:初始化 / 销毁视频处理组件、深度学习模型,处理信号以优雅退出。
核心组件与变量解析
1. 宏定义与头文件
#define LOG_TAG "SampleFD"
:定义日志标签,用于区分不同模块的日志输出。#define LOG_LEVEL LOG_LEVEL_INFO
:定义日志级别(INFO 级),控制日志输出的详细程度。- 头文件:包含平台相关的多媒体处理库(
middleware_utils.h
、vi_vo_utils.h
)、视频编码 / RTSP 库(rtsp.h
、sample_comm.h
)、深度学习库(cvi_tdl.h
)等,均为芯片厂商提供的底层接口。
2. 全局变量与同步机制
static volatile bool bExit
:线程退出标志(volatile
确保多线程下可见性),用于控制所有线程的循环退出。static cvtdl_face_t g_stFaceMeta
:存储人脸检测结果(如人脸坐标、数量等),供两个线程共享。static uint32_t g_size
:记录当前检测到的人脸数量,用于打印数量变化。MUTEXAUTOLOCK_INIT(ResultMutex)
:初始化互斥锁ResultMutex
,用于保护g_stFaceMeta
的读写(避免线程安全问题)。
3. 数据结构
SAMPLE_TDL_VENC_THREAD_ARG_S
:编码器线程的参数结构体,包含中间件上下文(pstMWContext
)和 TDL 服务句柄(stServiceHandle
),用于线程间传递必要的资源句柄。
核心线程功能解析
1. 编码器线程(run_venc
函数)
功能:从 VPSS 通道获取图像,叠加人脸框后通过 RTSP 推流。
流程:
- 循环直到
bExit
为true
:- 调用
CVI_VPSS_GetChnFrame(0, 0, &stFrame, 2000)
从 VPSS 组 0 的通道 0 获取图像帧(超时 2000ms)。 - 加锁(
MutexAutoLock
):从全局变量g_stFaceMeta
复制人脸信息到本地stFaceMeta
,并释放g_stFaceMeta
的资源。 - 调用
CVI_TDL_Service_FaceDrawRect
:在图像帧上绘制人脸矩形框(基于stFaceMeta
的信息)。 - 调用
SAMPLE_TDL_Send_Frame_RTSP
:将绘制后的图像通过 RTSP 协议推流。 - 释放图像帧和人脸元数据资源,若出错则设置
bExit
为true
,退出循环。
- 调用
2. TDL 线程(run_tdl_thread
函数)
功能:从 VPSS 通道获取图像,执行人脸检测,更新全局人脸信息。
流程:
- 循环直到
bExit
为true
:- 调用
CVI_VPSS_GetChnFrame(0, 1, &stFrame, 2000)
从 VPSS 组 0 的通道 1 获取图像帧。 - 调用
CVI_TDL_ScrFDFace(pstTDLHandle, &stFrame, &stFaceMeta)
:使用 RetinaFace 模型检测人脸,结果存入stFaceMeta
。 - 若人脸数量(
stFaceMeta.size
)变化,打印最新数量。 - 加锁:将本地
stFaceMeta
复制到全局g_stFaceMeta
(供编码器线程使用)。 - 释放图像帧和人脸元数据资源,若出错则设置
bExit
为true
,退出循环。
- 调用
信号处理(SampleHandleSig
函数)
- 作用:捕获
SIGINT
(Ctrl+C)和SIGTERM
(终止信号),设置bExit = true
,触发所有线程退出循环,实现程序优雅终止。
主函数(main
函数)
功能:初始化系统资源、创建线程、协调程序生命周期。
关键步骤:
- 参数检查:要求输入 RetinaFace 模型路径(
argv[1]
),否则打印用法并退出。 - 信号注册:绑定
SIGINT
和SIGTERM
到SampleHandleSig
。 - 配置初始化:
- 获取视频输入(VI)配置(从
sensor_cfg.ini
读取传感器信息)。 - 配置 VPSS(视频处理子系统):创建 2 个通道,分别用于推流(通道 0)和人脸检测(通道 1),设置图像尺寸为 1280x720。
- 配置 VBPool(视频缓冲区):3 个缓冲区分别用于 VPSS 通道 0、通道 1 和 TDL 预处理。
- 配置 VENC(视频编码器)和 RTSP 参数。
- 获取视频输入(VI)配置(从
- 中间件初始化:调用
SAMPLE_TDL_Init_WM
初始化视频中间件(整合 VI、VPSS、VENC、RTSP 等组件)。 - TDL 初始化:
- 创建 TDL(深度学习)句柄,绑定 VPSS 资源。
- 加载 RetinaFace 模型(
CVI_TDL_OpenModel
)。
- 线程创建:
- 启动编码器线程(
run_venc
)和 TDL 线程(run_tdl_thread
)。 - 调用
pthread_join
等待线程结束。
- 启动编码器线程(
- 资源释放:销毁 TDL 句柄、服务句柄和中间件资源,程序退出。
关键技术点
- 多线程协作:两个线程分别负责 “人脸检测” 和 “图像绘制 + 推流”,通过互斥锁(
ResultMutex
)同步人脸数据,避免竞态条件。 - 视频处理流水线:VI(输入)→ VPSS(预处理 + 分发)→ TDL(人脸检测)/VENC(编码)→ RTSP(推流),各组件通过句柄和 API 交互。
- 资源管理:严格的资源释放逻辑(如
CVI_VPSS_ReleaseChnFrame
释放图像帧、CVI_TDL_Free
释放人脸元数据),避免内存泄漏。 - 实时性保障:通过超时机制(
CVI_VPSS_GetChnFrame
的 2000ms 超时)和轻量化处理,确保视频流实时性。
小结
该程序是一个典型的嵌入式实时视频分析应用,基于专用视频处理芯片的 API,实现了 “人脸检测 + RTSP 推流” 的端到端功能,可用于安防监控、人脸门禁等场景。其核心设计在于通过多线程和同步机制,高效协调视频采集、AI 推理和网络推流的流程。
编译测试
代码下载
代码网站地址:GitHub - milkv-duo/duo-tdl-examples: Duo TDL-SDK samples for SDK V2。
编译这个例子不需要下载完整的SDK。这个例子中的代码已经足够了。
进入代码目录:
cd duo-tdl-examples
加载编译环境:
source envsetup.sh
第一次加载会自动下载所需的编译工具链,下载后的目录名为host-tools
,下次再加载编译环境时,会检测该目录,如果已存在则不会再次下载。前面已经编译过SDK,所以不需要再下载host-tools。可以直接编辑envsetuo.sh修改host-tools路径。
加载编译环境时需要按提示输入所需编译目标:
Select Product:
1. Duo (CV1800B)
2. Duo256M (SG2002) or DuoS (SG2000)
目标板是 DuoS 选择 2
。由于 Duo256M 和 DuoS 支持 RISCV 和 ARM 两种架构,还需要按提示继续选择:
Select Arch:
1. ARM64
2. RISCV64
Which would you like:
我们是 RISCV 系统则选择 2
。
同一个终端中,只需要加载一次编译环境即可。
人脸检测的示例程序为 sample_vi_fd
,进入到 sample_vi_fd 目录:
cd sample_vi_fd
使用 make
命令编译:
make
将当前目录中生成的 sample_vi_fd
程序通过 scp 或者其他方式上传到 Duo 系列开发板中进行测试。如需清除编译生成的程序,可以执行 make clean
清除。
通过网络直接上传到 Duo S开发板中的程序,可能没有执行权限,需要先在开发板的系统里通过 chmod
命令添加可执行权限:
chmod +x sample_vi_fd
下载 scrfd_det_face_432_768_INT8_cv181x.cvimodel 模型并上传至板端。
在开发板中测试该人脸检测示例的命令为 sample_vi_fd + 人脸检测模型文件
,注意 Duo 和 Duo256M/DuoS 中使用的模型是不同的:
-
Duo (CV180X)
./sample_vi_fd /mnt/cvimodel/scrfd_320_256_ir_0x.cvimodel
-
Duo256M/DuoS (SG200X)
./sample_vi_fd /mnt/cvimodel/scrfd_768_432_int8_1x.cvimodel
此时将摄像头对准人脸,终端日志中会打印当前检测到人脸的数量。
结束语
至此验证了程序的正确性,但是这个程序有个问题,就是它的RTSP地址永远是eth0的IP地址,没法修改,而我们在测试中使用的wlan。要想改变地址,似乎只有自己写rtsp程序部分。