说明地址https://github.com/piddnad/DDColor效果模型信息Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- nameinput tensorFloat[1, 3, 256, 256] --------------------------------------------------------------- Outputs ------------------------- nameoutput tensorFloat[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 Tensorfloat inputTensor; private ListNamedOnnxValue inputContainer; private IDisposableReadOnlyCollectionDisposableNamedOnnxValue inferenceResult; private DisposableNamedOnnxValue[] resultOnnxValues; private Tensorfloat 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); // 如需 GPUsessionOptions.AppendExecutionProvider_CUDA(0); onnxSession new InferenceSession(modelPath, sessionOptions); inputContainer new ListNamedOnnxValue(); 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.Enabled false; 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 通道范围 0100 Mat lab new Mat(); Cv2.CvtColor(rgb, lab, ColorConversionCodes.RGB2Lab); Mat[] labChannels Cv2.Split(lab); Mat L labChannels[0]; // L 通道float范围 0100 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 图像LresizedL, a0, b0 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 图像范围 01 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 DenseTensorfloat(inputData, new[] { 1, 3, height, width }); // 2. 推理 var inputs new ListNamedOnnxValue { 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).AsTensorfloat(); 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 (0100) 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(); } } }