共计 4927 个字符,预计需要花费 13 分钟才能阅读完成。
背景与需求
传统安防系统常面临两个核心问题:误报率高(False Alarm)和响应延迟大(Latency)。在实际项目中,我们经常遇到摄像头捕捉到窗帘晃动或宠物移动就触发警报的情况,而真正的人员入侵却因处理延迟错过最佳响应时间。

对于 C#开发者而言,深度学习应用开发还存在生态支持不足的问题。Python 阵营有丰富的教程和库,但 C# 领域的相关资料往往零散不全。这正是本文希望解决的痛点。
技术选型对比
YOLO 版本选择
YOLOv5 和 YOLOv8 都是优秀的目标检测模型,但在 C# 环境下部署时有明显差异:
- YOLOv5:
- ONNX 导出稳定,社区资源丰富
- 输入分辨率固定为 640×640
-
后处理(Post-processing)逻辑简单
-
YOLOv8:
- 精度更高,支持实例分割(Instance Segmentation)
- 动态输入分辨率(需注意 C# 端的预处理适配)
- 输出层结构变化较大
建议新手从 YOLOv5 开始,待熟悉流程后再迁移到 v8。
图像库选型
OpenCVSharp 和 EmguCV 是 C# 最常用的两个 OpenCV 封装:
// OpenCVSharp 示例
using OpenCvSharp;
Mat image = Cv2.ImRead("test.jpg");
// EmguCV 示例
using Emgu.CV;
using Emgu.CV.Structure;
Mat image = CvInvoke.Imread("test.jpg");
基准测试显示(1080p 图像处理):
| 操作 | OpenCVSharp | EmguCV |
|---|---|---|
| 高斯模糊 | 12ms | 15ms |
| Canny 边缘检测 | 18ms | 22ms |
| 色彩空间转换 | 5ms | 7ms |
OpenCVSharp 在性能上略胜一筹,且 API 更接近原生 OpenCV。
核心实现详解
1. ONNX 模型加载与推理
首先通过 NuGet 安装依赖:
Install-Package Microsoft.ML.OnnxRuntime
Install-Package Microsoft.ML.OnnxRuntime.Gpu
模型加载示例:
// 初始化推理会话
var options = new SessionOptions();
options.AppendExecutionProvider_CUDA(); // GPU 加速
using var session = new InferenceSession("yolov5s.onnx", options);
// 输入张量名称(可通过 Netron 查看)string inputName = session.InputMetadata.Keys.First();
2. 图像预处理管道
YOLO 需要特定的输入格式:
- 调整大小至 640×640
- 归一化到 0 - 1 范围
- 转换为 NCHW 格式(Number-Channel-Height-Width)
Mat Preprocess(Mat src)
{
// 调整大小
Mat resized = new Mat();
Cv2.Resize(src, resized, new Size(640, 640));
// 转换为 float32 并归一化
Mat normalized = new Mat();
resized.ConvertTo(normalized, MatType.CV_32FC3, 1.0/255);
// 转换为 NCHW
var inputTensor = new DenseTensor<float>(new[] {1, 3, 640, 640});
for (int y = 0; y < 640; y++)
{for (int x = 0; x < 640; x++)
{var pixel = normalized.At<Vec3f>(y, x);
inputTensor[0, 0, y, x] = pixel.Item0; // R
inputTensor[0, 1, y, x] = pixel.Item1; // G
inputTensor[0, 2, y, x] = pixel.Item2; // B
}
}
return inputTensor;
}
3. 后处理与非极大值抑制(NMS)
模型输出包含大量冗余检测框,需要 NMS 过滤:
List<Detection> Postprocess(float[] outputs, float confThreshold=0.5, float iouThreshold=0.5)
{var detections = new List<Detection>();
// 解析输出维度(YOLOv5 输出为 1x25200x85)for (int i = 0; i < 25200; i++)
{
int offset = i * 85;
float confidence = outputs[offset + 4];
if (confidence < confThreshold)
continue;
// 解析类别概率
float[] classScores = new float[80];
Array.Copy(outputs, offset + 5, classScores, 0, 80);
int classId = Array.IndexOf(classScores, classScores.Max());
// 添加检测结果
detections.Add(new Detection {Box = ParseBBox(outputs, offset),
Confidence = confidence,
ClassId = classId
});
}
// 执行 NMS
return ApplyNMS(detections, iouThreshold);
}
多线程处理架构
实时系统需要并行处理图像采集和模型推理:
flowchart TB
subgraph 采集线程
A[摄像头帧捕获] --> B[帧队列]
end
subgraph 推理线程
B --> C[预处理]
C --> D[模型推理]
D --> E[后处理]
end
subgraph UI 线程
E --> F[绘制检测框]
F --> G[触发警报]
end
关键代码实现:
// 线程安全帧队列
BlockingCollection<Mat> frameQueue = new BlockingCollection<Mat>(10);
// 采集线程
Task.Run(() =>
{using var capture = new VideoCapture(0);
while (true)
{var frame = new Mat();
capture.Read(frame);
frameQueue.Add(frame.Clone());
}
});
// 推理线程
Task.Run(() =>
{while (true)
{var frame = frameQueue.Take();
var inputTensor = Preprocess(frame);
var outputs = session.Run(new[] {inputName}, inputTensor);
var detections = Postprocess(outputs);
// 更新 UI 需 Invoke
BeginInvoke((Action)(() => UpdateUI(detections)));
}
});
入侵检测业务逻辑
区域检测实现
- 定义多边形警戒区:
List<Point> securityZone = new List<Point>
{new Point(100, 100),
new Point(900, 100),
new Point(900, 700),
new Point(100, 700)
};
- 判断人员是否进入区域:
bool IsInSecurityZone(Rect bbox)
{var center = new Point(bbox.X + bbox.Width/2, bbox.Y + bbox.Height/2);
return Cv2.PointPolygonTest(securityZone, center, false) >= 0;
}
滞留时间计算
// 跟踪字典
Dictionary<int, DateTime> personTimers = new Dictionary<int, DateTime>();
void ProcessDetections(List<Detection> detections)
{foreach (var det in detections.Where(d => d.ClassId == 0)) // 0 为人
{if (!personTimers.ContainsKey(det.TrackingId))
{personTimers[det.TrackingId] = DateTime.Now;
}
else if ((DateTime.Now - personTimers[det.TrackingId]).TotalSeconds > 30)
{TriggerAlarm("人员滞留超过 30 秒");
}
}
}
性能优化技巧
GPU 加速配置
- 确认 CUDA 和 cuDNN 版本匹配
- ONNX Runtime 需安装 GPU 包:
Install-Package Microsoft.ML.OnnxRuntime.Gpu -Version 1.15.0
- 创建会话时显式指定:
var sessionOptions = SessionOptions.MakeSessionOptionWithCudaProvider(0); // 0 为设备 ID
内存泄漏排查
重点检查以下资源释放:
- OpenCV 的
Mat对象 - ONNX 的
InferenceSession - GDI+ 绘图对象
推荐使用 using 语句或实现IDisposable:
using (var mat = new Mat())
{// 处理代码} // 自动释放
常见问题解决方案
模型量化精度损失
- 使用 QAT(Quantization Aware Training)而非 PTQ(Post-Training Quantization)
- 保留 FP32 版本的模型用于关键场景
- 测试时对比量化前后 mAP(平均精度)变化
跨平台依赖问题
Linux 部署需注意:
- 安装正确版本的 libOpenCV
- 设置 LD_LIBRARY_PATH 包含 ONNX Runtime 库路径
- 对 ARM 平台需编译对应版本的 OpenCVSharp
扩展思考
多摄像头支持
建议架构:
class CameraManager
{Dictionary<int, VideoCapture> cameras = new Dictionary<int, VideoCapture>();
void AddCamera(int index, string rtspUrl)
{var cap = new VideoCapture(rtspUrl);
cameras.Add(index, cap);
StartProcessingThread(index);
}
void StartProcessingThread(int camId)
{Task.Run(() => ProcessCameraStream(camId));
}
}
与现有系统集成
- 通过 HTTP API 触发安防设备:
async Task TriggerAlarm(string message)
{using var client = new HttpClient();
var content = new StringContent($"{{\"message\":\"{message}\"}}");
await client.PostAsync("http://alarm-system/api/trigger", content);
}
- 保存证据到 NVR:
void SaveEvidence(Mat frame, List<Detection> detections)
{string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string path = $"/nvr/evidence_{timestamp}.mp4";
using var writer = new VideoWriter(path, FourCC.H264, 25, frame.Size());
writer.Write(frame);
}
总结
本文详细介绍了如何使用 C# 和 YOLO 构建实时人员检测系统。核心要点包括:
- ONNX Runtime 的高效模型加载
- 多线程帧处理管道设计
- OpenCVSharp 的图像处理技巧
- 入侵检测的业务逻辑实现
实际部署时,建议先在测试环境验证性能,再逐步扩展到生产环境。对于关键场所,可考虑使用 YOLOv8+DeepSORT 实现更精准的跟踪功能。
正文完
