OpenCV em STM32F7-Discovery

OpenCV em STM32F7-Discovery Sou um dos desenvolvedores do sistema operacional Caixa de entrada, e neste artigo falarei sobre como consegui rodar o OpenCV na placa STM32746G.

Se você digitar algo como “OpenCV na placa STM32” em um mecanismo de busca, poderá encontrar algumas pessoas interessadas em usar esta biblioteca em placas STM32 ou outros microcontroladores.
Existem vários vídeos que, a julgar pelo nome, deveriam demonstrar o que é necessário, mas geralmente (em todos os vídeos que vi) na placa STM32, apenas a imagem era recebida da câmera e o resultado era exibido na tela, e o processamento da imagem em si foi feito em um computador comum ou em placas mais poderosas (por exemplo, Raspberry Pi).

Por que é difícil?

A popularidade das consultas de pesquisa é explicada pelo fato de que OpenCV é a biblioteca de visão computacional mais popular, o que significa que mais desenvolvedores estão familiarizados com ela, e a capacidade de executar código pronto para desktop em um microcontrolador simplifica muito o processo de desenvolvimento. Mas por que ainda não existem receitas populares prontas para resolver esse problema?

O problema de usar OpenCV em xales pequenos está relacionado a duas características:

  • Se você compilar a biblioteca mesmo com um conjunto mínimo de módulos, ela simplesmente não caberá na memória flash do mesmo STM32F7Discovery (mesmo sem levar em conta o sistema operacional) devido a um código muito grande (vários megabytes de instruções)
  • A própria biblioteca é escrita em C++, o que significa
    • Precisa de suporte para tempo de execução positivo (exceções, etc.)
    • Pouco suporte para LibC/Posix, que geralmente é encontrado em SO para sistemas embarcados - você precisa de uma biblioteca padrão plus e uma biblioteca de modelo STL padrão (vetor, etc.)

Transferir para Embox

Como de costume, antes de portar qualquer programa para o sistema operacional, é uma boa ideia tentar construí-lo na forma pretendida pelos desenvolvedores. No nosso caso, não há problemas com isso - o código-fonte pode ser encontrado em githabe, a biblioteca é construída sob GNU/Linux com o cmake usual.

A boa notícia é que o OpenCV pode ser construído como uma biblioteca estática pronta para uso, o que facilita a portabilidade. Coletamos uma biblioteca com uma configuração padrão e vemos quanto espaço eles ocupam. Cada módulo é coletado em uma 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 você pode ver na última linha, .bss e .data não ocupam muito espaço, mas o código tem mais de 70 MiB. É claro que se isso estiver vinculado estaticamente a um aplicativo específico, o código ficará menor.

Vamos tentar descartar o máximo de módulos possível para que um exemplo mínimo seja montado (que, por exemplo, simplesmente produzirá a versão do OpenCV), então procuramos cmake .. -LA e desligue nas opções tudo que desliga.

        -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 um lado, este é apenas um módulo da biblioteca, por outro lado, sem otimização do compilador para tamanho do código (-Os). ~3 MiB de código ainda é bastante, mas já dá esperança de sucesso.

Executar no emulador

É muito mais fácil depurar no emulador, então primeiro certifique-se de que a biblioteca funcione no qemu. Como plataforma emulada, escolhi o Integrator/CP, pois em primeiro lugar, também é ARM e, em segundo lugar, o Embox suporta saída gráfica para esta plataforma.

O Embox possui um mecanismo para construção de bibliotecas externas, usando ele adicionamos o OpenCV como um módulo (passando todas as mesmas opções para o build "mínimo" na forma de bibliotecas estáticas), após isso adiciono uma aplicação simples que fica assim:

version.cpp:

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

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

    return 0;
}

Montamos o sistema, executamos - 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 próximo passo é rodar algum exemplo, de preferência um dos padrões oferecidos pelos próprios desenvolvedores. no seu site. Eu escolhi detector de fronteira astuto.

O exemplo teve que ser ligeiramente reescrito para exibir a imagem com o resultado diretamente no buffer de quadro. Eu tive que fazer isso, porque. função imshow() pode desenhar imagens através das interfaces QT, GTK e Windows, que, claro, definitivamente não estarão na configuração do STM32. Na verdade, o QT também pode ser executado no STM32F7Discovery, mas isso será discutido em outro artigo 🙂

Após um breve esclarecimento em qual formato o resultado do detector de borda é armazenado, obtemos uma imagem.

OpenCV em STM32F7-Discovery

imagem original

OpenCV em STM32F7-Discovery

resultado

Executando no STM32F7Discovery

No 32F746GDISCOVERY existem várias seções de memória de hardware que podemos usar de uma forma ou de outra

  1. RAM de 320 KiB
  2. Flash de 1MiB para imagem
  3. SDRAM de 8 MiB
  4. 16MiB QSPI NAND Flash
  5. slot para cartão microSD

