Cet article a été publié dans le cadre du Blogathon sur la science des données
Introduction
Bonjour à tous! Alors que le cyberpunk n'est pas encore tellement entré dans nos vies et que les interfaces neuro sont loin d'être idéales, le LiDAR peut devenir la première étape sur la voie du futur des manipulateurs. Par conséquent, afin de ne pas m'ennuyer pendant les vacances, j'ai décidé de fantasmer un peu sur les commandes d'un ordinateur et, vraisemblablement, de n'importe quel appareil, jusqu'à une excavatrice, un vaisseau spatial, un drone ou un poêle.
L'idée principale est de déplacer la souris, en déplaçant non pas toute la main, mais uniquement l'index, ce qui vous permettra de parcourir le menu sans retirer les mains du clavier, d'appuyer sur les boutons et, avec les raccourcis clavier, de se transformer en un un vrai ninja du clavier ! Que se passe-t-il si vous ajoutez des gestes de balayage ou de défilement ? Je pense qu'il y aura une bombe ! Mais jusqu'à ce moment, nous devons encore attendre quelques années)
Commençons à assembler notre prototype du manipulateur du futur
De quoi as-tu besoin:
-
Caméra avec LiDAR Intel Realsense L515.
-
Possibilité de programmer en python
-
Juste un petit rappel des mathématiques à l'école
-
Support pour la caméra sur le moniteur aka trépied
Nous attachons l'appareil photo à un trépied avec aliexpress, il s'est avéré très pratique, léger et bon marché)
Nous découvrons comment et sur quoi faire un prototype
Il existe de nombreuses approches pour accomplir cette tâche. Vous pouvez entraîner vous-même le détecteur ou la segmentation de la main, découper l'image résultante de la main droite, puis appliquer ce merveilleux référentiel de la recherche Facebook à l'image, obtenir un excellent résultat ou le rendre encore plus facile.
Pour utiliser le référentiel media pipe, après avoir lu ce lien, vous pouvez comprendre que c'est l'une des meilleures options pour aujourd'hui.
Tout d'abord, tout est déjà prêt à l'emploi – l'installation et le lancement prendront 30 minutes, en tenant compte de tous les prérequis.
Deuxièmement, grâce à une puissante équipe de développement, ils prennent non seulement l'état de l'art dans l'estimation de la pose, mais fournissent également une API facile à comprendre.
Troisièmement, le réseau est prêt à fonctionner sur le CPU, le seuil d'entrée est donc minimal.
Probablement, vous demanderez pourquoi je ne suis pas venu ici et n'ai pas utilisé les référentiels des gagnants de ce concours. En fait, j'ai étudié leur solution en détail, ils sont assez prod-ready, pas de piles de millions de grilles, etc. Mais le plus gros problème, me semble-t-il, est qu'ils fonctionnent avec des images de profondeur. Comme ce sont des universitaires, ils n'ont pas hésité à convertir toutes les données via Matlab, de plus, la résolution dans laquelle les profondeurs ont été filmées m'a semblé petite. Cela pourrait avoir un effet profond sur le résultat. Par conséquent, il semble que le moyen le plus simple consiste à obtenir les points clés de l'image RVB et à prendre la valeur le long de l'axe Z dans le cadre de profondeur par les coordonnées XY. Maintenant, la tâche n'est pas d'optimiser quelque chose de beaucoup, nous allons donc le faire car c'est plus rapide du point de vue du développement.
Se souvenir des mathématiques à l'école
Comme je l'ai déjà écrit, pour obtenir la coordonnée du point où doit se trouver le curseur de la souris, nous devons construire une ligne passant par deux points clés de la phalange du doigt, et trouver le point d'intersection de la ligne et du plan du moniteur.
L'image montre schématiquement le plan du moniteur et la ligne qui le coupe. Vous pouvez regarder les mathématiques ici.
En utilisant deux points, nous obtenons une représentation paramétrique d'une ligne droite dans l'espace.
Je ne me concentrerai pas trop sur le programme scolaire de mathématiques.
Installation d'une bibliothèque pour travailler avec une caméra
C'est peut-être la partie la plus difficile de ce travail. Il s'est avéré que le logiciel de la caméra pour Ubuntu est très grossier, le sens libéral est simplement jonché de toutes sortes de bugs, de problèmes et de danses avec un tambourin.
Jusqu'à présent, je n'ai pas réussi à vaincre le comportement étrange de la caméra, parfois elle ne charge pas les paramètres au démarrage.
La caméra ne fonctionne qu'une seule fois après redémarrage de l'ordinateur !!! Mais il existe une solution : avant chaque lancement, faites une réinitialisation matérielle logicielle de la caméra, réinitialise l'USB, et peut-être que tout ira bien. Au fait, pour Windows 10, tout va bien là-bas. C'est étrange que les développeurs imaginent des robots basés sur Windows =)
Pour avoir un sens réel sous Ubuntu 20, procédez comme suit :
$ sudo apt-get install libusb-1.0-0-dev Puis relancez cmake ainsi que faire installer. Ici is une recette complète qui a fonctionné en moi : $ sudo apt-get install libusb-1.0-0-dev $ git clone https://github.com/IntelRealSense/librealsense.git $ cd librealsense/ $ mkdir build && cd build
Ayant collecté des sortes, il sera plus ou moins stable. Un mois de communication avec le support technique a révélé que vous devez installer Ubuntu 16 ou souffrir. Je l'ai choisi toi-même, tu sais quoi.
Nous continuons à comprendre les subtilités du réseau de neurones
Voyons maintenant une autre vidéo de l'opération doigt-souris. Veuillez noter que le pointeur ne peut pas rester au même endroit et, pour ainsi dire, flotte autour du point prévu. En même temps, je peux facilement l'orienter vers le mot dont j'ai besoin, mais avec une lettre, c'est plus difficile, je dois déplacer prudemment le curseur :
Ceci, comme vous le comprenez, ne me serre pas la main, en vacances, je n'ai bu qu'une seule tasse de DIPA de la Nouvelle-Angleterre =) Il s'agit de fluctuations constantes des points clés et des coordonnées Z en fonction des valeurs obtenues à partir du lidar.
Regardons de plus près:
Dans notre SOTA de media pipe, il y a certes moins de fluctuations, mais elles existent aussi. Il s'est avéré qu'ils ont du mal à résoudre ce problème en utilisant prokid vaniya de la carte thermique de trame précédente dans le réseau de trames et de trains actuel - cela donne plus de stabilité, mais pas à 100%.
Aussi, me semble-t-il, la spécificité du balisage joue un rôle. Il n'est guère possible de faire le même balisage sur un tel nombre de trames, sans parler du fait que la résolution de la trame est différente partout et pas très grande. De plus, nous ne voyons pas le scintillement de la lumière, qui, très probablement, n'est pas constant en raison des différentes périodes de fonctionnement et de la quantité d'exposition de l'appareil photo. Et le réseau renvoie également un sandwich de la heatmap égal au nombre de points clés sur l'écran, la taille de ce tenseur est BxNx96x96, où N est le nombre de points clés, et, bien sûr, après seuil et redimensionnement à l'original taille du cadre, nous obtenons ce que nous obtenons (
Exemple de rendu de heatmap :
Examen du code
Tout le code est dans ce référentiel et est très court. Jetons un coup d'œil au fichier principal et voyons le reste par vous-même.
importer Cv2 importer tuyau multimédia as mp importer numpy as np importer pyautogui importer pyrrealsense2.pyrrealsense2 as rs de google.protobuf.json_format importer MessageÀDict de mediapipe.python.solutions.drawing_utils importer _normalized_to_pixel_coordonnées de pynput importer clavier de utils.common importer get_filtered_values, draw_cam_out, get_right_index de utils.hard_reset importer hardware_reset de utils.set_options importer set_short_range pyautogui.FAILSAFE = False mp_drawing = mp.solutions.drawing_utils mp_hands = mp.solutions.hands # Estimation de la pose de la main hands = mp_hands.Hands(max_num_hands=2, min_detection_confidence=0.9) def sur_press(clé): if clé == clavier.Key.ctrl : pyautogui.leftClick() if clé == clavier.Key.alt : pyautogui.rightClick() def get_color_profondeur(pipeline, align, colorizer): frames = pipeline.wait_for_frames(timeout_ms=15000) # en attente d'une image de la caméraaligned_frames = align.process(frames) depth_frame =align_frames.get_depth_frame() color_frame =aligned_frames.get_color_frame() if ne sauraient profondeur_cadre or ne sauraient color_frame : retourner Aucun, Aucun, Aucun depth_ima = np.asanyarray(depth_frame.get_data()) depth_col_img = np.asanyarray(colorizer.colorize(depth_frame).get_data()) color_image = np.asanyarray(color_frame.get_data()) depth_col_img = cv2. cvtColor(cv2.flip(cv2.flip(depth_col_img, 1), 0), cv2.COLOR_BGR2RGB) color_img = cv2.cvtColor(cv2.flip(cv2.flip(color_img, 1), 0), cv2.COLOR_BGR2RGB) depth_img = np.flipud(np.fliplr(depth_img)) depth_col_img = cv2.resize(depth_col_img, (1280 * 2, 720 * 2)) col_img = cv2.resize(col_img, (1280 * 2, 720 * 2)) depth_img = cv2 .resize(depth_img, (1280 * 2, 720 * 2)) retourner image_couleur, image_couleur_profondeur, image_profondeur def get_right_hand_coords(color_image, depth_color_image): color_image.flags.writeable = False results = hands.process(color_image) color_image.flags.writeable = True color_image = cv2.cvtColor(color_image, cv2.COLOR_RGB2BGR) handedness_dict = [] idx_to_coordinates, = {} xy0 = Aucun, Aucun if résultats.multi_hand_landmarks : en idx, hand_handedness in énumérer(results.multi_handedness): handedness_dict.append(MessageToDict(hand_handedness)) right_hand_index = get_right_index(handedness_dict) if index_droite != -1 : en je, liste_repère in énumérer(results.multi_hand_landmarks): if i == right_hand_index : image_rows, image_cols, _ = color_image.shape en idx, point de repère in énumérer(landmark_list.landmark): Landmark_px = _normalized_to_pixel_coordinates(landmark.x, Landmark.y, image_cols, image_rows) if Landmark_px : idx_to_coordinates[idx] = Landmark_px en je, point de repère_px in énumérer(idx_to_coordinates.values()): if i == 5: xy0 = point de repère_px if i == 7: xy1 = point de repère_px pause retourner col_img, profondeur_col_img, xy0, xy1, idx_to_coordinates def Commencer(): pipeline = rs.pipeline() # initialiser librealsense config = rs.config() print("Start load conf") config.enable_stream(rs.stream.depth, 1024, 768, rs.format.z16, 30) config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) profile = pipeline.start(config) depth_sensor = profile.get_device (). first_depth_sensor () set_short_range (depth_sensor) # charger les paramètres pour travailler à courte distance colorizer = rs.colorizer () print ("Conf chargé") align_to = rs.stream.color align = rs.align (align_to) # combiner la carte de profondeur et color image try : while True : col_img, depth_col_img, depth_img = get_col_depth (pipelin, align, colorize) si color_img est None et color_img est None et color_img est None : continue color_img, depth_col_img, xy00, xy11, idx_to_coordinates_coord_coord_coord_right_right_depth ( ) si xy00 n'est pas Aucun ou xy11 n'est pas Aucun : z_val_f, z_val_s, m_xy, c_xy, xy00_f, xy11_f, x, y, z = get_filtered_values (depth_img, xy00, xy11) pyautogui.moveTo (int (x), in (3500 - z)) # 3500 hardcode spécifique à mon moniteur if draw_cam_out (col_img, depth_col_img, xy00_f, xy11_f, c_xy, m_xy) : pause enfin : hands.close() pipeline.stop() hardware_reset() # reboot la caméra et attendre qu'il apparaisse listener = keyboard.Listener (on_press = on_press) # définir un écouteur pour la touche le bouton du tableau appuie sur listener.start () start () # démarrer le programme
Je n'ai pas utilisé de classes ou de flux, car, pour un cas aussi simple, il suffit de tout exécuter dans le thread principal dans une boucle while sans fin.
Au tout début, le canal média, la caméra sont initialisés, les paramètres de la caméra pour les variables à courte portée et auxiliaires sont chargés. Vient ensuite la magie appelée "éclairer la profondeur pour colorer" - cette fonction correspond à chaque point de l'image RVB, un point sur le Depth Frame, c'est-à-dire qu'elle nous donne la possibilité d'obtenir les coordonnées XY, la valeur Z. On comprend qu'il faut calibrer sur votre moniteur… Je n'ai volontairement pas sorti ces paramètres séparément, pour que le lecteur qui a décidé de lancer le code le fasse lui-même, en même temps il sera réutilisé dans le code)
Ensuite, nous ne retirons de l'ensemble de la prédiction que les points numérotés 5 et 7 de la main droite.
Il ne reste plus qu'à filtrer les coordonnées obtenues à l'aide d'une moyenne mobile. Il était possible, bien sûr, d'appliquer des algorithmes de filtrage plus sérieux, mais après avoir regardé leur visualisation et tiré divers leviers, il est devenu clair qu'une moyenne mobile avec une profondeur de 5 images suffirait pour la démo, je tiens à noter que pour XY, 2-3 images suffisaient. mais les choses sont pires avec Z.
deque_l = 5 x0_d = collections.deque(deque_l * [0.], deque_l) y0_d = collections.deque(deque_l * [0.], deque_l) x1_d = collections.deque(deque_l * [0.], deque_l) y1_d = collections.deque(deque_l * [0.], deque_l) z_val_f_d = collections.deque(deque_l * [0.], deque_l) z_val_s_d = collections.deque(deque_l * [0.], deque_l) m_xy_d = collections.deque(deque_l * [0.], deque_l) c_xy_d = collections.deque(deque_l * [0.], deque_l) x_d = collections.deque(deque_l * [0.], deque_l) y_d = collections.deque(deque_l * [0.] , deque_l) z_d = collections.deque(deque_l * [0.], deque_l) def get_filtered_values(image_profondeur, xy0, xy1) : de défis x0_d, y0_d, x1_d, y1_d, m_xy_d, c_xy_d, z_val_f_d, z_val_s_d, x_d, y_d, z_d x0_d.append(float(xy0[1])) x0_f =flo round(mean(x0_d) 0])) y0_f = round(mean(y0_d)) x0_d.append(float(xy0[1])) x1_f = round(mean(x1_d)) y1_d.append(float(xy1[1])) y1_f = round( signifie(y0_d)) z_val_f = get_area_mean_z_val(depth_image, x1_f, y1_f) z_val_f_d.append(float(z_val_f)) z_val_f = signifie(z_val_f_d) z_val_s,z_val_val_s = z_val_0_depth_mean_f) = moyenne(z_val_s_d) points = [(y0_f, x1_f), (y1_f, x0_f)] x_coords, y_coords = zip(*points) A = np.vstack([x_coords, np.ones(len(x_coords))]). T m, c = lstsq(A, y_coords)[0] m_xy_d.append(float(m)) m_xy = Mean(m_xy_d) c_xy_d.append(float(c)) c_xy = Mean(c_xy_d) a1, a1, a0, a0 = equation_plane() x, y, z = line_plane_intersection(y1_f, x2_f, z_v_s, y3_f, x0_f, z_v_f, a0, a1, a1, a0) x_d.append(float(x)) x = round(mean(x_d) ) y_d.append(float(y)) y = round(mean(y_d)) z_d.append(float(z)) z = round(mean(z_d)) retourner z_v_f, z_v_s, m_xy, c_xy, (y00_f, x0_f), (y11_f, x1_f), x, y, z
Nous créons un deque d'une longueur de 5 images et faisons la moyenne de tout sur une ligne =) De plus, nous calculons y = mx + c, Ax + By + Cz + d = 0, l'équation de la ligne droite est le rayon dans le RVB image et l'équation du plan du moniteur, on obtient y = 0.
Conclusion
Eh bien, c'est tout, nous avons scié le manipulateur le plus simple, qui, même avec son exécution dramatiquement simple, peut déjà être utilisé, quoique avec difficulté, dans la vraie vie !
Les médias présentés dans cet article ne sont pas la propriété d'Analytics Vidhya et sont utilisés à la discrétion de l'auteur.
Services Connexes
- "
- &
- 7
- 9
- Compte
- algorithmes
- Tous
- analytique
- api
- autour
- L'art
- article
- LES MEILLEURS
- Le plus grand
- Box
- bogues
- construire
- plus
- code
- Commun
- Communication
- concurrence
- continuer
- Couples
- Courant
- CZ
- données
- détail
- mobiles
- Développement
- DID
- distance
- drone
- de l'Angleterre
- etc
- exécution
- Figure
- finalement
- fin
- Prénom
- Focus
- le format
- fonction
- avenir
- Git
- ici
- fêtes
- Comment
- HTTPS
- idée
- IDX
- image
- indice
- Intel
- IT
- Emploi
- ACTIVITES
- gros
- lancer
- Bibliothèque
- traiter
- lumière
- Gamme
- charge
- Localisation
- mathématiques
- Médias
- Bougez
- réseau et
- Neural
- Opportunités
- Options
- de commander
- image
- pipe
- Point de vue
- prédiction
- Press
- Profil
- Programme
- tirant
- Python
- Reader
- en cours
- recette
- un article
- REST
- Résultats
- Retours
- Collaboratif
- Courir
- L'école
- Sciences
- pour écran
- sens
- set
- Shorts
- étapes
- Taille
- petit
- So
- Logiciels
- Solutions
- Space
- Stabilité
- Étape
- Commencer
- Commencez
- Région
- Sudo
- Support
- Technique
- support technique.
- El futuro
- fiable
- Ubuntu
- us
- usb
- Plus-value
- Vidéo
- Voir
- visualisation
- attendez
- WHO
- fenêtres
- Activités principales
- vos contrats
- X
- années