Soy uno de los desarrolladores del sistema operativo , y en este artículo hablaré sobre cómo logré ejecutar OpenCV en la placa STM32746G.
Si buscas en internet algo como «OpenCV en placa STM32», puedes encontrar a muchas personas interesadas en usar esta biblioteca en placas STM32 u otros microcontroladores.
Hay varios videos que, según el título, deberían demostrar lo que se necesita, pero generalmente (en todos los videos que he visto) en la placa STM32 solo se capturaba la imagen de la cámara y se mostraba el resultado en la pantalla, mientras que el procesamiento de la imagen se hacía en una computadora común o en placas más potentes (por ejemplo, Raspberry Pi).
¿Por qué es complicado?
La popularidad de las búsquedas se explica porque OpenCV es la biblioteca de visión por computadora más popular, lo que significa que más desarrolladores están familiarizados con ella, y la posibilidad de ejecutar código diseñado para escritorio en un microcontrolador simplifica mucho el proceso de desarrollo. Pero, ¿por qué todavía no hay recetas populares listas para resolver este problema?
El problema de usar OpenCV en placas pequeñas se debe a dos características:
- Si se compila la biblioteca incluso con un conjunto mínimo de módulos, en la memoria flash de la STM32F7Discovery no cabe (incluso sin tener en cuenta el sistema operativo) debido al gran tamaño del código (varios megabytes de instrucciones)
- La propia biblioteca está escrita en C++, por lo que
- Se necesita soporte para el tiempo de ejecución de C++ (excepciones, etc.)
- Hay poco soporte para LibC/Posix, que normalmente existe en los sistemas operativos para sistemas embebidos: se necesita una biblioteca estándar de C++ y la biblioteca estándar de plantillas STL (vector, etc.)
Portando a Embox
Como de costumbre, antes de portar cualquier programa a un sistema operativo, es buena idea intentar compilarlo en la forma en que lo idearon los desarrolladores. En nuestro caso, no hay problemas con esto: se pueden encontrar los fuentes en , la biblioteca se compila en GNU/Linux fácilmente con cmake.
De las buenas noticias — OpenCV se puede compilar como una biblioteca estática directamente, lo que hace que el puerto sea más fácil. Compilamos la biblioteca con la configuración estándar y vemos cuánto espacio ocupa. Cada módulo se compila en una biblioteca separada.
> tamaño lib/*so --totales
texto datos bss dec hex nombre del archivo
1945822 15431 960 1962213 1df0e5 lib/libopencv_calib3d.so
17081885 170312 25640 17277837 107a38d lib/libopencv_core.so
10928229 137640 20192 11086061 a928ed lib/libopencv_dnn.so
842311 25680 1968 869959 d4647 lib/libopencv_features2d.so
423660 8552 184 432396 6990c lib/libopencv_flann.so
8034733 54872 1416 8091021 7b758d lib/libopencv_gapi.so
90741 3452 304 94497 17121 lib/libopencv_highgui.so
6338414 53152 968 6392534 618ad6 lib/libopencv_imgcodecs.so
21323564 155912 652056 22131532 151b34c lib/libopencv_imgproc.so
724323 12176 376 736875 b3e6b lib/libopencv_ml.so
429036 6864 464 436364 6a88c lib/libopencv_objdetect.so
6866973 50176 1064 6918213 699045 lib/libopencv_photo.so
698531 13640 160 712331 ade8b lib/libopencv_stitching.so
466295 6688 168 473151 7383f lib/libopencv_video.so
315858 6972 11576 334406 51a46 lib/libopencv_videoio.so
76510375 721519 717496 77949390 4a569ce (TOTALES)Como se puede ver en la última línea, .bss y .data no ocupan mucho espacio, mientras que el código supera los 70 MiB. Es evidente que si esto se vincula estáticamente con una aplicación específica, el tamaño del código será menor.
Intentaremos eliminar tantos módulos como sea posible para compilar un ejemplo mínimo (que, por ejemplo, simplemente muestre la versión de OpenCV), así que prestemos atención. cmake .. -LA y desactivamos en las opciones todo lo que se puede desactivar.
-DBUILD_opencv_java_bindings_generator=OFF
-DBUILD_opencv_stitching=OFF
-DWITH_PROTOBUF=OFF
-DWITH_PTHREADS_PF=OFF
-DWITH_QUIRC=OFF
-DWITH_TIFF=OFF
-DWITH_V4L=OFF
-DWITH_VTK=OFF
-DWITH_WEBP=OFF> tamaño lib/libopencv_core.a --totales
texto datos bss dec hex nombre del archivo
3317069 36425 17987 3371481 3371d9 (TOTALES)Por un lado, este es solo un módulo de la biblioteca, pero por otro lado, esto es sin optimización del compilador por tamaño de código (-Os). ~3 MiB de código aún es bastante, pero ya da esperanzas de éxito.
Ejecutar en un emulador
Es mucho más fácil depurarse en un emulador, así que primero nos aseguraremos de que la biblioteca funcione en qemu. Como plataforma emulada, elegí Integrator/CP, ya que, por un lado, también es ARM, y por otro, Embox soporta salida gráfica para esta plataforma.
En Embox hay un mecanismo para compilar bibliotecas externas, con él añadimos OpenCV como módulo (pasando todas las mismas opciones para la 'compilación mínima' en forma de bibliotecas estáticas), después de eso, añado una aplicación simple que se ve así:
version.cpp:
#include
#include
int main() {
printf("OpenCV: %s", cv::getBuildInformation().c_str());
return 0;
}Compilamos el sistema, ejecutamos — obtenemos la salida esperada.
root@embox: /#opencv_version
OpenCV:
Configuración general para OpenCV 4.0.1 =====================================
Control de versión: bd6927bdf-dirty
Plataforma:
Marca de tiempo: 2019-06-21T10:02:18Z
Host: Linux 5.1.7-arch1-1-ARCH x86_64
Objetivo: Generic arm-unknown-none
CMake: 3.14.5
Generador de CMake: Unix Makefiles
Herramienta de construcción de CMake: /usr/bin/make
Configuración: Debug
Características de CPU/HW:
Baseline:
solicitado: DETECT
deshabilitado: VFPV3 NEON
C/C++:
¿Construido como bibliotecas dinámicas?: NO
El siguiente paso es ejecutar algún ejemplo, lo mejor es uno estándar que ofrezcan los propios desarrolladores . Elegí .
Tuve que modificar un poco el ejemplo para mostrar la imagen con el resultado directamente en el frame buffer. Hice esto porque la función imshow() puede dibujar imágenes a través de interfaces QT, GTK y Windows, las cuales, evidentemente, no estarán en la configuración para STM32. En realidad, también se puede ejecutar QT en STM32F7Discovery, pero eso se abordará en otro artículo 🙂
Después de investigar brevemente en qué formato se almacena el resultado del trabajo del detector de bordes, obtenemos la imagen.

La imagen original

Resultado
Ejecución en STM32F7Discovery
En 32F746GDISCOVERY hay varias secciones de memoria que podemos utilizar de alguna manera
- 320KiB de RAM
- 1MiB de memoria flash para la imagen
- 8MiB de SDRAM
- 16MiB de memoria flash QSPI NAND
- Conector para tarjeta microSD
La tarjeta SD se puede usar para almacenar imágenes, pero en el contexto de ejecutar un ejemplo mínimo, no es muy útil.
La pantalla tiene una resolución de 480×272, lo que significa que la memoria para el frame buffer será de 522,240 bytes con una profundidad de 32 bits, es decir, esto es más que el tamaño de la RAM, así que el frame buffer y la pila (que se requiere también para OpenCV para almacenar datos de imágenes y estructuras auxiliares) se ubicarán en SDRAM, mientras que el resto (memoria para pilas y otras necesidades del sistema) irá en RAM.
Si tomas la configuración mínima para STM32F7Discovery (eliminando toda la red, todos los comandos, haciendo las pilas lo más pequeñas posible, etc.) y agregas OpenCV con ejemplos, la memoria requerida será la siguiente:
text data bss dec hex filename
2876890 459208 312736 3648834 37ad42 build/base/bin/emboxPara aquellos que no están muy familiarizados con las secciones a dónde se asignan, aclaro: en .text y .rodata se encuentran instrucciones y constantes (en términos simples, datos de solo lectura), en .data se encuentran datos modificables, en .bss se encuentran variables 'inicializadas en cero', que, sin embargo, necesitan espacio (esta sección 'irá' a la RAM).
La buena noticia es que .data/.bss deben caber, pero el problema es que .text hay solo 1MiB de memoria disponible para la imagen. Se puede eliminar la imagen del ejemplo y leerla, por ejemplo, desde una tarjeta SD a la memoria al iniciar, pero fruits.png pesa aproximadamente 330KiB, así que eso no resolverá el problema: la mayor parte .text consiste precisamente en el código de OpenCV. .text En gran medida, solo queda una opción: cargar parte del código en una memoria QSPI (tiene un modo de operación especial para mapear memoria en el bus del sistema, así que el procesador podrá acceder a esos datos directamente). Sin embargo, surge un problema: primero, la memoria de la memoria flash QSPI no está disponible inmediatamente después de reiniciar el dispositivo (es necesario inicializar por separado el modo de memoria mapeada); segundo, no se puede 'flashear' esta memoria con un gestor de arranque habitual.
Como resultado, se decidió enlazar todo el código en QSPI y flashearlo con un gestor de arranque personalizado que obtendrá el binario necesario por TFTP.
La idea de portar esta biblioteca a Embox surgió hace aproximadamente un año, pero una y otra vez se pospuso por varias razones. Una de ellas es el soporte de libstdc++ y de la biblioteca estándar de plantillas. El problema del soporte de C++ en Embox está más allá del alcance de este artículo, así que solo diré aquí que logramos obtener este soporte en el volumen necesario para trabajar con esta biblioteca 🙂
Resultado
Como resultado, estos problemas fueron superados (al menos en medida suficiente para que funcione el ejemplo de OpenCV), y el ejemplo se ejecutó. El procesamiento que lleva más tiempo en la placa para buscar los bordes con el filtro de Canny toma 40 segundos. Esto es, por supuesto, demasiado tiempo (hay consideraciones sobre cómo optimizar esto, se podría escribir un artículo separado en caso de éxito).
Sin embargo, el objetivo intermedio era crear un prototipo que mostrara la posibilidad fundamental de ejecutar OpenCV en STM32, y, por lo tanto, este objetivo se logró, ¡hurra!

tl;dr: guía paso a paso
0: Descargamos los fuentes de Embox, por ejemplo, así:
git clone https://github.com/embox/embox && cd ./embox
1: Comencemos con la construcción del gestor de arranque que 'flasheará' la memoria flash QSPI.make confload-arm/stm32f7cube
hacer confload-arm/stm32f7cubeAhora necesitamos configurar la red, ya que cargaremos la imagen a través de TFTP. Para establecer las direcciones IP de la placa y del host, se debe modificar el archivo conf/rootfs/network.
Ejemplo de configuración:
iface eth0 inet static
address 192.168.2.2
netmask 255.255.255.0
gateway 192.168.2.1
hwaddress aa:bb:cc:dd:ee:02gateway — dirección del host desde donde se cargará la imagen, address — dirección de la placa.
Después de esto, compilamos el cargador:
make2: Carga normal del cargador (disculpen el juego de palabras) en la placa — aquí no hay nada específico, debe hacerse como para cualquier otra aplicación para STM32F7Discovery. Si no sabe cómo hacerlo, puede leer sobre esto. .
3: Compilación de la imagen con la configuración para OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: Extracción de las secciones ELF que se deben escribir en QSPI, en qspi.bin
arm-none-eabi-objcopy -O binary build/base/bin/embox build/base/bin/qspi.bin
--only-section=.text --only-section=.rodata
--only-section='.ARM.ex*'
--only-section=.dataEn el directorio conf hay un script que hace esto, así que se puede ejecutar.
./conf/qspi_objcopy.sh # El binario necesario -- build/base/bin/qspi.bin5: Con tftp cargamos qspi.bin.bin en la memoria flash QSPI. En el host, para esto, hay que copiar qspi.bin en la carpeta raíz del servidor tftp (normalmente es /srv/tftp/ o /var/lib/tftpboot/; los paquetes para el servidor correspondiente están en la mayoría de las distribuciones populares, generalmente se llama tftpd o tftp-hpa, a veces hay que hacer systemctl start tftpd.service para iniciar).
# вариант для tftpd
sudo cp build/base/bin/qspi.bin /srv/tftp
# вариант для tftp-hpa
sudo cp build/base/bin/qspi.bin /var/lib/tftpbootEn Embox (es decir, en el cargador) hay que ejecutar el siguiente comando (suponiendo que la dirección del servidor es 192.168.2.1):
embox> qspi_loader qspi.bin 192.168.2.16: Con el comando goto hay que "saltar" a la memoria QSPI. La ubicación concreta variará según cómo se enlace la imagen; se puede ver esta dirección con el comando mem 0x90000000 (la dirección de inicio se encuentra en la segunda palabra de 32 bits de la imagen); también será necesario establecer la pila con la bandera. -s, la dirección de la pila se encuentra en 0x90000000, ejemplo:
embox>mem 0x90000000
0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275
↑ ↑
esta dirección esta dirección
de la pila de la primera
instrucción
embox>goto -i 0x9000c27f -s 0x20023200 # La bandera -i es necesaria para prohibir interrupciones durante la inicialización del sistema
7: Iniciamos
embox> edges 20y disfrutamos de una búsqueda de bordes de 40 segundos 🙂
Si algo sale mal, escriba un issue en , o en la lista de correo embox-devel@googlegroups.com, o en los comentarios aquí.
Fuente: habr.com
