Comment j'ai fait un système de suivi optique





C'était en 2015. Les lunettes de réalité virtuelle Oculus DK2 venaient d'apparaître en vente, le marché des jeux VR gagnait rapidement en popularité.



Les opportunités du joueur dans de tels jeux étaient limitées. Seuls 6 degrés de liberté des mouvements de la tête ont été surveillés - rotation (avec une inertie dans les lunettes) et mouvement dans un petit volume dans la zone de visibilité d'une caméra infrarouge fixée sur le moniteur. Le processus de jeu consistait à s'asseoir sur une chaise avec une manette de jeu dans les mains, à faire pivoter la tête dans différentes directions et à lutter contre les nausées.



Cela n'avait pas l'air très cool, mais j'ai vu cela comme une opportunité de faire quelque chose d'intéressant, en utilisant mon expérience dans le développement électronique et ma soif de nouveaux projets. Comment ce système pourrait-il être amélioré?



Bien sûr, débarrassez-vous de la manette de jeu, des fils, donnez au joueur la possibilité de se déplacer librement dans l'espace, de voir ses mains et ses pieds, d'interagir avec l'environnement, les autres joueurs et de vrais objets interactifs.



Je l'ai vu comme ça:



  1. Nous prenons plusieurs joueurs, mettons des lunettes VR, un ordinateur portable et des capteurs sur leurs bras, leurs jambes et leur torse.
  2. Nous prenons une pièce composée de plusieurs pièces, couloirs, portes, l'équipons d'un système de suivi, accrochons des capteurs et des serrures magnétiques sur les portes, ajoutons plusieurs objets interactifs et créons un jeu dans lequel la géométrie d'un lieu virtuel répète exactement la géométrie d'une pièce réelle.
  3. Nous créons un jeu. Le jeu est une quête multijoueur dans laquelle plusieurs joueurs mettent de l'équipement et se retrouvent dans un monde virtuel. Dans ce document, ils se voient, se voient, peuvent se promener dans les lieux, ouvrir des portes et résoudre ensemble des problèmes de jeu.


J'ai dit cette idée à mon ami, qui l'a acceptée de façon inattendue avec beaucoup d'enthousiasme et m'a proposé de prendre en charge les questions d'organisation. Nous avons donc décidé de brouiller le démarrage.



Pour implémenter la fonctionnalité déclarée, il était nécessaire de créer deux technologies principales:



  1. une combinaison composée de capteurs sur les bras, les jambes et le torse qui suit la position des parties du corps du joueur
  2. un système de suivi qui suit les joueurs et les objets interactifs dans l'espace 3D.


Le développement de la deuxième technologie sera discuté dans cet article. Peut-être que plus tard, j'écrirai sur le premier.



Système de pistage.



Bien sûr, nous n'avions pas de budget pour tout cela, nous devions donc tout fabriquer à partir de matériaux de rebut. Pour la tâche de suivi des joueurs dans l'espace, j'ai décidé d'utiliser des caméras optiques et des marqueurs LED fixés sur des lunettes VR. Je n'avais aucune expérience d'un tel développement, mais j'ai déjà entendu quelque chose sur OpenCV, Python et j'ai pensé que je pouvais le faire.



Comme prévu, si le système sait où se trouve la caméra et comment elle est orientée, alors par la position de l'image du marqueur sur le cadre, vous pouvez déterminer la ligne droite dans l'espace 3D sur laquelle se trouve ce marqueur. L'intersection de deux de ces lignes donne la position finale du marqueur.







En conséquence, les caméras devaient être fixées au plafond pour que chaque point de l'espace soit vu par au moins deux caméras (mieux vaut mieux éviter d'obstruer la vue par les corps des joueurs). Il a fallu environ 60 caméras pour couvrir les locaux supposés d'une superficie d'environ 100 mètres carrés. J'ai choisi les premières webcams USB bon marché disponibles à l'époque.







Ces webcams doivent être connectées à quelque chose. Des expériences ont montré que lors de l'utilisation de rallonges USB (au moins bon marché), les caméras ont commencé à pépiner. Par conséquent, j'ai décidé de diviser les webcams en groupes de 8 pièces et de les coller dans les unités centrales montées au plafond. Il n'y avait que 10 ports USB sur mon ordinateur personnel, il est donc temps de commencer à développer un banc de test.



