OpenCV на STM32F7-Discovery

OpenCV на STM32F7-Discovery Я адзін з распрацоўшчыкаў аперацыйнай сістэмы Embox, і ў гэтым артыкуле я раскажу пра тое, як у мяне атрымалася запусціць OpenCV на плаце STM32746G.

Калі ўбіць у пошукавік нешта накшталт OpenCV on STM32 board, можна знайсці даволі шмат тых, хто цікавіцца выкарыстаннем гэтай бібліятэкі на платах STM32 або іншых мікракантролерах.
Ёсць некалькі відэа, якія, судзячы па назове, павінны дэманстраваць тое, што трэба, але звычайна (ва ўсіх відэа, якія я бачыў) на плаце STM32 выраблялася толькі атрыманне малюначка з камеры і выснова выніку на экран, а сама апрацоўка малюнка рабілася альбо на звычайным кампутары, альбо на поплатках помощнее (напрыклад, Raspberry Pi).

Чаму гэта складана?

Папулярнасць пошукавых запытаў тлумачыцца тым, што OpenCV – самая папулярная бібліятэка кампутарнага зроку, а значыць, з ёй знаёма больш распрацоўнікаў, ды і магчымасць запускаць гатовы для дэсктопа код на мікракантролеры значна спрашчае працэс распрацоўкі. Але чаму да гэтага часу няма нейкіх папулярных гатовых рэцэптаў вырашэння гэтай праблемы?

Праблема выкарыстання OpenCV на невялікіх хустках звязана з двума асаблівасцямі:

  • Калі скампіляваць бібліятэку нават з мінімальным наборам модуляў, у флэш-памяць той жа STM32F7Discovery яна проста не ўлезе (нават без уліку АС) з-за вельмі вялікага кода (некалькі мегабайт інструкцый)
  • Сама бібліятэка напісана на C++, а значыць
    • Патрэбна падтрымка плюсавага рантайму (выключэнні і да т.п.)
    • Мала падтрымкі LibC/Posix, якія звычайна ёсць у АС для ўбудаваных сістэм – патрэбна стандартная бібліятэка плюсаў і стандартная бібліятэка шаблонаў STL (vector і т.д.)

Партаванне на Embox

Як звычайна, перад партаваннем якіх-небудзь праграм у аперацыйную сістэму нядрэнна паспрабаваць сабраць яе ў тым выглядзе, у якім гэта задумвалі распрацоўшчыкі. У нашым выпадку праблем з гэтым не ўзнікае - зыходнікі можна знайсці на гітхабе, бібліятэка збіраецца пад GNU/Linux звычайным cmake-ам.

З добрых навін - OpenCV са скрынкі можна збіраць у выглядзе статычнай бібліятэкі, што робіць партаванне прасцей. Збіраны бібліятэку са стандартным канфігам і глядзім, колькі месца яны займае. Кожны модуль збіраецца ў асобную бібліятэку.

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

Як відаць з апошняга радка, .bss і .data займаюць не так шмат месца, затое кода больш за 70 МіБ. Зразумела, што калі гэта злінкаваць статычна з канкрэтным дадаткам, кода стане менш.

Паспрабуем выкінуць як мага больш модуляў, каб сабраўся мінімальны прыклад (які, напрыклад, проста выведзе версію OpenCV), так што глядзім cmake .. -LA і адключаем у опцыях усё, што адключаецца.

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

З аднаго боку, гэта толькі адзін модуль бібліятэкі, з другога боку, гэта без аптымізацыі кампілятарам па памеры кода (-Os). ~3 МіБ кода – гэта ўсё яшчэ дастаткова шмат, але ўжо дае надзею на поспех.

Запуск у эмулятары

На эмулятары адладжвацца значна прасцей, таму спачатку пераканаемся, што бібліятэка працуе на qemu. У якасці эмуляванай платформы я абраў Integrator/CP, т.к. па-першае, гэта таксама ARM, а па-другое, Embox падтрымлівае выснову графікі для гэтай платформы.

