You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android NDK中如何用cv::Mat包装AImage中的YUV数据?

如何用cv::Mat包装Android NDK的AImage YUV数据

核心结论

不需要强制将数据memcpy到cv::Mat的单个平面中,只要正确处理AImage各平面的内存指针、尺寸和步长(stride),就能直接用cv::Mat包装原始YUV数据。关键在于AImage的各平面内存不一定连续,必须分别处理每个平面的参数。


1. 先明确AImage的平面特性

不管是摄像头的NV21半平面,还是解码器的YUV420平面格式,AImage的每个平面都有独立的内存指针和步长(stride)——步长是指每行数据的字节数,可能大于图像实际宽度(硬件对齐要求),不能直接等同于图像宽度。


2. 分格式处理方案

情况1:YUV420平面格式(3个独立平面)

Y平面尺寸为宽×高,U、V平面尺寸为宽/2 × 高/2,三个平面内存完全独立。

代码示例:直接包装原始平面

// 获取AImage基础信息
AImage* aimage = ...; // 你的AImage实例
int32_t img_width = AImage_getWidth(aimage);
int32_t img_height = AImage_getHeight(aimage);

// 提取Y平面数据
uint8_t* y_data = AImage_getPlaneData(aimage, 0);
int32_t y_stride = AImage_getPlaneStride(aimage, 0);
cv::Mat y_mat(img_height, img_width, CV_8UC1, y_data, y_stride);

// 提取U平面数据
uint8_t* u_data = AImage_getPlaneData(aimage, 1);
int32_t u_stride = AImage_getPlaneStride(aimage, 1);
cv::Mat u_mat(img_height/2, img_width/2, CV_8UC1, u_data, u_stride);

// 提取V平面数据
uint8_t* v_data = AImage_getPlaneData(aimage, 2);
int32_t v_stride = AImage_getPlaneStride(aimage, 2);
cv::Mat v_mat(img_height/2, img_width/2, CV_8UC1, v_data, v_stride);
  • 这里的cv::Mat只是包装了原始内存指针,没有拷贝数据,效率极高。
  • 注意:AImage的生命周期必须长于这三个cv::Mat,否则会出现悬空指针问题。

情况2:NV21半平面格式(2个平面)

Y平面尺寸为宽×高,第二个平面是VU交替的半平面,尺寸为宽×高/2。

代码示例:分别包装Y和VU平面

AImage* aimage = ...;
int32_t img_width = AImage_getWidth(aimage);
int32_t img_height = AImage_getHeight(aimage);

// 包装Y平面
uint8_t* y_data = AImage_getPlaneData(aimage, 0);
int32_t y_stride = AImage_getPlaneStride(aimage, 0);
cv::Mat y_mat(img_height, img_width, CV_8UC1, y_data, y_stride);

// 包装VU半平面
uint8_t* vu_data = AImage_getPlaneData(aimage, 1);
int32_t vu_stride = AImage_getPlaneStride(aimage, 1);
cv::Mat vu_mat(img_height/2, img_width, CV_8UC1, vu_data, vu_stride);

如果需要转成BGR格式(需要连续内存)

如果要使用OpenCV的cv::cvtColor将NV21转成BGR,由于该函数要求输入为连续的单通道Mat(尺寸高×3/2 × 宽),此时需要将Y和VU平面拷贝到连续内存:

// 创建连续的NV21 Mat
cv::Mat nv21_continuous(img_height + img_height/2, img_width, CV_8UC1);
// 拷贝Y平面
memcpy(nv21_continuous.data, y_data, img_width * img_height);
// 拷贝VU平面
memcpy(nv21_continuous.data + img_width * img_height, vu_data, img_width * img_height/2);

// 转BGR
cv::Mat bgr_mat;
cv::cvtColor(nv21_continuous, bgr_mat, cv::COLOR_NV212BGR);

3. 关键注意事项

  • 不要假设内存连续:AImage的平面内存由硬件缓冲区分配,各平面之间可能是离散的,绝对不能直接用Y平面的指针+Y平面总字节数来获取UV/VU平面的起始地址。
  • 必须使用stride参数:硬件对齐会导致stride大于图像宽度,忽略stride会导致图像数据错位。
  • 生命周期管理:AImage被释放后,对应的cv::Mat会变成无效指针,必须保证cv::Mat的使用范围在AImage的生命周期内。

内容的提问来源于stack exchange,提问作者Kartik Aiyer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 07:50:13