C# OnnxRuntime 部署 DDColor

0 阅读3分钟

说明

地址:github.com/piddnad/DDC…

图片

效果

图片
图片

模型信息

Model Properties
-------------------------
---------------------------------------------------------------

Inputs
-------------------------
name:input
tensor:Float[1, 3, 256, 256]
---------------------------------------------------------------

Outputs
-------------------------
name:output
tensor:Float[1, 2, 256, 256]
---------------------------------------------------------------

项目

图片

代码

using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Forms;
using Size = OpenCvSharp.Size;

namespace Onnx_Demo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private readonly string fileFilter"图像文件|*.bmp;*.jpg;*.jpeg;*.tiff;*.png";
        private string imagePath"";
        private string modelPath;
        private Mat originalImage;       
        private Mat resultImage;            
        private SessionOptions sessionOptions;
        private InferenceSession onnxSession;
        private Tensor<float> inputTensor;
        private List<NamedOnnxValue> inputContainer;
        private IDisposableReadOnlyCollection<DisposableNamedOnnxValue> inferenceResult;
        private DisposableNamedOnnxValue[] resultOnnxValues;
        private Tensor<float> outputTensor;

        private const int ModelInputSize = 256;   // 模型固定输入尺寸

        private void Form1_Load(object sender, EventArgs e)
        {
            modelPath"model/ddcolor_paper_tiny.onnx";
            sessionOptions = new SessionOptions();
            sessionOptions.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO;
            sessionOptions.AppendExecutionProvider_CPU(0);
            // 如需 GPU:sessionOptions.AppendExecutionProvider_CUDA(0);
            onnxSession = new InferenceSession(modelPath, sessionOptions);
            inputContainer = new List<NamedOnnxValue>();

            string testImg"test_img/Einstein, Rejection, and Crafting a Future.jpeg";
            if (System.IO.File.Exists(testImg))
            {
                imagePath = testImg;
                pictureBox1.Image = new Bitmap(imagePath);
                originalImage = new Mat(imagePath);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = fileFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return;

            imagePath = ofd.FileName;
            pictureBox1.Image?.Dispose();
            pictureBox1.Image = new Bitmap(imagePath);
            originalImage = new Mat(imagePath);
            pictureBox2.Image = null;
            textBox1.Text"";
        }


        private void button2_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(imagePath) || originalImage == null)
            {
                MessageBox.Show("请先选择图片!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            button2.Enabledfalse;
            pictureBox2.Image = null;
            textBox1.Text"";
            Application.DoEvents();

            try
            {
                int origH = originalImage.Height;
                int origW = originalImage.Width;

                // ========== 1. 预处理 ==========
                // 1.1 BGR -> RGB,归一化到 [0,1] 浮点
                Mat rgb = new Mat();
                Cv2.CvtColor(originalImage, rgb, ColorConversionCodes.BGR2RGB);
                rgb.ConvertTo(rgb, MatType.CV_32FC3, 1.0 / 255.0);

                // 1.2 RGB -> LAB,提取 L 通道(范围 0~100)
                Mat lab = new Mat();
                Cv2.CvtColor(rgb, lab, ColorConversionCodes.RGB2Lab);
                Mat[] labChannels = Cv2.Split(lab);
                Mat L = labChannels[0];   // L 通道,float,范围 0~100
                Mat originalL = L.Clone(); // 保存原始 L 用于最终合并

                // 1.3 构造模型输入:灰度 RGB 图(通过 Lab -> RGB 转换,与 Python 代码一致)
                // 调整 L 到模型输入尺寸 256x256
                Mat resizedL = new Mat();
                Cv2.Resize(L, resizedL, new Size(ModelInputSize, ModelInputSize), 0, 0, InterpolationFlags.Linear);

                // 创建三通道 Lab 图像:L=resizedL, a=0, b=0
                Mat grayLab = new Mat();
                Mat[] grayLabChannels = new Mat[3];
                grayLabChannels[0] = resizedL;
                grayLabChannels[1] = Mat.Zeros(resizedL.Size(), MatType.CV_32FC1);
                grayLabChannels[2] = Mat.Zeros(resizedL.Size(), MatType.CV_32FC1);
                Cv2.Merge(grayLabChannels, grayLab);

                // Lab -> RGB,得到灰度 RGB 图像(范围 0~1)
                Mat grayRgb = new Mat();
                Cv2.CvtColor(grayLab, grayRgb, ColorConversionCodes.Lab2RGB);

                // 1.4 转换为 CHW 格式的 float 数组
                int height = grayRgb.Height;
                int width = grayRgb.Width;
                float[] inputData = new float[3 * height * width];
                Mat[] rgbChannels = Cv2.Split(grayRgb); // 顺序: R, G, B
                int index = 0;
                for (int c = 0; c < 3; c++)
                {
                    float[] channelData = new float[height * width];
                    System.Runtime.InteropServices.Marshal.Copy(rgbChannels[c].Data, channelData, 0, height * width);
                    foreach (float val in channelData)
                        inputData[index++] = val;
                }

                var inputTensor = new DenseTensor<float>(inputData, new[] { 1, 3, height, width });

                // ========== 2. 推理 ==========
                var inputs = new List<NamedOnnxValue> { NamedOnnxValue.CreateFromTensor("input", inputTensor) };
                DateTime t1 = DateTime.Now;
                using (var results = onnxSession.Run(inputs))
                {
                    DateTime t2 = DateTime.Now;
                    var output = results.First(item => item.Name == "output").AsTensor<float>();
                    float[] outputData = output.ToArray(); // shape: [1, 2, 256, 256]
                    float minVal = outputData.Min();
                    float maxVal = outputData.Max();

                    // ========== 3. 后处理 ==========
                    int outH = ModelInputSize;
                    int outW = ModelInputSize;

                    // 3.1 将输出 ab 通道转换为双通道 Mat (HWC, float)
                    Mat abMat = new Mat(outH, outW, MatType.CV_32FC2);
                    int idx = 0;
                    for (int y = 0; y < outH; y++)
                    {
                        for (int x = 0; x < outW; x++)
                        {
                            Vec2f ab;
                            ab.Item0 = outputData[idx];                     // a 通道
                            ab.Item1 = outputData[idx + outH * outW];       // b 通道
                            abMat.Set(y, x, ab);
                            idx++;
                        }
                    }

                    // 3.2 将 ab 双通道图放大到原始图像尺寸(双线性插值)
                    Mat resizedAb = new Mat();
                    Cv2.Resize(abMat, resizedAb, new Size(origW, origH), 0, 0, InterpolationFlags.Linear);

                    // 3.3 合并原始 L 与放大后的 ab 得到 LAB 图像
                    Mat[] labResultChannels = new Mat[3];
                    labResultChannels[0] = originalL;                       // L (0~100)
                    Mat[] abChannels = Cv2.Split(resizedAb);                // abChannels[0] = a, abChannels[1] = b
                    labResultChannels[1] = abChannels[0];
                    labResultChannels[2] = abChannels[1];

                    Mat labResult = new Mat();
                    Cv2.Merge(labResultChannels, labResult);

                    // 3.4 LAB -> RGB -> BGR(用于显示/保存)
                    Mat rgbResult = new Mat();
                    Cv2.CvtColor(labResult, rgbResult, ColorConversionCodes.Lab2RGB);
                    Mat bgrResult = new Mat();
                    Cv2.CvtColor(rgbResult, bgrResult, ColorConversionCodes.RGB2BGR);

                    // 3.5 将像素值从 [0,1] 转到 [0,255] 并转为 8UC3
                    bgrResult.ConvertTo(bgrResult, MatType.CV_8UC3, 255.0);
                    resultImage = bgrResult.Clone();

                    pictureBox2.Image = new Bitmap(resultImage.ToMemoryStream());
                    textBox1.Text = $"推理耗时: {(t2 - t1).TotalMilliseconds:F2} ms";

                    // 释放资源
                    abMat.Dispose();
                    resizedAb.Dispose();
                    foreach (var m in abChannels) m.Dispose();
                    labResult.Dispose();
                    rgbResult.Dispose();

                }

                // 释放资源
                rgb.Dispose();
                lab.Dispose();
                L.Dispose();
                resizedL.Dispose();
                grayLab.Dispose();
                grayRgb.Dispose();
                foreach (var m in rgbChannels) m.Dispose();
                foreach (var m in labChannels) m.Dispose();
                foreach (var m in grayLabChannels) m.Dispose();
               
            }
            catch (Exception ex)
            {
                MessageBox.Show($"推理失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                textBox1.Text = "推理出错";
            }
            finally
            {
                button2.Enabled = true;
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            if (resultImage == null || resultImage.Empty())
            {
                MessageBox.Show("请先进行推理!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Title = "保存图像";
            sfd.Filter = "PNG图片 (*.png)|*.png|JPEG图片 (*.jpg)|*.jpg|BMP图片 (*.bmp)|*.bmp";
            sfd.FilterIndex = 1;
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                string ext = System.IO.Path.GetExtension(sfd.FileName).ToLower();
                ImageFormat format = ImageFormat.Png;
                if (ext == ".jpg" || ext == ".jpeg")
                    format = ImageFormat.Jpeg;
                elseif (ext == ".bmp")
                    format = ImageFormat.Bmp;

                using (var stream = resultImage.ToMemoryStream())
                using (var bitmap = new Bitmap(stream))
                {
                    bitmap.Save(sfd.FileName, format);
                }
                MessageBox.Show($"保存成功!\n位置: {sfd.FileName}", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        private void pictureBox1_DoubleClick(object sender, EventArgs e)
        {
            ShowImageInNormalWindow(pictureBox1.Image);
        }

        private void pictureBox2_DoubleClick(object sender, EventArgs e)
        {
            ShowImageInNormalWindow(pictureBox2.Image);
        }

        private void ShowImageInNormalWindow(Image img)
        {
            if (img == null) return;
            Form frm = new Form
            {
                Width = img.Width + 20,
                Height = img.Height + 40,
                StartPosition = FormStartPosition.CenterScreen,
                Text = "查看大图"
            };
            PictureBox pb = new PictureBox
            {
                Image = img,
                Dock = DockStyle.Fill,
                SizeMode = PictureBoxSizeMode.Zoom
            };
            frm.Controls.Add(pb);
            frm.ShowDialog();
        }
    }
}