智算多多



我们采用分层解耦架构,从底层硬件到上层业务全链路隔离,既保证核心模块的通用性,又可针对不同产线场景快速定制,同时满足国产化信创适配、工业级稳定性、低延迟高帧率的核心要求。
YOLOv9凭借可编程梯度信息(PGI)与广义高效层聚合网络(GELAN),在小目标缺陷检测、低对比度缺陷识别上,相比前代YOLOv8有3.2%的mAP提升,推理速度提升15%,是工业缺陷检测的最优模型选择之一。
工业场景的模型训练和通用目标检测完全不同,核心痛点集中在4点:
| 增强类型 | 工业场景适配方案 | 核心作用 |
|---|---|---|
| 几何变换 | -15°+15°随机旋转、0.8~1.2倍缩放、水平/垂直翻转、小范围平移 | 适配产品姿态偏移、尺寸变化 |
| 光照扰动 | 0.7~1.3倍亮度调整、0.8~1.2倍对比度调整、高斯噪声添加 | 适配产线光照变化、金属反光 |
| 小目标增强 | 缺陷区域局部放大、Copy-Paste缺陷复制粘贴 | 提升小目标缺陷的检出率 |
| 混合增强 | Mosaic、MixUp混合增强 | 提升模型泛化能力,减少过拟合 |
针对工业缺陷检测的核心需求,我们对YOLOv9的超参数做专属优化:
# 核心超参数配置(缺陷检测专属) # 训练参数 batch: 16 # 根据GPU显存调整,最小不低于8,避免loss震荡 epochs: 100 imgsz: 640 # 小目标缺陷可提升至800,平衡速度与精度 workers: 8 # 损失函数权重(工业场景定位优先) box: 1.2 # 提升定位损失权重,缺陷边界定位更精准 cls: 0.5 # 工业场景类别少,降低分类损失权重 obj: 1.0 # 学习率策略 lr0: 0.01 lrf: 0.01 warmup_epochs: 5 # 数据增强 hsv_h: 0.015 # 工业场景色调变化小,降低扰动幅度 hsv_s: 0.1 hsv_v: 0.3 degrees: 15 translate: 0.1 scale: 0.2 fliplr: 0.5 flipud: 0.2 mosaic: 1.0 mixup: 0.1 # 类别权重(解决正负样本不均衡) class_weights: [1.0, 3.0, 2.5, 4.0] # 稀有缺陷类别提升权重
from ultralytics import YOLO
# 加载训练好的最优模型
model = YOLO("./runs/train/exp/weights/best.pt")
# 导出为ONNX格式,输入尺寸640x640,opset=12,启用NMS,FP16量化
success = model.export(format="onnx", imgsz=640, opset=12, nms=True, half=True)我们采用微软官方开源的ONNX Runtime作为推理引擎,它原生支持.NET平台,跨平台能力强,支持CPU/GPU/国产NPU加速,性能媲美TensorRT,完全无需Python/C++中间层,彻底打破语言壁垒。
NuGet安装核心依赖包:
# 核心推理引擎(CPU版) Install-Package Microsoft.ML.OnnxRuntime Install-Package Microsoft.ML.OnnxRuntime.Managed # GPU加速版(需安装CUDA与cuDNN) Install-Package Microsoft.ML.OnnxRuntime.Gpu # 跨平台图像处理库(替代System.Drawing,符合信创要求) Install-Package SixLabors.ImageSharp Install-Package SixLabors.ImageSharp.Drawing
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// YOLOv9 ONNX Runtime推理引擎
/// 零Python/C++中间层
/// 跨平台全架构适配
/// 工业缺陷检测专属优化
/// </summary>
public class YoloV9InferenceEngine : IDisposable
{
#region 私有字段
private readonly InferenceSession _inferenceSession;
private readonly string _inputName;
private readonly string _outputName;
private readonly int _inputSize;
private readonly float _confidenceThreshold;
private readonly float _iouThreshold;
private readonly string[] _classNames;
private readonly bool _useGpu;
#endregion
#region 构造函数
/// <summary>
/// 初始化YOLOv9推理引擎
/// </summary>
/// <param name="modelPath">ONNX模型路径</param>
/// <param name="classNames">缺陷类别名称数组</param>
/// <param name="inputSize">模型输入尺寸,默认640</param>
/// <param name="confidenceThreshold">置信度阈值,默认0.5</param>
/// <param name="iouThreshold">IOU阈值,默认0.45</param>
/// <param name="useGpu">是否启用GPU加速</param>
/// <param name="gpuDeviceId">GPU设备ID</param>
public YoloV9InferenceEngine(
string modelPath,
string[] classNames,
int inputSize = 640,
float confidenceThreshold = 0.5f,
float iouThreshold = 0.45f,
bool useGpu = false,
int gpuDeviceId = 0)
{
_inputSize = inputSize;
_confidenceThreshold = confidenceThreshold;
_iouThreshold = iouThreshold;
_classNames = classNames;
_useGpu = useGpu;
// 配置推理会话选项
var sessionOptions = new SessionOptions();
if (useGpu)
{
// 启用GPU加速
sessionOptions.AppendExecutionProvider_CUDA(gpuDeviceId);
sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL;
}
else
{
// CPU多线程优化
sessionOptions.ExecutionMode = ExecutionMode.ORT_PARALLEL;
sessionOptions.IntraOpNumThreads = Environment.ProcessorCount;
sessionOptions.InterOpNumThreads = 1;
}
// 启用全图优化,提升推理速度
sessionOptions.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL;
sessionOptions.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR;
// 加载ONNX模型
_inferenceSession = new InferenceSession(modelPath, sessionOptions);
// 获取模型输入输出名称
_inputName = _inferenceSession.InputMetadata.Keys.First();
_outputName = _inferenceSession.OutputMetadata.Keys.First();
}
#endregion
#region 图像预处理
/// <summary>
/// 图像预处理:ROI裁剪、等比例缩放、归一化、转Tensor
/// </summary>
/// <param name="image">原始图像</param>
/// <param name="roi">检测ROI区域(可选)</param>
/// <returns>预处理后的Tensor</returns>
private DenseTensor<float> PreprocessImage(Image<Rgb24> image, Rectangle? roi = null)
{
// 1. ROI裁剪,只处理检测区域,提升推理速度,减少误检
if (roi.HasValue)
{
image = image.Clone(x => x.Crop(roi.Value));
}
// 2. 等比例缩放(保持宽高比,填充黑边,避免检测框错位)
int originalWidth = image.Width;
int originalHeight = image.Height;
float scaleRatio = Math.Min((float)_inputSize / originalWidth, (float)_inputSize / originalHeight);
int newWidth = (int)(originalWidth * scaleRatio);
int newHeight = (int)(originalHeight * scaleRatio);
int padLeft = (_inputSize - newWidth) / 2;
int padTop = (_inputSize - newHeight) / 2;
// 缩放+填充
image = image.Clone(x => x
.Resize(newWidth, newHeight)
.Pad(_inputSize, _inputSize, Color.Black)
.Crop(new Rectangle(padLeft, padTop, newWidth, newHeight))
.Pad(_inputSize, _inputSize, Color.Black));
// 3. 归一化+转Tensor(YOLOv9默认归一化到0-1)
var tensor = new DenseTensor<float>(new[] { 1, 3, _inputSize, _inputSize });
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < _inputSize; y++)
{
var pixelRow = accessor.GetRowSpan(y);
for (int x = 0; x < _inputSize; x++)
{
var pixel = pixelRow[x];
tensor[0, 0, y, x] = pixel.R / 255.0f;
tensor[0, 1, y, x] = pixel.G / 255.0f;
tensor[0, 2, y, x] = pixel.B / 255.0f;
}
}
});
return tensor;
}
#endregion
#region 推理结果解析
/// <summary>
/// 推理结果解析:置信度过滤、坐标映射、结果封装
/// </summary>
/// <param name="outputTensor">推理输出Tensor</param>
/// <param name="originalWidth">原始图像宽度</param>
/// <param name="originalHeight">原始图像高度</param>
/// <param name="roi">检测ROI区域</param>
/// <returns>缺陷检测结果列表</returns>
private List<DefectDetectionResult> ParseResult(DenseTensor<float> outputTensor, int originalWidth, int originalHeight, Rectangle? roi = null)
{
var results = new List<DefectDetectionResult>();
// YOLOv9启用NMS后的输出格式:[1, num_detections, 6]
// 6个维度分别为:x1, y1, x2, y2, confidence, class_id
int detectionCount = outputTensor.Dimensions[1];
for (int i = 0; i < detectionCount; i++)
{
float x1 = outputTensor[0, i, 0];
float y1 = outputTensor[0, i, 1];
float x2 = outputTensor[0, i, 2];
float y2 = outputTensor[0, i, 3];
float confidence = outputTensor[0, i, 4];
int classId = (int)outputTensor[0, i, 5];
// 置信度过滤
if (confidence < _confidenceThreshold)
continue;
// 坐标映射:从模型输入尺寸还原到原始图像尺寸
int roiWidth = roi.HasValue ? roi.Value.Width : originalWidth;
int roiHeight = roi.HasValue ? roi.Value.Height : originalHeight;
float scaleRatio = Math.Min((float)_inputSize / roiWidth, (float)_inputSize / roiHeight);
int padLeft = (_inputSize - (int)(roiWidth * scaleRatio)) / 2;
int padTop = (_inputSize - (int)(roiHeight * scaleRatio)) / 2;
// 还原到ROI坐标系
x1 = (x1 - padLeft) / scaleRatio;
y1 = (y1 - padTop) / scaleRatio;
x2 = (x2 - padLeft) / scaleRatio;
y2 = (y2 - padTop) / scaleRatio;
// 还原到原始图像坐标系
if (roi.HasValue)
{
x1 += roi.Value.X;
y1 += roi.Value.Y;
x2 += roi.Value.X;
y2 += roi.Value.Y;
}
// 限制坐标在图像范围内
x1 = Math.Max(0, Math.Min(x1, originalWidth));
y1 = Math.Max(0, Math.Min(y1, originalHeight));
x2 = Math.Max(0, Math.Min(x2, originalWidth));
y2 = Math.Max(0, Math.Min(y2, originalHeight));
// 封装结果
results.Add(new DefectDetectionResult
{
ClassId = classId,
ClassName = _classNames[classId],
Confidence = confidence,
X1 = (int)x1,
Y1 = (int)y1,
X2 = (int)x2,
Y2 = (int)y2,
Width = (int)(x2 - x1),
Height = (int)(y2 - y1)
});
}
return results;
}
#endregion
#region 核心推理方法
/// <summary>
/// 执行缺陷检测推理
/// </summary>
/// <param name="image">原始图像</param>
/// <param name="roi">检测ROI区域(可选)</param>
/// <returns>缺陷检测结果列表</returns>
public List<DefectDetectionResult> Detect(Image<Rgb24> image, Rectangle? roi = null)
{
// 1. 图像预处理
var inputTensor = PreprocessImage(image, roi);
// 2. 构建输入
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor(_inputName, inputTensor)
};
// 3. 执行推理
using var runResult = _inferenceSession.Run(inputs);
// 4. 解析结果
var outputData = runResult.First().AsEnumerable<float>().ToArray();
var outputTensor = new DenseTensor<float>(outputData, new[] { 1, runResult.First().AsTensor<float>().Dimensions[1], 6 });
var detectionResults = ParseResult(outputTensor, image.Width, image.Height, roi);
return detectionResults;
}
#endregion
#region 资源释放
public void Dispose()
{
_inferenceSession?.Dispose();
}
#endregion
}
/// <summary>
/// 缺陷检测结果类
/// </summary>
public class DefectDetectionResult
{
public int ClassId { get; set; }
public string ClassName { get; set; }
public float Confidence { get; set; }
public int X1 { get; set; }
public int Y1 { get; set; }
public int X2 { get; set; }
public int Y2 { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
针对工业产线的高节拍要求,我们可以通过以下方式进一步提升推理性能:
工业相机是视觉系统的眼睛,采集的图像质量直接决定了检测效果,我们采用统一相机抽象接口设计,适配全品牌国产相机,同时内置断线重连、硬件触发、图像预处理等工业级功能。
using System;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System.Threading.Tasks;
/// <summary>
/// 工业相机统一抽象接口
/// 适配海康/大华/大恒等全品牌国产相机
/// </summary>
public interface IIndustrialCamera
{
#region 事件
/// <summary>
/// 图像采集完成事件
/// </summary>
event Action<Image<Rgb24>> ImageGrabbed;
/// <summary>
/// 相机连接状态变化事件
/// </summary>
event Action<bool> ConnectionStatusChanged;
/// <summary>
/// 相机异常事件
/// </summary>
event Action<string> ErrorOccurred;
#endregion
#region 属性
bool IsConnected { get; }
string CameraModel { get; }
string SerialNumber { get; }
#endregion
#region 核心方法
Task<CameraInfo[]> EnumerateCamerasAsync();
Task<bool> ConnectAsync(CameraInfo cameraInfo);
Task DisconnectAsync();
Task<bool> SetParametersAsync(int exposureTimeUs, float gainDb, TriggerMode triggerMode);
Task<bool> StartGrabbingAsync();
Task<bool> StopGrabbingAsync();
Task<bool> SoftwareTriggerAsync();
#endregion
}
/// <summary>
/// 相机信息类
/// </summary>
public class CameraInfo
{
public string Manufacturer { get; set; }
public string Model { get; set; }
public string SerialNumber { get; set; }
public string InterfaceType { get; set; }
}
/// <summary>
/// 相机触发模式枚举
/// </summary>
public enum TriggerMode
{
Continuous = 0,
Software = 1,
Hardware = 2
}
针对工业缺陷检测场景,我们需要对采集的图像做专属预处理,提升缺陷对比度,降低环境干扰:
视觉检测的最终目的是控制产线完成缺陷剔除、产线启停等动作,我们采用之前验证过的双看门狗+指数退避重连+状态回滚的工业级PLC通信框架,保证产线联动的绝对稳定。
工业视觉系统的核心不是实验室里的高准确率,而是产线环境下7*24h不间断稳定运行,我们设计了一套五层稳定性保障体系,覆盖全链路异常场景。
我们在数十个汽车、3C、新能源产线项目中踩过90%的坑,总结出最核心的避坑指南,帮你少走弯路,一次落地成功。
本文带大家打通了YOLOv9工业缺陷模型训练→C#原生ONNX部署→工业相机采集→上位机业务开发→PLC产线联动→7*24h稳定性保障的全流程,彻底解决了C#工业视觉开发的跨语言痛点、落地鸿沟、稳定性难题,打造了一套100%自主可控、全栈国产化适配的工业缺陷检测系统。
这套方案经过数十个产线项目验证,可实现: