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

.NET MAUI Android中EmguCV Mat对象为空且ImageGrabbed事件不触发

.NET MAUI中使用Emgu.CV捕获相机帧失败:Mat为空且ImageGrabbed事件不触发

我在.NET MAUI的Android平台上用Emgu.CV捕获相机帧,已经安装了对应平台的Emgu.CV运行时。能正常访问相机,但Mat对象始终为空,无法获取画面;同时_capture.ImageGrabbed += _capture_ImageGrabbed;注册的事件完全不触发。相同代码在Windows Forms和WPF里能正常运行。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_VIDEO" />
    <queries>
        <intent>
            <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
        <intent>
            <action android:name="android.media.action.VIDEO_CAPTURE" />
        </intent>
    </queries>
</manifest>

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
    private VideoCapture _capture = null;
    //private bool _captureInProgress;
    private Mat _frame;

    public MainPage()
    {
        InitializeComponent();
        //CvInvoke.UseOpenCL = false;
        CvInvokeAndroid.Init();
        _capture = new VideoCapture(0, VideoCapture.API.Android);
        _capture.ImageGrabbed += _capture_ImageGrabbed;
        _frame = new Mat();
    }

    private void captureButton_Clicked(object sender, EventArgs e)
    {
        _capture.Start();        
    }

    private void _capture_ImageGrabbed(object sender, EventArgs e)
    {
        _capture.Retrieve(_frame, 0);
    }

    private void ManuelFrame_Clicked(object sender, EventArgs e)
    {
        _capture.Retrieve(_frame, 0);
        if (_frame.IsEmpty)
        {
            statusLabel.Text = "frame is null";
        }
        else statusLabel.Text = "frame is not null";
    }
}

相关截图

调试状态截图
Android模拟器界面
相机访问状态截图

可以确认相机能被访问,但调用Retrieve后Mat还是空。试过切换到Android 13(API 33)、手动触发获取帧,都没用。


更新
我照搬了EmguCV官方MAUI示例代码,但ImageGrabbed事件依旧不触发,Mat还是空。示例代码如下:

public partial class MainPage : ContentPage
{
    private VideoCapture _capture = null;
    //private bool _captureInProgress;
    private Mat _mat;
    private Mat _renderMat;
    private string _defaultButtonText = "Start Camera";
    protected string _StopCameraButtonText = "Stop Camera";
    private string _deaultImage;
    ButtonTextImagePage buttonPage = new ButtonTextImagePage();

    private IProcessAndRenderModel _model;

    public MainPage()
    {
        InitializeComponent();
        //CvInvoke.UseOpenCL = false;
        CvInvokeAndroid.Init();
        InitVideoCapture();
    }

    private void _capture_ImageGrabbed(object sender, EventArgs e)
    {
        if (_mat == null)
            _mat = new Mat();
        _capture.Retrieve(_mat);

        if (_renderMat == null)
            _renderMat = new Mat();

        _mat.CopyTo(_renderMat);
    }

    protected virtual async void captureButton_Clicked(object sender, EventArgs e)
    {
        var button = buttonPage.GetButton();

        if(captureButton.Text.Equals(_StopCameraButtonText))
        {
#if __ANDROID__ && __USE_ANDROID_CAMERA2__
                StopCapture();
                //AndroidImageView.Visibility = ViewStates.Invisible;
#else
            _capture.Stop();
            _capture.Dispose();
            _capture = null;
#endif
            captureButton.Text = _defaultButtonText;
            return;
        }

        Mat[] images;
        if(_deaultImage == null)
        {
            images = new Mat[0];
        }
        else
        {
            images = await buttonPage.LoadImages(new string[] { _deaultImage });

            if (images == null || (images.Length > 0 && images[0] == null)) return;
        }

        if(images.Length == 0)
        {
#if __ANDROID__ && __USE_ANDROID_CAMERA2__
                
                StartCapture(async delegate (Object captureSender, Mat m)
                {
                    //Skip the frame if busy, 
                    //Otherwise too many frames arriving and will eventually saturated the memory.
                    if (!_isBusy)
                    {
                        _isBusy = true;
                        try
                        {
                            String message = String.Empty;
                            await Task.Run(() => 
                            {
                                if (_renderMat == null)
                                    _renderMat = new Mat();
                                using (InputArray iaImage = m.GetInputArray())
                                {
                                    iaImage.CopyTo(_renderMat);
                                }
                                message = _model.ProcessAndRender(m, _renderMat);
                            });
                            SetImage(_renderMat);
                            SetMessage(message);

                        }
                        finally
                        {
                            _isBusy = false;
                        }
                    }
                });
#else
            //Handle video
            if (_capture == null)
            {
                InitVideoCapture();
            }

            if (_capture != null)
                _capture.Start();
#endif
            captureButton.Text = _StopCameraButtonText;
        }
        else
        {
            if (_renderMat == null) _renderMat = new Mat();
            images[0].CopyTo(_renderMat);
        }

    }

