OpenCV en STM32F7-Discovery

OpenCV en STM32F7-Discovery Soy uno de los desarrolladores del sistema operativo. Empaque, y en este artículo hablaré sobre cómo logré ejecutar OpenCV en la placa STM32746G.

Si escribe algo como "OpenCV en placa STM32" en un motor de búsqueda, puede encontrar bastantes personas interesadas en usar esta biblioteca en placas STM32 u otros microcontroladores.
Hay varios videos que, a juzgar por el nombre, deberían demostrar lo que se necesita, pero generalmente (en todos los videos que vi) en la placa STM32, solo se recibía la imagen de la cámara y se mostraba el resultado en la pantalla, y el procesamiento de la imagen en sí se realizó en una computadora normal o en placas más potentes (por ejemplo, Raspberry Pi).

¿Por qué es difícil?

La popularidad de las consultas de búsqueda se explica por el hecho de que 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 capacidad de ejecutar código listo para escritorio en un microcontrolador simplifica enormemente el proceso de desarrollo. Pero, ¿por qué todavía no hay recetas populares preparadas para resolver este problema?

El problema de usar OpenCV en chales pequeños está relacionado con dos características:

  • Si compila la biblioteca incluso con un conjunto mínimo de módulos, simplemente no cabrá en la memoria flash del mismo STM32F7Discovery (incluso sin tener en cuenta el sistema operativo) debido a un código muy grande (varios megabytes de instrucciones)
  • La biblioteca en sí está escrita en C++, lo que significa
    • Necesita soporte para tiempo de ejecución positivo (excepciones, etc.)
    • Poco soporte para LibC/Posix, que generalmente se encuentra en el sistema operativo para sistemas integrados: necesita una biblioteca estándar plus y una biblioteca de plantillas STL estándar (vector, etc.)

Portar a Embox

Como de costumbre, antes de portar cualquier programa al sistema operativo, es una buena idea intentar compilarlo en la forma en que los desarrolladores lo concibieron. En nuestro caso, no hay problemas con esto: el código fuente se puede encontrar en githabe, la librería está construida bajo GNU/Linux con el habitual cmake.

La buena noticia es que OpenCV se puede compilar como una biblioteca estática lista para usar, lo que facilita la portabilidad. Recopilamos una biblioteca con una configuración estándar y vemos cuánto espacio ocupan. Cada módulo se recopila en una biblioteca separada.

