Обнаружение глаз с помощью 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 для рендеринга.