Logotipo de Zephyrnet

Estimación de la postura de la mano basada en LiDAR en 30 minutos

Fecha:

Este artículo fue publicado como parte del Blogatón de ciencia de datos

Introducción

¡Hola, todos! Si bien el cyberpunk aún no ha entrado mucho en nuestras vidas y las interfaces neurológicas están lejos de ser ideales, LiDAR puede convertirse en la primera etapa en el camino hacia el futuro de los manipuladores. Por eso, para no aburrirme durante las vacaciones, decidí fantasear un poco con los controles de una computadora y, presumiblemente, cualquier dispositivo, hasta una excavadora, nave espacial, dron o estufa.

La idea principal es mover el mouse, moviendo no toda la mano, sino solo el dedo índice, lo que le permitirá recorrer el menú sin quitar las manos del teclado, presionar botones y, junto con las teclas de acceso rápido, convertirse en un ninja de teclado real! ¿Qué sucede si agrega gestos de deslizamiento o desplazamiento? ¡Creo que habrá una bomba! Pero hasta este momento todavía tenemos que esperar un par de años)

Empecemos a montar nuestro prototipo del manipulador del futuro.

Que es lo que necesita:

  1. Cámara con LiDAR Intel Realsense L515.

  2. Capacidad para programar en python

  3. Solo recuerda un poco las matemáticas de la escuela

  4. Montaje de la cámara en el monitor, también conocido como trípode

Conectamos la cámara a un trípode con aliexpress, resultó ser muy conveniente, liviano y económico)

imagen | Estimación de la postura de la mano LiDAR

FUENTE

imagen 2 | Estimación de la postura de la mano LiDAR

FUENTE

Descubrimos cómo y qué hacer un prototipo.

Hay muchos enfoques para realizar esta tarea. Puede entrenar el detector o la segmentación manual usted mismo, recortar la imagen resultante de la mano derecha y luego aplicar este maravilloso repositorio de la investigación de Facebook a la imagen, obtener un resultado excelente o hacerlo aún más fácil.

Para usar el repositorio de media pipe, después de leer este enlace, Puedes entender que esta es una de las mejores opciones para hoy.

En primer lugar, todo está listo para usar: la instalación y el inicio tardarán 30 minutos, teniendo en cuenta todos los requisitos previos.

En segundo lugar, gracias a un poderoso equipo de desarrollo, no solo toman el estado del arte en la estimación de la postura de la mano, sino que también brindan una API fácil de entender.

En tercer lugar, la red está lista para ejecutarse en la CPU, por lo que el umbral de entrada es mínimo.

Probablemente, me preguntarán por qué no vine aquí y no usé los repositorios de los ganadores de esta competencia. De hecho, estudié su solución con cierto detalle, están bastante preparados, no hay pilas de millones de cuadrículas, etc. Pero el mayor problema, me parece, es que funcionan con imágenes de profundidad. Como estos son académicos, no dudaron en convertir todos los datos a través de Matlab, además, la resolución en la que se filmaron las profundidades me pareció pequeña. Esto podría tener un efecto profundo en el resultado. Por lo tanto, parece que la forma más fácil es obtener los puntos clave en la imagen RGB y tomar el valor a lo largo del eje Z en el Cuadro de profundidad por las coordenadas XY. Ahora la tarea no es optimizar mucho algo, así que lo haremos ya que es más rápido desde el punto de vista del desarrollo.

Recordando las matemáticas escolares

Como ya escribí, para obtener la coordenada del punto donde debería estar el cursor del mouse, necesitamos construir una línea que pase por dos puntos clave de la falange del dedo y encontrar el punto de intersección de la línea y el plano del monitor.

plano de la imagen | Estimación de la postura de la mano LiDAR

FUENTE

La imagen muestra esquemáticamente el plano del monitor y la línea que lo cruza. Puedes mirar las matemáticas aquí.

Usando dos puntos, obtenemos una representación paramétrica de una línea recta en el espacio.

representación paramétrica | Estimación de la postura de la mano LiDAR
las variables

No me centraré demasiado en el plan de estudios de matemáticas de la escuela.

Instalación de una biblioteca para trabajar con una cámara

Esta es quizás la parte más difícil de este trabajo. Al final resultó que, el software de la cámara para Ubuntu es muy tosco, el sentido liberal simplemente está plagado de todo tipo de errores, fallas y bailes con una pandereta.

Hasta ahora, no he podido vencer el extraño comportamiento de la cámara, a veces no carga parámetros al inicio.

¡La cámara funciona solo una vez después de reiniciar la computadora! Pero hay una solución: antes de cada lanzamiento, haga un restablecimiento completo del software de la cámara, restablezca el USB y tal vez todo esté bien. Por cierto, para Windows 10 todo está bien ahí. Es extraño que los desarrolladores imaginen robots basados ​​en Windows =)

Para tener un sentido real en Ubuntu 20, haga esto:

$ sudo apt-get install libusb-1.0-0-dev Luego, vuelva a ejecutar cmake y hacer la instalación. Aquí is una receta completa que funcionó para yo: $ sudo apt-get install libusb-1.0-0-dev $ git clone https://github.com/IntelRealSense/librealsense.git $ cd librealsense / $ mkdir build && cd build

Habiendo recolectado de tipos, será más o menos estable. Un mes de comunicación con el soporte técnico reveló que necesita instalar Ubuntu 16 o sufrir. Lo elegí tú mismo, ¿sabes qué?

Seguimos comprendiendo las complejidades de la red neuronal.

Ahora veamos otro video de la operación con el dedo y el mouse. Tenga en cuenta que el puntero no puede permanecer en un lugar y, por así decirlo, flota alrededor del punto previsto. Al mismo tiempo, puedo dirigirlo fácilmente a la palabra que necesito, pero con una letra, es más difícil, tengo que mover con cuidado el cursor:

Esto, como comprenderá, no es darme la mano, en vacaciones solo bebía una taza de New England DIPA =) Se trata de fluctuaciones constantes de puntos clave y coordenadas Z basadas en los valores obtenidos del lidar.

Miremos más de cerca:

En nuestro SOTA from media pipe, ciertamente hay menos fluctuaciones, pero también existen. Al final resultó que, están luchando con esto mediante el uso de prokid vaniya del mapa de calor del cuadro anterior en el cuadro actual y la red de trenes; da más estabilidad, pero no al 100%.

Además, me parece que la especificidad del marcado juega un papel. Difícilmente es posible hacer el mismo marcado en tantos fotogramas, sin mencionar el hecho de que la resolución del fotograma es diferente en todas partes y no muy grande. Además, no vemos el parpadeo de la luz, que, muy probablemente, no sea constante debido a los diferentes periodos de funcionamiento y la cantidad de exposición de la cámara. Y la red también devuelve un sándwich del mapa de calor igual al número de puntos clave en la pantalla, el tamaño de este tensor es BxNx96x96, donde N es el número de puntos clave y, por supuesto, después del umbral y el cambio de tamaño al original. tamaño del marco, obtenemos lo que obtenemos

Ejemplo de renderizado de mapa de calor:

estimación de la pose de la mano | Estimación de la postura de la mano LiDAR

FUENTE

Revisión de código

Todo el código está en este repositorio y es muy corto. Echemos un vistazo al archivo principal y veamos el resto por ti mismo.

importar cv2
importar tubo de medios as mp
importar numpy as np
importar piautogui
importar pyrealsense2.pyrealsense2 as rs
Desde google.protobuf.json_formato importar Mensaje a dictado
Desde mediapipe.python.solutions.drawing_utils importar _normalizado_a_coordenadas_de_píxeles
Desde pynput importar teclado
Desde utils.común importar get_filtered_values, draw_cam_out, get_right_index
Desde utils.hard_reset importar reinicio_hardware
Desde utils.set_opciones importar set_short_range pyautogui.FAILSAFE = Falso mp_drawing = mp.solutions.drawing_utils mp_hands = mp.solutions.hands # Estimación de la postura de la mano hands = mp_hands.Hands (max_num_hands = 2, min_detection_confidence = 0.9) def en_prensa(llave):
if clave == keyboard.Key.ctrl: pyautogui.leftClick ()
if clave == keyboard.Key.alt: pyautogui.rightClick ()
def obtener_color_profundidad(pipeline, align, colorizer): frames = pipeline.wait_for_frames (timeout_ms = 15000) # esperando un fotograma de la cámara align_frames = align.process (fotogramas) depth_frame = align_frames.get_depth_frame () color_frame = alineado_frames.get_color_frame ()
if no marco_de_profundidad or no color_marco:
volvemos Ninguno, Ninguno, Ninguno 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 np.flipud (np.fliplr (profundidad_img)) profundidad_col_img = cv2.resize (profundidad_col_img, (1280 * 2, 720 * 2)) col_img = cv2.resize (col_img, (1280 * 2, 720 * 2)) profundidad_img = cv2 .resize (imagen_de_profundidad, (1280 * 2, 720 * 2))
volvemos imagen_color, profundidad_color_imagen, profundidad_imagen
def get_right_hand_coords(color_image, depth_color_image): color_image.flags.writeable = Resultados falsos = hands.process (color_image) color_image.flags.writeable = True color_image = cv2.cvtColor (color_image, cv2.COLOR_RGB2BGR) handness_dict = [] id0x_d xy1 = Ninguno, Ninguno
if resultados.multi_hand_landmarks:
para idx, destreza manual in enumerar (resultados.multi_handedness): handedness_dict.append (MessageToDict (hand_handedness)) right_hand_index = get_right_index (handedness_dict)
if right_hand_index! = -1:
para yo, lista_de_hito in enumerar (resultados.multi_hand_landmarks):
if i == índice_mano_derecha: filas_imagen, columnas_imagen, _ = color_imagen.forma
para idx, punto de referencia in enumerate (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
para yo, landmark_px in enumerar (idx_to_coordinates.values ​​()):
if i == 5: xy0 = hito_px
if i == 7: xy1 = hito_px
romper
volvemos col_img, profundidad_col_img, xy0, xy1, idx_to_coordinates
def comienzo(): pipeline = rs.pipeline () # initialize 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) # cargar parámetros para trabajar a una distancia corta colorizer = rs.colorizer () print ("Conf cargado") align_to = rs.stream.color align = rs.align (align_to) # combinar mapa de profundidad y prueba de imagen en color: while True: col_img, depth_col_img, depth_img = get_col_depth (canalizar, alinear, colorear) si color_img es Ninguno y color_img es Ninguno y color_img es Ninguno: continue color_img, depth_col_img, xy00, xy11, idx_to_coordinates, deep_right_gords ) si xy00 no es None o xy11 no es None: 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), int (3500 - z)) # 3500 hardcode específico para mi monitor si draw_cam_out (col_img, depth_col_img, xy00_f, xy11_f, c_xy, m_xy): break finalmente: hands.close () pipeline.stop () hardware_reset () # reinicia la cámara y esperar a que aparezca listener = keyboard.Listener (on_press = on_press) # establecer un oyente para la tecla botón de tablero presiona listener.start () start () # inicia el programa

No usé clases ni flujos, porque, para un caso tan simple, es suficiente ejecutar todo en el hilo principal en un bucle while sin fin.

Al principio, se inicializan la canalización de medios, la cámara, se cargan los ajustes de la cámara para las variables auxiliares y de corto alcance. Luego viene la magia llamada "iluminar la profundidad al color": esta función hace coincidir cada punto de la imagen RGB, un punto en el cuadro de profundidad, es decir, nos da la oportunidad de obtener las coordenadas XY, el valor Z. Se entiende que es necesario calibrar en su monitor ... Deliberadamente no saqué estos parámetros por separado, para que el lector que decidiera ejecutar el código lo hiciera él mismo, al mismo tiempo se reutilizará en el código)

