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
相关产品推荐
相关产品推荐

