使用 C# 通过 Faster RCNN 深度学习进行对象检测
本示例演示如何使用 ONNX Runtime C# API 运行预训练的 Faster R-CNN 对象检测 ONNX 模型。
本示例的源代码可在此处获取:here。
目录
先决条件
要运行此示例,您需要以下内容:
- 为您的操作系统(Mac、Windows 或 Linux)安装 .NET Core 3.1 或更高版本。
- 将 Faster R-CNN ONNX 模型下载到您的本地系统。
- 下载 此示例图像 以测试模型。您也可以使用任何您喜欢的图像。
入门
现在我们已经准备好所有东西,我们可以开始添加代码来在图像上运行模型。为简单起见,我们将在程序的主方法中完成此操作。
读取路径
首先,让我们读取模型路径、要测试的图像路径以及输出图像路径。
string modelFilePath = args[0];
string imageFilePath = args[1];
string outImageFilePath = args[2];
读取图像
接下来,我们将使用跨平台图像库 ImageSharp 读取图像。
using Image<Rgb24> image = Image.Load<Rgb24>(imageFilePath, out IImageFormat format);
注意,我们特意读取 Rgb24
类型,以便稍后可以高效地对图像进行预处理。
调整图像大小
接下来,我们将把图像调整到模型期望的合适大小;建议将图像调整到高度和宽度都在 [800, 1333] 范围内的尺寸。
float ratio = 800f / Math.Min(image.Width, image.Height);
using Stream imageStream = new MemoryStream();
image.Mutate(x => x.Resize((int)(ratio * image.Width), (int)(ratio * image.Height)));
image.Save(imageStream, format);
预处理图像
接下来,我们将根据 模型的要求 对图像进行预处理。
var paddedHeight = (int)(Math.Ceiling(image.Height / 32f) * 32f);
var paddedWidth = (int)(Math.Ceiling(image.Width / 32f) * 32f);
var mean = new[] { 102.9801f, 115.9465f, 122.7717f };
// Preprocessing image
// We use DenseTensor for multi-dimensional access
DenseTensor<float> input = new(new[] { 3, paddedHeight, paddedWidth });
image.ProcessPixelRows(accessor =>
{
for (int y = paddedHeight - accessor.Height; y < accessor.Height; y++)
{
Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
for (int x = paddedWidth - accessor.Width; x < accessor.Width; x++)
{
input[0, y, x] = pixelSpan[x].B - mean[0];
input[1, y, x] = pixelSpan[x].G - mean[1];
input[2, y, x] = pixelSpan[x].R - mean[2];
}
}
});
在这里,我们正在创建一个所需大小 (通道数, 填充后高度, 填充后宽度)
的 Tensor,访问像素值,对其进行预处理,最后将它们分配到 Tensor 的适当索引处。
设置输入
// 固定 DenseTensor 内存并在 OrtValue 张量中直接使用 // 在 ortValue 释放时取消固定
using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
input.Buffer, new long[] { 3, paddedHeight, paddedWidth });
接下来,我们将创建模型的输入。
var inputs = new Dictionary<string, OrtValue>
{
{ "image", inputOrtValue }
};
要检查 ONNX 模型的输入节点名称,您可以使用 Netron 来可视化模型并查看输入/输出名称。在此示例中,该模型的输入节点名称是 image
。
运行推理
接下来,我们将创建一个推理会话并运行输入。
using var session = new InferenceSession(modelFilePath);
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = session.Run(runOptions, inputs, session.OutputNames);
后处理输出
接下来,我们需要对输出进行后处理,以获取每个框的边界框以及相关的标签和置信度分数。
var boxesSpan = results[0].GetTensorDataAsSpan<float>();
var labelsSpan = results[1].GetTensorDataAsSpan<long>();
var confidencesSpan = results[2].GetTensorDataAsSpan<float>();
const float minConfidence = 0.7f;
var predictions = new List<Prediction>();
for (int i = 0; i < boxesSpan.Length - 4; i += 4)
{
var index = i / 4;
if (confidencesSpan[index] >= minConfidence)
{
predictions.Add(new Prediction
{
Box = new Box(boxesSpan[i], boxesSpan[i + 1], boxesSpan[i + 2], boxesSpan[i + 3]),
Label = LabelMap.Labels[labelsSpan[index]],
Confidence = confidencesSpan[index]
});
}
}
注意,我们只取置信度高于 0.7 的边界框,以去除误报。
查看预测结果
接下来,我们将在图像上绘制边界框以及相关的标签和置信度分数,以查看模型的表现。
using var outputImage = File.OpenWrite(outImageFilePath);
Font font = SystemFonts.CreateFont("Arial", 16);
foreach (var p in predictions)
{
image.Mutate(x =>
{
x.DrawLines(Color.Red, 2f, new PointF[] {
new PointF(p.Box.Xmin, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymin),
new PointF(p.Box.Xmax, p.Box.Ymax),
new PointF(p.Box.Xmax, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymax),
new PointF(p.Box.Xmin, p.Box.Ymin)
});
x.DrawText($"{p.Label}, {p.Confidence:0.00}", font, Color.White, new PointF(p.Box.Xmin, p.Box.Ymin));
});
}
image.Save(outputImage, format);
对于每个边界框预测,我们使用 ImageSharp 绘制红线来创建边界框,并绘制标签和置信度文本。
运行程序
现在程序已经创建好,我们可以使用以下命令运行它:
dotnet run [path-to-model] [path-to-image] [path-to-output-image]
例如,运行
dotnet run ~/Downloads/FasterRCNN-10.onnx ~/Downloads/demo.jpg ~/Downloads/out.jpg
会在图像中检测到以下对象: