OpenCV no STM32F7-Discovery

OpenCV no STM32F7-Discovery Sou um dos desenvolvedores do sistema operacional Embox, e neste artigo, vou contar como consegui rodar o OpenCV na placa STM32746G.

Se você pesquisar algo como "OpenCV na placa STM32", encontrará muitas pessoas interessadas em usar essa biblioteca em placas STM32 ou outros microcontroladores.
Existem vários vídeos que, pelo título, devem demonstrar o que é necessário, mas geralmente (em todos os vídeos que vi) na placa STM32 apenas a captura de imagem da câmera e a exibição do resultado na tela foram realizadas, e o processamento da imagem era feito em um computador comum ou em placas mais robustas (como um Raspberry Pi).

Por que isso é difícil?

A popularidade das pesquisas se deve ao fato de que o OpenCV é a biblioteca de visão computacional mais popular, o que significa que mais desenvolvedores estão familiarizados com ela, além de que a possibilidade de executar código destinado a desktop em um microcontrolador torna o processo de desenvolvimento muito mais fácil. Mas por que ainda não existem receitas prontas populares para resolver esse problema?

O problema de usar o OpenCV em placas pequenas está relacionado a duas características:

  • Se você compilar a biblioteca mesmo com o conjunto mínimo de módulos, ela simplesmente não caberá na memória flash da mesma STM32F7Discovery (mesmo sem considerar o sistema operacional) devido ao tamanho do código (vários megabytes de instruções)
  • A própria biblioteca é escrita em C++, o que significa
    • É necessária a compatibilidade com tempo de execução C++ (exceções, etc.)
    • Houve pouca compatibilidade com LibC/Posix, que normalmente está presente em sistemas operacionais para sistemas embarcados — uma biblioteca padrão C++ e uma biblioteca padrão de templates STL (vector, etc.) são necessárias.

Portando para o Embox

Como de costume, antes de portar qualquer programa para um sistema operacional, é bom tentar compilá-lo da forma como os desenvolvedores o imaginaram. No nosso caso, não há problemas com isso — os arquivos-fonte podem ser encontrados em github, a biblioteca é compilada sob GNU/Linux com o cmake regular.

Das boas notícias — o OpenCV pode ser compilado como uma biblioteca estática, o que torna o port para outras plataformas mais simples. Compilamos a biblioteca com a configuração padrão e verificamos quanto espaço ela ocupa. Cada módulo é compilado em uma biblioteca separada.

> tamanho lib/*so --totais
   texto    dados     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 (TOTAIS)

Como podemos ver na última linha, .bss e .data não ocupam muito espaço, mas o código ultrapassa 70 MiB. É claro que, se isso for vinculado estaticamente a um aplicativo específico, o código será reduzido.

Vamos tentar eliminar o máximo possível de módulos para conseguir um exemplo mínimo (que, por exemplo, simplesmente mostrará a versão do OpenCV), então vamos lá: cmake .. -LA e desativamos nas opções tudo que pode ser desativado.

        -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

> tamanho lib/libopencv_core.a --totais
   texto    dados     bss     dec     hex filename
3317069   36425   17987 3371481  3371d9 (TOTAIS)

Por um lado, este é apenas um módulo da biblioteca, por outro lado, isso é sem a otimização do compilador para o tamanho do código (-Os). ~3 MiB de código ainda é uma quantidade considerável, mas já traz esperança de sucesso.

Execução no emulador

Depurar no emulador é muito mais fácil, então primeiro vamos garantir que a biblioteca funcione no qemu. Para a plataforma emulada, escolhi Integrator/CP, pois, em primeiro lugar, é também ARM, e em segundo lugar, a Embox suporta a saída gráfica para essa plataforma.

Na Embox, há um mecanismo para construir bibliotecas externas, com ele adicionamos o OpenCV como um módulo (passando as mesmas opções para a "montagem mínima" em forma de bibliotecas estáticas), depois disso, adiciono um aplicativo simples que se parece com isto:

version.cpp:

#include 
#include 

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

    return 0;
}

Compilamos o sistema, executamos — obtemos a saída esperada.

root@embox: /#opencv_version                                                     
OpenCV: 
Configuração geral para OpenCV 4.0.1 =====================================
  Controle de versão:               bd6927bdf-dirty

  Plataforma:
    Data e hora:                   2019-06-21T10:02:18Z
    Host:                        Linux 5.1.7-arch1-1-ARCH x86_64
    Alvo:                      Genérico arm-unknown-none
    CMake:                       3.14.5
    Gerador CMake:             Unix Makefiles
    Ferramenta de construção CMake:            /usr/bin/make
    Configuração:               Debug

  Recursos de CPU/HW:
    Base:
      solicitado:                 DETECT
      desativado:                  VFPV3 NEON

  C/C++:
    Compilado como bibliotecas dinâmicas?:      NÃO

O próximo passo é executar algum exemplo, de preferência um padrão dos que os próprios desenvolvedores oferecem em seu site. Eu escolhi o detector de bordas de Canny.

Tive que reescrever um pouco o exemplo para exibir a imagem com o resultado diretamente no framebuffer. Isso foi necessário, pois a função imshow() consegue renderizar imagens por meio das interfaces QT, GTK e Windows, que, claro, não estarão no config para STM32. Na verdade, o QT também pode ser executado no STM32F7Discovery, mas isso será abordado em outro artigo 🙂

Após alguma investigação sobre qual formato é usado para armazenar o resultado do detector de bordas, obtemos a imagem.

OpenCV no STM32F7-Discovery

Imagem original

OpenCV no STM32F7-Discovery

Resultado

Execução no STM32F7Discovery

No 32F746GDISCOVERY existem várias seções de memória que podemos utilizar de alguma forma

  1. 320KiB de memória RAM
  2. 1MiB de memória flash para a imagem
  3. 8MiB de SDRAM
  4. 16MiB de flash NAND QSPI
  5. Conector para cartão microSD

O cartão SD pode ser utilizado para armazenar imagens, mas no contexto de executar um exemplo mínimo, isso não é muito útil.
O display possui resolução de 480×272, o que significa que a memória para o framebuffer será de 522 240 bytes a uma profundidade de 32 bits, ou seja, isso é maior do que o tamanho da memória RAM, então o framebuffer e a heap (que será necessária para armazenar dados de imagens e estruturas auxiliares do OpenCV) ficarão na SDRAM; o restante (memória para pilhas e outras necessidades do sistema) irá para a RAM.

Se pegarmos a configuração mínima para o STM32F7Discovery (remover toda a rede, todos os comandos, minimizar as pilhas, etc.) e adicionar o OpenCV com exemplos, a memória necessária será a seguinte:

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

Para aqueles que não estão muito familiarizados com quais seções vão para onde, explico: .text e .rodata contém instruções e constantes (grosso modo, dados somente para leitura), .data onde ficam os dados mutáveis, .bss onde estão as variáveis "zeradas" que, no entanto, precisam de espaço (essa seção "irá" para a RAM).

A boa notícia é que .data/.bss devem ser alocados, mas o problema é que .text só há 1MiB de memória disponível para a imagem. Podemos descartar a .text imagem do exemplo e lê-la, por exemplo, de um cartão SD para a memória durante a inicialização, mas fruits.png pesa cerca de 330KiB, então isso não resolve o problema: a maior parte .text consiste exatamente no código do OpenCV.

Em suma, restou apenas uma opção — carregar parte do código na memória flash QSPI (ela possui um modo especial de operação para mapeamento de memória no barramento do sistema, permitindo que o processador acesse esses dados diretamente). No entanto, surge um problema: primeiro, a memória da flash QSPI não está acessível imediatamente após a reinicialização do dispositivo (é necessário inicializar separadamente o modo de mapeamento de memória); segundo, não é possível "gravar" essa memória com o carregador usual.

No final, foi decidido linkar todo o código na QSPI e gravá-lo com um carregador específico, que irá receber o binário necessário via TFTP.

Resultado

A ideia de portar essa biblioteca para o Embox surgiu há cerca de um ano, mas foi adiada repetidamente por várias razões. Uma delas é o suporte ao libstdc++ e à biblioteca de templates padrão. O problema do suporte ao C++ no Embox vai além do escopo deste artigo, portanto, direi aqui apenas que conseguimos obter esse suporte na medida necessária para o funcionamento desta biblioteca 🙂

No final, esses problemas foram superados (pelo menos, em um grau suficiente para o funcionamento do exemplo OpenCV), e o exemplo foi executado. A placa leva 40 longos segundos para encontrar bordas com o filtro de Canny. Isso é, claro, tempo demais (há maneiras de otimizar isso, e podemos escrever um artigo separado sobre isso, caso o sucesso aconteça).

OpenCV no STM32F7-Discovery

No entanto, o objetivo intermediário era criar um protótipo que mostrasse a viabilidade de executar o OpenCV no STM32, e esse objetivo foi alcançado, viva!

tl;dr: instruções passo a passo

0: Baixe os fontes do Embox, por exemplo assim:

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

1: Começaremos pela construção do carregador, que "gravará" a flash QSPI.

    make confload-arm/stm32f7cube

Agora é necessário configurar a rede, pois vamos carregar a imagem via TFTP. Para definir os endereços IP da placa e do host, é preciso alterar o arquivo conf/rootfs/network.

Exemplo de configuração:

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 — endereço do host de onde a imagem será carregada, address — endereço da placa.

Após isso, compilamos o carregador:

    make

2: Carregamento normal do carregador (desculpe pelo trocadilho) na placa — aqui não há nada específico, deve ser feito como para qualquer outro aplicativo para STM32F7Discovery. Se você não sabe como fazer isso, pode ler sobre. aqui.
3: Compilação da imagem com a configuração para OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extraindo seções do ELF que precisam ser gravadas no QSPI, em 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

No diretório conf, há um script que faz isso, portanto, você pode executá-lo

    ./conf/qspi_objcopy.sh # O binário necessário -- build/base/bin/qspi.bin

5: Com tftp, carregue qspi.bin.bin no flash QSPI. No host, é necessário copiar qspi.bin para a pasta raiz do servidor tftp (normalmente é /srv/tftp/ ou /var/lib/tftpboot/; pacotes para o servidor correspondente estão disponíveis na maioria das distribuições populares, geralmente chamado tftpd ou tftp-hpa, às vezes é necessário executar 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

No Embox (ou seja, no carregador), você deve executar o seguinte comando (supondo que o endereço do servidor seja 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Usando o comando goto você precisa 'pular' para a memória QSPI. A localização específica variará dependendo de como a imagem é vinculada, você pode verificar esse endereço com o comando mem 0x90000000 (o endereço de início está no segundo palavra de 32 bits da imagem); você também precisará definir a pilha com o sinalizador -s, o endereço da pilha está em 0x90000000, exemplo:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              este é o endereço    este é o endereço 
                da pilha        da primeira
                           instrução

    embox>goto -i 0x9000c27f -s 0x20023200 # O sinalizador -i é necessário para proibir interrupções durante a inicialização do sistema

    

7: Iniciamos

    embox> edges 20

e aproveitamos 40 segundos de busca de bordas 🙂

Se algo não der certo, escreva uma issue em nosso repositório, ou na lista de discussão em embox-devel@googlegroups.com, ou nos comentários aqui.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster