C++中如何正确高效实现ncnn 4D张量的转置操作
ncnn张量从NHWC(1,640,640,3)转置到NCHW(1,3,640,640)实现方案
首先先指出你原有代码的几个错误:
ncnn::Mat是带引用计数的值类型,不需要用new创建,直接栈上分配即可,new返回指针和变量类型不匹配,还会造成内存泄漏。- 循环条件
l > in.c判断符写反,循环永远不会执行。 ncnn::Mat的[]运算符是按通道索引取对应通道的起始地址,不支持线性元素索引,直接用会内存越界。- 你之前的reshape维度顺序和目标布局不匹配,额外增加了不必要的转置开销。
最高效实现(无转置开销)
你需要的NCHW(1,3,640,640)是CV类模型最常用的输入布局,ncnn的from_pixels接口默认输出的就是CHW布局的张量,根本不需要手动转置,直接使用即可,单batch场景下不需要显式构造4维张量,ncnn会自动处理batch维度:
ncnn::Mat preprocess(const cv::Mat& rgba) { int width = rgba.cols; int height = rgba.rows; // 直接生成CHW布局的RGB张量,内存排列为R通道全量->G通道全量->B通道全量 ncnn::Mat in = ncnn::Mat::from_pixels(rgba.data, ncnn::Mat::PIXEL_RGBA2RGB, width, height); // 归一化处理 const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f}; in.substract_mean_normalize(0, norm_vals); // 若模型强制要求4维输入,按NCHW顺序reshape即可,无内存拷贝开销 // 4维ncnn::Mat参数顺序为w(对应W), h(对应H), d(对应C), c(对应N) // in = in.reshape(640, 640, 3, 1); return in; }
手动转置实现(适配必须处理NHWC输入的场景)
如果你拿到的输入已经是NHWC布局的(1,640,640,3)张量,需要手动转置,注意按缓存友好的顺序写循环,保证写入内存连续,提升运行效率:
ncnn::Mat preprocess(const cv::Mat& rgba) { const int N = 1, H = 640, W = 640, C = 3; int width = rgba.cols; int height = rgba.rows; ncnn::Mat in = ncnn::Mat::from_pixels(rgba.data, ncnn::Mat::PIXEL_RGBA2RGB, width, height); // reshape为NHWC布局:w=C(最内层通道), h=W, d=H, c=N(最外层batch) in = in.reshape(C, W, H, N); const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f}; in.substract_mean_normalize(0, norm_vals); // 创建NCHW布局的输出张量:w=W, h=H, d=C, c=N ncnn::Mat transposed(W, H, C, N, sizeof(float)); float* src_ptr = (float*)in.data; float* dst_ptr = (float*)transposed.data; // 按缓存友好顺序循环:外层遍历batch、通道,内层遍历行、列,保证目标内存连续写入 for (int n = 0; n < N; n++) { for (int c = 0; c < C; c++) { for (int h = 0; h < H; h++) { for (int w = 0; w < W; w++) { // NHWC布局源地址偏移:n*H*W*C + h*W*C + w*C + c int src_idx = n * H * W * C + h * W * C + w * C + c; // NCHW布局目标地址偏移:n*C*H*W + c*H*W + h*W + w int dst_idx = n * C * H * W + c * H * W + h * W + w; dst_ptr[dst_idx] = src_ptr[src_idx]; } } } } return transposed; }
转置实现注意点
- 不要用四重循环乱序遍历,上述循环顺序的缓存命中率最高,在640*640分辨率下比乱序循环快3~5倍。
- 必须先拿到Mat的data指针强转为对应类型(这里是float*)再做偏移访问,不要直接用
[]运算符访问元素。 - 归一化操作尽量在转置前做,减少转置时的内存访问开销。
内容的提问来源于stack exchange,提问作者Lizozom
相关产品推荐
相关产品推荐

