Обнаружение глаз с помощью OpenCVSharp в Unity (проблемы с кадрами)

В настоящее время я работаю над проектом, включающим интеграцию OpenCVSharp в Unity, чтобы обеспечить отслеживание движений в игровой среде. Мне удалось встроить OpenCVSharp в редактор Unity, и в настоящее время в игре работает функция обнаружения глаз (не отслеживания). Он может найти ваши глаза в изображении с веб-камеры, а затем отобразить их текущее местоположение на текстуре, которую я отображаю на сцене.

Однако это вызывает ОГРОМНОЕ падение fps, главным образом потому, что каждый кадр превращает текстуру веб-камеры в IPLimage, чтобы OpenCV мог справиться с этим. Затем он должен преобразовать его обратно в 2D-текстуру, которая будет отображаться в сцене, после того, как будет сделано все обнаружение глаза. Понятно, что это слишком много для процессора. (Насколько я могу судить, используя только 1 ядро ​​на моем процессоре).

Есть ли способ сделать все обнаружение глаз без преобразования текстуры в IPLimage? Или любой другой способ исправить падение fps. Вот некоторые вещи, которые я пробовал:

  • Ограничение фреймов, которые он обновляет. Однако это просто заставляет его работать плавно, а затем ужасно заикается на кадре, который он должен обновить.

  • Глядя на многопоточность, но насколько я знаю, Unity не позволяет этого. Насколько я могу судить, он использует только одно ядро ​​на моем процессоре, что выглядит немного глупо. Если бы был способ изменить это, это могло бы решить проблему?

  • Пробовал разные разрешения на камере, однако разрешение, на котором игра может плавно работать, слишком мало для того, чтобы глаз мог быть обнаружен, не говоря уже о том, чтобы отслеживать.

Я включил приведенный ниже код, если вы хотите посмотреть его в редакторе кода, то здесь есть ссылка на файл C#. Любые предложения или помощь будет принята с благодарностью!

Для справки я использовал здесь код (обнаружение глаз с помощью opencvsharp).

using UnityEngine;
using System.Collections;
using System;
using System.IO;
using OpenCvSharp;
//using System.Xml;

//using OpenCvSharp.Extensions;
//using System.Windows.Media;
//using System.Windows.Media.Imaging;



public class CaptureScript : MonoBehaviour
{
    public GameObject planeObj;
    public WebCamTexture webcamTexture;     //Texture retrieved from the webcam
    public Texture2D texImage;              //Texture to apply to plane
    public string deviceName;

    private int devId = 1;
    private int imWidth = 640;              //camera width
    private int imHeight = 360;             //camera height
    private string errorMsg = "No errors found!";
    static IplImage matrix;                 //Ipl image of the converted webcam texture

    CvColor[] colors = new CvColor[]
    {
        new CvColor(0,0,255),
        new CvColor(0,128,255),
        new CvColor(0,255,255),
        new CvColor(0,255,0),
        new CvColor(255,128,0),
        new CvColor(255,255,0),
        new CvColor(255,0,0),
        new CvColor(255,0,255),
    };

    const double Scale = 1.25;
    const double ScaleFactor = 2.5;
    const int MinNeighbors = 2;


// Use this for initialization
    void Start ()
    {
            //Webcam initialisation
            WebCamDevice[] devices = WebCamTexture.devices;
            Debug.Log ("num:" + devices.Length);

            for (int i=0; i<devices.Length; i++) {
                    print (devices [i].name);
                    if (devices [i].name.CompareTo (deviceName) == 1) {
                            devId = i;
                    }
            }

            if (devId >= 0) {
                    planeObj = GameObject.Find ("Plane");
                    texImage = new Texture2D (imWidth, imHeight, TextureFormat.RGB24, false);
                    webcamTexture = new WebCamTexture (devices [devId].name, imWidth, imHeight, 30);
                    webcamTexture.Play ();

                    matrix = new IplImage (imWidth, imHeight, BitDepth.U8, 3);
            }


    }

    void Update ()
    {
        if (devId >= 0)
        {
                //Convert webcam texture to iplimage
                Texture2DtoIplImage();

            /*DO IMAGE MANIPULATION HERE*/

            //do eye detection on iplimage
            EyeDetection();


            /*END IMAGE MANIPULATION*/

            if (webcamTexture.didUpdateThisFrame) 
            {
                //convert iplimage to texture
                IplImageToTexture2D();
            }

        } 
        else 
        {
            Debug.Log ("Can't find camera!");
        }

    }

