mirror of https://github.com/emgucv/emgucv.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
487 lines
16 KiB
487 lines
16 KiB
//----------------------------------------------------------------------------
|
|
// Copyright (C) 2004-2025 by EMGU Corporation. All rights reserved.
|
|
//----------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
#if __ANDROID__ && __USE_ANDROID_CAMERA2__
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.Runtime;
|
|
using Android.Views;
|
|
using Android.Widget;
|
|
using Android.OS;
|
|
using Android.Graphics;
|
|
using Android.Preferences;
|
|
//using String=System.String;
|
|
#endif
|
|
|
|
#if __IOS__
|
|
using UIKit;
|
|
using CoreGraphics;
|
|
#endif
|
|
|
|
using Emgu.CV;
|
|
using Emgu.CV.CvEnum;
|
|
using Emgu.CV.Dnn;
|
|
using Emgu.CV.Face;
|
|
using Emgu.CV.Models;
|
|
using Emgu.CV.Structure;
|
|
using Emgu.CV.Util;
|
|
using Emgu.Util;
|
|
using Environment = System.Environment;
|
|
using Point = System.Drawing.Point;
|
|
|
|
namespace Emgu.CV.Platform.Maui.UI
|
|
{
|
|
/// <summary>
|
|
/// Generic page that can take a IProcessAndRenderModel and apply it to images / camera stream
|
|
/// </summary>
|
|
public class ProcessAndRenderPage
|
|
: ButtonTextImagePage
|
|
{
|
|
private VideoCapture _capture = null;
|
|
private Mat _mat;
|
|
private Mat _renderMat;
|
|
private Mat _buffer;
|
|
private String _defaultButtonText;
|
|
private IProcessAndRenderModel _model;
|
|
private String _deaultImage;
|
|
|
|
/// <summary>
|
|
/// The text displayed on the button that stops the camera.
|
|
/// </summary>
|
|
protected String _StopCameraButtonText = "Stop Camera";
|
|
|
|
/// <summary>
|
|
/// Get the model associated with this page
|
|
/// </summary>
|
|
public IProcessAndRenderModel Model
|
|
{
|
|
get
|
|
{
|
|
return _model;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize video capture
|
|
/// </summary>
|
|
/// <returns>True if video capture initialized</returns>
|
|
public bool InitVideoCapture()
|
|
{
|
|
var openCVConfigDict = CvInvoke.ConfigDict;
|
|
bool haveVideoio = (openCVConfigDict["HAVE_OPENCV_VIDEOIO"] != 0);
|
|
if (haveVideoio && (
|
|
Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android
|
|
|| Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.MacCatalyst
|
|
|| Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI))
|
|
{
|
|
#if __ANDROID__
|
|
if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
|
|
{
|
|
var context = Android.App.Application.Context;
|
|
var cameraManager = (Android.Hardware.Camera2.CameraManager) context.GetSystemService(Android.Content.Context.CameraService);
|
|
var cameraCount = cameraManager?.GetCameraIdList()?.Length;
|
|
return cameraCount > 0;
|
|
}
|
|
#endif
|
|
|
|
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 true;
|
|
}
|
|
else
|
|
{
|
|
_capture.Dispose();
|
|
_capture = null;
|
|
}
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the default camera option based on the device platform.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns true if the device platform is iOS, otherwise it initializes video capture and returns the result of that operation.
|
|
/// </returns>
|
|
protected override bool GetDefaultCameraOption()
|
|
{
|
|
if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.iOS)
|
|
return true;
|
|
else
|
|
return InitVideoCapture();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a Generic page that can take a IProcessAndRenderModel and apply it to images / camera stream
|
|
/// </summary>
|
|
/// <param name="model">The IProcessAndRenderModel</param>
|
|
/// <param name="defaultButtonText">The text to be displayed on the button</param>
|
|
/// <param name="defaultImage">The default image file name to use for processing. If set to null, it will use realtime camera stream instead.</param>
|
|
/// <param name="defaultLabelText">The text to be displayed on the label</param>
|
|
/// <param name="additionalButtons">Additional buttons to be added to the page if needed</param>
|
|
public ProcessAndRenderPage(
|
|
IProcessAndRenderModel model,
|
|
String defaultButtonText,
|
|
String defaultImage,
|
|
String defaultLabelText = null,
|
|
Microsoft.Maui.Controls.Button[] additionalButtons = null
|
|
)
|
|
: base(additionalButtons)
|
|
{
|
|
#if __IOS__
|
|
AllowAvCaptureSession = true;
|
|
outputRecorder.BufferReceived += OutputRecorder_BufferReceived;
|
|
#endif
|
|
_deaultImage = defaultImage;
|
|
_defaultButtonText = defaultButtonText;
|
|
|
|
var button = this.GetButton();
|
|
button.Text = _defaultButtonText;
|
|
button.Clicked += OnButtonClicked;
|
|
|
|
var label = this.GetLabel();
|
|
label.Text = defaultLabelText;
|
|
|
|
_model = model;
|
|
|
|
Picker.SelectedIndexChanged += Picker_SelectedIndexChanged;
|
|
}
|
|
|
|
#if __IOS__
|
|
|
|
//private int _counter = 0;
|
|
private void OutputRecorder_BufferReceived(object sender, OutputRecorder.BufferReceivedEventArgs e)
|
|
{
|
|
if (_mat == null)
|
|
_mat = new Mat();
|
|
try
|
|
{
|
|
//_counter++;
|
|
#region read image into _mat
|
|
var sampleBuffer = e.Buffer;
|
|
using (CoreVideo.CVPixelBuffer pixelBuffer = sampleBuffer.GetImageBuffer() as CoreVideo.CVPixelBuffer)
|
|
{
|
|
// Lock the base address
|
|
pixelBuffer.Lock(CoreVideo.CVPixelBufferLock.ReadOnly);
|
|
using (CoreImage.CIImage ciImage = new CoreImage.CIImage(pixelBuffer))
|
|
{
|
|
ciImage.ToArray(_mat, ImreadModes.ColorBgr);
|
|
}
|
|
pixelBuffer.Unlock(CoreVideo.CVPixelBufferLock.ReadOnly);
|
|
}
|
|
#endregion
|
|
|
|
if (_renderMat == null)
|
|
_renderMat = new Mat();
|
|
|
|
using (InputArray iaImage = _mat.GetInputArray())
|
|
iaImage.CopyTo(_renderMat);
|
|
|
|
String msg = _model.ProcessAndRender(_mat, _renderMat);
|
|
SetImage(_renderMat);
|
|
SetMessage(msg);
|
|
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine(exception);
|
|
SetImage(null);
|
|
SetMessage(exception.Message);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
_model.Clear();
|
|
}
|
|
|
|
private bool ProcessFrame(IInputArray m)
|
|
{
|
|
if (_renderMat == null)
|
|
_renderMat = new Mat();
|
|
|
|
try
|
|
{
|
|
String msg;
|
|
if (_model.RenderMethod == RenderType.Update)
|
|
{
|
|
if (_buffer == null)
|
|
_buffer = new Mat();
|
|
using (InputArray iaM = m.GetInputArray())
|
|
{
|
|
iaM.CopyTo(_buffer);
|
|
msg = _model.ProcessAndRender(m, _buffer);
|
|
_buffer.CopyTo(_renderMat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
msg = _model.ProcessAndRender(m, _renderMat);
|
|
}
|
|
|
|
SetImage(_renderMat);
|
|
SetMessage(msg);
|
|
return true;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine(exception);
|
|
SetImage(null);
|
|
SetMessage(exception.Message);
|
|
#if DEBUG
|
|
throw;
|
|
#else
|
|
return false;
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
private void _capture_ImageGrabbed(object sender, EventArgs e)
|
|
{
|
|
if (_mat == null)
|
|
_mat = new Mat();
|
|
_capture.Retrieve(_mat);
|
|
|
|
ProcessFrame(_mat);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to call when the button is clicked
|
|
/// </summary>
|
|
/// <param name="sender">The sender</param>
|
|
/// <param name="args">Additional arguments</param>
|
|
protected virtual async void OnButtonClicked(Object sender, EventArgs args)
|
|
{
|
|
var button = GetButton();
|
|
|
|
if (button.Text.Equals(_StopCameraButtonText))
|
|
{
|
|
#if __ANDROID__
|
|
if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
|
|
StopCapture();
|
|
else
|
|
{
|
|
_capture.Stop();
|
|
_capture.Dispose();
|
|
_capture = null;
|
|
}
|
|
#elif __IOS__
|
|
this.StopCaptureSession();
|
|
#else
|
|
_capture.Stop();
|
|
_capture.Dispose();
|
|
_capture = null;
|
|
#endif
|
|
|
|
button.Text = _defaultButtonText;
|
|
Picker.IsEnabled = true;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Picker.IsEnabled = false;
|
|
}
|
|
|
|
Mat[] images;
|
|
if (_deaultImage == null)
|
|
{
|
|
//Force to use Camera
|
|
images = new Mat[0];
|
|
}
|
|
else
|
|
{
|
|
images = await LoadImages(new string[] { _deaultImage });
|
|
|
|
if (images == null || (images.Length > 0 && images[0] == null))
|
|
return;
|
|
}
|
|
|
|
SetMessage("Please wait while we initialize the model...");
|
|
SetImage(null);
|
|
|
|
Picker p = this.Picker;
|
|
if ((!p.IsVisible) || p.SelectedIndex < 0)
|
|
await _model.Init(DownloadManager_OnDownloadProgressChanged, null);
|
|
else
|
|
{
|
|
await _model.Init(DownloadManager_OnDownloadProgressChanged, p.Items[p.SelectedIndex].ToString());
|
|
}
|
|
|
|
if (!_model.Initialized)
|
|
{
|
|
String failMsg = "Failed to initialize model";
|
|
Console.WriteLine(failMsg);
|
|
SetImage(null);
|
|
SetMessage(failMsg);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetMessage("Model initialized");
|
|
}
|
|
|
|
if (images.Length == 0)
|
|
{
|
|
#if __ANDROID__
|
|
PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.Camera>();
|
|
if (status != PermissionStatus.Granted)
|
|
{
|
|
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
|
if (status != PermissionStatus.Granted)
|
|
{
|
|
SetMessage("Failed to get camera permission.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
|
|
{
|
|
StartCapture(
|
|
async delegate (Object captureSender, Mat m)
|
|
{
|
|
//Skip the frame if busy,
|
|
//Otherwise too many frames arriving and will eventually saturated the memory.
|
|
if (!IsAndroidCamera2Busy)
|
|
{
|
|
IsAndroidCamera2Busy = true;
|
|
try
|
|
{
|
|
//This run on the main thread, using the async pattern with the ProcessFrame is required,
|
|
//otherwise it will freeze up the main thread/app.
|
|
await Task.Run(
|
|
() =>
|
|
{
|
|
ProcessFrame(m);
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
IsAndroidCamera2Busy = false;
|
|
}
|
|
}
|
|
},
|
|
-1,
|
|
_preferredCameraId
|
|
);
|
|
} else
|
|
{
|
|
//Handle video
|
|
if (_capture == null)
|
|
{
|
|
InitVideoCapture();
|
|
}
|
|
|
|
if (_capture != null)
|
|
_capture.Start();
|
|
}
|
|
#elif __IOS__
|
|
CheckVideoPermissionAndStart();
|
|
#else
|
|
//Handle video
|
|
if (_capture == null)
|
|
{
|
|
SetMessage("Initializing camera, please wait...");
|
|
await Task.Run(() => { this.InitVideoCapture(); });
|
|
SetMessage("Camera initialized.");
|
|
|
|
}
|
|
|
|
if (_capture != null)
|
|
_capture.Start();
|
|
|
|
#endif
|
|
button.Text = _StopCameraButtonText;
|
|
}
|
|
else
|
|
{
|
|
ProcessFrame(images[0]);
|
|
Picker.IsEnabled = true;
|
|
/*
|
|
if (_renderMat == null)
|
|
_renderMat = new Mat();
|
|
images[0].CopyTo(_renderMat);
|
|
|
|
try
|
|
{
|
|
String message = _model.ProcessAndRender(images[0], _renderMat);
|
|
|
|
SetImage(_renderMat);
|
|
SetMessage(message);
|
|
Picker.IsEnabled = true;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Console.WriteLine(exception);
|
|
SetImage(null);
|
|
SetMessage(exception.Message);
|
|
|
|
#if DEBUG
|
|
throw;
|
|
#endif
|
|
}*/
|
|
}
|
|
}
|
|
|
|
private static String ByteToSizeStr(long byteCount)
|
|
{
|
|
if (byteCount < 1024)
|
|
{
|
|
return String.Format("{0} B", byteCount);
|
|
}
|
|
else if (byteCount < 1024 * 1024)
|
|
{
|
|
return String.Format("{0} KB", byteCount / 1024);
|
|
}
|
|
else
|
|
{
|
|
return String.Format("{0} MB", byteCount / (1024 * 1024));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to call when the model download progress has been changed
|
|
/// </summary>
|
|
/// <param name="totalBytesToReceive">The total number of bytes to be received</param>
|
|
/// <param name="bytesReceived">The bytes received so far</param>
|
|
/// <param name="progressPercentage">The download progress percentage</param>
|
|
protected void DownloadManager_OnDownloadProgressChanged(long? totalBytesToReceive, long bytesReceived, double? progressPercentage)
|
|
{
|
|
String msg;
|
|
if (totalBytesToReceive != null)
|
|
msg = String.Format("{0} of {1} downloaded ({2}%)", ByteToSizeStr(bytesReceived), ByteToSizeStr(totalBytesToReceive.Value), progressPercentage);
|
|
else
|
|
msg = String.Format("{0} downloaded", ByteToSizeStr(bytesReceived));
|
|
SetMessage(msg);
|
|
}
|
|
|
|
}
|
|
}
|