> size lib/*so --totals
   text    data     bss     dec     hex filename
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 (TOTALS)

Como puede ver en la última línea, .bss y .data no ocupan mucho espacio, pero el código tiene más de 70 MiB. Está claro que si esto está vinculado estáticamente a una aplicación específica, el código será menor.

Intentemos tirar tantos módulos como sea posible para que se ensamble un ejemplo mínimo (que, por ejemplo, simplemente generará la versión de OpenCV), así que buscamos cmake .. -LA y apaga en las opciones todo lo que apaga.

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

> size lib/libopencv_core.a --totals
   text    data     bss     dec     hex filename
3317069   36425   17987 3371481  3371d9 (TOTALS)

Por un lado, este es solo un módulo de la biblioteca, por otro lado, esto no tiene optimización del compilador para el tamaño del código (-Os). ~3 MiB de código sigue siendo bastante, pero ya da esperanzas de éxito.

Ejecutar en el emulador

Es mucho más fácil depurar en el emulador, así que primero asegúrese de que la biblioteca funcione en qemu. Como plataforma emulada, elegí Integrator/CP, porque en primer lugar, también es ARM y, en segundo lugar, Embox admite la salida de gráficos para esta plataforma.

Embox tiene un mecanismo para construir bibliotecas externas, al usarlo agregamos OpenCV como un módulo (pasando todas las mismas opciones para la compilación "mínima" en forma de bibliotecas estáticas), luego de eso agrego una aplicación simple que se ve así:

version.cpp:

#include <stdio.h>
#include <opencv2/core/utility.hpp>

int main() {
    printf("OpenCV: %s", cv::getBuildInformation().c_str());

    return 0;
}

Ensamblamos el sistema, lo ejecutamos, obtenemos el resultado esperado.

root@embox:/#opencv_version                                                     
OpenCV: 
General configuration for OpenCV 4.0.1 =====================================
  Version control:               bd6927bdf-dirty

  Platform:
    Timestamp:                   2019-06-21T10:02:18Z
    Host:                        Linux 5.1.7-arch1-1-ARCH x86_64
    Target:                      Generic arm-unknown-none
    CMake:                       3.14.5
    CMake generator:             Unix Makefiles
    CMake build tool:            /usr/bin/make
    Configuration:               Debug

  CPU/HW features:
    Baseline:
      requested:                 DETECT
      disabled:                  VFPV3 NEON

  C/C++:
    Built as dynamic libs?:      NO
< Дальше идут прочие параметры сборки -- с какими флагами компилировалось,
  какие модули OpenCV включены в сборку и т.п.>

El siguiente paso es ejecutar algún ejemplo, preferiblemente uno de los estándar que ofrecen los propios desarrolladores. en tu sitio. Elegí detector de fronteras astuto.

El ejemplo tuvo que reescribirse ligeramente para mostrar la imagen con el resultado directamente en el búfer de fotogramas. Tuve que hacer esto, porque. función imshow() puede dibujar imágenes a través de las interfaces QT, GTK y Windows, que, por supuesto, definitivamente no estarán en la configuración de STM32. De hecho, QT también se puede ejecutar en STM32F7Discovery, pero esto se discutirá en otro artículo 🙂

Después de una breve aclaración en qué formato se almacena el resultado del detector de bordes, obtenemos una imagen.

OpenCV en STM32F7-Discovery

foto original

OpenCV en STM32F7-Discovery

resultado

Ejecutándose en STM32F7Discovery

En 32F746GDISCOVERY hay varias secciones de memoria de hardware que podemos usar de una forma u otra

  1. RAM de 320 KiB
  2. Flash de 1MiB para imagen
  3. SDRAM de 8 MB
  4. Flash NAND QSPI de 16MiB
  5. ranura para tarjeta microSD

Se puede usar una tarjeta SD para almacenar imágenes, pero en el contexto de ejecutar un ejemplo mínimo, esto no es muy útil.
La pantalla tiene una resolución de 480×272, lo que significa que la memoria framebuffer será de 522 bytes a una profundidad de 240 bits, es decir esto es más que el tamaño de la RAM, por lo que el framebuffer y el montón (que serán necesarios, incluso para OpenCV, para almacenar datos para imágenes y estructuras auxiliares) se ubicarán en SDRAM, todo lo demás (memoria para pilas y otras necesidades del sistema ) irá a la RAM .

Si tomamos la configuración mínima para STM32F7Discovery (eliminar toda la red, todos los comandos, hacer las pilas lo más pequeñas posible, etc.) y agregar OpenCV con ejemplos allí, 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 qué secciones van a dónde, les explico: en .text и .rodata instrucciones y constantes (en términos generales, datos de solo lectura) se encuentran en .data los datos son mutables, .bss hay variables "anuladas" que, sin embargo, necesitan un lugar (esta sección "irá" a la RAM).

La buena noticia es que .data/.bss debe encajar, pero con .text el problema es que solo hay 1MiB de memoria para la imagen. se puede tirar .text la imagen del ejemplo y la lea, por ejemplo, desde la tarjeta SD a la memoria al inicio, pero fruit.png pesa alrededor de 330 KiB, por lo que esto no resolverá el problema: la mayoría .text consiste en el código OpenCV.

En general, solo queda una cosa: cargar una parte del código en un flash QSPI (tiene un modo de operación especial para asignar memoria al bus del sistema, de modo que el procesador pueda acceder a estos datos directamente). En este caso, surge un problema: en primer lugar, la memoria de una unidad flash QSPI no está disponible inmediatamente después de reiniciar el dispositivo (debe inicializar por separado el modo de asignación de memoria) y, en segundo lugar, no puede "flashear" esta memoria con un gestor de arranque familiar.

Como resultado, se decidió vincular todo el código en QSPI y actualizarlo con un cargador autoescrito que recibirá el binario requerido a través de TFTP.

resultado

La idea de portar esta biblioteca a Embox apareció hace aproximadamente un año, pero una y otra vez fue pospuesta por varias razones. Uno de ellos es el soporte para libstdc++ y la biblioteca de plantillas estándar. El problema del soporte de C++ en Embox está más allá del alcance de este artículo, así que aquí solo diré que logramos lograr este soporte en la cantidad adecuada para que esta biblioteca funcione 🙂

Al final, estos problemas se superaron (al menos lo suficiente para que funcionara el ejemplo de OpenCV) y se ejecutó el ejemplo. El tablero tarda 40 largos segundos en buscar límites usando el filtro Canny. Esto, por supuesto, es demasiado largo (hay consideraciones sobre cómo optimizar este asunto, será posible escribir un artículo separado sobre esto en caso de éxito).

OpenCV en STM32F7-Discovery

Sin embargo, el objetivo intermedio era crear un prototipo que mostrara la posibilidad fundamental de ejecutar OpenCV en STM32, respectivamente, este objetivo se logró, ¡hurra!

tl;dr: instrucciones paso a paso

0: descarga las fuentes de Embox, así:

    git clone https://github.com/embox/embox && cd ./embox

1: Comencemos ensamblando un gestor de arranque que "flasheará" una unidad flash QSPI.

    make confload-arm/stm32f7cube

Ahora necesita configurar la red, porque. Subiremos la imagen a través de TFTP. Para configurar las direcciones IP de la placa y del host, debe editar 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 de host desde donde se cargará la imagen, address - dirección de la junta.

Después de eso, recopilamos el gestor de arranque:

    make

2: La carga habitual del cargador de arranque (perdón por el juego de palabras) en la placa: no hay nada específico aquí, debe hacerlo como cualquier otra aplicación para STM32F7Discovery. Si no sabes cómo hacerlo, puedes leer al respecto. aquí.
3: Compilar una imagen con una configuración para OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extracto de las secciones ELF para 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

Hay un script en el directorio conf que hace esto, por lo que puede ejecutarlo

    ./conf/qspi_objcopy.sh # Нужный бинарник -- build/base/bin/qspi.bin

5: Con tftp, descargue qspi.bin.bin en una unidad flash QSPI. En el host, para hacer esto, copie qspi.bin a la carpeta raíz del servidor tftp (generalmente /srv/tftp/ o /var/lib/tftpboot/; los paquetes para el servidor correspondiente están disponibles en las distribuciones más populares, generalmente llamados tftpd o tftp-hpa, a veces tienes que hacer systemctl start tftpd.service para comenzar).

    # вариант для 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 gestor de arranque), debe ejecutar el siguiente comando (asumimos que el servidor tiene la dirección 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Con comando goto necesita "saltar" a la memoria QSPI. La ubicación específica variará dependiendo de cómo se vincule la imagen, puede ver esta dirección con el comando mem 0x90000000 (la dirección de inicio cabe en la segunda palabra de 32 bits de la imagen); también deberá marcar la pila -s, la dirección de la pila está en 0x90000000, ejemplo:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              это адрес    это  адрес 
                стэка        первой
                           инструкции

    embox>goto -i 0x9000c27f -s 0x20023200 # Флаг -i нужен чтобы запретить прерывания во время инициализации системы

    < Начиная отсюда будет вывод не загрузчика, а образа с OpenCV >

7: lanzamiento

    embox> edges 20

y disfruta de la búsqueda de fronteras de 40 segundos 🙂

Si algo sale mal, escriba un problema en nuestro repositorio, o a la lista de correo [email protected], o en un comentario aquí.

Fuente: habr.com

Añadir un comentario