Este artigo foi publicado como parte do Blogathon de Ciência de Dados
Introdução
Olá pessoal! Embora o cyberpunk ainda não tenha entrado muito em nossas vidas e as interfaces neuro estejam longe do ideal, o LiDAR pode se tornar o primeiro estágio no caminho para o futuro dos manipuladores. Portanto, para não ficar entediado nas férias, resolvi fantasiar um pouco sobre os controles de um computador e, presumivelmente, de qualquer dispositivo, até uma escavadeira, nave espacial, drone ou fogão.
A ideia principal é mover o mouse, movendo não a mão inteira, mas apenas o dedo indicador, o que permitirá percorrer o menu sem tirar as mãos do teclado, pressionar botões e, junto com as teclas de atalho, se transformar em um ninja de teclado real! O que acontece se você adicionar gestos de deslizar ou rolar? Acho que vai haver uma bomba! Mas até este momento ainda temos que esperar alguns anos)
Vamos começar a montar nosso protótipo do manipulador do futuro
O que você precisa:
-
Câmera com LiDAR Intel Realsense L515.
-
Capacidade de programar em python
-
Lembre-se um pouco da matemática escolar
-
Monte para a câmera no monitor, também conhecido como tripé
Colocamos a câmera em um tripé com aliexpress, acabou sendo muito conveniente, leve e barato)
Descobrimos como e sobre o que fazer um protótipo
Existem muitas abordagens para realizar essa tarefa. Você mesmo pode treinar o detector ou a segmentação da mão, recortar a imagem resultante da mão direita e depois aplicar este maravilhoso repositório de pesquisas do Facebook à imagem, obter um resultado excelente ou torná-lo ainda mais fácil.
Para usar o repositório de pipe de mídia, depois de ler este link, você pode entender que esta é uma das melhores opções da atualidade.
Em primeiro lugar, já está tudo pronto para uso - a instalação e o lançamento levarão 30 minutos, levando em consideração todos os pré-requisitos.
Em segundo lugar, graças a uma equipe de desenvolvimento poderosa, eles não apenas levam o estado da arte na estimativa de postura, mas também fornecem uma API fácil de entender.
Terceiro, a rede está pronta para funcionar na CPU, portanto, o limite de entrada é mínimo.
Provavelmente, você vai perguntar por que não vim aqui e não usei os repositórios dos vencedores desta competição. Na verdade, estudei sua solução com alguns detalhes, eles estão bastante prontos, sem pilhas de milhões de grades, etc. Mas o maior problema, me parece, é que eles trabalham com imagens de profundidade. Por se tratarem de acadêmicos, eles não hesitaram em converter todos os dados através do Matlab, além disso, a resolução em que as profundidades foram filmadas me pareceu pequena. Isso pode ter um efeito profundo no resultado. Portanto, parece que a maneira mais fácil é obter os pontos-chave na imagem RGB e obter o valor ao longo do eixo Z no quadro de profundidade pelas coordenadas XY. Agora a tarefa não é otimizar muito algo, então faremos porque é mais rápido do ponto de vista do desenvolvimento.
Lembrando a matemática escolar
Como já escrevi, para obter a coordenada do ponto onde o cursor do mouse deveria estar, precisamos construir uma linha passando por dois pontos-chave da falange do dedo e encontrar o ponto de intersecção da linha e do plano do monitor.
A imagem mostra esquematicamente o plano do monitor e a linha que o cruza. Você pode olhar para a matemática aqui.
Usando dois pontos, obtemos uma representação paramétrica de uma linha reta no espaço.
Não vou me concentrar muito no currículo de matemática da escola.
Instalando uma biblioteca para trabalhar com uma câmera
Esta é talvez a parte mais difícil deste trabalho. Acontece que o software da câmera para o Ubuntu é muito bruto, o sentido liberal está simplesmente cheio de todos os tipos de insetos, falhas e danças com um pandeiro.
Até agora, não consegui derrotar o comportamento estranho da câmera, às vezes ela não carrega parâmetros na inicialização.
A câmera funciona apenas uma vez após reiniciar o computador !!! Mas há uma solução: antes de cada lançamento, faça um hard reset do software da câmera, resete o USB, e talvez tudo dê certo. Já agora, para o Windows 10 está tudo bem aí. É estranho que os desenvolvedores imaginem robôs baseados no Windows =)
Para ter um sentido real no Ubuntu 20, faça o seguinte:
$ sudo apt-get install libusb-1.0-0-dev Em seguida, execute novamente o cmake e make install. Aqui is uma receita completa que funcionou para eu: $ sudo apt-get install libusb-1.0-0-dev $ git clone https://github.com/IntelRealSense/librealsense.git $ cd librealsense / $ mkdir build && cd build
Depois de coletar de tipos, será mais ou menos estável. Um mês de comunicação com o suporte técnico revelou que você precisa instalar o Ubuntu 16 ou sofrerá. Eu escolhi você mesmo, você sabe o quê.
Continuamos a compreender os meandros da rede neural
Agora vamos ver outro vídeo da operação dedo e mouse. Observe que o ponteiro não pode ficar em um lugar e, por assim dizer, flutua em torno do ponto pretendido. Ao mesmo tempo, posso facilmente direcioná-lo para a palavra de que preciso, mas com uma letra é mais difícil, preciso mover o cursor com cuidado:
Isso, como você entende, não é um aperto de mão, nas férias eu bebia apenas uma caneca de New England DIPA =) É tudo sobre flutuações constantes de pontos-chave e coordenadas Z com base nos valores obtidos do lidar.
Vamos olhar mais de perto:
Em nosso SOTA de tubo de mídia, certamente existem menos flutuações, mas elas também existem. Como se viu, eles estão lutando contra isso usando prokid vaniya do mapa de calor do quadro anterior no quadro atual e na rede ferroviária - dá mais estabilidade, mas não 100%.
Além disso, parece-me que a especificidade da marcação desempenha um papel. É quase impossível fazer a mesma marcação em tal número de quadros, sem mencionar o fato de que a resolução do quadro é diferente em todos os lugares e não muito grande. Além disso, não vemos a oscilação da luz, que, provavelmente, não é constante devido aos diferentes períodos de operação e a quantidade de exposição da câmera. E a rede também retorna um sanduíche do mapa de calor igual ao número de pontos-chave na tela, o tamanho desse tensor é BxNx96x96, onde N é o número de pontos-chave e, claro, após o limite e redimensionar para o original tamanho do quadro, nós temos o que temos (
Exemplo de renderização de mapa de calor:
Revisão de código
Todo o código está neste repositório e é muito curto. Vamos dar uma olhada no arquivo principal e ver o resto por si mesmo.
importar cv2 importar tubo de mídia as mp importar numpy as np importar Pyautogui importar pyrealsense2.pyrealsense2 as rs da google.protobuf.json_format importar Mensagem para Dict da mediapipe.python.solutions.drawing_utils importar _normalizadas_para_pixel_coordenadas da entrada importar teclado da utils.comum importar get_filtered_values, draw_cam_out, get_right_index da utilitários.hard_reset importar reinicialização de hardware da utilitários.set_options importar set_short_range pyautogui.FAILSAFE = Falso mp_drawing = mp.solutions.drawing_utils mp_hands = mp.solutions.hands # Estimativa de postura das mãos hands = mp_hands.Hands (max_num_hands = 2, min_detection_confidence = 0.9) def on_press(chave): if key == keyboard.Key.ctrl: pyautogui.leftClick () if key == keyboard.Key.alt: pyautogui.rightClick () def get_color_profundidade(pipeline, align, colorizer): frames = pipeline.wait_for_frames (timeout_ms = 15000) # esperando por um quadro da câmera align_frames = align.process (frames) depth_frame = align_frames.get_depth_frame () color_frame = align_frames.get_color_frame () if não deep_frame or não quadro_cor: retorno Nenhum, Nenhum, Nenhum 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_ 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)) retorno imagem_cor, imagem_cor_profundidade, imagem_profundidade 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 = [] idx_to_coordinates = {xy0} xy1 = Nenhum, Nenhum if resultados.multi_hand_landmarks: para idx, mão_handedness in enumerar (results.multi_handedness): handedness_dict.append (MessageToDict (hand_handedness)) right_hand_index = get_right_index (Handness_dict) if right_hand_index! = -1: para i, lista_marca in enumerar (results.multi_hand_landmarks): if i == right_hand_index: image_rows, image_cols, _ = color_image.shape para idx, ponto de referência in enumerar (mark_list.landmark): landmark_px = _normalized_to_pixel_coordinates (landmark.x, landmark.y, image_cols, image_rows) if Marcos_px: idx_para_coordenadas [idx] = Marco_px para i ,mark_px in enumerar (idx_to_coordinates.values ()): if i == 5: xy0 = marco_px if i == 7: xy1 = marco_px quebrar retorno col_img, profundidade_col_img, xy0, xy1, idx_to_coordinates def começo(): 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) # parâmetros de carga para trabalhar em uma distância curta colorizer = rs.colorizer () print ("Conf carregado") align_to = rs.stream.color align = rs.align (align_to) # combinar mapa de profundidade e color image try: enquanto True: col_img, depth_col_img, depth_img = get_col_depth (pipelin, alinhar, colorir) se color_img for None e color_img for None e color_img for None: continue color_img, depth_col_img, xy00, xy11, idx_to_coordimg_coordimg ) se xy00 não for Nenhum ou xy11 não for Nenhum: 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 meu monitor se draw_cam_out (col_img, depth_col_img, xy00_f, xy11_f, c_xy, m_xy): break finally: hands.close () pipeline.stop () hardware_reset () # reinicie a câmera e espere que apareça listener = keyboard.Listener (on_press = on_press) # definir um ouvinte para a chave botão da placa pressiona listener.start () start () # inicia o programa
Não usei classes ou streams, pois, para um caso tão simples, basta executar tudo no thread principal em um loop while sem fim.
No início, o canal de mídia, a câmera são inicializados, as configurações da câmera para as variáveis de curto alcance e auxiliares são carregadas. Em seguida, vem a mágica chamada “luz de profundidade para colorir” - esta função combina cada ponto da imagem RGB, um ponto no Quadro de Profundidade, ou seja, nos dá a oportunidade de obter as coordenadas XY, o valor Z. Entende-se que é necessário calibrar em seu monitor ... deliberadamente não retirei esses parâmetros separadamente, para que o leitor que decidiu executar o código o faça ele mesmo, ao mesmo tempo em que será reutilizado no código)
Em seguida, pegamos de toda a previsão apenas os pontos numerados 5 e 7 da mão direita.
A única coisa que resta a fazer é filtrar as coordenadas obtidas usando uma média móvel. Claro que era possível aplicar algoritmos de filtragem mais sérios, mas depois de olhar para sua visualização e puxar várias alavancas, ficou claro que uma média móvel com uma profundidade de 5 quadros seria suficiente para a demonstração, quero observar que para XY, 2-3 frames foram suficientes. mas as coisas são piores com Z.
deque_l = 5 x0_d = coleções.deque (deque_l * [0.], deque_l) y0_d = coleções.deque (deque_l * [0.], deque_l) x1_d = coleções.deque (deque_l * [0.], deque_l) y1_d = coleções.deque (deque_l * [0.], deque_l) z_val_f_d = coleções.deque (deque_l * [0.], deque_l) z_val_s_d = coleções.deque (deque_l * [0.], deque_l) m_xy_d = coleções.deque (deque_l * [0.], deque_l) c_xy_d = coleções.deque (deque_l * [0.], deque_l) x_d = coleções.deque (deque_l * [0.], deque_l) y_d = coleções.deque (deque_l * [0.] , deque_l) z_d = Collections.deque (deque_l * [0.], deque_l) def get_filtered_values(imagem_de_profundidade, 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 = arredondado (média (x0_d.ap) (x0_d) y0_d 0])) y0_f = round (mean (y0_d)) x1_d.append (float (xy1 [1])) x1_f = round (mean (x1_d)) y1_d.append (float (xy1 [0])) y1_f = round ( média (y1_d)) z_val_f = get_area_mean_z_val (profundidade_imagem, x0_f, y0_f) z_val_f_d.append (float (z_val_f)) z_val_f = média (z_val_f_d) z_val_s = get_area_mean_val_val_f_d) (z_val_val_flot_val_val_f) (profundidade_val_val_foto) (z_val_val_val_val) (profundidade_val_f_val) (profundidade_val_f1) = média (z_val_s_d) pontos = [(y1_f, x0_f), (y0_f, x1_f)] x_coords, y_coords = zip (* pontos) 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 = média (m_xy_d) c_xy_d.append (float (c)) c_xy = média (c_xy_d) a0, a0, a1, a2 = equation_plane () x, y, z = line_plane_intersection (y3_f, x0_f, z_v_s, y0_f, x1_f, z_v_f, a1, a0, a1, a2) x_d.append (float (x)) x = redondo (média (x_d) ) y_d.append (float (y)) y = round (mean (y_d)) z_d.append (float (z)) z = round (mean (z_d)) retorno z_v_f, z_v_s, m_xy, c_xy, (y00_f, x0_f), (y11_f, x1_f), x, y, z
Criamos um deque com um comprimento de 5 quadros e calculamos a média de tudo em uma linha =) Além disso, calculamos y = mx + c, Ax + By + Cz + d = 0, a equação para a linha reta é o raio no RGB imagem e a equação do plano do monitor, obtemos y = 0.
Conclusão
Bem, isso é tudo, serramos o manipulador mais simples, que, mesmo com sua execução dramaticamente simples, já pode ser usado, embora com dificuldade, na vida real!
As mídias mostradas neste artigo não são propriedade da Analytics Vidhya e são usadas a critério do autor.
Relacionado
- "
- &
- 7
- 9
- Conta
- algoritmos
- Todos os Produtos
- analítica
- api
- por aí
- Arte
- artigo
- MELHOR
- O maior
- Caixa
- erros
- construir
- mais próximo
- código
- comum
- Comunicação
- competição
- continuar
- Casal
- Atual
- CZ
- dados,
- detalhe
- desenvolvedores
- Desenvolvimento
- DID
- distância
- zangão
- Inglaterra
- etc.
- execução
- Figura
- Finalmente
- final
- Primeiro nome
- Foco
- formato
- função
- futuro
- Git
- SUA PARTICIPAÇÃO FAZ A DIFERENÇA
- férias
- Como funciona o dobrador de carta de canal
- HTTPS
- idéia
- IDX
- imagem
- índice
- Intel
- IT
- Trabalho
- Chave
- grande
- lançamento
- Biblioteca
- LIDAR
- leve
- Line
- carregar
- mapa,
- matemática
- Mídia
- mover
- rede
- Neural
- Oportunidade
- Opções
- ordem
- fotografia
- tubo
- Ponto de vista
- predição
- imprensa
- Perfil
- Agenda
- puxando
- Python
- Leitor
- Leitura
- receita
- pesquisa
- DESCANSO
- Resultados
- Retorna
- robôs
- Execute
- Escola
- Ciência
- Peneira
- sentido
- conjunto
- Baixo
- simples
- Tamanho
- pequeno
- So
- Software
- Soluções
- Espaço
- Estabilidade
- Etapa
- começo
- inicialização
- Estado
- Sudo
- ajuda
- Dados Técnicos:
- suporte técnico
- O Futuro
- tempo
- Ubuntu
- us
- usb
- valor
- Vídeo
- Ver
- visualização
- esperar
- QUEM
- Windows
- Atividades:
- trabalho
- X
- anos