1. YOLOv8目标出入计数系统概述
目标出入计数是智能监控、客流统计等场景中的常见需求,传统方案依赖背景建模或光流法,存在准确率低、适应性差的问题。基于YOLOv8的目标检测方案通过深度学习实现了更高精度的物体识别与轨迹追踪。这个项目展示了如何将YOLOv8模型转换为ONNX格式,并在C#环境中部署完整的出入计数系统。
系统工作流程分为三个核心环节:首先使用Python训练YOLOv8模型并导出为ONNX格式,然后在C#中通过ONNX Runtime加载模型进行推理,最后结合目标跟踪算法实现进出区域的计数逻辑。相比直接使用Python部署,C#方案更适合工业上位机、Windows桌面应用等场景,且能更好地与现有.NET生态系统集成。
提示:虽然YOLOv8官方主要支持Python,但通过ONNX中间格式可以突破语言限制,这也是工业界常用的跨平台部署方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与模型转换
2.1 基础环境配置
Python侧需要准备YOLOv8的训练环境:
bash复制pip install ultralytics onnx onnxruntime
C#项目需通过NuGet安装必要的依赖:
bash复制Install-Package Microsoft.ML.OnnxRuntime
Install-Package OpenCvSharp4
Install-Package OpenCvSharp4.runtime.win
2.2 模型训练与ONNX导出
使用官方YOLOv8命令行工具训练自定义数据集:
python复制from ultralytics import YOLO
model = YOLO('yolov8n.pt') # 加载预训练模型
model.train(data='coco128.yaml', epochs=100) # 训练配置
训练完成后导出ONNX模型需特别注意输出节点设置:
python复制model.export(format='onnx', dynamic=True, simplify=True)
关键参数说明:
dynamic=True:允许可变尺寸输入simplify=True:启用模型简化优化opset=12:建议使用的ONNX算子集版本
注意:导出时务必确认模型输出包含三个检测头(如output0, output1, output2),这是后续C#解析的关键。
3. C# ONNX推理核心实现
3.1 模型加载与会话创建
创建InferenceSession时需指定执行提供者:
csharp复制var sessionOptions = new SessionOptions();
sessionOptions.AppendExecutionProvider_CPU(); // 也可使用CUDA/DML
var session = new InferenceSession("yolov8n.onnx", sessionOptions);
3.2 输入数据预处理
OpenCVSharp处理图像输入的标准流程:
csharp复制using var image = Cv2.ImRead("test.jpg");
var inputTensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });
var resizeImage = Cv2.Resize(image, new Size(640, 640));
Cv2.CvtColor(resizeImage, resizeImage, ColorConversionCodes.BGR2RGB);
for (int y = 0; y < 640; y++)
{
for (int x = 0; x < 640; x++)
{
inputTensor[0, 0, y, x] = resizeImage.At<Vec3b>(y, x)[0] / 255f;
inputTensor[0, 1, y, x] = resizeImage.At<Vec3b>(y, x)[1] / 255f;
inputTensor[0, 2, y, x] = resizeImage.At<Vec3b>(y, x)[2] / 255f;
}
}
3.3 推理执行与结果解析
YOLOv8输出解析需要理解其特有的检测头格式:
csharp复制var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("images", inputTensor)
};
using var outputs = session.Run(inputs);
var outputArray = outputs.First().AsTensor<float>().ToArray();
var predictions = ProcessOutput(outputArray, image.Width, image.Height);
后处理核心算法:
csharp复制List<Prediction> ProcessOutput(float[] output, int originalW, int originalH)
{
var predictions = new List<Prediction>();
for (int i = 0; i < output.Length; i += 85)
{
float confidence = output[i + 4];
if (confidence < 0.5) continue;
// 解析类别概率
var classProbs = new float[80];
Array.Copy(output, i + 5, classProbs, 0, 80);
var maxIndex = Array.IndexOf(classProbs, classProbs.Max());
// 计算实际坐标
float centerX = output[i] * originalW;
float centerY = output[i + 1] * originalH;
float width = output[i + 2] * originalW;
float height = output[i + 3] * originalH;
predictions.Add(new Prediction(
new Rect(
(int)(centerX - width / 2),
(int)(centerY - height / 2),
(int)width,
(int)height),
maxIndex,
confidence));
}
return predictions;
}
4. 目标出入计数逻辑实现
4.1 区域检测线设置
定义虚拟检测线作为计数边界:
csharp复制public class CountingLine
{
public Point Start { get; set; }
public Point End { get; set; }
public int InCount { get; set; }
public int OutCount { get; set; }
public bool IsCrossed(Point prevCenter, Point currentCenter)
{
// 使用向量叉积判断线段穿越
var d1 = Direction(currentCenter, Start, End);
var d2 = Direction(prevCenter, Start, End);
var d3 = Direction(currentCenter, prevCenter, Start);
var d4 = Direction(currentCenter, prevCenter, End);
return ((d1 * d2 < 0) && (d3 * d4 < 0));
}
private float Direction(Point p1, Point p2, Point p3)
{
return (p3.X - p2.X) * (p1.Y - p2.Y) - (p1.X - p2.X) * (p3.Y - p2.Y);
}
}
4.2 多目标跟踪策略
基于IOU的简单跟踪方案:
csharp复制public class ObjectTracker
{
private Dictionary<int, TrackedObject> _objects = new();
private int _nextId = 1;
public void Update(IEnumerable<Prediction> detections)
{
// 第一步:匹配现有跟踪器
var unmatchedDetections = new List<Prediction>(detections);
var matchedPairs = new List<(int TrackId, Prediction Detection)>();
foreach (var kvp in _objects)
{
var bestMatch = unmatchedDetections
.OrderBy(d => CalculateIoU(kvp.Value.LastRect, d.Rectangle))
.LastOrDefault();
if (bestMatch != null && CalculateIoU(kvp.Value.LastRect, bestMatch.Rectangle) > 0.3)
{
matchedPairs.Add((kvp.Key, bestMatch));
unmatchedDetections.Remove(bestMatch);
}
}
// 第二步:更新匹配目标
foreach (var pair in matchedPairs)
{
_objects[pair.TrackId].Update(pair.Detection.Rectangle);
}
// 第三步:初始化新目标
foreach (var detection in unmatchedDetections)
{
_objects.Add(_nextId++, new TrackedObject(detection.Rectangle));
}
}
private float CalculateIoU(Rect a, Rect b)
{
// 计算交并比
}
}
5. 完整系统集成与优化
5.1 性能优化技巧
- 异步流水线处理:
csharp复制var processingTask = Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
var frame = _frameQueue.Take();
using var inputTensor = PreprocessFrame(frame);
var results = RunInference(inputTensor);
var counts = UpdateCountingLogic(results);
UpdateUI(counts);
}
});
- 模型量化加速:
bash复制python -m onnxruntime.tools.convert_onnx_models_to_ort yolov8n.onnx --optimization_level extended
- 内存池优化:
csharp复制var memoryInfo = OrtMemoryInfo.DefaultInstance;
var inputOrtValue = OrtValue.CreateTensorValueFromMemory(
memoryInfo,
inputTensor.Buffer,
new long[] { 1, 3, 640, 640 });
5.2 常见问题排查
-
输出形状不匹配:
- 现象:运行时抛出"Invalid dimensions"异常
- 检查:
session.InputMetadata["images"].Dimensions应与输入张量严格一致 - 解决:确保预处理后的数据为[1,3,640,640]的NCHW格式
-
CUDA内存泄漏:
- 现象:长时间运行后内存持续增长
- 解决:定期调用
session.EndProfiling()并手动释放资源
-
计数误判:
- 现象:同一物体被重复计数
- 优化:增加轨迹平滑滤波和穿越方向验证
6. 实际部署经验分享
在工业现场部署时,我们总结出几个关键点:
-
光照适应:
- 在预处理阶段加入自动对比度增强
csharp复制
Cv2.CLAHE clahe = CLAHE.Create(); clahe.Apply(grayFrame, enhancedFrame); -
模型裁剪:
- 对固定场景可移除无关类别输出
python复制model = YOLO('yolov8n.pt') model.model = model.model[:-3] # 移除最后3层 -
跨平台兼容:
- 在Linux上运行需额外处理:
bash复制
dotnet add package Microsoft.ML.OnnxRuntime.Runtime.Linux-x64 -
硬件加速选择:
- Intel CPU:使用OpenVINO后端
- NVIDIA GPU:启用CUDA执行提供者
- AMD设备:配置DirectML后端
这个方案已经在多个智能仓库的货物出入统计项目中稳定运行,相比传统方案将计数准确率从82%提升到96%以上。核心优势在于YOLOv8的高精度检测配合C#的工业级执行效率,特别适合需要与PLC、MES等工业系统集成的场景。