A continuación, tomamos de toda la predicción solo los puntos numerados 5 y 7 de la mano derecha.

puntos de mano

Lo único que queda por hacer es filtrar las coordenadas obtenidas mediante una media móvil. Por supuesto, era posible aplicar algoritmos de filtrado más serios, pero después de observar su visualización y tirar de varias palancas, quedó claro que una media móvil con una profundidad de 5 fotogramas sería suficiente para la demostración, quiero señalar que para XY, 2-3 fotogramas fueron suficientes. pero las cosas van peor con Z.

deque_l = 5 x0_d = colecciones.deque (deque_l * [0.], deque_l) y0_d = colecciones.deque (deque_l * [0.], deque_l) x1_d = colecciones.deque (deque_l * [0.], deque_l) y1_d = colecciones.deque (deque_l * [0.], deque_l) z_val_f_d = colecciones.deque (deque_l * [0.], deque_l) z_val_s_d = collections.deque (deque_l * [0.], deque_l) m_xy_d = colecciones.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 = colecciones.deque (deque_l * [0.], deque_l)
def obtener_valores_filtrados(imagen_profundidad, xy0, xy1):
global 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 = round (mean (x0_dap)) y0_d 0])) y0_f = round (mean (y0_d)) x0_d.append (float (xy1 [1])) x1_f = round (mean (x1_d)) y1_d.append (float (xy1 [1])) y0_f = round ( mean (y1_d)) z_val_f = get_area_mean_z_val (profundidad_imagen, x1_f, y0_f) z_val_f_d.append (float (z_val_f)) z_val_f = mean (z_val_f_d) z_val_s = get_area_fat_f_d) z_val_s = get_area_fat_f_zage_0 (float_fat_f_d) = media (z_val_s_d) puntos = [(y1_f, x1_f), (y0_f, x0_f)] x_coords, y_coords = zip (* puntos) A = np.vstack ([x_coords, np.ones (len (x_coords))]). T m, c = lstsq (A, y_coords) [1] 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, a0, a0, a1 = ecuación_plano () x, y, z = línea_plano_intersección (y2_f, x3_f, z_v_s, y0_f, x0_f, z_v_f, a1, a1, a0, a1) x_d.append (float (x)) x = redondo (media (x_d) ) y_d.append (float (y)) y = round (media (y_d)) z_d.append (float (z)) z = round (media (z_d))
volvemos z_v_f, z_v_s, m_xy, c_xy, (y00_f, x0_f), (y11_f, x1_f), x, y, z

Creamos una deque con una longitud de 5 cuadros y promediamos todo en una fila =) Además, calculamos y = mx + c, Ax + By + Cz + d = 0, la ecuación para la línea recta es el rayo en el RGB imagen y la ecuación del plano del monitor, lo obtenemos y = 0.

Conclusión

Bueno, eso es todo, cortamos el manipulador más simple, que, incluso con su ejecución dramáticamente simple, ya se puede usar, aunque con dificultad, en la vida real.

Los medios que se muestran en este artículo no son propiedad de Analytics Vidhya y se utilizan a discreción del autor.

PlatoAi. Web3 reinventado. Inteligencia de datos ampliada.
Haga clic aquí para acceder.

Fuente: https://www.analyticsvidhya.com/blog/2021/08/hand-pose-estimation-based-on-lidar-in-30-minutes/

punto_img

Información más reciente

punto_img