共计 1981 个字符,预计需要花费 5 分钟才能阅读完成。
为什么选择 YOLO 算法
YOLO(You Only Look Once)作为单阶段目标检测的标杆算法,在工业质检中能快速定位缺陷位置,安防监控场景下可实现实时多人脸追踪,其平衡速度和精度的特性尤其适合.NET 生态下的边缘计算部署。相比传统方法,YOLO 将检测任务转化为回归问题,单次前向传播即可输出所有目标的类别和位置信息。

技术选型:三大方案对比
- TensorFlow.NET
- 优势:完整支持 TensorFlow 算子,可直接加载 PB 格式模型
- 劣势:文档较少,GPU 内存管理复杂
-
典型报错:
TFException: Output tensors must be on CPU -
ML.NET
- 优势:微软官方维护,与 ASP.NET Core 深度集成
- 劣势:ONNX 支持有限,无法自定义预处理层
-
典型场景:已有训练好的 ONNX 模型时快速接入
-
Python 服务调用
- 优势:可利用完整 Python 生态
- 劣势:需要维护跨进程通信,延迟增加 2 - 3 倍
推荐方案:生产环境首选 ML.NET+ONNX 组合,开发阶段可用 TensorFlow.NET 调试。
核心实现步骤
图像预处理
using OpenCvSharp;
// 归一化并转换通道顺序
Mat Preprocess(Mat src, Size targetSize)
{
// BGR 转 RGB
Cv2.CvtColor(src, src, ColorConversionCodes.BGR2RGB);
// 归一化到 0 - 1 范围
Mat normalized = new Mat();
src.ConvertTo(normalized, MatType.CV_32FC3, 1.0/255);
// 调整尺寸并中心裁剪
Mat resized = new Mat();
Cv2.Resize(normalized, resized, targetSize);
return resized;
}
模型加载方式对比
文件直接加载:
var session = new InferenceSession("yolov5s.onnx");
内存流加载(适合加密模型):
byte[] modelBytes = File.ReadAllBytes("yolov5s.onnx");
using var stream = new MemoryStream(modelBytes);
var session = new InferenceSession(stream);
NMS 实现关键点
List<Detection> ApplyNMS(IReadOnlyList<Detection> detections,
float iouThreshold = 0.5f)
{
// 按置信度降序排序
var sorted = detections.OrderByDescending(d => d.Confidence).ToList();
for (int i = 0; i < sorted.Count; i++) {
// 计算当前框与后续所有框的 IoU
for (int j = i + 1; j < sorted.Count; j++) {float iou = CalculateIoU(sorted[i].Box, sorted[j].Box);
// IoU 超过阈值则移除低置信度框
if (iou > iouThreshold) {sorted.RemoveAt(j);
j--;
}
}
}
return sorted;
}
// IoU 计算公式:$\frac{area(A \cap B)}{area(A \cup B)}$
性能优化实战
分辨率对 FPS 的影响
| 分辨率 | 平均 FPS (RTX 3060) | 显存占用 |
|---|---|---|
| 416×416 | 45.2 | 1.8GB |
| 640×640 | 28.7 | 3.2GB |
多线程注意事项
- 每个线程创建独立的
InferenceSession实例 - 使用
CUDAExecutionProvider时设置gpu_mem_limit - 监控
nvidia-smi中的显存波动
常见避坑指南
ONNX 导出问题
- 使用
opset_version=12兼容 ML.NET - 验证算子支持列表:
python -c "import onnxruntime as ort; print(ort.get_all_providers())"
显存泄漏排查
- 在
finally块中强制调用session.Dispose() - 使用
GC.Collect()后观察显存释放 - 通过
Process.GetCurrentProcess().PrivateMemorySize64监控
进阶思考
尝试用 Span<T> 重构张量传输代码:
float[] tensorData = new float[3 * 640 * 640];
var span = new Span<float>(tensorData);
// 直接操作内存块替代逐像素赋值
通过本教程,我们完成了从图像预处理到模型部署的全流程实践。YOLO 在 C# 中的表现令人惊喜——在消费级显卡上能达到实时检测的要求。建议下一步尝试将模型封装为 gRPC 服务,或探索使用 TorchSharp 进行模型微调。
正文完
