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

//----------------------------------------------------------------------------
// 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);
}
}
}