YOLO CPU 前处理优化:5 种 HWC→NCHW 转换方法全网最详对比(速度测试+工程级代码)
在 YOLO 模型的 CPU 部署中,图像格式转换(HWC → NCHW)是前处理阶段最关键的一步。OpenCV 读取图像默认是HWC(高×宽×通道)格式,而模型输入要求NCHW(批次×通道×高×宽)格式,转换效率直接决定前处理速度。
本文纯 CPU 实现、无 GPU 依赖、无复杂语法,提供5 种完全独立的 HWC→NCHW 转换方法,附带完整可运行代码 + 真实速度对比,帮你在工程中直接选用最优方案!
一、背景知识
1. 格式区别
- HWC(OpenCV 默认):像素交织存储 →
BGR BGR BGR ... - NCHW(模型输入):通道连续存储 →
BBBB... GGGG... RRRR...
2. YOLOv5 标准 CPU 前处理流程
- 读取图像(BGR)
- 缩放尺寸到 640×640
- BGR → RGB
- 归一化
/255.0 - HWC → NCHW(本文核心)
二、测试环境
- 系统:Linux x86_64
- OpenCV:4.2.0(兼容老版本)
- 输入尺寸:640×640(YOLOv5 标准输入)
- 测试方式:每种方法运行 100 次取平均耗时
- 编译:C++11
三、5 种 HWC→NCHW 实现方法(完整可运行)
所有方法独立函数、直接调用、无耦合。
方法1:三层循环遍历(教学版、最容易理解)
使用at<Vec3f>逐像素访问,代码直观,但速度最慢。
voidhwc_to_nchw_loop3(constMat&float_rgb,float*nchw){intH=float_rgb.rows;intW=float_rgb.cols;for(intc=0;c<3;c++){for(inth=0;h<H;h++){for(intw=0;w<W;w++){nchw[c*H*W+h*W+w]=float_rgb.at<Vec3f>(h,w)[c];}}}}方法2:单循环指针扁平化(手写循环最快)
将图像视为一维数组,单循环同时写 3 个通道,效率极高。
voidhwc_to_nchw_flat_ptr(constMat&float_rgb,float*nchw){intH=float_rgb.rows;intW=float_rgb.cols;intarea=H*W;constfloat*src=(constfloat*)float_rgb.data;for(inti=0;i<area;i++){nchw[0*area+i]=src[i*3+0];nchw[1*area+i]=src[i*3+1];nchw[2*area+i]=src[i*3+2];}}方法3:按行指针遍历(OpenCV 官方推荐风格)
逐行获取数据指针,缓存友好,工业代码常用风格。
voidhwc_to_nchw_row_ptr(constMat&float_rgb,float*nchw){intH=float_rgb.rows;intW=float_rgb.cols;intarea=H*W;float*c0=nchw+0*area;float*c1=nchw+1*area;float*c2=nchw+2*area;for(inty=0;y<H;y++){constfloat*row=float_rgb.ptr<float>(y);for(intx=0;x<W;x++){c0[y*W+x]=row[x*3+0];c1[y*W+x]=row[x*3+1];c2[y*W+x]=row[x*3+2];}}}方法4:split 直接写入目标内存(速度冠军、工程首选)
利用 OpenCV 内置split,直接将通道拆分到 NCHW 内存,底层优化、极快。
voidhwc_to_nchw_split(constMat&float_rgb,float*nchw){intH=float_rgb.rows;intW=float_rgb.cols;intarea=H*W;Match0(H,W,CV_32F,nchw+0*area);Match1(H,W,CV_32F,nchw+1*area);Match2(H,W,CV_32F,nchw+2*area);vector<Mat>mats;mats.push_back(ch0);mats.push_back(ch1);mats.push_back(ch2);split(float_rgb,mats);}方法5:split + memcpy 拆分拷贝(稳定通用)
先 split 拆分,再用memcpy拷贝,兼容性极强、不易出错。
voidhwc_to_nchw_split_copy(constMat&float_rgb,float*nchw){intH=float_rgb.rows;intW=float_rgb.cols;intarea=H*W;vector<Mat>channels;split(float_rgb,channels);memcpy(nchw+0*area,channels[0].data,area*sizeof(float));memcpy(nchw+1*area,channels[1].data,area*sizeof(float));memcpy(nchw+2*area,channels[2].data,area*sizeof(float));}四、完整主程序(带速度测试)
#include<iostream>#include<vector>#include<opencv2/opencv.hpp>#include<chrono>usingnamespacestd;usingnamespacecv;// 把上面 5 个函数粘贴在这里 ……voidpreprocess(constMat&bgr,Mat&float_rgb,inttarget_w,inttarget_h){Mat resized;resize(bgr,resized,Size(target_w,target_h));Mat rgb;cvtColor(resized,rgb,COLOR_BGR2RGB);rgb.convertTo(float_rgb,CV_32F,1.0f/255.0f);}intmain(){Mat bgr=imread("test.jpg");constintWIDTH=640,HEIGHT=640;vector<float>buffer(3*HEIGHT*WIDTH);Mat float_rgb;cout<<"\n======== 5种HWC->NCHW方法速度对比 ========\n"<<endl;// 方法1preprocess(bgr,float_rgb,WIDTH,HEIGHT);autot1=chrono::high_resolution_clock::now();for(inti=0;i<100;i++)hwc_to_nchw_loop3(float_rgb,buffer.data());autot2=chrono::high_resolution_clock::now();cout<<"方法1 loop3: "<<chrono::duration<float,milli>(t2-t1).count()/100<<" ms\n";// 方法2preprocess(bgr,float_rgb,WIDTH,HEIGHT);autot3=chrono::high_resolution_clock::now();for(inti=0;i<100;i++)hwc_to_nchw_flat_ptr(float_rgb,buffer.data());autot4=chrono::high_resolution_clock::now();cout<<"方法2 flat_ptr: "<<chrono::duration<float,milli>(t4-t3).count()/100<<" ms\n";// 方法3preprocess(bgr,float_rgb,WIDTH,HEIGHT);autot5=chrono::high_resolution_clock::now();for(inti=0;i<100;i++)hwc_to_nchw_row_ptr(float_rgb,buffer.data());autot6=chrono::high_resolution_clock::now();cout<<"方法3 row_ptr: "<<chrono::duration<float,milli>(t6-t5).count()/100<<" ms\n";// 方法4preprocess(bgr,float_rgb,WIDTH,HEIGHT);autot7=chrono::high_resolution_clock::now();for(inti=0;i<100;i++)hwc_to_nchw_split(float_rgb,buffer.data());autot8=chrono::high_resolution_clock::now();cout<<"方法4 split: "<<chrono::duration<float,milli>(t8-t7).count()/100<<" ms\n";// 方法5preprocess(bgr,float_rgb,WIDTH,HEIGHT);autot11=chrono::high_resolution_clock::now();for(inti=0;i<100;i++)hwc_to_nchw_split_copy(float_rgb,buffer.data());autot12=chrono::high_resolution_clock::now();cout<<"方法5 split_copy: "<<chrono::duration<float,milli>(t12-t11).count()/100<<" ms\n";return0;}五、速度对比结果(真实测试)
======== 5种HWC->NCHW方法速度对比 ======== 方法1 loop3: 0.57 ms 方法2 flat_ptr: 0.48 ms 方法3 row_ptr: 0.47 ms 方法4 split: 0.17 ms ✅ 最快 方法5 split_copy: 0.21 ms六、结论与工程建议
速度排名
split(方法4) > split_copy(方法5) > row_ptr(方法3) > flat_ptr(方法2) > loop3(方法1)
最佳方案推荐
- 追求极致速度→方法4 split(首选)
- 追求兼容性/稳定性→方法5 split_copy
- 教学/理解原理→ 方法1 / 方法2
- 工业部署正式使用→方法4 split(速度比手写循环快 2~3 倍)
七、适用场景
- YOLOv5 / YOLOv8 / YOLOv9 前处理
- TensorRT / ONNX 模型 CPU 前处理
- C++ 部署、嵌入式部署、纯 CPU 环境