У Embox ёсць механізм для зборкі вонкавых бібліятэк, з яго дапамогай дадаем OpenCV як модуль (перадаўшы ўсё тыя ж опцыі для «мінімальнай» зборкі ў выглядзе статычных бібліятэк), пасля гэтага дадаю найпростае прыкладанне, якое выглядае так:

version.cpp:

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

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

    return 0;
}

Збіраны сістэму, запускаем - атрымліваем чаканую выснову.

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

Наступны крок - запусціць які-небудзь прыклад, лепш за ўсё які-небудзь стандартны з тых, што прапануюць самі распрацоўшчыкі у сябе на сайце. Я выбраў дэтэктар межаў Кэні.

Прыклад прыйшлося крыху перапісаць, каб адлюстроўваць карцінку з вынікам напрамую ў фрэйм-буфер. Зрабіць гэта прыйшлося, т.я. функцыя imshow() умее адмалёўваць выявы праз інтэрфейсы QT, GTK і Windows, якіх, само сабой, у канфігу для STM32 сапраўды не будзе. Насамрэч, QT таксама можна запусціць на STM32F7Discovery, але пра гэта будзе расказана ўжо ў іншым артыкуле 🙂

Пасля нядоўгага высвятлення, у якім менавіта фармаце захоўваецца вынік працы дэтэктара меж, атрымліваем малюнак.

OpenCV на STM32F7-Discovery

Арыгінальная карцінка

OpenCV на STM32F7-Discovery

Вынік

Запуск на STM32F7Discovery

На 32F746GDISCOVERY ёсць некалькі апаратных раздзелаў памяці, якія мы можам так ці інакш выкарыстоўваць

  1. 320KiB аператыўнай памяці
  2. 1MiB флэш-памяці для выявы
  3. 8MiB SDRAM
  4. 16MiB QSPI NAND-флэшка
  5. Раз'ём для microSD-карткі

SD-карту можна выкарыстоўваць для захоўвання малюнкаў, але ў кантэксце запуску мінімальнага прыкладу гэта не вельмі карысна.
Дысплей мае дазвол 480×272, а значыць, памяць пад фрэймбуфер складзе 522 240 байт пры глыбіні 32 біта, г.зн. гэта больш, чым памер аператыўнай памяці, так што фрэймбуфер і кучу (якая запатрабуецца ў тым ліку для OpenCV, каб захоўваць дадзеныя для малюнкаў і дапаможных структур) будзем размяшчаць у SDRAM, усё астатняе (памяць пад стэкі і іншыя сістэмныя патрэбы) адправіцца ў RAM .

Калі ўзяць мінімальны канфіг для STM32F7Discovery (выкінуць усю сетку, усе каманды, зрабіць стэкі як мага менш і г.д.) і дадаць туды OpenCV з прыкладамі, з патрабаванай памяццю будзе наступнае:

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

Для тых, хто не вельмі знаёмы з тым, якія секцыі куды складаецца, растлумачу: у .text и .rodata ляжаць інструкцыі і канстанты (грубіянска кажучы, readonly-дадзеныя), у .data ляжаць дадзеныя змяняныя, у .bss ляжыць "зануленыя" зменныя, якім, тым не менш, трэба месца (гэтая секцыя "адправіцца" у RAM).

Добрая навіна ў тым, што .data/.bss павінны змяшчацца, а вось з .text бяда - пад вобраз ёсць толькі 1MiB памяці. Можна выкінуць з .text карцінку з прыкладу і чытаць яе, напрыклад, з SD-карты ў памяць пры запуску, але fruits.png важыць прыкладна 330KiB, так што праблему гэта не вырашыць: большая частка .text складаецца менавіта з кода OpenCV.

Па вялікім рахунку, застаецца толькі адно – загрузка часткі кода на QSPI-флэшку (у яе ёсць спец. рэжым працы для мэпавання памяці на сістэмную шыну, так што працэсар зможа звяртацца да гэтых дадзеных напроста). Пры гэтым узнікае праблема: па-першае, памяць QSPI-флэшкі недаступная адразу пасля перазагрузкі прылады (трэба асобна ініцыялізаваць memory-mapped-рэжым), па-другое, нельга "прашыць" гэтую памяць звыклым загрузнікам.

