You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求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说明文本
  • 若无GPU支持,将推理后端改为Backend.OpenCV和Target.Cpu
  • 可调整_confidenceThreshold(置信度阈值)和_nmsThreshold(非极大值抑制阈值)优化检测效果

内容的提问来源于stack exchange,提问作者Hardik Zinzala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 19:20:16