Visión del robot Raspberry Pi: mapa de profundidad
Hoy, toda la tecnología de "construcción de drones" se está volviendo activamente más barata. Excepto por uno: obtener un mapa del espacio circundante. Existen dos extremos: o costosos lidares (miles de dólares) y soluciones ópticas para construir un mapa de profundidad (muchos cientos de dólares), o soluciones bastante centavas como telémetros ultrasónicos.Por lo tanto, surgió una idea sobre la base de una Raspberry Pi económica con una cámara para hacer una solución que estaría en un nicho vacío y le permitiría obtener un mapa de profundidad "a bajo costo". Y para hacer esto en un lenguaje de programación simple como Python, de modo que esté disponible para que los principiantes experimenten. En realidad, quería contarles sobre mis resultados. Los scripts resultantes con fotos de muestra también se pueden ejecutar en el escritorio.Mapa de profundidad de una cámara.
Primero, algunas palabras sobre la parte óptica. Para crear un mapa de profundidad, siempre se utilizan dos imágenes, desde las cámaras izquierda y derecha. Y tenemos frambuesas con una cámara. Por lo tanto, se desarrolló un divisor óptico, que como resultado da un par estéreo a la cámara.En términos simples: si miras la foto, entonces, desde el cuadro negro, dos ojos de la cámara te miran. Pero en realidad la cámara es una. Solo un poco de magia óptica.
La foto muestra la duodécima iteración del dispositivo. Tomó mucho tiempo obtener un diseño estable confiable, que al mismo tiempo sería económico. La parte más difícil son los espejos internos, que se fabricaron por encargo mediante deposición al vacío de aluminio. Si usa espejos estándar, en los que la capa reflectante se encuentra debajo del vidrio, y no por encima de él, entonces en la unión forman un espacio que arruina radicalmente toda la imagen.¿En qué trabajaremos?
La imagen de frambuesa de Raspbian Wheezy se tomó como base, se instalaron Python 2.7 y OpenCV 2.4, bueno, los paquetes necesarios para las pequeñas cosas: matplotlib, numpy y otros. Todos los tipos y un enlace a la imagen final de la tarjeta se presentan al final del artículo. La descripción de los scripts en forma de lecciones se puede encontrar en el sitio web del proyecto.Preparando una imagen para construir un mapa de profundidad
Como nuestra solución no está hecha de metal y sin una óptica ultra precisa, es posible que se produzcan pequeñas desviaciones de la geometría ideal como resultado del ensamblaje. Además, la cámara está unida al dispositivo con tornillos, por lo que su posición puede no ser la ideal. El problema con la ubicación de la cámara se resuelve manualmente, y la compensación por la "curvatura" del ensamblaje de la estructura se realizará en el software.Script One - Alineación de cámara
La unión de los espejos en la imagen idealmente debe ser vertical y centrada. Es difícil hacerlo a simple vista, por lo que se hizo el primer guión. Simplemente captura la imagen de la cámara en modo de vista previa en vivo, la muestra en la pantalla y, en el centro, en la superposición, dibuja una franja blanca a lo largo de la cual se está alineando. Después de que la cámara está orientada correctamente, apretamos los tornillos con más fuerza y se completa el montaje.Lo que es interesante en el código del primer script- –
- – , cv.imshow() , . , . , , .
- – , .. . «» , camera.hflip = True
El segundo script: obtenemos un par estéreo "limpio"
Nuestras imágenes izquierda y derecha se unen en el centro de la imagen. La unión en la foto tiene un ancho distinto de cero: puede deshacerse de ella solo quitando los espejos del dispositivo, lo que aumenta el tamaño de la estructura. La cámara de la frambuesa tiene un enfoque establecido en infinito, y los objetos ubicados cerca (en nuestro caso, esto es una unión) simplemente se "desenfocan". Por lo tanto, solo necesita decirle al script lo que, en nuestra opinión, es una zona “mala”, para que el par estéreo se corte en imágenes. Se realizó un segundo guión que muestra una imagen y le permite usar las teclas para indicar la zona que se va a cortar.Así es como se ve el proceso:Lo que es interesante en el código del segundo script- , , cv2.rectangle(). , , . , , Enter .
- , . , .
- JSON. , , .
- . , , , . , ./src . . pf_1280_720.txt – , .
- , . . :
loadImagePath = ""
El tercer guión: una serie de fotos para calibrar
La ciencia básica dice que para construir con éxito un mapa de profundidad, el par estéreo debe calibrarse. Es decir, todos los puntos clave de la imagen de la izquierda deben estar a la misma altura y en la imagen de la derecha. En esta situación, la función StereoBM, que es nuestro único tiempo real, puede hacer su trabajo con éxito.Para la calibración, necesitamos imprimir una imagen de referencia, hacer una serie de fotos y entregarla al algoritmo de calibración, que calculará todas las distorsiones y guardará los parámetros para que las imágenes vuelvan a la normalidad.Entonces, imprima el " tablero de ajedrez " y péguelo sobre una superficie plana y dura. Para simplificar, la foto en serie se hizo un script con un temporizador de cuenta regresiva, que se muestra en la parte superior del video.Así es como se ve el guión de fotografía en serie en el trabajo:Lo que es interesante en el tercer código de script- . , . , , «» . , – , . camera.capture () , use_video_port=True.
- – camera.annotate_text() . 5 – .
- -, , ./src
Cabe señalar que la "corrección" de la serie realizada es fundamental para los resultados de la calibración. Un poco más adelante veremos el resultado que se obtiene con fotografías tomadas incorrectamente.Script 4: cortar fotos en pares estéreo
Después de tomar una serie de fotos, haremos otro script de servicio que tome la serie completa de fotos y las corte en pares de imágenes, izquierda y derecha, y las guarde en una carpeta. zonas en el centro de la imagen. El guión es bastante ordinario, así que escondí el video debajo de un spoiler.Un ejemplo de trabajo y un enlace a las fuentes del cuarto guión. Lo más interesante es la calibración, el quinto script
El script de calibración alimenta todos los pares estéreo desde la carpeta ./src a la función de calibración y se sumerge en la reflexión. Después de su difícil trabajo (para 15 imágenes de 1280x720 en la primera frambuesa se tarda unos 5 minutos), toma el último par estéreo, "corrige" las imágenes (rectifica) y muestra versiones ya corregidas con las que puede construir un mapa de profundidad.Así es como se ve el guión en el trabajo:Lo que es interesante en el código del quinto guión "Algo salió mal".
Hay momentos en que los resultados de la calibración son inesperados.Aquí hay un par de ejemplos sorprendentes:
De hecho, la calibración es un momento decisivo. Lo que obtengamos en la etapa de construcción de un mapa de profundidad depende directamente de su calidad. Después de una gran cantidad de experimentos, surgió la siguiente lista de requisitos de disparo:- La imagen del ajedrez no debe ser paralela al plano de la foto, siempre en diferentes ángulos. Pero incluso sin fanatismo: si mantiene el tablero casi perpendicular al plano de la foto, el guión simplemente no encontrará el ajedrez en la imagen.
- Luz, buena luz. Con poca luz en la habitación, e incluso al sacar imágenes del video, la calidad de la imagen disminuye. En mi caso, la luz en el 90% de los casos corrigió de inmediato la situación.
- Internet escribe que la pizarra debe ocupar el espacio máximo en la imagen siempre que sea posible. Realmente ayuda.
Así es como se ve un par estéreo "fijo" con buenos resultados de calibración:
Script 6: el primer intento de construir un mapa de profundidad
Como si todo estuviera listo, ya puedes construir un mapa de profundidad. Cargamos los resultados de la calibración, tomamos una foto y construimos audazmente un mapa de profundidad usando cv2.StereoBMObtenemos algo como esto:
el resultado no es muy impresionante, obviamente necesitamos torcer algo. Bueno, procedamos a una afinación más precisa en el próximo séptimo guión. Allí usaremos para la construcción no 2 parámetros, como en StereoBM (), sino casi 10, que es mucho más interesante.Aquí están las fuentes del sexto guiónScript 7 - mapa de profundidad con configuraciones avanzadas
Cuando los parámetros no son 2 sino 10, es incorrecto ordenar sus opciones con un reinicio constante de los scripts. Por lo tanto, se creó un guión para una conveniente sintonización interactiva del mapa de profundidad. La tarea no era complicar el código con la interfaz, por lo que todo se hizo en matplotlib. El dibujo de la interfaz en matplotlib en frambuesas es bastante lento, por lo que generalmente transfiero la carpeta de trabajo de frambuesas a la computadora portátil y selecciono los parámetros allí. Así es como funciona el guión:Después de haber seleccionado los parámetros, el script con el botón Guardar guarda el resultado en el archivo 3dmap_set.txt en formato JSON.¿Qué es interesante en el código de la 7ma secuencia de comandos?- 7-
- . , - , .
- , . , numOfDisparities 16, . update(val), . numOfDisparities 65.57 64. , .
- matplotlib , .
El trabajo práctico con el mapa de profundidad mostró que, en primer lugar, debe seleccionar el parámetro minDisparity y, junto con él, numOfDisparities. Bueno, recuerde que numOfDisparities en realidad cambia discretamente, con el paso 16.Después de configurar este par, puede jugar con otros parámetros.Las características de la configuración de la tarjeta ya dependen del gusto del usuario y dependen de la tarea a resolver. Puede llevar el mapa a una gran cantidad de partes pequeñas o mostrar áreas ampliadas. Para una simple evitación de obstáculos por parte de los robots, el segundo es más adecuado. Para una nube de puntos, la primera, pero los problemas de rendimiento aparecen aquí (volveremos a ellos).Que queremos ver
Bueno, quizás uno de los puntos más importantes es el ajuste de la "hipermetropía" de nuestro dispositivo. Cuando configuro el mapa de profundidad, suelo colocar un objeto a una distancia de unos 30 cm, el segundo en un metro y el resto dos metros más adelante. Y al configurar los dos primeros parámetros (minDisparity y numOfDisparities) en el séptimo script, logro lo siguiente:- Objeto más cercano (30 cm) - rojo
- Objeto en medio metro - amarillo o verde
- Objetos de 2-3 metros - verde o azul claro
Como resultado, obtenemos un sistema configurado para reconocer la zona "cercana" de obstáculos en un radio de 5-10 metros.Trabajando con video sobre la marcha - guión octavo, final
Bueno, ahora tenemos un sistema personalizado ya hecho, y tendríamos que obtener un resultado práctico. Estamos tratando de construir un mapa de profundidad en tiempo real usando el video de nuestra cámara y mostrarlo en tiempo real a medida que se actualiza.Lo que es interesante en el código del octavo script En la carrera por la velocidad
Entonces, la primera medición se realizó en la primera Raspberry con un procesador de un solo núcleo.- 4 segundos - construyendo un mapa en la imagen de 1280x720 Esto es mucho.- 2.5 segundos - en la Raspberry Pi 2, ya mejor.El análisis mostró que en este caso, solo se usa un núcleo en la segunda frambuesa. Lío! Reconstruí OpenCV usando la biblioteca de paralelización TBB.- 1.5 segundos - inicie en la segunda frambuesa usando múltiples núcleos. De hecho, resultó que solo se usan 2 núcleos, esto todavía tiene que ser manipulado. Resultó que no solo me encontré con este problema , por lo que todavía hay espacio para moverme.A juzgar por el algoritmo, la velocidad de operación debería depender linealmente del tamaño de los datos procesados. Por lo tanto, si reduce la resolución 2 veces, entonces, en teoría, todo debería funcionar 4 veces más rápido.- 0.3 segundos , o aproximadamente 3-4 FPS - con una resolución medio reducida de 640x360. La teoría fue confirmada.Planes adicionales
En primer lugar, quería aprovechar al máximo el multinúcleo de la segunda frambuesa. Echaré un vistazo más de cerca a las fuentes de la función StereoBM y trataré de entender por qué el trabajo no está funcionando del todo.La siguiente etapa promete mucha más aventura: este es el uso de GPU de frambuesas para acelerar los cálculos.Aquí se dibujan tres posibles caminos:Si tenía experiencia trabajando con TBB para Raspberry OpenCV, o si estaba lidiando con la codificación de GPU de frambuesa, le agradeceré sugerencias adicionales . Logré encontrar bastantes desarrollos listos para usar por una simple razón: las frambuesas con dos cámaras son una ocurrencia rara. Si conecta dos cámaras web a través de USB, entonces vienen grandes frenos, y solo Raspberry Pi Compute puede funcionar con dos cámaras nativas, que también necesitan un devboard fuerte con cordones y adaptadores.Enlaces utiles:
Guiones de trabajo:Configuración de OpenCV y Python en frambuesas:Biblioteca StereoVision:GPU trabajoBueno, un artículo interesante sobre el habr sobre " Para reconocer imágenes, no es necesario reconocerlas "Source: https://habr.com/ru/post/es388259/
All Articles