L'architecture que j'ai proposée est la suivante:

Une boule en acrylique givrée d'une guirlande avec une LED RVB collée à l'intérieur est accrochée sur chaque verre. Plusieurs joueurs étaient censés être dans le jeu en même temps, donc pour l'identification, j'ai décidé de les séparer par couleur - R, G, B, RG, RB, GB, RB. Voici à quoi cela ressemblait:







la première tâche à accomplir est d'écrire un programme pour trouver la balle sur le cadre.



Trouver la balle sur le cadre



J'ai dû chercher les coordonnées du centre de la balle et sa couleur pour l'identification dans chaque cadre qui provenait de la caméra. Cela semble facile. Je télécharge OpenCV pour Python, branche la caméra sur USB, écris un script. Pour minimiser l'influence des objets inutiles dans le cadre, j'ai réglé l'exposition et la vitesse d'obturation de l'appareil photo au minimum, et la luminosité de la LED est élevée pour obtenir des points lumineux sur un fond sombre. Dans la première version, l'algorithme était le suivant:



  1. ( , , – ).
  2. .
  3. ( )






Cela semble fonctionner, mais il y a des nuances.



Premièrement, sur une caméra bon marché, la matrice est assez bruyante, ce qui conduit à des fluctuations constantes des contours des clusters binarisés et, par conséquent, à des secousses du centre. Il est impossible pour les joueurs de modifier l'image dans des lunettes VR, ce problème a donc dû être résolu. Les tentatives d'utiliser d'autres types de binarisation adaptative avec des paramètres différents n'ont pas donné beaucoup d'effet.



Deuxièmement, la résolution de la caméra n'est que de 640 * 480, donc à une certaine distance (pas très grande), la balle est visible sous forme de quelques pixels dans le cadre et l'algorithme de recherche de bord cesse de fonctionner normalement.



Je devais trouver un nouvel algorithme. L'idée suivante m'est venue à l'esprit:



  1. Convertir l'image en niveaux de gris
  2. Gaussian blur – ,
  3. ,


Cela fonctionne beaucoup mieux, les coordonnées du centre sont stationnaires lorsque la balle est stationnaire, et cela fonctionne même à une grande distance de la caméra.



Pour vous assurer que tout cela fonctionnera avec 8 caméras sur un ordinateur, vous devez effectuer un test de résistance.



Test de charge Je



connecte 8 caméras à mon bureau, les arrange pour que chacune voie des points lumineux et exécute un script où l'algorithme décrit fonctionne dans 8 processus indépendants (grâce à la bibliothèque multiprocesseur Python) et traite tous les threads à la fois.



Et ... je tombe immédiatement sur un échec. Les images de la caméra apparaissent et disparaissent, la fréquence d'images passe de 0 à 100, un cauchemar. L'enquête a montré que certains des ports USB de mon ordinateur sont connectés au même bus via un concentrateur interne, c'est pourquoi la vitesse du bus est divisée entre plusieurs ports et n'est plus suffisante pour le débit binaire de la caméra. Brancher les caméras sur différents ports de l'ordinateur dans différentes combinaisons a montré que je n'ai que 4 bus USB indépendants. J'ai dû trouver une carte mère avec 8 bus, ce qui était une quête assez difficile.



Divulgacher
Intel B85, 10 usb . 10- , OpenCV, .. 8 (?)



Je continue le test de charge. Cette fois, toutes les caméras sont connectées et émettent des flux normaux, mais je rencontre immédiatement le problème suivant: les fps bas. Le processeur est chargé à 100% et ne parvient à traiter que 8 à 10 images par seconde à partir de chacune des huit webcams.







Il semble que le code doit être optimisé. Le goulot d'étranglement s'est avéré être un flou gaussien (ce n'est pas surprenant, car vous devez convoluer avec une matrice 9 * 9 pour chaque pixel de l'image). La réduction du noyau n'a pas sauvé la situation. J'ai dû chercher une autre méthode pour trouver les centres des taches sur les cadres.