    private void InitVideoCapture()
    {
        var openCVConfigDict = CvInvoke.ConfigDict;
        bool haveVideoio = (openCVConfigDict["HAVE_OPENCV_VIDEOIO"] != 0);
        if (haveVideoio && Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android)
        {
#if __ANDROID__ && __USE_ANDROID_CAMERA2__
                return true;
#else
            if (CvInvoke.Backends.Length > 0)
            {
                if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android)
                {
                    _capture = new VideoCapture(0, VideoCapture.API.Android);
                }
                else
                {
                    _capture = new VideoCapture();
                }
                if (_capture.IsOpened)
                {
                    _capture.ImageGrabbed += _capture_ImageGrabbed;
                    return;
                }
                else
                {
                    _capture.Dispose();
                    _capture = null;
                }
            }
#endif
        }
        return;
    }
}

解决方法

1. 动态申请相机权限

Android 6.0+要求动态申请相机权限,仅在Manifest声明无效。在启动相机前添加权限检查:

private async Task CheckCameraPermission()
{
    var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
    if (status != PermissionStatus.Granted)
    {
        status = await Permissions.RequestAsync<Permissions.Camera>();
    }
    if (status != PermissionStatus.Granted)
    {
        statusLabel.Text = "相机权限未授权";
        return;
    }
}

修改captureButton_Clicked方法,先执行权限检查:

private async void captureButton_Clicked(object sender, EventArgs e)
{
    await CheckCameraPermission();
    if (_capture != null && _capture.IsOpened)
    {
        _capture.Start();
    }
}

2. 修正VideoCapture初始化逻辑

  • 检查_capture.IsOpened是否为true,如果为false,尝试更换设备索引(比如用1访问前置相机):
    _capture = new VideoCapture(1, VideoCapture.API.Android);
    
  • 或者不指定设备索引,让系统自动选择可用相机:
    _capture = new VideoCapture(VideoCapture.API.Android);
    

3. 改用手动轮询获取帧

如果ImageGrabbed事件始终无法触发,用定时器手动轮询替代:

private Timer _frameTimer;

// 在MainPage构造函数或权限检查通过后初始化
_frameTimer = new Timer(async (state) =>
{
    if (_capture == null || !_capture.IsOpened) return;

    using var tempMat = new Mat();
    if (_capture.Read(tempMat) && !tempMat.IsEmpty)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            tempMat.CopyTo(_frame);
            statusLabel.Text = "frame is not null";
            // 这里可以添加帧渲染到UI的逻辑
        });
    }
}, null, 0, 30); // 30ms间隔,约30fps

停止相机时记得释放定时器:

private void StopCamera()
{
    _capture?.Stop();
    _capture?.Dispose();
    _capture = null;
    _frameTimer?.Dispose();
}

4. 确认Emgu.CV版本与编译选项

  • 确保安装的是Emgu.CV.Maui包,且版本为最新稳定版;
  • 如果尝试使用Camera2 API,在Android项目的编译符号中添加__USE_ANDROID_CAMERA2__(右键项目→属性→生成→条件编译符号)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:45:56