求Emgu.CV.Maui实时目标检测可用示例(.NET Maui环境)
.NET Maui + Emgu.CV.Maui.Mini 实时目标检测可用示例
1. 项目基础配置
- 安装NuGet包:
Emgu.CV.Maui.Mini(选择最新稳定版) - 在
MauiProgram.cs中注册Emgu CV服务:
builder.Services.AddEmguCV();
2. 页面布局(MainPage.xaml)
使用Emgu的CameraView获取实时帧,叠加检测结果到Image控件:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:emgu="clr-namespace:Emgu.CV.Maui;assembly=Emgu.CV.Maui.Mini" x:Class="YourAppNamespace.MainPage"> <Grid> <!-- 实时相机预览 --> <emgu:CameraView x:Name="cameraView" CaptureMode="Continuous" IsEnabled="True"/> <!-- 叠加检测结果 --> <Image x:Name="detectionOverlay" Aspect="AspectFill" InputTransparent="True"/> </Grid> </ContentPage>
3. 后台逻辑(MainPage.xaml.cs)
以YOLOv8n为例实现实时检测,包含模型加载、帧处理、结果绘制:
using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Dnn; using Emgu.CV.Structure; using Emgu.CV.Util; using System.Reflection; namespace YourAppNamespace; public partial class MainPage : ContentPage { private Net _yoloModel; private readonly List<string> _classNames = new(); private readonly Size _modelInputSize = new(640, 640); private readonly float _confidenceThreshold = 0.5f; private readonly float _nmsThreshold = 0.4f; public MainPage() { InitializeComponent(); } protected override void OnAppearing() { base.OnAppearing(); LoadDetectionModel(); cameraView.FrameReady += ProcessCameraFrame; } protected override void OnDisappearing() { base.OnDisappearing(); cameraView.FrameReady -= ProcessCameraFrame; _yoloModel?.Dispose(); } // 加载YOLO模型和类别名称 private void LoadDetectionModel() { // 读取嵌入式的YOLOv8n模型文件(需放在Resources/Raw,设置生成操作为EmbeddedResource) var modelStream = Assembly.GetExecutingAssembly() .GetManifestResourceStream("YourAppNamespace.Resources.Raw.yolov8n.onnx"); _yoloModel = DnnInvoke.ReadNetFromOnnx(modelStream); // 根据设备选择推理后端(有GPU用Cuda,否则用CPU) _yoloModel.SetPreferableBackend(Backend.Cuda); _yoloModel.SetPreferableTarget(Target.Cuda); // 读取COCO类别名称文件 var classStream = Assembly.GetExecutingAssembly() .GetManifestResourceStream("YourAppNamespace.Resources.Raw.coco.names"); using var reader = new StreamReader(classStream); string line; while ((line = reader.ReadLine()) != null) { _classNames.Add(line.Trim()); } } // 处理相机帧并执行检测 private void ProcessCameraFrame(object sender, Emgu.CV.Maui.FrameReadyEventArgs e) { using var frame = e.Frame; if (frame == null || _yoloModel == null) return; // 帧预处理:转换为模型输入格式 using var blob = DnnInvoke.BlobFromImage( frame, 1 / 255.0, _modelInputSize, new Scalar(0, 0, 0), swapRB: true, crop: false); _yoloModel.SetInput(blob); // 模型推理 using var outputs = _yoloModel.Forward(); var validDetections = ParseYoloOutputs(outputs, frame.Size); // 在帧上绘制检测框 DrawDetections(frame, validDetections); // 更新UI显示结果 MainThread.BeginInvokeOnMainThread(() => { detectionOverlay.Source = frame.ToImageSource(); }); } // 解析YOLO输出结果 private List<(float x1, float y1, float x2, float y2, float conf, int classId)> ParseYoloOutputs(Mat outputs, Size frameSize) { var detections = new List<(float x1, float y1, float x2, float y2, float conf, int classId)>(); var rows = outputs.Rows; for (int i = 0; i < rows; i++) { var row = outputs.Row(i); var confScores = row.ColRange(4, outputs.Cols).ToArray<float>(); var maxConf = confScores.Max(); // 过滤低置信度结果 if (maxConf < _confidenceThreshold) continue; var classId = Array.IndexOf(confScores, maxConf); var x = row.GetValue<float>(0); var y = row.GetValue<float>(1); var w = row.GetValue<float>(2); var h = row.GetValue<float>(3); // 转换为原始帧坐标 var x1 = (x - w / 2) * frameSize.Width / _modelInputSize.Width; var y1 = (y - h / 2) * frameSize.Height / _modelInputSize.Height; var x2 = (x + w / 2) * frameSize.Width / _modelInputSize.Width; var y2 = (y + h / 2) * frameSize.Height / _modelInputSize.Height; // 确保坐标在帧范围内 x1 = Math.Clamp(x1, 0, frameSize.Width); y1 = Math.Clamp(y1, 0, frameSize.Height); x2 = Math.Clamp(x2, 0, frameSize.Width); y2 = Math.Clamp(y2, 0, frameSize.Height); detections.Add((x1, y1, x2, y2, maxConf, classId)); } // 非极大值抑制(去除重复检测框) var indices = new VectorOfInt(); var boxes = detections.Select(d => new Rectangle((int)d.x1, (int)d.y1, (int)(d.x2 - d.x1), (int)(d.y2 - d.y1))).ToArray(); var confidences = detections.Select(d => d.conf).ToArray(); DnnInvoke.NMSBoxes(boxes, confidences, _confidenceThreshold, _nmsThreshold, indices); var filteredDetections = new List<(float x1, float y1, float x2, float y2, float conf, int classId)>(); for (int i = 0; i < indices.Size; i++) { filteredDetections.Add(detections[indices[i]]); } return filteredDetections; } // 绘制检测框和标签 private void DrawDetections(Mat frame, List<(float x1, float y1, float x2, float y2, float conf, int classId)> detections) { foreach (var det in detections) { var rect = new Rectangle((int)det.x1, (int)det.y1, (int)(det.x2 - det.x1), (int)(det.y2 - det.y1)); // 绘制红色边框 CvInvoke.Rectangle(frame, rect, new Bgr(Color.Red).MCvScalar, 2); // 绘制标签背景和文本 var label = $"{_classNames[det.classId]}: {det.conf:P2}"; var labelSize = CvInvoke.GetTextSize(label, FontFace.HersheySimplex, 0.5, 1, out var baseline); var labelRect = new Rectangle(rect.X, rect.Y - labelSize.Height - baseline, labelSize.Width, labelSize.Height + baseline); CvInvoke.Rectangle(frame, labelRect, new Bgr(Color.Red).MCvScalar, -1); CvInvoke.PutText(frame, label, new Point(rect.X, rect.Y - baseline), FontFace.HersheySimplex, 0.5, new Bgr(Color.White).MCvScalar, 1); } } }
4. 关键注意事项
- 模型文件(如
yolov8n.onnx)和类别文件(coco.names)需放入项目的Resources/Raw目录,设置生成操作为Embedded Resource - 平台权限配置:
- Android:在
AndroidManifest.xml中添加相机权限<uses-permission android:name="android.permission.CAMERA" /> - iOS:在
Info.plist中添加NSCameraUsageDescription说明文本
- Android:在
- 若无GPU支持,将推理后端改为
Backend.OpenCV和Target.Cpu - 可调整
_confidenceThreshold(置信度阈值)和_nmsThreshold(非极大值抑制阈值)优化检测效果
内容的提问来源于stack exchange,提问作者Hardik Zinzala
相关产品推荐
相关产品推荐