La solution a été soudainement trouvée dans la fonction SimpleBlobDetector intégrée à OpenCV. Elle fait exactement ce dont j'ai besoin et très rapidement. L'avantage est obtenu grâce à la binarisation séquentielle de l'image avec différents seuils et à la recherche de contours. Le résultat est un maximum de 30 images par seconde avec une charge CPU inférieure à 40%. Test de charge réussi!



Classification des couleurs



La tâche suivante consiste à classer le marqueur par sa couleur. La valeur de couleur moyenne sur les pixels directs donne des composants RVB qui sont très instables et varient considérablement en fonction de la distance à la caméra et de la luminosité de la LED. Mais il existe une excellente solution: la traduction de l'espace RVB avec HSV (teinte, saturation, valeur). Dans cette représentation, le pixel au lieu de "rouge", "bleu", "vert" est décomposé en composants "teinte", "saturation", "luminosité". Dans ce cas, la saturation et la luminosité peuvent simplement être exclues et classées uniquement par teinte.







Détails techniques
, «» . , . , . «» .



:



  1. (, R – )
  2. , , . «hue – saturation»
  3. . , .
  4. , , . . , , . .. , . , - , , .






Et donc, pour le moment, j'ai appris à trouver et à identifier des marqueurs dans les cadres d'un grand nombre de caméras. Vous pouvez maintenant passer à l'étape suivante: le suivi dans l'espace.



suivi



J'ai utilisé un modèle d'appareil photo à sténopé dans lequel tous les rayons tombent sur la matrice via un point situé à la distance focale de la matrice.







Ce modèle transformera les coordonnées bidimensionnelles d'un point du cadre en équations tridimensionnelles d'une ligne droite dans l'espace.



Pour suivre les coordonnées 3D du marqueur, vous devez obtenir au moins deux lignes d'intersection dans l'espace à partir de différentes caméras et trouver le point de leur intersection. Il n'est pas difficile de voir le marqueur avec deux caméras, mais pour construire ces lignes, il faut que le système sache tout sur les caméras connectées: où elles sont suspendues, sous quels angles, la distance focale de chaque objectif. Le problème est que rien de tout cela n'est connu. Le calcul des paramètres nécessite une sorte de procédure d'étalonnage.



Suivi de l'étalonnage



Dans la première version, j'ai décidé de rendre l'étalonnage du suivi aussi primitif que possible.



  1. J'accroche le premier bloc de huit caméras au plafond, je les connecte à une unité centrale suspendue là-bas, je dirige les caméras de manière à ce qu'elles couvrent le volume maximum du jeu.
  2. À l'aide d'un niveau laser et d'un télémètre, je mesure les coordonnées XYZ de toutes les caméras dans un seul système de coordonnées
  3. Pour calculer les orientations et les focales des caméras, je mesure les coordonnées d'autocollants spéciaux. Je raccroche les autocollants comme suit:

    • Dans l'interface d'affichage d'une image de la camĂ©ra, je dessine deux points. Un au centre du cadre, un autre 200 pixels Ă  droite du centre

    • Si vous regardez le cadre, ces points tombent quelque part sur le mur, le sol ou tout autre objet Ă  l'intĂ©rieur de la pièce. J'accroche des autocollants en papier aux endroits appropriĂ©s et je dessine des points dessus avec un marqueur
    • Je mesure les coordonnĂ©es XYZ de ces points en utilisant le mĂŞme niveau et le mĂŞme tĂ©lĂ©mètre. Au total, pour un bloc de huit camĂ©ras, vous devez mesurer les coordonnĂ©es des camĂ©ras elles-mĂŞmes et deux points supplĂ©mentaires pour chacune. Ceux. 24 triplets de coordonnĂ©es. Et il devrait y avoir une dizaine de ces blocs. Cela s'avère un long travail morne. Mais rien, je ferai le calibrage automatisĂ© plus tard.
    • Je lance le processus de calcul basĂ© sur les donnĂ©es mesurĂ©es




Il existe deux systèmes de coordonnées: un global, associé à la pièce, et l'autre local à chaque caméra. Dans mon algorithme, le résultat pour chaque caméra doit être une matrice 4 * 4, contenant son emplacement et son orientation, vous permettant de convertir les coordonnées locales en coordonnées globales.







L'idée est la suivante:



  1. Nous prenons la matrice d'origine avec zéro rotation et décalage.
  2. , .
  3. , .
  4. , . , . . 200 . , .
  5. (, 200 ).


Ce problème pourrait sûrement être résolu analytiquement, mais pour simplifier, j'ai utilisé une solution numérique sur la descente de gradient. Ce n'est pas effrayant, car les calculs seront effectués une fois après l'installation des caméras.



Pour visualiser les résultats de l'étalonnage, j'ai réalisé une interface 2D avec une carte, sur laquelle le script dessine les étiquettes des caméras et les directions dans lesquelles elles voient les marqueurs. Les triangles représentent les orientations de la caméra et les angles de vision.







Test du suivi



Vous pouvez lancer la visualisation, qui montrera si les orientations de la caméra ont été correctement identifiées et si les images sont interprétées correctement. Idéalement, les lignes provenant des icônes de caméra devraient se croiser en un point.



Voici ce qui s'est passé:





Cela ressemble à la vérité, mais la précision pourrait clairement être plus élevée. La première raison d'imperfection qui m'est venue à l'esprit est la distorsion des objectifs des caméras. Cela signifie que ces distorsions doivent être compensées d'une manière ou d'une autre.



Calibrage de la caméra

La caméra idéale n'a qu'un seul paramètre important pour moi: la distance focale. La courbe réelle de la caméra doit également prendre en compte la distorsion de l'objectif et le décalage du centre de la matrice.

Pour mesurer ces paramètres, il existe une procédure d'étalonnage standard, au cours de laquelle un ensemble de photographies d'un damier sont prises avec une caméra mesurée, dans laquelle les angles entre les carrés sont reconnus avec une précision de sous-pixel.







Le résultat de l'étalonnage est une matrice contenant les distances focales selon deux axes et le décalage de la matrice par rapport au centre optique. Tout cela est mesuré en pixels.







Ainsi qu'un vecteur de coefficients de distorsion







qui vous permet de compenser les distorsions de l'objectif à l'aide de transformations de coordonnées de pixels.



En appliquant des transformations avec ces coefficients aux coordonnées du marqueur sur le cadre, vous pouvez amener le système à un modèle de caméra sténopé idéale.



Lancer un nouveau test de suivi:







bien mieux! Cela a l'air si beau que cela semble mĂŞme fonctionner.



Mais le processus de calibrage s'avère très morne: mesurez directement les coordonnées de chaque caméra, commencez à afficher l'image de chaque caméra, accrochez des autocollants, mesurez les coordonnées de chaque autocollant, écrivez les résultats dans un tableau, calibrez les objectifs. Tout cela a pris quelques jours et un kilogramme de nerfs. J'ai décidé de m'occuper du suivi et d'écrire quelque chose de plus automatisé.



Calcul des coordonnées des marqueurs



Et donc, j'ai un tas de lignes droites, dispersées dans l'espace, aux intersections desquelles il devrait y avoir des marqueurs. Seules les lignes droites dans l'espace ne se coupent pas réellement, mais se croisent, c'est-à-dire passer à une certaine distance les uns des autres. Ma tâche est de trouver le point le plus près possible des deux lignes droites. Formellement parlant, vous devez trouver le milieu du segment qui est perpendiculaire aux deux lignes.







La longueur du segment AB est également utile, car il reflète la «qualité» du résultat obtenu. Plus il est court, plus les lignes droites sont proches les unes des autres, meilleur est le résultat.



Ensuite, j'ai écrit un algorithme de suivi qui calcule les intersections de lignes par paires (dans la même couleur, à partir de caméras suffisamment éloignées les unes des autres), recherche le meilleur et l'utilise comme coordonnées de marqueur. Sur les images suivantes, il essaie d'utiliser la même paire de caméras pour éviter un saut de coordonnées lors du passage au suivi avec d'autres caméras.



En parallèle, en développant une combinaison avec des capteurs, j'ai découvert un phénomène étrange. Tous les capteurs ont montré des valeurs différentes de l'angle de lacet (direction dans le plan horizontal), comme si chacun avait son propre nord. Tout d'abord, il a été utile de vérifier si je me suis trompé dans les algorithmes de filtrage des données ou dans la disposition de la carte, mais je n'ai rien trouvé. Ensuite, j'ai décidé de regarder les données brutes du magnétomètre et j'ai vu le problème.

Le champ magnétique dans notre chambre était dirigé VERTICALEMENT VERS LE BAS! Apparemment, cela est dû au fer dans la structure du bâtiment.



Mais les lunettes VR utilisent également un magnétomètre. Pourquoi n'ont-ils pas cet effet? Je vais vérifier. Il s'est avéré qu'il avait aussi des lunettes ... Si vous restez assis, vous pouvez voir comment le monde virtuel tourne lentement mais sûrement autour de vous dans une direction aléatoire. En 10 minutes, il part à presque 180 degrés. Dans notre jeu, cela conduira inévitablement à une désynchronisation entre les réalités virtuelles et réelles et à des lunettes cassées contre les murs.



Il semble qu'en plus des coordonnées des verres, vous devrez déterminer leur direction dans le plan horizontal. La solution se suggère - de mettre non pas un, mais deux marqueurs identiques sur les verres. Cela vous permettra de déterminer la direction avec une précision de 180 degrés, mais compte tenu de la présence de capteurs inertiels intégrés, cela suffit amplement.







Le système dans son ensemble a fonctionné, mais avec de petits montants. Mais la décision a été prise de lancer la quête, qui était sur le point d'être achevée par notre développeur gamedev qui a rejoint notre mini-équipe. L'ensemble de l'aire de jeu a été détruit, des portes avec capteurs et serrures magnétiques ont été installées et deux objets interactifs ont été réalisés:







Les joueurs ont mis des lunettes, des combinaisons et des sacs à dos d'ordinateur et sont entrés dans l'aire de jeu. Les coordonnées de suivi leur ont été envoyées via wi-fi et utilisées pour positionner le personnage virtuel. Tout a bien fonctionné, les visiteurs sont contents. Le plus agréable était de regarder l'horreur et les cris des visiteurs particulièrement impressionnables aux moments où des fantômes virtuels les attaquaient depuis l'obscurité =)







Mise à l'échelle



Soudain, nous avons reçu une commande pour un grand jeu de tir VR pour 8 joueurs avec des armes à la main. Et ce sont 16 objets qui ont besoin d'être tremblés. J'ai eu de la chance que le scénario ait supposé la possibilité de diviser le tracking en deux zones de 4 joueurs chacune, j'ai donc décidé qu'il n'y aurait aucun problème, je pourrais accepter la commande et ne me soucier de rien. Il était impossible de tester le système à la maison. nécessitait une grande surface et beaucoup d'équipement qui seraient achetés par le client, donc avant l'installation, j'ai décidé de passer du temps à automatiser l'étalonnage du suivi.



Auto-étalonnage



Il était incroyablement peu pratique de diriger les caméras, d'accrocher tous ces autocollants, de mesurer manuellement les coordonnées. Je voulais me débarrasser de tous ces processus - accrocher les caméras du bulldozer, marcher au hasard avec le marqueur dans l'espace et exécuter l'algorithme d'étalonnage. En théorie, cela devrait être possible, mais la manière d'aborder l'écriture d'un algorithme n'est pas claire.



La première étape a été de centraliser l'ensemble du système. Au lieu de diviser la zone de jeu en blocs de 8 caméras, j'ai créé un serveur unique, qui a reçu les coordonnées des points sur les cadres de toutes les caméras à la fois.



L'idée est la suivante:



  1. J'accroche des caméras et les dirige vers l'aire de jeu à l'œil nu
  2. Je lance le mode d'enregistrement sur le serveur, dans lequel tous les points 2D provenant des caméras sont enregistrés dans un fichier
  3. Je me promène dans un endroit sombre du jeu avec un marqueur dans mes mains
  4. J'arrête l'enregistrement et démarre le calcul des données d'étalonnage, qui calcule les emplacements, les orientations et les distances focales de toutes les caméras.
  5. à la suite du paragraphe précédent, un seul espace rempli de caméras est obtenu. Car cet espace n'est pas lié à des coordonnées réelles, il a un décalage et une rotation aléatoires, que je soustrais manuellement.


J'ai dû parcourir une énorme quantité de matériel sur l'algèbre linéaire et écrire plusieurs centaines de lignes de code Python. À tel point que je me souviens à peine comment cela fonctionne.











Voici à quoi ressemble un bâton de calibrage spécial imprimé sur une imprimante.



Tester un grand projet



Les problèmes ont commencé lors des tests effectués dans l'installation quelques semaines avant le lancement du projet. Identifier 8 couleurs de marqueurs différentes fonctionnait terriblement, les joueurs de test se téléportaient constamment les uns dans les autres, certaines couleurs ne différaient pas du tout des reflets externes dans le complexe commercial. De vaines tentatives de réparer quelque chose à chaque nuit sans sommeil me plongeaient de plus en plus dans le désespoir. Tout cela était aggravé par le manque de performances du serveur lors du calcul de dizaines de milliers de lignes droites par seconde.



Lorsque le taux de cortisol dans le sang dépassait le maximum théorique, j'ai décidé de regarder le problème sous un angle différent. Comment réduire le nombre de points colorés sans réduire le nombre de marqueurs? Rendre le suivi actif. Laissez chaque joueur, par exemple, toujours avoir une corne rouge sur la corne gauche. Et le second s'allume parfois en vert à l'arrivée d'une commande du serveur de sorte qu'à un moment il n'est éclairé que par un joueur. Il s'avère que la lumière verte semblera sauter d'un joueur à l'autre, mettant à jour la liaison de suivi à la lumière rouge et réinitialisant l'erreur d'orientation du magnétomètre.



Pour ce faire, je devais courir jusqu'au chipidip le plus proche, acheter des LED, des fils, des transistors, un fer à souder, du ruban électrique et accrocher la fonctionnalité de contrôle des LED sur la carte de la combinaison, qui n'était pas conçue pour cela, à la morve. C'est bien que lors de la pose de la planche, juste au cas où, j'ai accroché quelques pattes libres de stm-ki sur les plots de contact.







Les algorithmes de suivi devaient être considérablement compliqués, mais à la fin cela a fonctionné! Les téléportations des joueurs entre eux ont disparu, la charge sur le processeur a chuté, les fusées ont cessé d'interférer.





Le projet a été lancé avec succès, j'ai d'abord créé de nouvelles cartes de costumes avec prise en charge du suivi actif, et nous avons fait une mise à jour matérielle.







Comment ça s'est terminé?



Depuis 3 ans, nous avons ouvert de nombreux points de divertissement à travers le monde, mais le coronavirus a fait ses propres ajustements, ce qui nous a donné l'opportunité de changer la direction du travail dans une direction plus socialement utile. Nous réussissons maintenant à développer des simulateurs médicaux en RV. Notre équipe est encore petite et nous nous efforçons activement d'élargir notre personnel. S'il y a des développeurs UE4 expérimentés à la recherche de travail parmi les lecteurs de Habr, écrivez-moi s'il vous plaît.







Moment amusant traditionnel Ă  la fin de l'article:



Périodiquement, lors de tests avec un grand nombre de joueurs, un bug est survenu dans lequel le joueur a été soudainement téléporté pendant une courte période à une hauteur de plusieurs mètres, ce qui a provoqué une réaction correspondante. Il s'est avéré que mon modèle de caméra supposait l'intersection de la matrice avec une ligne infinie provenant du marqueur. Mais elle n'a pas pris en compte le fait que la caméra a un avant et un arrière, alors le système a cherché l'intersection de lignes sans fin, même si le point est derrière la caméra. Par conséquent, il y a eu des situations où deux caméras différentes ont vu deux marqueurs différents, mais le système pensait qu'il s'agissait d'un marqueur à une hauteur de plusieurs mètres.







Le système a littéralement fonctionné à travers le cul.



All Articles