说明
效果
模型信息
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.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 通道(范围 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();
}
}
}