集成Arm Compute库:arm_compute::Image与cv::Mat互转及零拷贝咨询
我之前在项目中把OpenCV和Arm Compute Library(ACL)结合使用时,刚好解决过这两种图像格式转换的问题,分享下我的实践经验:
拷贝数据的转换方式
这种方式兼容性最好,不管内存布局是否一致都能工作,缺点是会产生数据拷贝的开销。
cv::Mat → arm_compute::Image
首先要注意通道顺序:OpenCV默认加载的图像是BGR格式,而ACL的常见彩色格式是RGB(比如FORMAT_RGB888),所以第一步需要转换通道顺序。然后根据cv::Mat的尺寸和数据类型初始化ACL的Image,最后完成数据拷贝:
#include <arm_compute/runtime/Image.h> #include <opencv2/opencv.hpp> arm_compute::Image convert_mat_to_acl_image(const cv::Mat& mat) { cv::Mat rgb_mat; // 转换OpenCV默认的BGR为ACL常用的RGB cv::cvtColor(mat, rgb_mat, cv::COLOR_BGR2RGB); // 初始化ACL Image的TensorInfo arm_compute::TensorShape shape(rgb_mat.cols, rgb_mat.rows); arm_compute::TensorInfo info(shape, 1, arm_compute::Format::RGB888); arm_compute::Image acl_img; acl_img.allocator()->init(info); acl_img.allocator()->allocate(); // 拷贝数据:如果Mat是连续的,直接memcpy;否则逐行拷贝 if (rgb_mat.isContinuous()) { std::memcpy(acl_img.map(), rgb_mat.data, rgb_mat.total() * rgb_mat.elemSize()); } else { const size_t row_stride_acl = acl_img.info()->strides_in_bytes()[1]; for (int y = 0; y < rgb_mat.rows; ++y) { std::memcpy(acl_img.map() + y * row_stride_acl, rgb_mat.ptr(y), rgb_mat.cols * rgb_mat.elemSize()); } } acl_img.unmap(); return acl_img; }
arm_compute::Image → cv::Mat
反向转换时,同样需要处理通道顺序,把ACL的RGB转回OpenCV的BGR:
cv::Mat convert_acl_image_to_mat(const arm_compute::Image& acl_img) { const auto info = acl_img.info(); // 创建对应尺寸和类型的cv::Mat cv::Mat rgb_mat(info->dimension(1), info->dimension(0), CV_8UC3); // 拷贝数据 if (rgb_mat.isContinuous()) { std::memcpy(rgb_mat.data, acl_img.map(), rgb_mat.total() * rgb_mat.elemSize()); } else { const size_t row_stride_acl = info->strides_in_bytes()[1]; for (int y = 0; y < rgb_mat.rows; ++y) { std::memcpy(rgb_mat.ptr(y), acl_img.map() + y * row_stride_acl, rgb_mat.cols * rgb_mat.elemSize()); } } acl_img.unmap(); // 转换回OpenCV默认的BGR格式 cv::Mat bgr_mat; cv::cvtColor(rgb_mat, bgr_mat, cv::COLOR_RGB2BGR); return bgr_mat; }
零拷贝共享内存的实现方式
如果想避免数据拷贝,需要确保两者的内存布局完全兼容,这需要满足几个条件:内存对齐一致、步长匹配、通道顺序兼容。
核心前提
- 内存对齐:ACL的Image要求内存是16字节对齐的,OpenCV默认创建的cv::Mat(用
cv::Mat::create)通常满足这个要求,但如果是手动分配的内存需要额外注意。 - 通道顺序:如果ACL支持
FORMAT_BGR888(较新版本的ACL已支持),可以直接匹配OpenCV的BGR格式,无需转换通道;否则需要先将cv::Mat转为RGB。 - 步长匹配:cv::Mat的行步长(
step[0])需要和ACL Image的行步长一致。
cv::Mat → arm_compute::Image(零拷贝)
arm_compute::Image create_acl_image_from_mat_shared(cv::Mat& mat) { // 确保Mat是连续的(非连续的话比如ROI无法共享) if (!mat.isContinuous()) { mat = mat.clone(); } // 如果ACL支持BGR格式,跳过通道转换;否则转为RGB arm_compute::Format format = arm_compute::Format::BGR888; cv::Mat target_mat = mat; if (!arm_compute::data_type_from_format(format).is_valid()) { cv::cvtColor(mat, target_mat, cv::COLOR_BGR2RGB); format = arm_compute::Format::RGB888; } // 创建TensorInfo,指定自定义步长(匹配cv::Mat的行步长) arm_compute::TensorShape shape(target_mat.cols, target_mat.rows); arm_compute::TensorInfo info(shape, 1, format); info.set_strides_in_bytes({target_mat.elemSize(), target_mat.step[0]}); arm_compute::Image acl_img(info); // 导入cv::Mat的底层内存 bool import_success = acl_img.allocator()->import_memory(target_mat.data); if (!import_success) { // 导入失败(比如内存对齐不满足), fallback到拷贝方式 return convert_mat_to_acl_image(mat); } return acl_img; }
arm_compute::Image → cv::Mat(零拷贝)
cv::Mat create_mat_from_acl_image_shared(arm_compute::Image& acl_img) { const auto info = acl_img.info(); const arm_compute::Format format = info->format(); int cv_type = CV_8UC3; // 获取ACL Image的底层指针(注意:map后不能unmap,直到cv::Mat不再使用) uint8_t* acl_data = acl_img.map(); size_t row_stride = info->strides_in_bytes()[1]; cv::Mat mat(info->dimension(1), info->dimension(0), cv_type, acl_data, row_stride); // 处理通道顺序:如果ACL是RGB格式,转为BGR if (format == arm_compute::Format::RGB888) { cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR); } return mat; }
关键注意事项
- 生命周期管理:零拷贝时,原始数据的生命周期必须长于新创建的对象。比如用cv::Mat创建的ACL Image,必须保证cv::Mat在ACL Image使用期间不被释放,反之亦然。
- 非连续内存:如果cv::Mat是非连续的(比如截取ROI得到的Mat),零拷贝无法实现,只能使用拷贝方式。
- 数据类型匹配:除了8位彩色,还要注意其他数据类型的匹配,比如CV_32FC1对应ACL的
FORMAT_F32,CV_16UC1对应FORMAT_U16等,需要根据具体场景调整。
内容的提问来源于stack exchange,提问作者Flawed_Logicc
相关产品推荐
相关产品推荐