У выніку было вырашана злінкаваць увесь код у QSPI, а прашываць яго самапісным загрузнікам, які будзе атрымліваць патрэбны бінарнік па TFTP.

Вынік

Ідэя партаваць гэтую бібліятэку на Embox з'явілася яшчэ прыкладна год таму, але штораз гэта адкладалася з-за розных чыннікаў. Адна з іх - падтрымка libstdc++ і standart template library. Праблема падтрымкі C++ у Embox выходзіць за рамкі гэтага артыкула, таму тут толькі скажу, што нам удалося дабіцца гэтай падтрымкі ў патрэбным аб'ёме для працы гэтай бібліятэкі 🙂

У выніку гэтыя праблемы былі пераадолены (прынамсі, у дастатковай ступені для працы прыкладу OpenCV), і прыклад запусціўся. 40 працяглых секунд займае ў платы пошук межаў фільтрам Кэні. Гэта, вядома, занадта доўга (ёсць меркаванні, як гэтую справу аптымізаваць, пра гэта можна будзе напісаць асобны артыкул у выпадку поспеху).

OpenCV на STM32F7-Discovery

Тым не менш, прамежкавай мэтай было стварэнне прататыпа, які пакажа прынцыповую магчымасць запуску OpenCV на STM32, адпаведна, гэтая мэта была дасягнута, ура!

tl;dr: пакрокавая інструкцыя

0: Качаем зыходнікі Embox, напрыклад так:

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

1: Пачнём са зборкі загрузніка, які «прашые» QSPI-флэшку.

    make confload-arm/stm32f7cube

Цяпер трэба наладзіць сетку, т.я. загружаць выяву будзем па TFTP. Для таго, каб задаць IP-адрасы платы і хаста, трэба змяніць файл conf/rootfs/network.

Прыклад канфігурацыі:

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 - адрас хаста, адкуль будзе загружацца выява, address - адрас платы.

Пасля гэтага збіраем загрузнік:

    make

2: Звычайная загрузка загрузніка (прабачце за каламбур) на плату - тут нічога спецыфічнага, трэба гэта зрабіць як для любога іншага прыкладання для STM32F7Discovery. Калі вы не ведаеце, як гэта робіцца, можна пачытаць пра гэта тут.
3: Кампіляцыя выявы з канфігам для OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Выманне з ELF секцый, якія трэба запісаць у QSPI, у 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

У дырэкторыі conf ляжыць скрыпт, які гэта робіць, так што можна запусціць яго

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

5: З дапамогай tftp загружаем qspi.bin.bin на QSPI-флэшку. На хасце для гэтага трэба скапіяваць qspi.bin у каранёвую тэчку tftp-сервера (звычайна гэта /srv/tftp/ ці /var/lib/tftpboot/; пакеты для адпаведнага сервера ёсць у большасці папулярных дыстрыбутываў, звычайна завецца tftpd ці tftp-hpa, часам трэба зрабіць systemctl start tftpd.service для старту).

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

На Embox-е (г.зн. у загрузніку) трэба выканаць такую ​​каманду (мяркуем, што ў сервера адрас 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: З дапамогай каманды goto трэба "скокнуць" у QSPI-памяць. Канкрэтная лакацыя будзе вар'іравацца ў залежнасці ад таго, як выява злінкуецца, паглядзець гэты адрас можна камандай mem 0x90000000 (адрас старту ўкладваецца ў другое 32-бітнае слова выявы); таксама спатрэбіцца выставіць стэк сцягам -s, адрас стэка ляжыць па адрасе 0x90000000, прыклад:

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

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

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

7: Запускаем

    embox> edges 20

і атрымліваем асалоду ад 40-секундным пошукам меж 🙂

Калі нешта пойдзе не так - пішыце issue у нашым рэпазітары, ці ў рассылку [электронная пошта абаронена], ці ў каментары тут.

Крыніца: habr.com

Дадаць каментар