OpenCV en STM32F7-Discovery

OpenCV en STM32F7-Discovery Son un dos desenvolvedores do sistema operativo Embox, e neste artigo falarei de como conseguín executar OpenCV na placa STM32746G.

Se escribes algo como "OpenCV na placa STM32" nun motor de busca, podes atopar bastantes persoas que estean interesadas en usar esta biblioteca en placas STM32 ou noutros microcontroladores.
Hai varios vídeos que, a xulgar polo nome, deberían demostrar o que se necesita, pero normalmente (en todos os vídeos que vin) na placa STM32, só se recibiu a imaxe da cámara e o resultado mostrábase na pantalla, e o propio procesamento da imaxe realizouse nun ordenador normal ou en placas máis potentes (por exemplo, Raspberry Pi).

Por que é difícil?

A popularidade das consultas de busca explícase polo feito de que OpenCV é a biblioteca de visión por ordenador máis popular, o que significa que máis desenvolvedores están familiarizados con ela e que a capacidade de executar código preparado para o escritorio nun microcontrolador simplifica moito o proceso de desenvolvemento. Pero por que aínda non hai receitas populares preparadas para resolver este problema?

O problema de usar OpenCV en chales pequenos está relacionado con dúas características:

  • Se compilas a biblioteca mesmo cun conxunto mínimo de módulos, simplemente non caberá na memoria flash do mesmo STM32F7Discovery (mesmo sen ter en conta o SO) debido a un código moi grande (varios megabytes de instrucións)
  • A propia biblioteca está escrita en C++, o que significa
    • Necesita soporte para un tempo de execución positivo (excepcións, etc.)
    • Pouco soporte para LibC/Posix, que normalmente se atopa no sistema operativo para sistemas integrados: necesitas unha biblioteca estándar plus e unha biblioteca de modelos STL estándar (vector, etc.)

Portando a Embox

Como é habitual, antes de portar calquera programa ao sistema operativo, é unha boa idea tentar construílo na forma na que o pretendían os desenvolvedores. No noso caso, non hai problemas con isto - o código fonte pódese atopar en github, a biblioteca está construída baixo GNU/Linux co cmake habitual.

A boa noticia é que OpenCV pódese construír como unha biblioteca estática fóra da caixa, o que facilita a portabilidade. Recollemos unha biblioteca cunha configuración estándar e vemos canto espazo ocupan. Cada módulo está recollido nunha 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 podes ver na última liña, .bss e .data non ocupan moito espazo, pero o código supera os 70 MiB. Está claro que se esta está ligada de forma estática a unha aplicación específica, o código será menor.

Tentemos tirar tantos módulos como sexa posible para que se reúna un exemplo mínimo (que, por exemplo, simplemente sairá a versión OpenCV), así que miramos cmake .. -LA e desactiva nas opcións todo o que se apague.

        -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 unha banda, este é só un módulo da biblioteca, por outra banda, este é sen optimización do compilador para o tamaño do código (-Os). ~3 MiB de código aínda son bastante, pero xa dá esperanza de éxito.

Executar no emulador

É moito máis doado depurar o emulador, polo que primeiro asegúrate de que a biblioteca funciona en qemu. Como plataforma emulada, escollín Integrator / CP, porque en primeiro lugar, tamén é ARM e, en segundo lugar, Embox admite a saída de gráficos para esta plataforma.

Embox ten un mecanismo para construír bibliotecas externas, usándoo engadimos OpenCV como módulo (pasando todas as mesmas opcións para a compilación "mínima" en forma de bibliotecas estáticas), despois engado unha aplicación sinxela 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;
}

Montamos o sistema, executámolo: obtemos a saída esperada.

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 включены в сборку и т.п.>

O seguinte paso é executar algún exemplo, preferiblemente un dos estándar que ofrecen os propios desenvolvedores. no seu sitio. escollín detector de fronteiras canny.

O exemplo tivo que reescribirse lixeiramente para mostrar a imaxe co resultado directamente no búfer de fotogramas. Tiven que facelo, porque. función imshow() pode debuxar imaxes a través das interfaces QT, GTK e Windows, que, por suposto, definitivamente non estarán na configuración de STM32. De feito, QT tamén se pode executar en STM32F7Discovery, pero isto falarase noutro artigo 🙂

Despois dunha pequena aclaración en que formato se almacena o resultado do detector de bordos, obtemos unha imaxe.

OpenCV en STM32F7-Discovery

imaxe orixinal

OpenCV en STM32F7-Discovery

Resultado

Executándose en STM32F7Discovery

No 32F746GDISCOVERY hai varias seccións de memoria de hardware que podemos usar dun xeito ou doutro

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

Unha tarxeta SD pódese usar para almacenar imaxes, pero no contexto de executar un exemplo mínimo, isto non é moi útil.
A pantalla ten unha resolución de 480×272, o que significa que a memoria do framebuffer será de 522 bytes a unha profundidade de 240 bits, é dicir. isto é máis que o tamaño da RAM, polo que o framebuffer e o heap (que serán necesarios, incluso para OpenCV, para almacenar datos de imaxes e estruturas auxiliares) estarán situados en SDRAM, todo o demais (memoria para pilas e outras necesidades do sistema). ) irá á memoria RAM.

Se tomamos a configuración mínima para STM32F7Discovery (botar toda a rede, todos os comandos, facer as pilas o máis pequenas posible, etc.) e engadir alí OpenCV con exemplos, a memoria necesaria será a seguinte:

   text    data     bss     dec     hex filename
2876890  459208  312736 3648834  37ad42 build/base/bin/embox

Para os que non estean moi familiarizados con que seccións van a onde, explicarei: en .text и .rodata as instrucións e as constantes (en grosso modo, datos de só lectura) atópanse .data os datos son mutables, .bss hai variables "anuladas", que, con todo, precisan dun lugar (esta sección "irá" á RAM).

A boa noticia é que .data/.bss debería caber, pero con .text o problema é que só hai 1 MiB de memoria para a imaxe. Pódese botar fóra .text a imaxe do exemplo e léaa, por exemplo, desde a tarxeta SD na memoria ao iniciar, pero fruits.png pesa uns 330 KiB, polo que isto non resolverá o problema: a maioría .text consiste no código OpenCV.

En xeral, só queda unha cousa: cargar unha parte do código nun flash QSPI (ten un modo de operación especial para asignar a memoria ao bus do sistema, para que o procesador poida acceder a estes datos directamente). Neste caso, xorde un problema: en primeiro lugar, a memoria dunha unidade flash QSPI non está dispoñible inmediatamente despois de reiniciar o dispositivo (cómpre inicializar por separado o modo de asignación de memoria) e, en segundo lugar, non pode "flash" esta memoria con un cargador de arranque familiar.

Como resultado, decidiuse ligar todo o código en QSPI e flashear cun cargador de auto-escrito que recibirá o binario necesario a través de TFTP.

Resultado

A idea de portar esta biblioteca a Embox apareceu hai aproximadamente un ano, pero unha e outra vez aprazouse por diversos motivos. Un deles é o soporte para libstdc++ e a biblioteca de modelos estándar. O problema do soporte de C++ en Embox está fóra do alcance deste artigo, polo que aquí só direi que conseguimos este soporte na cantidade adecuada para que esta biblioteca funcione 🙂

Ao final, estes problemas superáronse (polo menos o suficiente para que funcione o exemplo de OpenCV) e o exemplo funcionou. O taboleiro tarda 40 segundos en buscar límites usando o filtro Canny. Isto, por suposto, é demasiado longo (hai consideracións sobre como optimizar este asunto, será posible escribir un artigo separado sobre isto en caso de éxito).

OpenCV en STM32F7-Discovery

Non obstante, o obxectivo intermedio era crear un prototipo que mostrase a posibilidade fundamental de executar OpenCV en STM32, respectivamente, este obxectivo conseguiuse, ¡hurra!

tl;dr: instrucións paso a paso

0: Descarga as fontes de Embox, como esta:

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

1: Comecemos por montar un cargador de arranque que "flasheará" unha unidade flash QSPI.

    make confload-arm/stm32f7cube

Agora cómpre configurar a rede, porque. Subiremos a imaxe a través de TFTP. Para configurar os enderezos IP do taboleiro e do host, cómpre editar o conf/rootfs/network.

Exemplo 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 - enderezo de host desde onde se cargará a imaxe, address - enderezo da xunta.

Despois diso, recollemos o cargador de arranque:

    make

2: A carga habitual do cargador de arranque (perdón polo xogo de palabras) no taboleiro: non hai nada específico aquí, cómpre facelo como para calquera outra aplicación para STM32F7Discovery. Se non sabes como facelo, podes ler sobre el aquí.
3: Compilación dunha imaxe cunha configuración para OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extracto das seccións ELF que se escribirán 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

Hai un script no directorio conf que fai isto, polo que pode executalo

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

5: Usando tftp, descargue qspi.bin.bin nunha unidade flash QSPI. No servidor, para iso, copie qspi.bin no cartafol raíz do servidor tftp (normalmente /srv/tftp/ ou /var/lib/tftpboot/; os paquetes para o servidor correspondente están dispoñibles na maioría das distribucións populares, normalmente chamadas tftpd ou tftp-hpa, ás veces tes que facelo systemctl start tftpd.service comezar).

    # вариант для tftpd
    sudo cp build/base/bin/qspi.bin /srv/tftp
    # вариант для tftp-hpa
    sudo cp build/base/bin/qspi.bin /var/lib/tftpboot

En Embox (é dicir, no cargador de arranque), cómpre executar o seguinte comando (supoñemos que o servidor ten o enderezo 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Con mando goto cómpre "saltar" á memoria QSPI. A localización específica variará dependendo de como se vincule a imaxe, podes ver este enderezo co comando mem 0x90000000 (o enderezo de inicio encaixa na segunda palabra de 32 bits da imaxe); tamén terás que marcar a pila -s, o enderezo da pila é 0x90000000, exemplo:

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

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

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

7: Lanzamento

    embox> edges 20

e goza da busca de fronteiras de 40 segundos 🙂

Se algo sae mal, escriba un problema noso repositorio, ou á lista de correo [protexido por correo electrónico], ou nun comentario aquí.

Fonte: www.habr.com

Engadir un comentario