Um cartão SD pode ser usado para armazenar imagens, mas no contexto da execução de um exemplo mínimo, isso não é muito útil.
A tela tem uma resolução de 480×272, o que significa que a memória framebuffer será de 522 bytes a uma profundidade de 240 bits, ou seja, isso é mais do que o tamanho da RAM, então o framebuffer e o heap (que serão necessários, inclusive para o OpenCV, para armazenar dados para imagens e estruturas auxiliares) estarão localizados na SDRAM, todo o resto (memória para pilhas e outras necessidades do sistema ) irá para a RAM .

Se pegarmos a configuração mínima para STM32F7Discovery (jogar fora toda a rede, todos os comandos, tornar as pilhas o mais pequenas possível, etc.) e adicionar OpenCV com exemplos lá, 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 onde, vou explicar: em .text и .rodata instruções e constantes (a grosso modo, dados somente leitura) estão .data os dados são mutáveis, .bss existem variáveis ​​"anuladas", que, no entanto, precisam de um lugar (esta seção "irá" para a RAM).

A boa notícia é que .data/.bss deve caber, mas com .text o problema é que há apenas 1MiB de memória para a imagem. Pode ser jogado fora .text a imagem do exemplo e leia-a, por exemplo, do cartão SD para a memória na inicialização, mas fruit.png pesa cerca de 330KiB, então isso não resolverá o problema: a maioria .text consiste no código OpenCV.

Em geral, resta apenas uma coisa - carregar uma parte do código em um flash QSPI (ele possui um modo especial de operação para mapear a memória para o barramento do sistema, para que o processador possa acessar esses dados diretamente). Nesse caso, surge um problema: em primeiro lugar, a memória da unidade flash QSPI não está disponível imediatamente após a reinicialização do dispositivo (você precisa inicializar separadamente o modo mapeado na memória) e, em segundo lugar, você não pode "flash" esta memória com um gerenciador de inicialização conhecido.

Como resultado, foi decidido vincular todo o código no QSPI e atualizá-lo com um carregador autoescrito que receberá o binário necessário via TFTP.

resultado

A ideia de portar esta biblioteca para o Embox surgiu há cerca de um ano, mas foi adiada várias vezes por vários motivos. Um deles é o suporte para libstdc++ e a biblioteca de modelos padrão. O problema do suporte a C++ no Embox está além do escopo deste artigo, então aqui direi apenas que conseguimos esse suporte na quantidade certa para que essa biblioteca funcione 🙂

No final, esses problemas foram superados (pelo menos o suficiente para o exemplo do OpenCV funcionar) e o exemplo foi executado. Leva 40 longos segundos para o quadro procurar limites usando o filtro Canny. Isso, claro, é muito longo (há considerações sobre como otimizar esse assunto, será possível escrever um artigo separado sobre isso em caso de sucesso).

OpenCV em STM32F7-Discovery

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

tl;dr: instruções passo a passo

0: Baixe as fontes do Embox, assim:

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

1: Vamos começar montando um bootloader que irá "flash" um pendrive QSPI.

    make confload-arm/stm32f7cube

Agora você precisa configurar a rede, porque. Faremos o upload da imagem via TFTP. Para definir os endereços IP da placa e do host, você precisa editar 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.

Depois disso, coletamos o bootloader:

    make

2: O carregamento normal do bootloader (desculpe o trocadilho) no quadro - não há nada específico aqui, você precisa fazer como em qualquer outro aplicativo para STM32F7Discovery. Se você não sabe como fazer isso, você pode ler sobre isso aqui.
3: Compilando uma imagem com uma configuração para OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extrair das seções ELF a serem gravadas em QSPI para 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

Existe um script no diretório conf que faz isso, então você pode executá-lo

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

5: Usando tftp, baixe qspi.bin.bin para uma unidade flash QSPI. No host, para fazer isso, copie qspi.bin para a pasta raiz do servidor tftp (geralmente /srv/tftp/ ou /var/lib/tftpboot/; pacotes para o servidor correspondente estão disponíveis nas distribuições mais populares, geralmente chamadas tftpd ou tftp-hpa, às vezes você tem que fazer 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 bootloader), você precisa executar o seguinte comando (assumimos que o servidor tenha o endereço 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Com comando goto você precisa "pular" para a memória QSPI. A localização específica irá variar dependendo de como a imagem está vinculada, você pode ver este endereço com o comando mem 0x90000000 (o endereço inicial se encaixa na segunda palavra de 32 bits da imagem); você também precisará sinalizar a pilha -s, o endereço da pilha está em 0x90000000, exemplo:

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

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

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

7: Lançamento

    embox> edges 20

e aproveite a pesquisa de borda de 40 segundos 🙂

Se algo der errado - escreva um problema em nosso repositório, ou para a lista de discussão [email protegido], ou em um comentário aqui.

Fonte: habr.com

Adicionar um comentário