C#加载PyTorch导出的ONNX模型无检测结果且报除零错误
Faster RCNN(FPN V2) ONNX模型C#推理异常问题排查
我在Python中训练了基于ResNet50的Faster RCNN(FPN V2)模型,并导出为ONNX格式。现在需要在C#中加载该模型完成目标预测,但遇到两个问题:要么无检测结果,要么频繁触发ONNX的“Attempted to divide by zero”错误。但Python端用相同模型推理完全正常。
模型训练时输入图像尺寸是720x576,但导出的ONNX模型要求输入为576x720,我已经调整了图像尺寸,但问题依旧,怀疑是C#端图像预处理或张量设置环节出错。
C#测试代码
private void cmdAnalyse_Click(object sender, EventArgs e) { // begin analysis if (this.txtONNXFile.Text == "") { MessageBox.Show("Please select an ONNX file"); return; } if (this.originalImage == null) { MessageBox.Show("Please select an image"); return; } // flip the width and height dimensions. Images are 720x576, but the model expects 576x720 this.rescaledImage = new Bitmap(originalImage.Height, originalImage.Width); Graphics graphics = Graphics.FromImage(rescaledImage); graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.DrawImage(originalImage, 0, 0, rescaledImage.Width, rescaledImage.Height); Microsoft.ML.OnnxRuntime.Tensors.Tensor<float> input = new Microsoft.ML.OnnxRuntime.Tensors.DenseTensor<float>(new[] { 1, 3, 720, 576 }); BitmapData bitmapData = rescaledImage.LockBits(new System.Drawing.Rectangle(0, 0, rescaledImage.Width, rescaledImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int stride = bitmapData.Stride; IntPtr scan0 = bitmapData.Scan0; unsafe { byte* ptr = (byte*)scan0; for (int y = 0; y < rescaledImage.Height; y++) { for (int x = 0; x < rescaledImage.Width; x++) { int offset = y * stride + x * 3; input[0, 0, y, x] = ptr[offset + 2]; // Red channel input[0, 1, y, x] = ptr[offset + 1]; // Green channel input[0, 2, y, x] = ptr[offset]; // Blue channel } } } rescaledImage.UnlockBits(bitmapData); var inputs = new List<Microsoft.ML.OnnxRuntime.NamedOnnxValue> { Microsoft.ML.OnnxRuntime.NamedOnnxValue.CreateFromTensor("images", input) }; // run inference var session = new Microsoft.ML.OnnxRuntime.InferenceSession(this.txtONNXFile.Text); Microsoft.ML.OnnxRuntime.IDisposableReadOnlyCollection<Microsoft.ML.OnnxRuntime.DisposableNamedOnnxValue> results = session.Run(inputs); // process results var resultsArray = results.ToArray(); float[] boxes = resultsArray[0].AsEnumerable<float>().ToArray(); long[] labels = resultsArray[1].AsEnumerable<long>().ToArray(); float[] confidences = resultsArray[2].AsEnumerable<float>().ToArray(); var predictions = new List<Prediction>(); var minConfidence = 0.0f; for (int i = 0; i < boxes.Length; i += 4) { var index = i / 4; if (confidences[index] >= minConfidence) { predictions.Add(new Prediction { Box = new Box(boxes[i], boxes[i + 1], boxes[i + 2], boxes[i + 3]), Label = LabelMap.Labels[labels[index]], Confidence = confidences[index] }); } } System.Drawing.Graphics graph = System.Drawing.Graphics.FromImage(this.rescaledImage); // Put boxes, labels and confidence on image and save for viewing foreach (var p in predictions) { System.Drawing.Pen pen = new System.Drawing.Pen(System.Drawing.Color.Red, 2); graph.DrawRectangle(pen, p.Box.Xmin, p.Box.Ymin, p.Box.Xmax - p.Box.Xmin, p.Box.Ymax - p.Box.Ymin); } graph.Flush(); graph.Dispose(); // rescale image back System.Drawing.Bitmap bmpResult = new Bitmap(this.originalImage.Width, this.originalImage.Height); graphics = Graphics.FromImage(bmpResult); graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; graphics.DrawImage(rescaledImage, 0, 0, originalImage.Width, originalImage.Height); graphics.Flush(); graphics.Dispose(); this.pbRibeye.Width = bmpResult.Width; this.pbRibeye.Height = bmpResult.Height; this.pbRibeye.Image = bmpResult; rescaledImage.Dispose(); }
Python正常推理代码
ort_session = onnxruntime.InferenceSession(ONNXFile) # Preprocess the input image image = Image.open(image_path) # Load the image using PIL resized_image = image.resize((576, 720)) # If this is omitted then I receive an error regarding the expected input dimensions transform = torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), # Convert PIL image to tensor ]) input_tensor = transform(resized_image) input_tensor = input_tensor.unsqueeze(0) # Add a batch dimension # Run the model outputs = ort_session.run(None, {'images': input_tensor.numpy()})
问题排查与修复
对比Python和C#的预处理流程,核心差异在图像归一化和张量维度映射两个环节:
1. 缺少图像归一化处理
Python中ToTensor()会自动将PIL图像的像素值从[0,255]的uint8类型缩放到[0.0,1.0]的float类型,但C#代码中直接将byte值赋值给float张量,没有做归一化。这会导致模型输入分布和训练时不一致,引发推理异常。
修复代码:
input[0, 0, y, x] = ptr[offset + 2] / 255.0f; // Red channel input[0, 1, y, x] = ptr[offset + 1] / 255.0f; // Green channel input[0, 2, y, x] = ptr[offset] / 255.0f; // Blue channel
2. 张量维度与模型要求不匹配
C#中创建张量时指定的是new[] {1,3,720,576},需要确认ONNX模型的输入维度顺序是否为NCHW(批量、通道、高度、宽度)。可以用Netron工具查看ONNX模型的输入节点信息:
- 如果模型要求
NCHW,当前张量维度正确; - 如果模型要求
NHWC,需要修改张量创建代码,并调整通道索引位置:// 创建NHWC格式的张量 var input = new Microsoft.ML.OnnxRuntime.Tensors.DenseTensor<float>(new[] {1,720,576,3}); // 赋值时调整通道位置 input[0, y, x, 0] = ptr[offset + 2] / 255.0f; // Red channel input[0, y, x, 1] = ptr[offset + 1] / 255.0f; // Green channel input[0, y, x, 2] = ptr[offset] / 255.0f; // Blue channel
3. 资源泄漏问题
C#代码中InferenceSession创建后未释放,会导致资源泄漏,建议用using语句包裹:
using (var session = new Microsoft.ML.OnnxRuntime.InferenceSession(this.txtONNXFile.Text)) { var results = session.Run(inputs); // 处理结果逻辑 }
4. 结果后处理对齐
确认模型输出的box坐标格式:如果是归一化坐标(0~1),需要先乘以缩放后的图像尺寸(576x720),再映射回原图像尺寸;如果是绝对坐标,当前后处理逻辑无需修改。
内容的提问来源于stack exchange,提问作者Karl M
相关产品推荐
相关产品推荐

