OpenCV no STM32F7-Discovery

Sou um dos desenvolvedores do sistema operacional Embox e, neste artigo, falarei sobre como consegui executar o OpenCV na placa STM32746G.


Se você acessar um mecanismo de busca como "OpenCV na placa STM32", poderá encontrar alguns interessados ​​em usar essa biblioteca em placas STM32 ou em outros microcontroladores.
Existem vários vídeos que, a julgar pelo nome, devem demonstrar o que é necessário, mas geralmente (em todos os vídeos que eu vi) na placa STM32, apenas a imagem foi recebida da câmera e o resultado foi exibido na tela, e o processamento da imagem foi realizado em em um computador comum ou em placas mais poderosas (por exemplo, Raspberry Pi).


Por que isso é difícil?


A popularidade das consultas de pesquisa é explicada pelo fato de o OpenCV ser 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 a área de trabalho no microcontrolador simplifica bastante o processo de desenvolvimento. Mas por que ainda não existem receitas prontas populares para resolver esse problema?


O problema do uso do OpenCV em pequenas placas está associado a dois recursos:


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

Portando para a Embox


Como de costume, antes de portar qualquer programa para o sistema operacional, é uma boa ideia tentar montá-lo da forma que os desenvolvedores pretendiam. No nosso caso, não há problemas com isso - as fontes podem ser encontradas no github , a biblioteca é construída no GNU / Linux com o cmake usual.


Das boas notícias - o OpenCV pronto para o uso pode ser montado como uma biblioteca estática, o que facilita a transferência. Coletamos a biblioteca com a configuração padrão e vemos quanto espaço eles ocupam. Cada módulo é montado 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 se tornará menor.


Vamos tentar lançar o máximo de módulos possível para que um exemplo mínimo (que, por exemplo, apenas exiba a versão do OpenCV) seja montado, assista ao cmake .. -LA e desative tudo o que estiver desabilitado nas opções.


  -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 de biblioteca, por outro lado, é sem otimização pelo compilador em termos de tamanho de código ( -Os ). ~ 3 MiB de código ainda é bastante, mas já dá esperança de sucesso.


Executar no emulador


A depuração no emulador é muito mais fácil, portanto, primeiro verifique se a biblioteca roda no qemu. Como plataforma emulada, escolhi o Integrator / CP, porque em primeiro lugar, também é ARM e, em segundo lugar, a Embox suporta saída gráfica para esta plataforma.


A Embox possui um mecanismo para a construção de bibliotecas externas, e adicionamos o OpenCV como módulo (passando todas as mesmas opções para a compilação "mínima" que as bibliotecas estáticas). Depois disso, adiciono o aplicativo mais simples que se parece com isso:


 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 conclusão 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 é executar um exemplo, o melhor de tudo, um padrão daqueles que os próprios desenvolvedores oferecem em seus sites . Eu escolhi o detector de borda de Canny .


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


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



Imagem original



Resultado


Executando em STM32F7Discovery


Existem várias partições de hardware no 32F746GDISCOVERY que podemos usar de qualquer maneira


  1. 320KiB RAM
  2. Flash de 1MiB para imagem
  3. 8MiB SDRAM
  4. Unidade flash 16MiB QSPI NAND
  5. Slot para cartão MicroSD

Um cartão SD pode ser usado para armazenar imagens, mas no contexto de executar um exemplo mínimo, isso não é muito útil.
A tela tem uma resolução de 480x272, o que significa que a memória do buffer de quadros será de 522.240 bytes a uma profundidade de 32 bits, ou seja, isso é mais do que o tamanho da RAM, então colocaremos o buffer de quadros e um monte (o que será necessário para o OpenCV armazenar dados para imagens e estruturas auxiliares) na SDRAM; todo o resto (memória para pilhas e outras necessidades do sistema) será direcionado 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 o OpenCV com exemplos lá, com a memória necessária, será o seguinte:


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

