#if !UNITY_WSA_10_0 using OpenCVForUnity.CoreModule; using OpenCVForUnity.ImgprocModule; using OpenCVForUnity.ObjdetectModule; using OpenCVForUnity.UnityUtils; using OpenCVForUnity.UnityUtils.Helper; using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.SceneManagement; namespace OpenCVForUnityExample { /// /// FaceDetectorYN WebCam Example /// An example of detecting human face in a image of WebCamTexture using the FaceDetectorYN class. /// https://github.com/opencv/opencv/blob/master/samples/dnn/face_detect.cpp /// https://docs.opencv.org/4.5.4/d0/dd4/tutorial_dnn_face.html /// [RequireComponent(typeof(WebCamTextureToMatHelper))] public class FaceDetectorYNWebCamExample : MonoBehaviour { /// /// The FaceDetectorYN. /// FaceDetectorYN faceDetector; /// /// The size for the network input. /// int inputSizeW = 320; int inputSizeH = 320; /// /// Filter out faces of score < score_threshold. /// float scoreThreshold = 0.9f; /// /// Suppress bounding boxes of iou >= nms_threshold /// float nmsThreshold = 0.3f; /// /// Keep top_k bounding boxes before NMS. /// int topK = 5000; /// /// The bgr mat. /// Mat bgrMat; /// /// The input mat. /// Mat inputMat; /// /// The texture. /// Texture2D texture; /// /// The webcam texture to mat helper. /// WebCamTextureToMatHelper webCamTextureToMatHelper; /// /// The FPS monitor. /// FpsMonitor fpsMonitor; /// /// MODEL_FILENAME /// protected static readonly string MODEL_FILENAME = "OpenCVForUnity/objdetect/face_detection_yunet_2023mar.onnx"; protected Scalar bBoxColor = new Scalar(255, 255, 0, 255); protected Scalar[] keyPointsColors = new Scalar[] { new Scalar(0, 0, 255, 255), // # right eye new Scalar(255, 0, 0, 255), // # left eye new Scalar(255, 255, 0, 255), // # nose tip new Scalar(0, 255, 255, 255), // # mouth right new Scalar(0, 255, 0, 255), // # mouth left new Scalar(255, 255, 255, 255) }; #if UNITY_WEBGL IEnumerator getFilePath_Coroutine; #endif // Use this for initialization void Start() { fpsMonitor = GetComponent(); webCamTextureToMatHelper = gameObject.GetComponent(); //if true, The error log of the Native side OpenCV will be displayed on the Unity Editor Console. Utils.setDebugMode(true); #if UNITY_WEBGL getFilePath_Coroutine = Utils.getFilePathAsync (MODEL_FILENAME, (result) => { getFilePath_Coroutine = null; if (string.IsNullOrEmpty(result)) { Debug.LogError(MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/objdetect/setup_objdetect_module.pdf” to make the necessary setup."); } else { faceDetector = FaceDetectorYN.create(result, "", new Size(inputSizeW, inputSizeH), scoreThreshold, nmsThreshold, topK); } webCamTextureToMatHelper.Initialize (); }); StartCoroutine (getFilePath_Coroutine); #else string fd_modelPath = Utils.getFilePath(MODEL_FILENAME); if (string.IsNullOrEmpty(fd_modelPath)) { Debug.LogError(MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/objdetect/setup_objdetect_module.pdf” to make the necessary setup."); } else { faceDetector = FaceDetectorYN.create(fd_modelPath, "", new Size(inputSizeW, inputSizeH), scoreThreshold, nmsThreshold, topK); } #if UNITY_ANDROID && !UNITY_EDITOR // Avoids the front camera low light issue that occurs in only some Android devices (e.g. Google Pixel, Pixel2). webCamTextureToMatHelper.avoidAndroidFrontCameraLowLightIssue = true; #endif webCamTextureToMatHelper.Initialize(); #endif } /// /// Raises the web cam texture to mat helper initialized event. /// public void OnWebCamTextureToMatHelperInitialized() { Debug.Log("OnWebCamTextureToMatHelperInitialized"); Mat webCamTextureMat = webCamTextureToMatHelper.GetMat(); texture = new Texture2D(webCamTextureMat.cols(), webCamTextureMat.rows(), TextureFormat.RGBA32, false); Utils.matToTexture2D(webCamTextureMat, texture); gameObject.GetComponent().material.mainTexture = texture; gameObject.transform.localScale = new Vector3(webCamTextureMat.cols(), webCamTextureMat.rows(), 1); Debug.Log("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation); if (fpsMonitor != null) { fpsMonitor.Add("width", webCamTextureMat.width().ToString()); fpsMonitor.Add("height", webCamTextureMat.height().ToString()); fpsMonitor.Add("orientation", Screen.orientation.ToString()); } float width = webCamTextureMat.width(); float height = webCamTextureMat.height(); float widthScale = (float)Screen.width / width; float heightScale = (float)Screen.height / height; if (widthScale < heightScale) { Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2; } else { Camera.main.orthographicSize = height / 2; } bgrMat = new Mat(webCamTextureMat.rows(), webCamTextureMat.cols(), CvType.CV_8UC3); inputMat = new Mat(new Size(inputSizeW, inputSizeH), CvType.CV_8UC3); } /// /// Raises the web cam texture to mat helper disposed event. /// public void OnWebCamTextureToMatHelperDisposed() { Debug.Log("OnWebCamTextureToMatHelperDisposed"); if (texture != null) { Texture2D.Destroy(texture); texture = null; } if (bgrMat != null) bgrMat.Dispose(); if (inputMat != null) inputMat.Dispose(); } /// /// Raises the web cam texture to mat helper error occurred event. /// /// Error code. public void OnWebCamTextureToMatHelperErrorOccurred(WebCamTextureToMatHelper.ErrorCode errorCode) { Debug.Log("OnWebCamTextureToMatHelperErrorOccurred " + errorCode); } // Update is called once per frame void Update() { if (webCamTextureToMatHelper.IsPlaying() && webCamTextureToMatHelper.DidUpdateThisFrame()) { Mat rgbaMat = webCamTextureToMatHelper.GetMat(); if (faceDetector == null) { 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); 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); Utils.matToTexture2D(rgbaMat, texture); return; } Imgproc.cvtColor(rgbaMat, bgrMat, Imgproc.COLOR_RGBA2BGR); Detection[] detections = Detect(bgrMat); foreach (var d in detections) { DrawDetection(d, rgbaMat); } Utils.matToTexture2D(rgbaMat, texture); } } /// /// Raises the destroy event. /// void OnDestroy() { webCamTextureToMatHelper.Dispose(); if (faceDetector != null) faceDetector.Dispose(); Utils.setDebugMode(false); #if UNITY_WEBGL if (getFilePath_Coroutine != null) { StopCoroutine (getFilePath_Coroutine); ((IDisposable)getFilePath_Coroutine).Dispose (); } #endif } /// /// Raises the back button click event. /// public void OnBackButtonClick() { SceneManager.LoadScene("OpenCVForUnityExample"); } /// /// Raises the play button click event. /// public void OnPlayButtonClick() { webCamTextureToMatHelper.Play(); } /// /// Raises the pause button click event. /// public void OnPauseButtonClick() { webCamTextureToMatHelper.Pause(); } /// /// Raises the stop button click event. /// public void OnStopButtonClick() { webCamTextureToMatHelper.Stop(); } /// /// Raises the change camera button click event. /// public void OnChangeCameraButtonClick() { webCamTextureToMatHelper.requestedIsFrontFacing = !webCamTextureToMatHelper.requestedIsFrontFacing; } protected virtual Detection[] Detect(Mat image) { Imgproc.resize(image, inputMat, inputMat.size()); float scaleRatioX = (float)image.width() / inputMat.width(); float scaleRatioY = (float)image.height() / inputMat.height(); Detection[] detections; using (Mat faces = new Mat()) { // 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. // The format of each row is as follows: // x1, y1, w, h, x_re, y_re, x_le, y_le, x_nt, y_nt, x_rcm, y_rcm, x_lcm, y_lcm // , 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} // stands for the coordinates of right eye, left eye, nose tip, the right corner and left corner of the mouth respectively. faceDetector.detect(inputMat, faces); detections = new Detection[faces.rows()]; for (int i = 0; i < faces.rows(); i++) { float[] buf = new float[Detection.Size]; faces.get(i, 0, buf); for (int x = 0; x < 14; x++) { if (x % 2 == 0) { buf[x] *= scaleRatioX; } else { buf[x] *= scaleRatioY; } } GCHandle gch = GCHandle.Alloc(buf, GCHandleType.Pinned); detections[i] = (Detection)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Detection)); gch.Free(); } } return detections; } protected virtual void DrawDetection(Detection d, Mat frame) { 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); Imgproc.circle(frame, new Point(d.rightEye.x, d.rightEye.y), 2, keyPointsColors[0], 2); Imgproc.circle(frame, new Point(d.leftEye.x, d.leftEye.y), 2, keyPointsColors[1], 2); Imgproc.circle(frame, new Point(d.nose.x, d.nose.y), 2, keyPointsColors[2], 2); Imgproc.circle(frame, new Point(d.rightMouth.x, d.rightMouth.y), 2, keyPointsColors[3], 2); Imgproc.circle(frame, new Point(d.leftMouth.x, d.leftMouth.y), 2, keyPointsColors[4], 2); string label = d.score.ToString(); int[] baseLine = new int[1]; Size labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine); float top = Mathf.Max(d.xy.y, (float)labelSize.height); float left = d.xy.x; Imgproc.rectangle(frame, new Point(left, top - labelSize.height), new Point(left + labelSize.width, top + baseLine[0]), Scalar.all(255), Core.FILLED); Imgproc.putText(frame, label, new Point(left, top), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0, 255)); } [StructLayout(LayoutKind.Sequential)] public readonly struct Detection { // Bounding box public readonly Vector2 xy; public readonly Vector2 wh; // Key points public readonly Vector2 rightEye; public readonly Vector2 leftEye; public readonly Vector2 nose; public readonly Vector2 rightMouth; public readonly Vector2 leftMouth; // Confidence score [0, 1] public readonly float score; // sizeof(Detection) public const int Size = 15 * sizeof(float); }; } } #endif