视频与图像处理
AC79 AIoT SDK 中的视频与图像处理能力,涵盖摄像头(DVP/SPI)图像采集、YUV 软缩放、JPEG 编解码、人脸检测、图像拼接、像素流(光流)分析、二维码编码、AVI/GIF 播放以及基于差异帧的视频回放(diff_image_to_video)等完整链路,并包含配套的示例工程与 UI 集成入口。
Purpose and Scope
本页面系统介绍 SDK 中与视频与图像处理相关的完整能力:从摄像头硬件采集(DVP/SPI)获得 YUV 数据,经过软件缩放、JPEG 编解码、图像拼接、光流计算、二维码生成等算法处理后,最终输出到 LCD 显示、文件存储或 UI 界面(AWTK 控件)的端到端机制。
页面覆盖以下内容:
- 摄像头采集链路(
apps/common/example/video/camera/)与 YUV 回调模型 - 图像算法与编解码(JPEG、图像拼接、像素流、二维码编码)
- 视频文件格式处理(AVI、GIF、diff_image_to_video 差异帧视频)
- 平台集成入口(
apps/common/ui/ui_action_video.c、apps/common/usb/host/usb_video.c、AWTK video-image 控件) - DevKitBoard 演示工程中的视频录制(
user_video_rec.c、video_rt_usr.c)
以下主题属于其他目录页的范畴,本页仅做交叉引用、不展开:音频处理(见"音频"相关目录页)、LCD 驱动底层时序(见显示相关目录页)、USB 协议栈本身(见 USB 相关目录页)。
Overview
在 AC79 这样的 AIoT SoC 平台上,视频与图像处理通常是一条"采集 → 处理 → 显示/存储"的流水线。SDK 采用回调驱动 + 线程隔离的设计:摄像头驱动在采集到一帧 YUV 数据后通过回调函数上抛,上层线程负责缩放、算法处理与显示,从而避免处理耗时阻塞采集,保证帧率均匀。
以 DVP 摄像头示例为例,数据流被刻意设计为"YUV 回调出数据后转为对应屏幕大小 YUV 交给下一个线程处理",使显示占用的资源与 YUV 采集接近同步,这是保证预览不撕裂、不卡顿的关键设计决策(见 main.c 的注释)。
图像处理方面,SDK 提供:
- JPEG 编解码:
jpeg_decoder_test(JPG → YUV → RGB)、jpeg_encode_test(YUV → JPG)、jpeg_encode_large_test(编码时自动缩放到目标分辨率),示例见 jpeg/main.c。 - 可选算法扩展:人脸检测(
child_face_detect.h)、图像拼接(image_stitch)、光流/像素流(pixel_flow/jFlow.c、sad8x8.c)、二维码编码(qr_enc、enc_qr_code)。 - 视频文件格式:AVI 播放、GIF 解码、
diff_image_to_video差异帧视频回放(AWTK 控件)。
Architecture
下图展示了视频与图像处理子系统的整体架构与数据流:
flowchart TD
subgraph sg_Source["采集层 (Source)"]
DVP["DVP Camera<br/>(camera/dvp)"]
SPI["SPI Camera<br/>(camera/spi)"]
USB["USB Video Host<br/>(usb_video.c)"]
end
subgraph sg_Process["处理层 (Process)"]
YUV_CB["get_yuv_init 回调<br/>get_yuv(yuv_buf, len, w, h)"]
SCALE["yuv_soft_scalling<br/>YUV420p_Soft_Scaling"]
JPEG["JPEG 编解码<br/>jpeg_encode/decode_test"]
STITCH["图像拼接<br/>image_stitch"]
FLOW["像素流/光流<br/>jFlow / sad8x8"]
QR["二维码编码<br/>qr_enc / enc_qr_code"]
FACE["人脸检测<br/>face_detect_process"]
end
subgraph sg_Output["输出层 (Output)"]
LCD["LCD 显示<br/>lcd_show_frame"]
AVI["AVI 播放"]
GIF["GIF 解码"]
DIFF["diff_image_to_video<br/>差异帧视频回放"]
UI["UI 视频动作<br/>ui_action_video"]
end
DVP --> YUV_CB
SPI --> YUV_CB
USB --> YUV_CB
YUV_CB --> SCALE
SCALE --> LCD
YUV_CB --> FACE
YUV_CB --> JPEG
JPEG --> AVI
JPEG --> GIF
JPEG --> DIFF
STITCH --> LCD
FLOW --> LCD
QR --> LCD
DIFF --> UI
架构说明:
- 采集层:DVP 并口摄像头与 SPI 摄像头由
get_yuv_init()注册 YUV 数据回调统一上抛帧数据;USB 视频走usb_video.c的 host 路径,同样汇入 YUV 处理链。 - 处理层:所有算法模块都消费 YUV 帧。
YUV420p_Soft_Scaling负责将采集分辨率软缩放到 LCD 分辨率,这是保证显示与采集同步的核心步骤;JPEG 编解码面向文件存储;图像拼接、光流、二维码、人脸检测属于可选的增值算法。 - 输出层:缩放后的 YUV 通过
lcd_show_frame送 LCD;JPEG 数据可封装为 AVI/GIF 或通过diff_image_to_video以差异帧方式回放,最终可接入 AWTK UI 或ui_action_video动作表。
上图中的组件名称与文件路径均来自仓库实际代码与目录结构;其中标注"示例"的模块(如
image_stitch、pixel_flow)在仓库中以独立示例目录存在,详细算法实现请以对应源码为准。
核心实现机制
摄像头采集与 YUV 回调模型
DVP 摄像头示例的核心是一条回调注册 + 线程隔离的流水线(main.c):
user_ui_lcd_init():随 UI/LCD 一起初始化底层显示;set_lcd_show_data_mode(CAMERA):将 LCD 数据模式从默认的 UI 模式切换到相机预览模式;get_yuv_init(get_yuv):向采集驱动注册 YUV 回调,驱动每采集一帧就调用一次;thread_fork("camera_to_lcd_fps_task", 11, 512, 32, 0, camera_to_lcd_fps_task, NULL):以优先级 11、栈 512 的独立线程运行预览任务,并通过late_initcall在系统初始化后期启动。
回调函数 get_yuv 内只做两件事:调用 lcd_show_frame 把 YUV 缩放后送显,以及(可选)喂给 face_detect_process 做人脸检测。这种"回调只做轻量投递、重处理放独立线程"的划分,避免了 YUV 数据在回调与显示线程之间反复拷贝,是帧率均匀性的关键。
JPEG 编解码
JPEG 示例(jpeg/main.c)展示了三个面向文件的测试接口:
jpeg_decoder_test(path):读取 JPG 文件解码为 YUV,再由 YUV 转 RGB;jpeg_encode_test(path, width, height):读取原始 YUV 文件编码为 JPG;jpeg_encode_large_test(path, width, height, out_width, out_height):编码 JPG 的同时自动完成缩放,可输出更大或更小的分辨率。
示例在 os_task_create(jpeg_yuv_test, NULL, 12, 1000, 0, "jpeg_yuv_test") 创建的任务中执行,并在 storage_device_ready() 就绪(SD 文件系统挂载完成)后才开始读写文件,避免在存储未就绪时产生 IO 错误。
Core Flow
相机预览 → LCD 显示链路
sequenceDiagram
participant CAM as Camera Driver<br/>(DVP/SPI)
participant CB as get_yuv 回调
participant LCD as LCD 驱动<br/>(lcd_show_frame)
participant TASK as camera_to_lcd_fps_task<br/>(线程, prio 11)
participant FACE as face_detect_process<br/>(可选)
TASK->>TASK: user_ui_lcd_init()
TASK->>LCD: set_lcd_show_data_mode(CAMERA)
TASK->>CAM: get_yuv_init(get_yuv)
loop 每采集一帧
CAM-->>CB: yuv_buf, len, in_w, in_h
CB->>LCD: lcd_show_frame(yuv_buf, len, w, h)<br/>内部完成 YUV 缩放
CB->>FACE: face_detect_process(&fp, &e, ...)<br/>(仅 FACE_DETECT_DEMO 开启时)
FACE-->>CB: num_box / bounding_box
CB-->>CAM: 返回,等待下一帧
end
JPEG 编解码链路
sequenceDiagram
participant TASK as jpeg_yuv_test 任务
participant FS as SD 文件系统
participant JPEG as JPEG 引擎
TASK->>FS: storage_device_ready() 轮询等待挂载
FS-->>TASK: ready
TASK->>JPEG: jpeg_encode_test(test.yuv, 320, 240)
JPEG->>FS: 读取 YUV 原始帧
JPEG->>JPEG: YUV → JPEG 编码
JPEG->>FS: 写出 test.jpg
TASK->>JPEG: jpeg_encode_large_test(test.yuv, 320, 240, 1280, 720)
JPEG->>JPEG: 编码 + 自动缩放
JPEG->>FS: 写出大分辨率 JPG
Note over TASK: 完成后循环 os_time_dly(10) 保持任务存活
流程设计意图
- 显示链路先初始化 LCD、切换相机数据模式,再注册 YUV 回调——顺序保证回调一触发就有可用的显示目标,避免空指针与黑屏竞态。
- 回调中只投递、不阻塞:
lcd_show_frame内部负责缩放与送显,人脸检测仅在选择FACE_DETECT_DEMO时编译进去,保证默认预览路径最简。 - JPEG 链路先等待存储就绪再执行文件 IO,是嵌入式文件系统场景下避免"找不到文件/写失败"的防御性编程。
Usage Examples
示例 1:DVP 摄像头预览到 LCD(含可选人脸检测)
以下代码取自 DVP 摄像头示例,展示了完整的 YUV 回调注册与相机预览初始化流程:
static void get_yuv(u8 *yuv_buf, u32 len, int yuv_in_w, int yuv_in_h)//YUV数据回调线程
{
/*******将YUV输出数据转成屏幕大小的YUV*********************/
lcd_show_frame(yuv_buf, len, yuv_in_w, yuv_in_h);
#ifdef FACE_DETECT_DEMO
frame fp = {0};
time1 = timer_get_ms();
printf("during:%d ms\n", time1 - time2);
time2 = time1;
fp.w = LCD_W;
fp.h = LCD_H;
fp.c = 1;
fp.pixel = yuv_buf;
int num_box = face_detect_process(&fp, &e, e.confidence_, e.bounding_box, e.alignment_temp);
printf("num_box=%d \n", num_box);
if (num_box) {
printf("x1:%d x2:%d y1:%d y2:%d\n", e.bounding_box[0].x1, e.bounding_box[0].x2, e.bounding_box[0].y1, e.bounding_box[0].y2);
}
#endif
}
static void camera_to_lcd_fps_task(void)
{
static struct lcd_device *lcd_dev;
/******ui_lcd_一起初始化数据***********/
user_ui_lcd_init();
#ifdef FACE_DETECT_DEMO
int fast_m = 1;
int num_r = 6;
int num_o = 3;
int thresh[3] = {0, -30, -20};
int smile_thresh = 0;
face_detect_init(fast_m, num_r, num_o, thresh, smile_thresh, &e);
#endif
/*****默认为UI模式*****/
set_lcd_show_data_mode(CAMERA);
/******YUV数据回调初始化**********/
get_yuv_init(get_yuv);
}
要点:get_yuv 回调直接复用采集缓冲(yuv_buf)送显,不做额外拷贝;face_detect_process 以同一 yuv_buf 作为输入帧,说明人脸检测与显示共用一份 YUV 数据,节省内存。
示例 2:JPEG 编码与缩放编码
static void jpeg_yuv_test(void)
{
int jpeg_decoder_test(const char *path);
int jpeg_encode_test(const char *path, int width, int height);
int jpeg_encode_large_test(const char *path, int width, int height, int out_width, int out_height);
int storage_device_ready(void);
while (!storage_device_ready()) {//等待sd文件系统挂载完成
os_time_dly(2);
}
//jpeg_decoder_test(CONFIG_ROOT_PATH"test.jpg");//JPG解码成YUV,YUV再转RGB
jpeg_encode_test(CONFIG_ROOT_PATH"test.yuv", 320, 240);//YUV编码成JPG
jpeg_encode_large_test(CONFIG_ROOT_PATH"test.yuv", 320, 240, 1280, 720);//YUV编码成JPG,大分辨率或者小分辨率(自动完成缩放)
while (1) {
os_time_dly(10);
}
}
static int c_main(void)
{
os_task_create(jpeg_yuv_test, NULL, 12, 1000, 0, "jpeg_yuv_test");
return 0;
}
late_initcall(c_main);
要点:JPEG 接口以文件路径为参数(配合 CONFIG_ROOT_PATH 定位存储根目录);任务结束后用 os_time_dly(10) 挂起而非退出,避免 late_initcall 创建的任务提前销毁导致文件写入被中断。
相关模块速览
除上述两个核心示例外,仓库中视频与图像处理目录还包含以下模块(路径与职责来自目录结构):
| 模块 | 路径 | 职责 |
|---|---|---|
| SPI 摄像头 | apps/common/example/video/camera/spi/main.c | SPI 接口摄像头采集示例,与 DVP 示例共用 YUV 回调模型 |
| 大图 JPEG 测试 | apps/common/example/video/image/jpeg/big_jpg_test.c | 大分辨率 JPEG 编解码压力测试 |
| JPEG 取帧 | apps/common/example/video/image/jpeg/get_jpg_test2.c | 从 JPEG 流中提取帧数据的测试 |
| JPEG 基础测试 | apps/common/example/video/image/jpeg/jpeg_test.c | JPEG 编解码基础用例 |
| GIF | apps/common/example/video/image/gif/main.c | GIF 动图解码示例 |
| 图像拼接 | apps/common/example/video/image/image_stitch/main.c | 多幅图像拼接示例 |
| 像素流/光流 | apps/common/example/video/image/pixel_flow/jFlow.c、jFlow_float.c、sad8x8.c | 帧间像素运动估计(含 8x8 SAD 块匹配) |
| 二维码编码 | apps/common/example/video/image/qr_enc/main.c、enc_qr_code/main.c | 二维码生成示例 |
| UI 视频动作 | apps/common/ui/ui_action_video.c、include/ui_action_video.h | UI 动作表中的视频相关动作入口 |
| USB 视频 | apps/common/usb/host/usb_video.c | USB Host 侧视频设备接入(UVC 类) |
| DevKitBoard 录制 | apps/demo/demo_DevKitBoard/user_video_rec.c、video_rt_usr.c | 板级视频录制与实时预览用户接口 |
| 视频缓冲配置 | apps/demo/demo_DevKitBoard/include/video_buf_config.h | 视频帧缓冲区大小与数量配置 |
| SPI 视频头 | apps/demo/demo_DevKitBoard/include/spi_video.h | SPI 视频接口声明 |
| AWTK 视频图像控件 | apps/common/example/third_party/awtk/awtk-widget/awtk-widget-video-image/ | AWTK 的 video_image 控件与 diff_image_to_video(差异帧转视频)加载器 |
说明:以上模块的详细算法内部实现未在本页逐一展开(受源码读取预算限制),上表职责基于文件路径、命名与示例目录上下文归纳,具体行为以对应源码为准。
diff_image_to_video 差异帧视频
awtk-widget-video-image 中的 diff_image_to_video 子目录(diff_image_to_video_loader.c/.h、diff_image_to_video_read_image_data.c/.h、diff_image_to_video_types.h)实现了一种差异帧视频回放机制:将连续的关键帧/差异帧图像数据按序加载并还原为视频画面,用于在资源受限的嵌入式 UI 上以低带宽播放动画视频。该机制与 video_image 控件配合,通过 video_image_register.c 注册到 AWTK 控件体系。
Configuration Options
视频与图像处理示例的开关与参数主要通过编译宏与任务参数控制:
| 配置项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
USE_CAMERA_DVP_SHOW_TO_LCD_DEMO | 编译宏 | 未定义 | 使能 DVP 摄像头显示到 LCD 的示例主体 |
FACE_DETECT_DEMO | 编译宏 | 未定义 | 使能人脸检测演示(引入 child_face_detect.h) |
isc_log_en() | 函数 | 返回 0 | 返回非 0 时屏蔽丢帧信息打印 |
thread_fork 优先级 | 任务参数 | 11 | camera_to_lcd_fps_task 线程优先级 |
thread_fork 栈大小 | 任务参数 | 512 | 相机预览线程栈大小(字) |
os_task_create 优先级 | 任务参数 | 12 | jpeg_yuv_test 任务优先级 |
os_task_create 栈大小 | 任务参数 | 1000 | JPEG 测试任务栈大小(字) |
CONFIG_ROOT_PATH | 宏 | 系统定义 | 存储设备根路径前缀,用于拼接测试文件路径 |
LCD_W / LCD_H | 宏 | 屏参定义 | 人脸检测帧宽高,取自 LCD 分辨率 |
video_buf_config.h | 头文件 | — | DevKitBoard 工程视频帧缓冲区配置 |
API Reference
以下 API 均来自示例源码中的实际调用(签名以源码声明为准):
get_yuv_init(void (*cb)(u8 *yuv_buf, u32 len, int w, int h))
注册 YUV 数据回调。摄像头驱动每采集完一帧即调用回调,回调线程上下文中应尽量轻量。
回调参数:
yuv_buf(u8*):YUV 帧数据缓冲(采集驱动持有)len(u32):帧数据长度w/h(int):采集分辨率宽高
调用示例见 camera/dvp/main.c。
set_lcd_show_data_mode(mode)
切换 LCD 显示数据模式,CAMERA 表示进入相机预览模式(区别于默认 UI 模式)。须在 get_yuv_init 之前调用,保证回调触发时显示通道已就绪。
lcd_show_frame(u8 *yuv_buf, u32 len, int w, int h)
将 YUV 帧送 LCD 显示,内部完成分辨率适配(配合 yuv_soft_scalling 的 YUV420p_Soft_Scaling)。这是预览链路的核心出口。
jpeg_encode_test(const char *path, int width, int height)
读取指定路径的原始 YUV 文件,编码为 JPG 后写出。
参数:
path:YUV 文件路径(配合CONFIG_ROOT_PATH)width/height:YUV 帧分辨率
返回: 0 表示成功,非 0 表示失败(具体错误码见实现源码)。
jpeg_encode_large_test(const char *path, int width, int height, int out_width, int out_height)
同 jpeg_encode_test,但编码时自动缩放输出到 out_width x out_height,支持大分辨率或小分辨率输出。
jpeg_decoder_test(const char *path)
读取 JPG 文件,解码为 YUV,再由 YUV 转 RGB。示例中默认注释掉,需要时取消注释启用。
face_detect_init(int fast_m, int num_r, int num_o, int thresh[], int smile_thresh, face_detect_f *e)
初始化人脸检测器。示例参数:fast_m=1(快速模式)、num_r=6(回归器数)、num_o=3(输出数)、thresh={0,-30,-20}(各阶段阈值)、smile_thresh=0。
face_detect_process(frame *fp, face_detect_f *e, ...)
对单帧 YUV 执行人脸检测,返回检测到的人脸框数量,e.bounding_box 输出各人脸坐标(x1/y1/x2/y2)。
thread_fork(name, prio, stack_size, qsize, 0, entry, arg) 与 os_task_create(entry, arg, prio, stack_size, 0, name)
系统线程/任务创建接口,分别用于预览任务与 JPEG 测试任务;late_initcall 保证在系统初始化后期自动拉起示例。
Failure Modes, Edge Cases & Concurrency
存储未就绪
JPEG 示例通过 while (!storage_device_ready()) os_time_dly(2); 轮询等待 SD 文件系统挂载,这是嵌入式场景常见的启动竞态。若忽略此等待,jpeg_encode_test 在路径不存在时可能因文件打开失败返回错误或导致任务异常。
回调与线程的并发模型
get_yuv 回调运行在采集驱动的上下文中,而 camera_to_lcd_fps_task 是独立线程(优先级 11)。设计上回调内不阻塞、不长时间持有锁,仅将帧投递给 lcd_show_frame 与(可选的)人脸检测。这意味着:
- 若算法处理耗时超过一帧间隔,会产生丢帧而非缓冲堆积——示例中
isc_log_en()的存在表明丢帧信息是受控可打印的; - 人脸检测直接复用
yuv_buf(采集缓冲),调用期间驱动不会复用该缓冲,否则会造成数据竞争;实际行为以驱动实现为准。
帧率与资源占用
预览链路的核心矛盾是"显示资源占用"与"采集速率"的平衡。示例注释明确指出:YUV 回调后先转成屏幕大小 YUV 再交给下一线程,使显示与 YUV 资源占用接近,才能同步均匀。若直接在回调中做高分辨率算法处理,会导致采集被拖慢、预览卡顿。
任务生命周期
- 预览任务用
thread_fork创建并以late_initcall启动;JPEG 测试任务用os_task_create创建,执行完文件操作后以os_time_dly(10)循环挂起保持任务存活,避免任务退出后被回收造成异常。 jpeg_yuv_test内先声明函数原型再调用,说明这些测试接口位于其他编译单元(JPEG 引擎库),调用方需自行保证链接。
分辨率边界
jpeg_encode_large_test支持任意输出分辨率(大于或小于输入),缩放是编码流程的内置步骤;超大输出(如 1280x720)需要足够的内存缓冲,示例以 1000 字栈任务运行,说明编解码主体在库内部管理缓冲。- 人脸检测帧宽高直接使用
LCD_W/LCD_H,若相机采集分辨率与 LCD 分辨率不匹配,需先经YUV420p_Soft_Scaling缩放,否则检测框坐标与实际画面会错位。
Performance & Operational Considerations
- 零拷贝投递:
lcd_show_frame(yuv_buf, ...)直接使用回调传入的采集缓冲,避免 YUV 数据在驱动、回调、显示之间反复拷贝,是低端 SoC 上保持帧率的关键。 - 线程优先级设计:预览线程优先级 11 高于 JPEG 任务(12),保证实时预览不被后台编码任务抢占过多;人脸检测可选编译,默认路径不引入额外计算开销。
- 丢帧可观测性:
isc_log_en()开关控制丢帧信息的打印,量产/调试时可按需开启,避免串口日志拖慢采集。 - 存储 IO 时序:JPEG/GIF/AVI 等文件型处理都必须等
storage_device_ready()后才可访问CONFIG_ROOT_PATH下的文件,运维上建议在应用启动脚本或业务层统一等待挂载事件。 - 视频缓冲配置:DevKitBoard 工程通过
video_buf_config.h配置帧缓冲数量与大小,多缓冲可以缓解采集与处理速度不匹配带来的丢帧,代价是内存占用上升,需要按分辨率实测调整。
Extension Points
- 新增图像算法模块:参照
image_stitch、pixel_flow等示例目录的组织方式,以"消费 YUV 帧"为接口约定接入处理层;若要上屏,最终通过lcd_show_frame输出。 - 新增采集源:DVP/SPI/USB 三类采集源最终都汇入
get_yuv_init回调模型,新的传感器驱动只要按同一回调契约上抛 YUV 帧,即可复用整条预览/显示链路。 - 人脸检测参数调优:
face_detect_init(fast_m, num_r, num_o, thresh, smile_thresh, &e)的阈值数组按阶段配置,可通过调整thresh在误检率与召回率之间权衡,fast_m切换快速模式以换取帧率。 - AWTK UI 集成:
video_image_register.c将video_image控件与diff_image_to_video加载器注册进 AWTK,可在 GUI 应用中直接使用差异帧视频资源,是嵌入式 UI 低带宽播放的扩展入口。 - UI 动作接入:
ui_action_video.c提供 UI 动作表中的视频相关动作,可在现有 UI 框架中增加新的视频控制动作。
Related Links
- DVP 摄像头示例源码
- JPEG 编解码示例源码
- UI 视频动作源码
- USB 视频 Host 源码
- DevKitBoard 视频录制源码
- AWTK 视频图像控件目录
- 相关目录页:音频处理(本目录 7.x 音频章节)、LCD 显示驱动(显示相关目录页)、USB 协议栈(USB 相关目录页)