Para aqueles que não estão familiarizados com as seções que estão sendo dobradas, explicarei: instruções e constantes (aproximadamente dados somente leitura) estão em .text e .rodata , os dados são mutáveis ​​em .data e "zero" estão em .bss variáveis ​​que, no entanto, precisam de um local (esta seção "irá" para a RAM).


A boa notícia é que .data / .bss deve caber, mas com problemas de .text - há apenas 1MiB de memória para a imagem. Você pode .text imagem do exemplo do .text e lê-la, por exemplo, do cartão SD para a memória na inicialização, mas o fruits.png pesa cerca de 330KiB, portanto isso não resolverá o problema: a maior parte do .text consiste no código OpenCV.


Em geral, há apenas uma coisa: carregar parte do código em uma unidade flash QSPI (ela possui um modo operacional especial para mapear 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 fica disponível imediatamente após a reinicialização do dispositivo (é necessário inicializar separadamente o modo de mapeamento de memória) e, em segundo lugar, não é possível piscar essa memória com o carregador de inicialização comum.


Como resultado, decidiu-se vincular todo o código no QSPI e atualizá-lo com um gerenciador de inicialização, que receberá o binário necessário via TFTP.


Resultado


A idéia de portar essa biblioteca para a Embox surgiu cerca de um ano atrás, mas repetidamente foi adiada devido a vários motivos. Um deles é o suporte à libstdc ++ e à biblioteca de modelos padrão. O problema de oferecer suporte ao C ++ na Embox está além do escopo deste artigo, portanto, aqui apenas direi que conseguimos obter esse suporte na quantidade certa para que esta biblioteca funcione :)


No final, esses problemas foram superados (pelo menos o suficiente para o exemplo do OpenCV funcionar), e o exemplo começou. 40 segundos longos levam o conselho a procurar limites pelo filtro Canny. Isso, é claro, é muito longo (há considerações sobre como otimizar esse assunto, será possível escrever um artigo separado sobre ele, se for bem-sucedido).




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, felicidades!

tl; dr: instruções passo a passo


0: Faça o download das fontes da Embox, por exemplo:


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

1: Vamos começar criando um gerenciador de inicialização que “piscará” a unidade flash QSPI.


  make confload-arm/stm32f7cube 

Agora você precisa configurar a rede, porque Carregaremos a imagem via TFTP. Para definir os endereços IP da placa e do host, você precisa modificar 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 é o endereço do host de onde a imagem será carregada; address é o endereço da placa.


Depois disso, colete o gerenciador de inicialização:


  make 

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


  make confload-platform/opencv/stm32f7discovery make 

4: Extraindo de seções 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 

O diretório conf contém um script que faz isso, para que você possa executá-lo


  ./conf/qspi_objcopy.sh #   -- build/base/bin/qspi.bin 

5: Usando tftp, carregue qspi.bin.bin em uma unidade flash QSPI. Para fazer isso, no host, copie o qspi.bin para a pasta raiz do servidor tftp (geralmente são / srv / tftp / ou / var / lib / tftpboot /; os pacotes para o servidor correspondente estão nas distribuições mais populares, geralmente chamadas de tftpd ou tftp-hpa, às vezes você precisa fazer o 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 

Na Embox (ou seja, no carregador de inicialização), você precisa executar o seguinte comando (supomos que o servidor tenha o endereço 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. O local específico varia de acordo com a vinculação da imagem. Você pode ver este endereço com mem 0x90000000 (o endereço inicial se encaixa na segunda palavra da imagem de 32 bits); você também precisa definir a pilha com o sinalizador -s , o endereço da pilha é 0x90000000, por exemplo:


  embox>mem 0x90000000 0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275 ↑ ↑        embox>goto -i 0x9000c27f -s 0x20023200 #  -i         <      ,    OpenCV > 

7: Executar


  embox> edges 20 

e desfrute de uma pesquisa de fronteira de 40 segundos :)


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

Source: https://habr.com/ru/post/pt457724/


All Articles