OpenCV en STM32F7-Discovery

OpenCV en STM32F7-Discovery Soy uno de los desarrolladores del sistema operativo Embox, 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 github, 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 en su sitio. Elegí el detector de bordes de Canny.

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.

OpenCV en STM32F7-Discovery

La imagen original

OpenCV en STM32F7-Discovery

Resultado

Ejecución en STM32F7Discovery

En 32F746GDISCOVERY hay varias secciones de memoria que podemos utilizar de alguna manera

  1. 320KiB de RAM
  2. 1MiB de memoria flash para la imagen
  3. 8MiB de SDRAM
  4. 16MiB de memoria flash QSPI NAND
  5. 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/embox

Para 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!

OpenCV en STM32F7-Discovery

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/stm32f7cube

Ahora 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:02

gateway — dirección del host desde donde se cargará la imagen, address — dirección de la placa.

Después de esto, compilamos el cargador:

    make

2: 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. aquí.
3: Compilación de la imagen con la configuración para OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: 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=.data

En 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.bin

5: 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/tftpboot

En 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.1

6: 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 20

y disfrutamos de una búsqueda de bordes de 40 segundos 🙂

Si algo sale mal, escriba un issue en nuestro repositorio, o en la lista de correo embox-devel@googlegroups.com, o en los comentarios aquí.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster