You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 00:27:00