FaceDetectorYNWebCamExample.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #if !UNITY_WSA_10_0
  2. using OpenCVForUnity.CoreModule;
  3. using OpenCVForUnity.ImgprocModule;
  4. using OpenCVForUnity.ObjdetectModule;
  5. using OpenCVForUnity.UnityUtils;
  6. using OpenCVForUnity.UnityUtils.Helper;
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Runtime.InteropServices;
  11. using UnityEngine;
  12. using UnityEngine.SceneManagement;
  13. namespace OpenCVForUnityExample
  14. {
  15. /// <summary>
  16. /// FaceDetectorYN WebCam Example
  17. /// An example of detecting human face in a image of WebCamTexture using the FaceDetectorYN class.
  18. /// https://github.com/opencv/opencv/blob/master/samples/dnn/face_detect.cpp
  19. /// https://docs.opencv.org/4.5.4/d0/dd4/tutorial_dnn_face.html
  20. /// </summary>
  21. [RequireComponent(typeof(WebCamTextureToMatHelper))]
  22. public class FaceDetectorYNWebCamExample : MonoBehaviour
  23. {
  24. /// <summary>
  25. /// The FaceDetectorYN.
  26. /// </summary>
  27. FaceDetectorYN faceDetector;
  28. /// <summary>
  29. /// The size for the network input.
  30. /// </summary>
  31. int inputSizeW = 320;
  32. int inputSizeH = 320;
  33. /// <summary>
  34. /// Filter out faces of score < score_threshold.
  35. /// </summary>
  36. float scoreThreshold = 0.9f;
  37. /// <summary>
  38. /// Suppress bounding boxes of iou >= nms_threshold
  39. /// </summary>
  40. float nmsThreshold = 0.3f;
  41. /// <summary>
  42. /// Keep top_k bounding boxes before NMS.
  43. /// </summary>
  44. int topK = 5000;
  45. /// <summary>
  46. /// The bgr mat.
  47. /// </summary>
  48. Mat bgrMat;
  49. /// <summary>
  50. /// The input mat.
  51. /// </summary>
  52. Mat inputMat;
  53. /// <summary>
  54. /// The texture.
  55. /// </summary>
  56. Texture2D texture;
  57. /// <summary>
  58. /// The webcam texture to mat helper.
  59. /// </summary>
  60. WebCamTextureToMatHelper webCamTextureToMatHelper;
  61. /// <summary>
  62. /// The FPS monitor.
  63. /// </summary>
  64. FpsMonitor fpsMonitor;
  65. /// <summary>
  66. /// MODEL_FILENAME
  67. /// </summary>
  68. protected static readonly string MODEL_FILENAME = "OpenCVForUnity/objdetect/face_detection_yunet_2023mar.onnx";
  69. protected Scalar bBoxColor = new Scalar(255, 255, 0, 255);
  70. protected Scalar[] keyPointsColors = new Scalar[] {
  71. new Scalar(0, 0, 255, 255), // # right eye
  72. new Scalar(255, 0, 0, 255), // # left eye
  73. new Scalar(255, 255, 0, 255), // # nose tip
  74. new Scalar(0, 255, 255, 255), // # mouth right
  75. new Scalar(0, 255, 0, 255), // # mouth left
  76. new Scalar(255, 255, 255, 255) };
  77. #if UNITY_WEBGL
  78. IEnumerator getFilePath_Coroutine;
  79. #endif
  80. // Use this for initialization
  81. void Start()
  82. {
  83. fpsMonitor = GetComponent<FpsMonitor>();
  84. webCamTextureToMatHelper = gameObject.GetComponent<WebCamTextureToMatHelper>();
  85. //if true, The error log of the Native side OpenCV will be displayed on the Unity Editor Console.
  86. Utils.setDebugMode(true);
  87. #if UNITY_WEBGL
  88. getFilePath_Coroutine = Utils.getFilePathAsync (MODEL_FILENAME, (result) => {
  89. getFilePath_Coroutine = null;
  90. if (string.IsNullOrEmpty(result))
  91. {
  92. Debug.LogError(MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/objdetect/setup_objdetect_module.pdf” to make the necessary setup.");
  93. }
  94. else
  95. {
  96. faceDetector = FaceDetectorYN.create(result, "", new Size(inputSizeW, inputSizeH), scoreThreshold, nmsThreshold, topK);
  97. }
  98. webCamTextureToMatHelper.Initialize ();
  99. });
  100. StartCoroutine (getFilePath_Coroutine);
  101. #else
  102. string fd_modelPath = Utils.getFilePath(MODEL_FILENAME);
  103. if (string.IsNullOrEmpty(fd_modelPath))
  104. {
  105. Debug.LogError(MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/objdetect/setup_objdetect_module.pdf” to make the necessary setup.");
  106. }
  107. else
  108. {
  109. faceDetector = FaceDetectorYN.create(fd_modelPath, "", new Size(inputSizeW, inputSizeH), scoreThreshold, nmsThreshold, topK);
  110. }
  111. #if UNITY_ANDROID && !UNITY_EDITOR
  112. // Avoids the front camera low light issue that occurs in only some Android devices (e.g. Google Pixel, Pixel2).
  113. webCamTextureToMatHelper.avoidAndroidFrontCameraLowLightIssue = true;
  114. #endif
  115. webCamTextureToMatHelper.Initialize();
  116. #endif
  117. }
  118. /// <summary>
  119. /// Raises the web cam texture to mat helper initialized event.
  120. /// </summary>
  121. public void OnWebCamTextureToMatHelperInitialized()
  122. {
  123. Debug.Log("OnWebCamTextureToMatHelperInitialized");
  124. Mat webCamTextureMat = webCamTextureToMatHelper.GetMat();
  125. texture = new Texture2D(webCamTextureMat.cols(), webCamTextureMat.rows(), TextureFormat.RGBA32, false);
  126. Utils.matToTexture2D(webCamTextureMat, texture);
  127. gameObject.GetComponent<Renderer>().material.mainTexture = texture;
  128. gameObject.transform.localScale = new Vector3(webCamTextureMat.cols(), webCamTextureMat.rows(), 1);
  129. Debug.Log("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation);
  130. if (fpsMonitor != null)
  131. {
  132. fpsMonitor.Add("width", webCamTextureMat.width().ToString());
  133. fpsMonitor.Add("height", webCamTextureMat.height().ToString());
  134. fpsMonitor.Add("orientation", Screen.orientation.ToString());
  135. }
  136. float width = webCamTextureMat.width();
  137. float height = webCamTextureMat.height();
  138. float widthScale = (float)Screen.width / width;
  139. float heightScale = (float)Screen.height / height;
  140. if (widthScale < heightScale)
  141. {
  142. Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2;
  143. }
  144. else
  145. {
  146. Camera.main.orthographicSize = height / 2;
  147. }
  148. bgrMat = new Mat(webCamTextureMat.rows(), webCamTextureMat.cols(), CvType.CV_8UC3);
  149. inputMat = new Mat(new Size(inputSizeW, inputSizeH), CvType.CV_8UC3);
  150. }
  151. /// <summary>
  152. /// Raises the web cam texture to mat helper disposed event.
  153. /// </summary>
  154. public void OnWebCamTextureToMatHelperDisposed()
  155. {
  156. Debug.Log("OnWebCamTextureToMatHelperDisposed");
  157. if (texture != null)
  158. {
  159. Texture2D.Destroy(texture);
  160. texture = null;
  161. }
  162. if (bgrMat != null)
  163. bgrMat.Dispose();
  164. if (inputMat != null)
  165. inputMat.Dispose();
  166. }
  167. /// <summary>
  168. /// Raises the web cam texture to mat helper error occurred event.
  169. /// </summary>
  170. /// <param name="errorCode">Error code.</param>
  171. public void OnWebCamTextureToMatHelperErrorOccurred(WebCamTextureToMatHelper.ErrorCode errorCode)
  172. {
  173. Debug.Log("OnWebCamTextureToMatHelperErrorOccurred " + errorCode);
  174. }
  175. // Update is called once per frame
  176. void Update()
  177. {
  178. if (webCamTextureToMatHelper.IsPlaying() && webCamTextureToMatHelper.DidUpdateThisFrame())
  179. {
  180. Mat rgbaMat = webCamTextureToMatHelper.GetMat();
  181. if (faceDetector == null)
  182. {
  183. Imgproc.putText(rgbaMat, "model file is not loaded.", new Point(5, rgbaMat.rows() - 30), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false);
  184. Imgproc.putText(rgbaMat, "Please read console message.", new Point(5, rgbaMat.rows() - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false);
  185. Utils.matToTexture2D(rgbaMat, texture);
  186. return;
  187. }
  188. Imgproc.cvtColor(rgbaMat, bgrMat, Imgproc.COLOR_RGBA2BGR);
  189. Detection[] detections = Detect(bgrMat);
  190. foreach (var d in detections)
  191. {
  192. DrawDetection(d, rgbaMat);
  193. }
  194. Utils.matToTexture2D(rgbaMat, texture);
  195. }
  196. }
  197. /// <summary>
  198. /// Raises the destroy event.
  199. /// </summary>
  200. void OnDestroy()
  201. {
  202. webCamTextureToMatHelper.Dispose();
  203. if (faceDetector != null)
  204. faceDetector.Dispose();
  205. Utils.setDebugMode(false);
  206. #if UNITY_WEBGL
  207. if (getFilePath_Coroutine != null) {
  208. StopCoroutine (getFilePath_Coroutine);
  209. ((IDisposable)getFilePath_Coroutine).Dispose ();
  210. }
  211. #endif
  212. }
  213. /// <summary>
  214. /// Raises the back button click event.
  215. /// </summary>
  216. public void OnBackButtonClick()
  217. {
  218. SceneManager.LoadScene("OpenCVForUnityExample");
  219. }
  220. /// <summary>
  221. /// Raises the play button click event.
  222. /// </summary>
  223. public void OnPlayButtonClick()
  224. {
  225. webCamTextureToMatHelper.Play();
  226. }
  227. /// <summary>
  228. /// Raises the pause button click event.
  229. /// </summary>
  230. public void OnPauseButtonClick()
  231. {
  232. webCamTextureToMatHelper.Pause();
  233. }
  234. /// <summary>
  235. /// Raises the stop button click event.
  236. /// </summary>
  237. public void OnStopButtonClick()
  238. {
  239. webCamTextureToMatHelper.Stop();
  240. }
  241. /// <summary>
  242. /// Raises the change camera button click event.
  243. /// </summary>
  244. public void OnChangeCameraButtonClick()
  245. {
  246. webCamTextureToMatHelper.requestedIsFrontFacing = !webCamTextureToMatHelper.requestedIsFrontFacing;
  247. }
  248. protected virtual Detection[] Detect(Mat image)
  249. {
  250. Imgproc.resize(image, inputMat, inputMat.size());
  251. float scaleRatioX = (float)image.width() / inputMat.width();
  252. float scaleRatioY = (float)image.height() / inputMat.height();
  253. Detection[] detections;
  254. using (Mat faces = new Mat())
  255. {
  256. // The detection output faces is a two - dimension array of type CV_32F, whose rows are the detected face instances, columns are the location of a face and 5 facial landmarks.
  257. // The format of each row is as follows:
  258. // x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt, x_rcm, y_rcm, x_lcm, y_lcm
  259. // , where x1, y1, w, h are the top - left coordinates, width and height of the face bounding box, { x, y}_{ re, le, nt, rcm, lcm}
  260. // stands for the coordinates of right eye, left eye, nose tip, the right corner and left corner of the mouth respectively.
  261. faceDetector.detect(inputMat, faces);
  262. detections = new Detection[faces.rows()];
  263. for (int i = 0; i < faces.rows(); i++)
  264. {
  265. float[] buf = new float[Detection.Size];
  266. faces.get(i, 0, buf);
  267. for (int x = 0; x < 14; x++)
  268. {
  269. if (x % 2 == 0)
  270. {
  271. buf[x] *= scaleRatioX;
  272. }
  273. else
  274. {
  275. buf[x] *= scaleRatioY;
  276. }
  277. }
  278. GCHandle gch = GCHandle.Alloc(buf, GCHandleType.Pinned);
  279. detections[i] = (Detection)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Detection));
  280. gch.Free();
  281. }
  282. }
  283. return detections;
  284. }
  285. protected virtual void DrawDetection(Detection d, Mat frame)
  286. {
  287. Imgproc.rectangle(frame, new Point(d.xy.x, d.xy.y), new Point(d.xy.x + d.wh.x, d.xy.y + d.wh.y), bBoxColor, 2);
  288. Imgproc.circle(frame, new Point(d.rightEye.x, d.rightEye.y), 2, keyPointsColors[0], 2);
  289. Imgproc.circle(frame, new Point(d.leftEye.x, d.leftEye.y), 2, keyPointsColors[1], 2);
  290. Imgproc.circle(frame, new Point(d.nose.x, d.nose.y), 2, keyPointsColors[2], 2);
  291. Imgproc.circle(frame, new Point(d.rightMouth.x, d.rightMouth.y), 2, keyPointsColors[3], 2);
  292. Imgproc.circle(frame, new Point(d.leftMouth.x, d.leftMouth.y), 2, keyPointsColors[4], 2);
  293. string label = d.score.ToString();
  294. int[] baseLine = new int[1];
  295. Size labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);
  296. float top = Mathf.Max(d.xy.y, (float)labelSize.height);
  297. float left = d.xy.x;
  298. Imgproc.rectangle(frame, new Point(left, top - labelSize.height),
  299. new Point(left + labelSize.width, top + baseLine[0]), Scalar.all(255), Core.FILLED);
  300. Imgproc.putText(frame, label, new Point(left, top), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0, 255));
  301. }
  302. [StructLayout(LayoutKind.Sequential)]
  303. public readonly struct Detection
  304. {
  305. // Bounding box
  306. public readonly Vector2 xy;
  307. public readonly Vector2 wh;
  308. // Key points
  309. public readonly Vector2 rightEye;
  310. public readonly Vector2 leftEye;
  311. public readonly Vector2 nose;
  312. public readonly Vector2 rightMouth;
  313. public readonly Vector2 leftMouth;
  314. // Confidence score [0, 1]
  315. public readonly float score;
  316. // sizeof(Detection)
  317. public const int Size = 15 * sizeof(float);
  318. };
  319. }
  320. }
  321. #endif