C#部署YOLOv4时如何为EmguCV.Mat/Onnx张量扩展维度
C# OnnxRuntime部署YOLOv4预处理维度适配方案
问题描述
- 部署栈:C# + OnnxRuntime,用EmguCV做图像加载和预处理,适配YOLOv4的416x416输入要求
- 第一版预处理通过双层循环逐像素做归一化、通道转换,功能正常但运行速度慢
- 第二版预处理调用EmguCV内置API做缩放、归一化、通道转换,移除了逐像素循环,速度提升明显,但输出张量形状为
(416,416,3),缺少长度为1的batch维度,不符合YOLOv4要求的输入形状(1,416,416,3),会触发维度不匹配报错。
初版功能正常但性能差的代码
static List<NamedOnnxValue> preprocess_CV(Mat im) { CvInvoke.Resize(im, im, new Size(416, 416)); var imData = im.ToImage<Bgr, Byte>().Data; Tensor<float> input = new DenseTensor<float>(new[] {1, im.Height, im.Width, 3}); for (int x = 0; x < im.Width; x++) for (int y = 0; y < im.Height; y++) { input[0, x, y, 0] = imData[x, y, 2] / (float)255.0; input[0, x, y, 1] = imData[x, y, 1] / (float)255.0; input[0, x, y, 2] = imData[x, y, 0] / (float)255.0; } List<NamedOnnxValue> inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input_1:0", input) }; return inputs; }
优化后存在维度问题的v2版代码
static List<NamedOnnxValue> preprocess_CV_v2(Mat im) { CvInvoke.Resize(im, im, new Size(416, 416)); im.ConvertTo(im, DepthType.Cv32F, 1 / 255.0); CvInvoke.CvtColor(im, im, ColorConversion.Bgr2Rgb); var imData = im.ToImage<Bgr, Byte>().Data; var input = imData.ToTensor<float>(); List<NamedOnnxValue> inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input_1:0", input) }; return inputs; }
注意:该版本代码除了维度缺失问题外,还存在隐藏的类型错误:调用
ConvertTo将Mat转为Cv32F32位浮点类型后,仍然调用ToImage<Bgr, Byte>()读取8位无符号字节数据,会导致读取到的像素值完全异常,后续修复方案已同步修正该问题。
修复方案
两种方案都可以保留v2版本的高性能,不需要回退到逐像素循环写法:
方案1:张量维度扩展+内存拷贝
先创建形状符合要求的4维DenseTensor,将预处理完成的3维图像数据通过内存块拷贝直接写入目标张量,全程无逐像素遍历,性能和v2版本基本持平:static List<NamedOnnxValue> preprocess_CV_fixed(Mat im) { CvInvoke.Resize(im, im, new Size(416, 416)); im.ConvertTo(im, DepthType.Cv32F, 1 / 255.0); CvInvoke.CvtColor(im, im, ColorConversion.Bgr2Rgb); // 直接读取浮点类型的图像数据 var rawData = im.GetData() as float[,,]; // 创建带batch维度的目标张量 var input = new DenseTensor<float>(new[] {1, im.Height, im.Width, 3}); // 内存块直接拷贝,性能远高于逐像素循环 Buffer.BlockCopy(rawData, 0, input.Buffer.Array, input.Buffer.Offset, rawData.Length * sizeof(float)); return new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input_1:0", input) }; }方案2:先给Mat插入batch维度再转张量
直接调用EmguCV内置的ExpandDims接口,在Mat的第0轴插入长度为1的维度,处理后Mat形状直接变为(1,416,416,3),再转Onnx张量即可直接匹配输入要求:static List<NamedOnnxValue> preprocess_CV_fixed_v2(Mat im) { CvInvoke.Resize(im, im, new Size(416, 416)); im.ConvertTo(im, DepthType.Cv32F, 1 / 255.0); CvInvoke.CvtColor(im, im, ColorConversion.Bgr2Rgb); // 第0轴插入batch维度 Mat expandedMat = new Mat(); CvInvoke.ExpandDims(im, expandedMat, 0); // 转换后张量形状直接符合要求 var input = expandedMat.ToTensor<float>(); return new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input_1:0", input) }; }
内容的提问来源于stack exchange,提问作者mary
相关产品推荐
相关产品推荐

