#if !(PLATFORM_LUMIN && !UNITY_EDITOR) #if !UNITY_WSA_10_0 using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.SceneManagement; using OpenCVForUnity.CoreModule; using OpenCVForUnity.DnnModule; using OpenCVForUnity.ImgprocModule; using OpenCVForUnity.ImgcodecsModule; using OpenCVForUnity.ObjdetectModule; using OpenCVForUnity.UnityUtils; using OpenCVForUnity.UnityUtils.Helper; using OpenCVRect = OpenCVForUnity.CoreModule.Rect; using OpenCVRange = OpenCVForUnity.CoreModule.Range; using System.Runtime.InteropServices; using System.Linq; namespace OpenCVForUnityExample { /// /// Facial Expression Recognition Example /// An example of using OpenCV dnn module with Facial Expression Recognition. /// Referring to https://github.com/opencv/opencv_zoo/tree/master/models/facial_expression_recognition. /// [RequireComponent(typeof(WebCamTextureToMatHelper))] public class FacialExpressionRecognitionExample : MonoBehaviour { [Header("TEST")] [TooltipAttribute("Path to test input image.")] public string testInputImage; /// /// The texture. /// Texture2D texture; /// /// The webcam texture to mat helper. /// WebCamTextureToMatHelper webCamTextureToMatHelper; /// /// The bgr mat. /// Mat bgrMat; /// /// The facial expression recognizer. /// FacialExpressionRecognizer facialExpressionRecognizer; /// /// The FPS monitor. /// FpsMonitor fpsMonitor; /// /// FACIAL_EXPRESSION_RECOGNITION_MODEL_FILENAME /// protected static readonly string FACIAL_EXPRESSION_RECOGNITION_MODEL_FILENAME = "OpenCVForUnity/dnn/facial_expression_recognition_mobilefacenet_2022july.onnx"; /// /// The facial expression recognition model filepath. /// string facial_expression_recognition_model_filepath; /// /// FACE_RECOGNITION_MODEL_FILENAME /// protected static readonly string FACE_RECOGNITION_MODEL_FILENAME = "OpenCVForUnity/dnn/face_recognition_sface_2021dec.onnx"; /// /// The face recognition model filepath. /// string face_recognition_model_filepath; /// /// The FaceDetectorYN Model. /// FaceDetectorYNModel faceDetector; int inputSizeW = 320; int inputSizeH = 320; float scoreThreshold = 0.9f; float nmsThreshold = 0.3f; int topK = 5000; /// /// FACE_DETECTION_MODEL_FILENAME /// protected static readonly string FACE_DETECTION_MODEL_FILENAME = "OpenCVForUnity/dnn/face_detection_yunet_2022mar.onnx"; /// /// The face detection model filepath. /// string face_detection_model_filepath; #if UNITY_WEBGL IEnumerator getFilePath_Coroutine; #endif // Use this for initialization void Start() { fpsMonitor = GetComponent(); webCamTextureToMatHelper = gameObject.GetComponent(); #if UNITY_WEBGL getFilePath_Coroutine = GetFilePath(); StartCoroutine(getFilePath_Coroutine); #else face_detection_model_filepath = Utils.getFilePath(FACE_DETECTION_MODEL_FILENAME); facial_expression_recognition_model_filepath = Utils.getFilePath(FACIAL_EXPRESSION_RECOGNITION_MODEL_FILENAME); face_recognition_model_filepath = Utils.getFilePath(FACE_RECOGNITION_MODEL_FILENAME); Run(); #endif } #if UNITY_WEBGL private IEnumerator GetFilePath() { var getFilePathAsync_0_Coroutine = Utils.getFilePathAsync(FACE_DETECTION_MODEL_FILENAME, (result) => { face_detection_model_filepath = result; }); yield return getFilePathAsync_0_Coroutine; var getFilePathAsync_1_Coroutine = Utils.getFilePathAsync(FACIAL_EXPRESSION_RECOGNITION_MODEL_FILENAME, (result) => { facial_expression_recognition_model_filepath = result; }); yield return getFilePathAsync_1_Coroutine; var getFilePathAsync_2_Coroutine = Utils.getFilePathAsync(FACE_RECOGNITION_MODEL_FILENAME, (result) => { face_recognition_model_filepath = result; }); yield return getFilePathAsync_2_Coroutine; getFilePath_Coroutine = null; Run(); } #endif // Use this for initialization void Run() { //if true, The error log of the Native side OpenCV will be displayed on the Unity Editor Console. Utils.setDebugMode(true); if (string.IsNullOrEmpty(face_detection_model_filepath)) { Debug.LogError(FACE_DETECTION_MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/dnn/setup_dnn_module.pdf” to make the necessary setup."); } else { faceDetector = new FaceDetectorYNModel(face_detection_model_filepath, "", new Size(inputSizeW, inputSizeH), scoreThreshold, nmsThreshold, topK); } if (string.IsNullOrEmpty(facial_expression_recognition_model_filepath) || string.IsNullOrEmpty(face_recognition_model_filepath)) { Debug.LogError(FACIAL_EXPRESSION_RECOGNITION_MODEL_FILENAME + " or " + FACE_RECOGNITION_MODEL_FILENAME + " is not loaded. Please read “StreamingAssets/OpenCVForUnity/dnn/setup_dnn_module.pdf” to make the necessary setup."); } else { facialExpressionRecognizer = new FacialExpressionRecognizer(facial_expression_recognition_model_filepath, face_recognition_model_filepath, ""); } if (string.IsNullOrEmpty(testInputImage)) { #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(); } else { ///////////////////// // TEST var getFilePathAsync_0_Coroutine = Utils.getFilePathAsync("OpenCVForUnity/dnn/" + testInputImage, (result) => { string test_input_image_filepath = result; if (string.IsNullOrEmpty(test_input_image_filepath)) Debug.Log("The file:" + testInputImage + " did not exist in the folder “Assets/StreamingAssets/OpenCVForUnity/dnn”."); Mat img = Imgcodecs.imread(test_input_image_filepath); if (img.empty()) { img = new Mat(424, 640, CvType.CV_8UC3, new Scalar(0, 0, 0)); Imgproc.putText(img, testInputImage + " is not loaded.", new Point(5, img.rows() - 30), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false); Imgproc.putText(img, "Please read console message.", new Point(5, img.rows() - 10), Imgproc.FONT_HERSHEY_SIMPLEX, 0.7, new Scalar(255, 255, 255, 255), 2, Imgproc.LINE_AA, false); } else { TickMeter tm = new TickMeter(); tm.start(); Mat faces = faceDetector.infer(img); tm.stop(); Debug.Log("FaceDetectorYNModel Inference time, ms: " + tm.getTimeMilli()); List expressions = new List(); // Estimate the expression of each face for (int i = 0; i < faces.rows(); ++i) { tm.reset(); tm.start(); // Facial expression recognizer inference Mat facialExpression = facialExpressionRecognizer.infer(img, faces.row(i)); tm.stop(); Debug.Log("FacialExpressionRecognizer Inference time (preprocess + infer + postprocess), ms: " + tm.getTimeMilli()); if (!facialExpression.empty()) expressions.Add(facialExpression); } faceDetector.visualize(img, faces, true, false); facialExpressionRecognizer.visualize(img, expressions, faces, true, false); } gameObject.transform.localScale = new Vector3(img.width(), img.height(), 1); float imageWidth = img.width(); float imageHeight = img.height(); float widthScale = (float)Screen.width / imageWidth; float heightScale = (float)Screen.height / imageHeight; if (widthScale < heightScale) { Camera.main.orthographicSize = (imageWidth * (float)Screen.height / (float)Screen.width) / 2; } else { Camera.main.orthographicSize = imageHeight / 2; } Imgproc.cvtColor(img, img, Imgproc.COLOR_BGR2RGB); Texture2D texture = new Texture2D(img.cols(), img.rows(), TextureFormat.RGB24, false); Utils.matToTexture2D(img, texture); gameObject.GetComponent().material.mainTexture = texture; }); StartCoroutine(getFilePathAsync_0_Coroutine); ///////////////////// } } /// /// Raises the webcam 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); } /// /// Raises the webcam texture to mat helper disposed event. /// public void OnWebCamTextureToMatHelperDisposed() { Debug.Log("OnWebCamTextureToMatHelperDisposed"); if (bgrMat != null) bgrMat.Dispose(); if (texture != null) { Texture2D.Destroy(texture); texture = null; } } /// /// Raises the webcam 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 || facialExpressionRecognizer == 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); } else { Imgproc.cvtColor(rgbaMat, bgrMat, Imgproc.COLOR_RGBA2BGR); //TickMeter tm = new TickMeter(); //tm.start(); Mat faces = faceDetector.infer(bgrMat); //tm.stop(); //Debug.Log("FaceDetectorYNModel Inference time, ms: " + tm.getTimeMilli()); List expressions = new List(); // Estimate the expression of each face for (int i = 0; i < faces.rows(); ++i) { //tm.reset(); //tm.start(); // Facial expression recognizer inference Mat facialExpression = facialExpressionRecognizer.infer(bgrMat, faces.row(i)); //tm.stop(); //Debug.Log("FacialExpressionRecognizer Inference time (preprocess + infer + postprocess), ms: " + tm.getTimeMilli()); if (!facialExpression.empty()) expressions.Add(facialExpression); } Imgproc.cvtColor(bgrMat, rgbaMat, Imgproc.COLOR_BGR2RGBA); //faceDetector.visualize(rgbaMat, faces, false, true); facialExpressionRecognizer.visualize(rgbaMat, expressions, faces, false, true); } Utils.matToTexture2D(rgbaMat, texture); } } /// /// Raises the destroy event. /// void OnDestroy() { webCamTextureToMatHelper.Dispose(); if (faceDetector != null) //faceDetector.Dispose(); faceDetector.dispose(); if (facialExpressionRecognizer != null) facialExpressionRecognizer.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; } private class FaceDetectorYNModel { Size input_size; float conf_threshold; float nms_threshold; int topK; int backend; int target; protected Scalar bBoxColor = new Scalar(0, 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) }; FaceDetectorYN detection_model; Mat input_sizeMat; public FaceDetectorYNModel(string modelFilepath, string configFilepath, Size inputSize, float confThreshold = 0.6f, float nmsThreshold = 0.3f, int topK = 5000, int backend = Dnn.DNN_BACKEND_OPENCV, int target = Dnn.DNN_TARGET_CPU) { // initialize if (!string.IsNullOrEmpty(modelFilepath)) { detection_model = FaceDetectorYN.create(modelFilepath, configFilepath, inputSize, confThreshold, nmsThreshold, topK, backend, target); } input_size = new Size(inputSize.width > 0 ? inputSize.width : 320, inputSize.height > 0 ? inputSize.height : 320); conf_threshold = Mathf.Clamp01(confThreshold); nms_threshold = Mathf.Clamp01(nmsThreshold); this.topK = topK; this.backend = backend; this.target = target; } protected virtual Mat preprocess(Mat image) { int h = (int)input_size.height; int w = (int)input_size.width; if (input_sizeMat == null) input_sizeMat = new Mat(new Size(w, h), CvType.CV_8UC3);// [h, w] Imgproc.resize(image, input_sizeMat, new Size(w, h)); return input_sizeMat;// [h, w, 3] } public virtual Mat infer(Mat image) { // cheack if (image.channels() != 3) { Debug.Log("The input image must be in BGR format."); return new Mat(); } // Preprocess Mat input_blob = preprocess(image); // Forward Mat results = new Mat(); detection_model.detect(input_blob, results); // Postprocess // scale_boxes float x_factor = image.width() / (float)input_size.width; float y_factor = image.height() / (float)input_size.height; for (int i = 0; i < results.rows(); ++i) { float[] results_arr = new float[14]; results.get(i, 0, results_arr); for (int j = 0; j < 14; ++j) { if (j % 2 == 0) { results_arr[j] = results_arr[j] * x_factor; } else { results_arr[j] = results_arr[j] * y_factor; } } results.put(i, 0, results_arr); } return results; } protected virtual Mat postprocess(Mat output_blob) { return output_blob; } public virtual void visualize(Mat image, Mat results, bool print_results = false, bool isRGB = false) { if (image.IsDisposed) return; if (results.empty() || results.cols() < 15) return; for (int i = results.rows() - 1; i >= 0; --i) { float[] box = new float[4]; results.get(i, 0, box); float[] conf = new float[1]; results.get(i, 14, conf); float[] landmarks = new float[10]; results.get(i, 4, landmarks); float left = box[0]; float top = box[1]; float right = box[0] + box[2]; float bottom = box[1] + box[3]; Scalar bbc = bBoxColor; Scalar bbcolor = isRGB ? bbc : new Scalar(bbc.val[2], bbc.val[1], bbc.val[0], bbc.val[3]); Imgproc.rectangle(image, new Point(left, top), new Point(right, bottom), bbcolor, 2); string label = String.Format("{0:0.0000}", conf[0]); int[] baseLine = new int[1]; Size labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine); top = Mathf.Max((float)top, (float)labelSize.height); Imgproc.rectangle(image, new Point(left, top - labelSize.height), new Point(left + labelSize.width, top + baseLine[0]), bbcolor, Core.FILLED); Imgproc.putText(image, label, new Point(left, top), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0, 255), 1, Imgproc.LINE_AA); // draw landmark points for (int j = 0; j < 10; j += 2) { Scalar c = keyPointsColors[(j / 2) % keyPointsColors.Length]; Scalar color = isRGB ? c : new Scalar(c.val[2], c.val[1], c.val[0], c.val[3]); Imgproc.circle(image, new Point(landmarks[j], landmarks[j + 1]), 2, color, 2); } } // Print results if (print_results) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < results.rows(); ++i) { float[] box = new float[4]; results.get(i, 0, box); float[] conf = new float[1]; results.get(i, 14, conf); float[] landmarks = new float[10]; results.get(i, 4, landmarks); sb.AppendLine(String.Format("-----------face {0}-----------", i + 1)); sb.AppendLine(String.Format("conf: {0:0.0000}", conf[0])); sb.AppendLine(String.Format("box: {0:0} {1:0} {2:0} {3:0}", box[0], box[1], box[2], box[3])); sb.Append("landmarks: "); foreach (var p in landmarks) { sb.Append(String.Format("{0:0} ", p)); } sb.AppendLine(); } Debug.Log(sb); } } public virtual void dispose() { if (detection_model != null) detection_model.Dispose(); if (input_sizeMat != null) input_sizeMat.Dispose(); input_sizeMat = null; } } private class FacialExpressionRecognizer { int backend; int target; string inputName = "data"; string outputName = "label"; Size input_size = new Size(112, 112); Scalar mean = new Scalar(0.5, 0.5, 0.5); Scalar std = new Scalar(0.5, 0.5, 0.5); Net facial_expression_recognition_net; List classNames; List palette; Mat input_sizeMat; Mat getDataMat; FaceRecognizerSF faceRecognizer; public FacialExpressionRecognizer(string modelFilepath, string SF_modelFilepath, string SF_configFilepath, int backend = Dnn.DNN_BACKEND_OPENCV, int target = Dnn.DNN_TARGET_CPU) { // initialize if (!string.IsNullOrEmpty(modelFilepath)) { facial_expression_recognition_net = Dnn.readNet(modelFilepath); } if (!string.IsNullOrEmpty(SF_modelFilepath)) { faceRecognizer = FaceRecognizerSF.create(SF_modelFilepath, SF_configFilepath, backend, target); } this.backend = backend; this.target = target; facial_expression_recognition_net.setPreferableBackend(this.backend); facial_expression_recognition_net.setPreferableTarget(this.target); classNames = new List(); classNames.Add("angry"); classNames.Add("disgust"); classNames.Add("fearful"); classNames.Add("happy"); classNames.Add("neutral"); classNames.Add("sad"); classNames.Add("surprised"); palette = new List(); palette.Add(new Scalar(255, 56, 56, 255)); palette.Add(new Scalar(82, 0, 133, 255)); palette.Add(new Scalar(52, 69, 147, 255)); palette.Add(new Scalar(255, 178, 29, 255)); palette.Add(new Scalar(55, 55, 55, 255)); palette.Add(new Scalar(100, 115, 255, 255)); palette.Add(new Scalar(255, 112, 31, 255)); } protected virtual Mat preprocess(Mat image, Mat bbox = null) { if (input_sizeMat == null) input_sizeMat = new Mat(input_size, CvType.CV_8UC3); if (bbox != null && faceRecognizer != null) { alignCrop(image, bbox, input_sizeMat); } else { Imgproc.resize(image, input_sizeMat, input_size); } // Create a 4D blob from a frame. Mat blob; blob = Dnn.blobFromImage(input_sizeMat, 1.0 / 255.0, input_sizeMat.size(), Scalar.all(0), true, false, CvType.CV_32F); // HWC to NCHW, BGR to RGB int c = input_sizeMat.channels(); int h = input_sizeMat.height(); int w = input_sizeMat.width(); Mat blob_cxhxw = blob.reshape(1, new int[] { c, h, w });// [c, h, w] for (int i = 0; i < c; ++i) { Mat blob_1xhw = blob_cxhxw.row(i).reshape(1, 1);// [1, h, w] => [1, h * w] // Subtract blob by mean. Core.subtract(blob_1xhw, new Scalar(mean.val[i]), blob_1xhw); // Divide blob by std. Core.divide(blob_1xhw, new Scalar(std.val[i]), blob_1xhw); } return blob;// [1, 112, 112, 3] } public virtual Mat infer(Mat image, Mat bbox = null) { // cheack if (image.channels() != 3) { Debug.Log("The input image must be in BGR format."); return new Mat(); } // Preprocess Mat input_blob = preprocess(image, bbox); // Forward facial_expression_recognition_net.setInput(input_blob, inputName); Mat output_blob = facial_expression_recognition_net.forward(outputName); // Postprocess Mat results = postprocess(output_blob); input_blob.Dispose(); return results; } protected virtual Mat postprocess(Mat output_blob) { Mat results = softmax(output_blob); return results;// [1, 7] } protected virtual Mat softmax(Mat src) { Mat dst = src.clone(); Core.MinMaxLocResult result = Core.minMaxLoc(src); Scalar max = new Scalar(result.maxVal); Core.subtract(src, max, dst); Core.exp(dst, dst); Scalar sum = Core.sumElems(dst); Core.divide(dst, sum, dst); return dst; } public virtual void visualize(Mat image, List results, Mat faces, bool print_results = false, bool isRGB = false) { if (image.IsDisposed) return; if (results.Count != faces.rows()) return; StringBuilder sb = null; if (print_results) sb = new StringBuilder(); for (int i = 0; i < results.Count; ++i) { float[] face_box = new float[4]; faces.get(i, 0, face_box); float left = face_box[0] + 2; float top = face_box[1] + 2; float right = face_box[0] + face_box[2] - 2; float bottom = face_box[1] + face_box[3] - 2; ClassificationData bmData = getBestMatchData(results[i]); int classId = (int)bmData.cls; string label = getClassLabel(bmData.cls) + ", " + String.Format("{0:0.0000}", bmData.conf); Scalar c = palette[classId % palette.Count]; Scalar color = isRGB ? c : new Scalar(c.val[2], c.val[1], c.val[0], c.val[3]); // draw box Imgproc.rectangle(image, new Point(left, top), new Point(right, bottom), color, 2); // draw label int[] baseLine = new int[1]; Size labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine); top = Mathf.Max((float)top, (float)labelSize.height); Imgproc.rectangle(image, new Point(left, top + 2), new Point(left + labelSize.width, top + labelSize.height + baseLine[0] + 2), color, Core.FILLED); Imgproc.putText(image, label, new Point(left, top + labelSize.height + 2), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, Scalar.all(255), 1, Imgproc.LINE_AA); // Print results if (print_results) { sb.AppendLine(String.Format("-----------expression {0}-----------", i + 1)); sb.AppendLine(String.Format("Best match: " + getClassLabel(bmData.cls) + ", " + bmData)); } } if (print_results) Debug.Log(sb); } public virtual void dispose() { if (facial_expression_recognition_net != null) facial_expression_recognition_net.Dispose(); if (input_sizeMat != null) input_sizeMat.Dispose(); input_sizeMat = null; if (getDataMat != null) getDataMat.Dispose(); getDataMat = null; if (faceRecognizer != null) faceRecognizer.Dispose(); } private void alignCrop(Mat src_img, Mat face_box, Mat aligned_img) { // The alignCrop method of FaceRecognizerSF is used here, because the implementation of the alignment and crop process is cumbersome. // This method returns an image of 112x112 pixels, the same as the Facial Expression Recognition model input. faceRecognizer.alignCrop(src_img, face_box, aligned_img); } [StructLayout(LayoutKind.Sequential)] public readonly struct ClassificationData { public readonly float cls; public readonly float conf; // sizeof(ClassificationData) public const int Size = 2 * sizeof(float); public ClassificationData(int cls, float conf) { this.cls = cls; this.conf = conf; } public override string ToString() { return "cls:" + cls + " conf:" + conf; } }; public virtual ClassificationData[] getData(Mat results) { if (results.empty()) return new ClassificationData[0]; int num = results.cols(); if (getDataMat == null) { getDataMat = new Mat(num, 2, CvType.CV_32FC1); float[] arange = Enumerable.Range(0, num).Select(i => (float)i).ToArray(); getDataMat.col(0).put(0, 0, arange); } Mat results_numx1 = results.reshape(1, num); results_numx1.copyTo(getDataMat.col(1)); var dst = new ClassificationData[num]; OpenCVForUnity.UtilsModule.MatUtils.copyFromMat(getDataMat, dst); return dst; } public virtual ClassificationData[] getSortedData(Mat results, int topK = 5) { if (results.empty()) return new ClassificationData[0]; int num = results.cols(); if (topK < 1 || topK > num) topK = num; var sortedData = getData(results).OrderByDescending(x => x.conf).Take(topK).ToArray(); return sortedData; } public virtual ClassificationData getBestMatchData(Mat results) { if (results.empty()) return new ClassificationData(); Core.MinMaxLocResult minmax = Core.minMaxLoc(results); return new ClassificationData((int)minmax.maxLoc.x, (float)minmax.maxVal); } public virtual string getClassLabel(float id) { int classId = (int)id; string className = string.Empty; if (classNames != null && classNames.Count != 0) { if (classId >= 0 && classId < (int)classNames.Count) { className = classNames[classId]; } } if (string.IsNullOrEmpty(className)) className = classId.ToString(); return className; } } } } #endif #endif