
Hoy queremos compartir una serie de ejemplos en Python para los estudiantes de OpenCV en la Raspberry Pi, a saber, la placa StereoPi de doble cámara. El código terminado (más la imagen de Raspbian) lo ayudará a seguir todos los pasos, comenzando con la captura de una imagen y terminando con la obtención de un mapa de profundidad del video capturado.
Introductorio
Debo enfatizar de inmediato que estos ejemplos son para una inmersión cómoda en el tema y no para una solución de producción. Si es un usuario avanzado de OpenCV y ha estado tratando con frambuesas, entonces sabe que para un funcionamiento adecuado es recomendable codificar una mordida e incluso usar una GPU de frambuesa. Al final del artículo, tocaré los "cuellos de botella" de la solución de Python y el rendimiento general con más detalle.
Con que estamos trabajando
Tenemos una configuración como el hierro:

Placa StereoPi a bordo del Raspberry Pi Compute Module 3+. Las dos cámaras más simples están conectadas para la versión Raspberry Pi V1 (en el sensor ov5647).
Lo que está instalado:
- Raspbian Stretch (kernel 4.14.98-v7 +)
- Python 3.5.3
- OpenCV 3.4.4 (precompilado, 'pip' de Python Wheels)
- Picamera 1.13
- StereoVision lib 1.0.3 (https://github.com/erget/StereoVision)
El proceso de instalación de todo el software está más allá del alcance de este artículo, y solo sugerimos descargar la imagen Raspbian terminada (enlaces al github al final del artículo).
Paso uno: captura una imagen
Para hacer esto, use el script 1_test.py
Abra la consola, vaya de la carpeta de inicio a la carpeta con ejemplos:
cd stereopi-tutorial
Ejecute el script:
python 1_test.py
Después de comenzar, se muestra una vista previa de nuestra imagen estéreo en la pantalla. El proceso se puede interrumpir presionando el botón Q. Esto guardará la última imagen capturada, que se utilizará en uno de los siguientes scripts para configurar el mapa de profundidad.
Este script le permite asegurarse de que todo el hardware funciona correctamente, así como obtener la primera imagen para uso futuro.
Así es como se ve el primer script:
Paso dos: recolecte imágenes para la calibración
Si hablamos de un caballo esférico en el vacío, entonces para obtener un mapa de profundidad de buena calidad, necesitamos tener dos cámaras absolutamente idénticas, cuyos ejes vertical y óptico sean perfectamente paralelos y los ejes horizontales coincidan. Pero en el mundo real, todas las cámaras son ligeramente diferentes, y no es posible organizarlas perfectamente. Por lo tanto, se inventó un truco de calibración de software. Usando dos cámaras del mundo real, se toma una gran cantidad de imágenes de un objeto previamente conocido (tenemos una imagen con un tablero de ajedrez), y luego un algoritmo especial calcula todas las "imperfecciones" e intenta corregir las imágenes para que estén cerca del ideal.
Este script realiza la primera etapa del trabajo, es decir, ayuda a hacer una serie de fotos para la calibración.
Antes de cada foto, el guión comienza una cuenta regresiva de 5 segundos. Esta vez, como regla, es suficiente para mover el tablero a una nueva posición, para asegurarse de que en ambas cámaras no se arrastre por los bordes y fije su posición (para que no haya borrosidad en la foto). Por defecto, el tamaño de la serie se establece en 30 fotos.
Lanzamiento
python 2_chess_cycle.py
Proceso:
Como resultado, tenemos una serie de fotos en la carpeta / escenas.
Cortamos las imágenes en parejas.
El tercer script 3_pairs_cut.py corta las fotos tomadas en imágenes "izquierda" y "derecha" y las guarda en la carpeta / pares. De hecho, podríamos excluir este script y hacer el corte sobre la marcha, pero es muy útil en futuros experimentos. Por ejemplo, puede guardar segmentos de diferentes series, usar sus scripts para trabajar con estos pares, o incluso copiar las imágenes tomadas en otras cámaras estéreo como pares.
Además, antes de cortar cada imagen, el script muestra su imagen, que a menudo le permite ver fotos fallidas antes del siguiente paso de calibración y simplemente eliminarlas.
Ejecute el script:
python 3_pairs_cut.py
Video corto:
En la imagen final hay un conjunto de fotografías y pares de cortes que utilizamos para nuestros experimentos.
Calibración
El script 4_calibration.py dibuja todos los pares con los tableros de ajedrez y calcula las correcciones necesarias para corregir las imágenes. El guión hizo un rechazo automático de fotos en las que no se encontró un tablero de ajedrez, por lo que en caso de fotos sin éxito el trabajo no se detiene. Después de cargar los 30 pares de imágenes, comienza el cálculo. Nos lleva aproximadamente un minuto y medio. Una vez finalizado, el script toma uno de los pares estéreo y, sobre la base de los parámetros de calibración calculados, los "corrige" y muestra una imagen rectificada en la pantalla. En este punto, puede evaluar la calidad de la calibración.
Ejecutar por comando:
python 4_calibration.py
Script de calibración en el trabajo:
Configuración del mapa de profundidad
El script 5_dm_tune.py carga la imagen tomada por el primer script y los resultados de la calibración. A continuación, se muestra una interfaz que le permite cambiar la configuración del mapa de profundidad y ver qué cambios. Consejo: antes de configurar los parámetros, tome un marco en el que simultáneamente tendrá objetos a diferentes distancias: cerca (30-40 centímetros), a una distancia promedio (metro o dos) y en la distancia. Esto le permitirá elegir los parámetros en los que los objetos cercanos serán rojos y los objetos distantes serán azul oscuro.
La imagen contiene un archivo con nuestra configuración de mapa de profundidad. Puede cargar nuestra configuración en un script simplemente haciendo clic en el botón "Cargar configuración"
Lanzamos:
python 5_dm_tune.py
Así es como se ve el proceso de configuración:
Mapa de profundidad en tiempo real
La última secuencia de comandos 6_dm_video.py crea un mapa de profundidad a partir del video utilizando los resultados de secuencias de comandos anteriores (calibración y configuración del mapa de profundidad).
Lanzamiento
python 6_dm_video.py
En realidad el resultado:
¡Esperamos que nuestros guiones sean útiles en sus experimentos!
Por si acaso, agregaré que todas las secuencias de comandos tienen procesamiento de pulsación de teclas, y puede interrumpir el trabajo presionando el botón Q. Si se detiene "bruscamente", por ejemplo, Ctrl + C, el proceso de interacción de Python con la cámara puede romperse y será necesario reiniciar la frambuesa.
Para avanzado
- La primera secuencia de comandos en el proceso muestra el tiempo promedio entre capturas de cuadros y, al finalizar, el FPS promedio. Esta es una herramienta simple y conveniente para seleccionar dichos parámetros de imagen en los que Python todavía no se está "ahogando". Con él, recogimos 1280x480 a 20 FPS, en el que el video se renderiza sin demora.
- Puede notar que capturamos un par estéreo en una resolución de 1280x480 y luego lo escalamos a 640x240.
Una pregunta razonable: ¿por qué todo esto y por qué no tomar inmediatamente la miniatura y no cargar nuestra pitón reduciéndola?
Respuesta: con la captura directa a resoluciones muy bajas, todavía hay problemas en el núcleo de frambuesa (la imagen se rompe). Por lo tanto, tomamos una resolución más grande y luego reducimos la imagen. Aquí usamos un pequeño truco: la imagen no se escala con Python, sino con la ayuda de la GPU, por lo que no hay carga en el núcleo del brazo. - ¿Por qué capturar video en formato BGRA, no BGR?
Utilizamos los recursos de la GPU para reducir el tamaño de la imagen, y el nativo para el módulo de cambio de tamaño es el formato BGRA. Si usamos BGR en lugar de BGRA, tendremos dos inconvenientes. El primero es ligeramente más bajo que el FPS final (en nuestras pruebas - 20 por ciento). El segundo es el trabajo constante en la consola "PiCameraAlfaStripping: uso de stripping alfa para convertir a formato no alpha; puede encontrar el formato alfa equivalente más rápido ". Buscar en Google llevó a la sección de documentación de Picamera, que describe este truco. - ¿Dónde está el PiRGBArray?
Esto es como la clase nativa de Picamera para trabajar con la cámara, pero aquí no se usa. Ya resultó que en nuestras pruebas, trabajar con una matriz numpy "hecha a mano" es mucho más rápido (aproximadamente una vez y media) que usar PiRGBArray. Esto no significa que PiRGBArray sea malo, lo más probable es que estas sean nuestras manos torcidas. - ¿Qué tan cargado es el porcentaje en el cálculo del mapa de profundidad?
Respondamos con una imagen:

Vemos que de los 4 núcleos del procesador, de hecho, solo uno está cargado, y eso es el 70%. Y esto a pesar del hecho de que trabajamos con una GUI, y estamos enviando imágenes y mapas de profundidad al usuario. Esto significa que hay un buen margen de rendimiento, y un ajuste fino de OpenCV con OpenMP y otras ventajas en C, así como un modo de "combate" sin una GUI pueden dar resultados muy interesantes. - ¿Cuál es el mapa de profundidad de FPS máximo obtenido con esta configuración?
El máximo alcanzado por nosotros fue de 17 FPS, al capturar 20 cuadros por segundo de la cámara. Los más "sensibles" en términos de parámetros de velocidad en la configuración del mapa de profundidad son MinDisparity y NumOfDisparities. Esto es lógico, ya que determinan el número de "pasos" realizados dentro del algoritmo por la ventana de búsqueda para comparar marcos. El segundo más sensible es preFilterCap, que afecta, en particular, la "suavidad" del mapa de profundidad. - ¿Qué pasa con la temperatura del procesador?
En Compute Module 3+ Lite (una nueva serie, con una "tapa" de hierro en el proceso) muestra aproximadamente los siguientes resultados:

- ¿Cómo usar la GPU?
Como mínimo, se puede utilizar para la andistorización y rectificación de imágenes en tiempo real, porque hay ejemplos ( aquí en WebGL ), Python Pi3d , así como el proyecto Processing ( ejemplos para frambuesas ).
Hay otro desarrollo interesante de Koichi Nakamura, llamado py-videocore . En nuestra correspondencia con él, expresó la idea de que para acelerar StereoBM puede usar su núcleo y OpenCV con el soporte de Cuda . En general, para la optimización, un borde intacto, como dicen.
Gracias por su atención, y aquí está el
enlace prometido a la fuente .