    void EyeDetection()
    {

        using(IplImage smallImg = new IplImage(new CvSize(Cv.Round (imWidth/Scale), Cv.Round(imHeight/Scale)),BitDepth.U8, 1))
        {
            using(IplImage gray = new IplImage(matrix.Size, BitDepth.U8, 1))
            {
                Cv.CvtColor (matrix, gray, ColorConversion.BgrToGray);
                Cv.Resize(gray, smallImg, Interpolation.Linear);
                Cv.EqualizeHist(smallImg, smallImg);
            }


            using(CvHaarClassifierCascade cascade = CvHaarClassifierCascade.FromFile (@"C:\Users\User\Documents\opencv\sources\data\haarcascades\haarcascade_eye.xml"))
            using(CvMemStorage storage = new CvMemStorage())
            {
                storage.Clear ();
                CvSeq<CvAvgComp> eyes = Cv.HaarDetectObjects(smallImg, cascade, storage, ScaleFactor, MinNeighbors, 0, new CvSize(30, 30));
                for(int i = 0; i < eyes.Total; i++)
                {
                    CvRect r = eyes[i].Value.Rect;
                    CvPoint center = new CvPoint{ X = Cv.Round ((r.X + r.Width * 0.5) * Scale), Y = Cv.Round((r.Y + r.Height * 0.5) * Scale) };
                    int radius = Cv.Round((r.Width + r.Height) * 0.25 * Scale);
                    matrix.Circle (center, radius, colors[i % 8], 3, LineType.AntiAlias, 0);
                }
            }

        }
    }

    void OnGUI ()
    {
            GUI.Label (new Rect (200, 200, 100, 90), errorMsg);
    }

    void IplImageToTexture2D ()
    {
            int jBackwards = imHeight;

            for (int i = 0; i < imHeight; i++) {
                    for (int j = 0; j < imWidth; j++) {
                            float b = (float)matrix [i, j].Val0;
                            float g = (float)matrix [i, j].Val1;
                            float r = (float)matrix [i, j].Val2;
                            Color color = new Color (r / 255.0f, g / 255.0f, b / 255.0f);


                            jBackwards = imHeight - i - 1; // notice it is jBackward and i
                            texImage.SetPixel (j, jBackwards, color);
                    }
            }
            texImage.Apply ();
            planeObj.renderer.material.mainTexture = texImage;

    }

    void Texture2DtoIplImage ()
    {
            int jBackwards = imHeight;

            for (int v=0; v<imHeight; ++v) {
                    for (int u=0; u<imWidth; ++u) {

                            CvScalar col = new CvScalar ();
                            col.Val0 = (double)webcamTexture.GetPixel (u, v).b * 255;
                            col.Val1 = (double)webcamTexture.GetPixel (u, v).g * 255;
                            col.Val2 = (double)webcamTexture.GetPixel (u, v).r * 255;

                            jBackwards = imHeight - v - 1;

                            matrix.Set2D (jBackwards, u, col);
                            //matrix [jBackwards, u] = col;
                    }
            }
    }
}

2 ответа

Вы также сможете добиться улучшения производительности, если будете использовать:

    Color32[] pixels;
    pixels = new Color32[webcamTexture.width * webcamTexture.height];
    webcamTexture.GetPixels32(pixels);

Документация Unity предполагает, что это может быть немного быстрее, чем вызов "GetPixels" (и, конечно, быстрее, чем вызов GetPixel для каждого пикселя), и тогда вам не нужно масштабировать каждый канал RGB против 255 вручную.

Вы можете переместить их из цикла обновления для каждого кадра:

using(CvHaarClassifierCascade cascade = CvHaarClassifierCascade.FromFile (@"C:\Users\User\Documents\opencv\sources\data\haarcascades\haarcascade_eye.xml"))
using(CvMemStorage storage = new CvMemStorage())

Нет причин для построения графа распознавателя в каждом кадре.

Потоки - это логичный способ двигаться вперед, если вам нужны обновления с реальной скоростью, само единство не имеет нити, но вы можете сложить другие потоки, если будете осторожны.

Сделайте текстуру -> изображение ipl в главном потоке, затем запустите событие, чтобы запустить ваш поток. Поток может выполнить всю работу CV, возможно, скомпоновать tex2d и затем вернуться к main для рендеринга.

Другие вопросы по тегам