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

  1. //----------------------------------------------------------------------------
  2. // Copyright (C) 2004-2025 by EMGU Corporation. All rights reserved.
  3. //----------------------------------------------------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.ComponentModel;
  7. using System.Diagnostics;
  8. using System.Text;
  9. using System.IO;
  10. using System.Drawing;
  11. using System.Linq;
  12. using System.Runtime.InteropServices;
  13. using System.Threading.Tasks;
  14. #if __ANDROID__ && __USE_ANDROID_CAMERA2__
  15. using Android.App;
  16. using Android.Content;
  17. using Android.Runtime;
  18. using Android.Views;
  19. using Android.Widget;
  20. using Android.OS;
  21. using Android.Graphics;
  22. using Android.Preferences;
  23. //using String=System.String;
  24. #endif
  25. #if __IOS__
  26. using UIKit;
  27. using CoreGraphics;
  28. #endif
  29. using Emgu.CV;
  30. using Emgu.CV.CvEnum;
  31. using Emgu.CV.Dnn;
  32. using Emgu.CV.Face;
  33. using Emgu.CV.Models;
  34. using Emgu.CV.Structure;
  35. using Emgu.CV.Util;
  36. using Emgu.Util;
  37. using Environment = System.Environment;
  38. using Point = System.Drawing.Point;
  39. namespace Emgu.CV.Platform.Maui.UI
  40. {
  41. /// <summary>
  42. /// Generic page that can take a IProcessAndRenderModel and apply it to images / camera stream
  43. /// </summary>
  44. public class ProcessAndRenderPage
  45. : ButtonTextImagePage
  46. {
  47. private VideoCapture _capture = null;
  48. private Mat _mat;
  49. private Mat _renderMat;
  50. private Mat _buffer;
  51. private String _defaultButtonText;
  52. private IProcessAndRenderModel _model;
  53. private String _deaultImage;
  54. /// <summary>
  55. /// The text displayed on the button that stops the camera.
  56. /// </summary>
  57. protected String _StopCameraButtonText = "Stop Camera";
  58. /// <summary>
  59. /// Get the model associated with this page
  60. /// </summary>
  61. public IProcessAndRenderModel Model
  62. {
  63. get
  64. {
  65. return _model;
  66. }
  67. }
  68. /// <summary>
  69. /// Initialize video capture
  70. /// </summary>
  71. /// <returns>True if video capture initialized</returns>
  72. public bool InitVideoCapture()
  73. {
  74. var openCVConfigDict = CvInvoke.ConfigDict;
  75. bool haveVideoio = (openCVConfigDict["HAVE_OPENCV_VIDEOIO"] != 0);
  76. if (haveVideoio && (
  77. Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android
  78. || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.MacCatalyst
  79. || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI))
  80. {
  81. #if __ANDROID__
  82. if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
  83. {
  84. var context = Android.App.Application.Context;
  85. var cameraManager = (Android.Hardware.Camera2.CameraManager) context.GetSystemService(Android.Content.Context.CameraService);
  86. var cameraCount = cameraManager?.GetCameraIdList()?.Length;
  87. return cameraCount > 0;
  88. }
  89. #endif
  90. if (CvInvoke.Backends.Length > 0)
  91. {
  92. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android)
  93. {
  94. _capture = new VideoCapture(0, VideoCapture.API.Android);
  95. }
  96. else
  97. {
  98. _capture = new VideoCapture();
  99. }
  100. if (_capture.IsOpened)
  101. {
  102. _capture.ImageGrabbed += _capture_ImageGrabbed;
  103. return true;
  104. }
  105. else
  106. {
  107. _capture.Dispose();
  108. _capture = null;
  109. }
  110. }
  111. }
  112. return false;
  113. }
  114. /// <summary>
  115. /// Determines the default camera option based on the device platform.
  116. /// </summary>
  117. /// <returns>
  118. /// Returns true if the device platform is iOS, otherwise it initializes video capture and returns the result of that operation.
  119. /// </returns>
  120. protected override bool GetDefaultCameraOption()
  121. {
  122. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.iOS)
  123. return true;
  124. else
  125. return InitVideoCapture();
  126. }
  127. /// <summary>
  128. /// Create a Generic page that can take a IProcessAndRenderModel and apply it to images / camera stream
  129. /// </summary>
  130. /// <param name="model">The IProcessAndRenderModel</param>
  131. /// <param name="defaultButtonText">The text to be displayed on the button</param>
  132. /// <param name="defaultImage">The default image file name to use for processing. If set to null, it will use realtime camera stream instead.</param>
  133. /// <param name="defaultLabelText">The text to be displayed on the label</param>
  134. /// <param name="additionalButtons">Additional buttons to be added to the page if needed</param>
  135. public ProcessAndRenderPage(
  136. IProcessAndRenderModel model,
  137. String defaultButtonText,
  138. String defaultImage,
  139. String defaultLabelText = null,
  140. Microsoft.Maui.Controls.Button[] additionalButtons = null
  141. )
  142. : base(additionalButtons)
  143. {
  144. #if __IOS__
  145. AllowAvCaptureSession = true;
  146. outputRecorder.BufferReceived += OutputRecorder_BufferReceived;
  147. #endif
  148. _deaultImage = defaultImage;
  149. _defaultButtonText = defaultButtonText;
  150. var button = this.GetButton();
  151. button.Text = _defaultButtonText;
  152. button.Clicked += OnButtonClicked;
  153. var label = this.GetLabel();
  154. label.Text = defaultLabelText;
  155. _model = model;
  156. Picker.SelectedIndexChanged += Picker_SelectedIndexChanged;
  157. }
  158. #if __IOS__
  159. //private int _counter = 0;
  160. private void OutputRecorder_BufferReceived(object sender, OutputRecorder.BufferReceivedEventArgs e)
  161. {
  162. if (_mat == null)
  163. _mat = new Mat();
  164. try
  165. {
  166. //_counter++;
  167. #region read image into _mat
  168. var sampleBuffer = e.Buffer;
  169. using (CoreVideo.CVPixelBuffer pixelBuffer = sampleBuffer.GetImageBuffer() as CoreVideo.CVPixelBuffer)
  170. {
  171. // Lock the base address
  172. pixelBuffer.Lock(CoreVideo.CVPixelBufferLock.ReadOnly);
  173. using (CoreImage.CIImage ciImage = new CoreImage.CIImage(pixelBuffer))
  174. {
  175. ciImage.ToArray(_mat, ImreadModes.ColorBgr);
  176. }
  177. pixelBuffer.Unlock(CoreVideo.CVPixelBufferLock.ReadOnly);
  178. }
  179. #endregion
  180. if (_renderMat == null)
  181. _renderMat = new Mat();
  182. using (InputArray iaImage = _mat.GetInputArray())
  183. iaImage.CopyTo(_renderMat);
  184. String msg = _model.ProcessAndRender(_mat, _renderMat);
  185. SetImage(_renderMat);
  186. SetMessage(msg);
  187. }
  188. catch (Exception exception)
  189. {
  190. Console.WriteLine(exception);
  191. SetImage(null);
  192. SetMessage(exception.Message);
  193. }
  194. }
  195. #endif
  196. private void Picker_SelectedIndexChanged(object sender, EventArgs e)
  197. {
  198. _model.Clear();
  199. }
  200. private bool ProcessFrame(IInputArray m)
  201. {
  202. if (_renderMat == null)
  203. _renderMat = new Mat();
  204. try
  205. {
  206. String msg;
  207. if (_model.RenderMethod == RenderType.Update)
  208. {
  209. if (_buffer == null)
  210. _buffer = new Mat();
  211. using (InputArray iaM = m.GetInputArray())
  212. {
  213. iaM.CopyTo(_buffer);
  214. msg = _model.ProcessAndRender(m, _buffer);
  215. _buffer.CopyTo(_renderMat);
  216. }
  217. }
  218. else
  219. {
  220. msg = _model.ProcessAndRender(m, _renderMat);
  221. }
  222. SetImage(_renderMat);
  223. SetMessage(msg);
  224. return true;
  225. }
  226. catch (Exception exception)
  227. {
  228. Console.WriteLine(exception);
  229. SetImage(null);
  230. SetMessage(exception.Message);
  231. #if DEBUG
  232. throw;
  233. #else
  234. return false;
  235. #endif
  236. }
  237. }
  238. private void _capture_ImageGrabbed(object sender, EventArgs e)
  239. {
  240. if (_mat == null)
  241. _mat = new Mat();
  242. _capture.Retrieve(_mat);
  243. ProcessFrame(_mat);
  244. }
  245. /// <summary>
  246. /// Function to call when the button is clicked
  247. /// </summary>
  248. /// <param name="sender">The sender</param>
  249. /// <param name="args">Additional arguments</param>
  250. protected virtual async void OnButtonClicked(Object sender, EventArgs args)
  251. {
  252. var button = GetButton();
  253. if (button.Text.Equals(_StopCameraButtonText))
  254. {
  255. #if __ANDROID__
  256. if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
  257. StopCapture();
  258. else
  259. {
  260. _capture.Stop();
  261. _capture.Dispose();
  262. _capture = null;
  263. }
  264. #elif __IOS__
  265. this.StopCaptureSession();
  266. #else
  267. _capture.Stop();
  268. _capture.Dispose();
  269. _capture = null;
  270. #endif
  271. button.Text = _defaultButtonText;
  272. Picker.IsEnabled = true;
  273. return;
  274. }
  275. else
  276. {
  277. Picker.IsEnabled = false;
  278. }
  279. Mat[] images;
  280. if (_deaultImage == null)
  281. {
  282. //Force to use Camera
  283. images = new Mat[0];
  284. }
  285. else
  286. {
  287. images = await LoadImages(new string[] { _deaultImage });
  288. if (images == null || (images.Length > 0 && images[0] == null))
  289. return;
  290. }
  291. SetMessage("Please wait while we initialize the model...");
  292. SetImage(null);
  293. Picker p = this.Picker;
  294. if ((!p.IsVisible) || p.SelectedIndex < 0)
  295. await _model.Init(DownloadManager_OnDownloadProgressChanged, null);
  296. else
  297. {
  298. await _model.Init(DownloadManager_OnDownloadProgressChanged, p.Items[p.SelectedIndex].ToString());
  299. }
  300. if (!_model.Initialized)
  301. {
  302. String failMsg = "Failed to initialize model";
  303. Console.WriteLine(failMsg);
  304. SetImage(null);
  305. SetMessage(failMsg);
  306. return;
  307. }
  308. else
  309. {
  310. SetMessage("Model initialized");
  311. }
  312. if (images.Length == 0)
  313. {
  314. #if __ANDROID__
  315. PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.Camera>();
  316. if (status != PermissionStatus.Granted)
  317. {
  318. status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
  319. if (status != PermissionStatus.Granted)
  320. {
  321. SetMessage("Failed to get camera permission.");
  322. return;
  323. }
  324. }
  325. if (CameraBackend == AndroidCameraBackend.AndroidCamera2)
  326. {
  327. StartCapture(
  328. async delegate (Object captureSender, Mat m)
  329. {
  330. //Skip the frame if busy,
  331. //Otherwise too many frames arriving and will eventually saturated the memory.
  332. if (!IsAndroidCamera2Busy)
  333. {
  334. IsAndroidCamera2Busy = true;
  335. try
  336. {
  337. //This run on the main thread, using the async pattern with the ProcessFrame is required,
  338. //otherwise it will freeze up the main thread/app.
  339. await Task.Run(
  340. () =>
  341. {
  342. ProcessFrame(m);
  343. });
  344. }
  345. finally
  346. {
  347. IsAndroidCamera2Busy = false;
  348. }
  349. }
  350. },
  351. -1,
  352. _preferredCameraId
  353. );
  354. } else
  355. {
  356. //Handle video
  357. if (_capture == null)
  358. {
  359. InitVideoCapture();
  360. }
  361. if (_capture != null)
  362. _capture.Start();
  363. }
  364. #elif __IOS__
  365. CheckVideoPermissionAndStart();
  366. #else
  367. //Handle video
  368. if (_capture == null)
  369. {
  370. SetMessage("Initializing camera, please wait...");
  371. await Task.Run(() => { this.InitVideoCapture(); });
  372. SetMessage("Camera initialized.");
  373. }
  374. if (_capture != null)
  375. _capture.Start();
  376. #endif
  377. button.Text = _StopCameraButtonText;
  378. }
  379. else
  380. {
  381. ProcessFrame(images[0]);
  382. Picker.IsEnabled = true;
  383. /*
  384. if (_renderMat == null)
  385. _renderMat = new Mat();
  386. images[0].CopyTo(_renderMat);
  387. try
  388. {
  389. String message = _model.ProcessAndRender(images[0], _renderMat);
  390. SetImage(_renderMat);
  391. SetMessage(message);
  392. Picker.IsEnabled = true;
  393. }
  394. catch (Exception exception)
  395. {
  396. Console.WriteLine(exception);
  397. SetImage(null);
  398. SetMessage(exception.Message);
  399. #if DEBUG
  400. throw;
  401. #endif
  402. }*/
  403. }
  404. }
  405. private static String ByteToSizeStr(long byteCount)
  406. {
  407. if (byteCount < 1024)
  408. {
  409. return String.Format("{0} B", byteCount);
  410. }
  411. else if (byteCount < 1024 * 1024)
  412. {
  413. return String.Format("{0} KB", byteCount / 1024);
  414. }
  415. else
  416. {
  417. return String.Format("{0} MB", byteCount / (1024 * 1024));
  418. }
  419. }
  420. /// <summary>
  421. /// Function to call when the model download progress has been changed
  422. /// </summary>
  423. /// <param name="totalBytesToReceive">The total number of bytes to be received</param>
  424. /// <param name="bytesReceived">The bytes received so far</param>
  425. /// <param name="progressPercentage">The download progress percentage</param>
  426. protected void DownloadManager_OnDownloadProgressChanged(long? totalBytesToReceive, long bytesReceived, double? progressPercentage)
  427. {
  428. String msg;
  429. if (totalBytesToReceive != null)
  430. msg = String.Format("{0} of {1} downloaded ({2}%)", ByteToSizeStr(bytesReceived), ByteToSizeStr(totalBytesToReceive.Value), progressPercentage);
  431. else
  432. msg = String.Format("{0} downloaded", ByteToSizeStr(bytesReceived));
  433. SetMessage(msg);
  434. }
  435. }
  436. }