Améliorer les Performances de Détection dans Unity : Les Secrets de Kinect Azure et OpenCV
Kinect Azure + OpenCV + Unity = un mauvais cocktail de DLL
Computer vision | Electronique | IOT
Pour un projet récent de détection de balles jetées contre un mur j’ai voulu utiliser la nouvelle caméra de profondeur Kinect Azure. Le projet ayant pour but d’être intégré à un jeu j’ai souhaité tout coder sur Unity en utilisant aussi OpenCV Sharp pour l’analyse d’image.
Le début des ennuis
J’ai commencé en prototypant ma pipeline de détection :
- Récupération des images couleurs et profondeur.
- Transformation de l’image de profondeur en nuage de points.
- Changement de système de coordonnées entre caméra couleur et profondeur.
- Changement de système de coordonnées entre la caméra couleur et le système de coordonnées de l’écran de projection (précédemment calculé grâce à la projection et analyse d’un chessboard).
- Reproduction du nuage de points en une image de profondeur (2D).
- Analyse d’image pour détecter des “blobs” qui sont mes balles touchant l’écran.
Dès les premières étapes j’ai commencé à noter des ralentissements étonnant qui se corrigeait généralement en changeant la façon dont j’appelais les fonctions de la Kinect : si je devait faire une opération sur chaque pixel, appeler sur Unity les fonctions de la DLL de la Kinect à chaque pixel était très long. Si je passais un tableau complet de pixels, l’opération était beaucoup plus rapide (d’un facteur 10 à 50).
On voit dans l’exemple ci-dessous un exemple très concret, avec la variable pointCloud de type Image qui vient de la DLL K4ADotNet de la Kinect. Si les valeurs sont stockées dans des variable avant d’exécuter les boucles, le temps d’exécution est bien plus rapide.
private Mat ConvertPointCloudImageToMat(Image pointCloud)
{
for (int i = 0; i < pointCloud.HeightPixels; i++)
{
for (int j = 0; j < pointCloud.WidthPixels; j++)
{
/// DO NOTHING
}
}
}
Temps d’exécution : 26ms
private Mat ConvertPointCloudImageToMat(Image pointCloud)
{
int height = pointCloud.HeightPixels;
int width = pointCloud.WidthPixels;
for (int i = 0; i < height ; i++)
{
for (int j = 0; j < width ; j++)
{
/// DO NOTHING
}
}
}
Temps d’exécution : 0.1ms
Plus j’avançais plus je remarquais un problème similaire avec OpenCV, lors de multiplication de pixels sur les Matrix.
Je compris enfin que le problème venait de Unity et sa gestion des DLL. Les appels aux fonctions des DLL sont très coûteux à cause d’important overhead. Il fait donc limiter au maximum les appels en essayant de “packager” les données à traiter pour les envoyer en un seul appel.
Que faire alors ?
Si vous avez beaucoup de traitement à faire avec la Kinect et même OpenCV, il est préférable de directement créer sa propre DLL dans laquelle les fonctionnalités les plus sensibles seront codées. Pour ma part je vais la faire en C++ car OpenCV et le SDK de la Kinect sont disponibles dans ce langage (et pas en C#).
S’il y a peu de traitement, je vous conseillerai de bien analyser les temps d’exécution de vos fonctions avec une StopWatch par exemple, et de changer les format d’appels. Favoriser les tableaux de pixels et l’image par image au pixel par pixel. Aussi pensez à utiliser la fonction très rapide Marshall.Copy pour les Intr en Array.
Voilà ci-dessous un autre exemple d’optimisation pour l’utilisation des Mat d’OpenCV. Il faut charger toutes les données dans un double[ ] et faire un Mat.put sur l’array complet. Les Matrix doivent être des bonnes dimensions au préalable.
private Mat ConvertPointCloudImageToMat(Image pointCloud)
{
int indx = 0;
int height = pointCloud.HeightPixels;
int width = pointCloud.WidthPixels;
double[] data = new double[3];
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
indx = j * 3 + i * width * 3;
data[0] = pointCloudBuffer[indx] * 0.001f;
data[1] = pointCloudBuffer[indx + 1] * 0.001f;
data[2] = pointCloudBuffer[indx + 2] * 0.001f;
pointCloudMat.put(i, j, data);
}
}
return pointCloudMat;
}
Temps d’exécution : 80ms
private Mat ConvertPointCloudImageToMat(Image pointCloud)
{
int indx = 0;
int height = pointCloud.HeightPixels;
int width = pointCloud.WidthPixels;
double[] data = new double[3* height* width];
for (int i = 0; i < pointCloudBuffer.Length; i+=3)
{
data[i] = pointCloudBuffer[i] * 0.001f;
data[i+1] = pointCloudBuffer[i+1] * 0.001f;
data[i+2] = pointCloudBuffer[i+2] * 0.001f;
}
pointCloudMat.put(0, 0, data);
return pointCloudMat;
}
Temps d’exécution : 17ms
Vous avez un projet ou besoin d'information?
N'hésitez pas à nous contacter !
contact@torrusvr.com
Bordeaux / Paris
Développement d'Expériences Immersives
Plan du site
Technologies immersives
Realité Virtuelle (RV)
Realité Augmentée (RA)
Action Game / Escape Game
Metaverse
Computer Vision
Projection Mapping
Jeux vidéos
Système embarqué
Internet des objets (IoT)
Industry 4.0
Système embarqué sur Unités Centrales (UC) Conception de Circuit Electronique (PCB)
IoT Gateway
Contact
contact@torrusvr.com