Я адзін з распрацоўшчыкаў аперацыйнай сістэмы
Калі ўбіць у пошукавік нешта накшталт OpenCV on STM32 board, можна знайсці даволі шмат тых, хто цікавіцца выкарыстаннем гэтай бібліятэкі на платах STM32 або іншых мікракантролерах.
Ёсць некалькі відэа, якія, судзячы па назове, павінны дэманстраваць тое, што трэба, але звычайна (ва ўсіх відэа, якія я бачыў) на плаце STM32 выраблялася толькі атрыманне малюначка з камеры і выснова выніку на экран, а сама апрацоўка малюнка рабілася альбо на звычайным кампутары, альбо на поплатках помощнее (напрыклад, Raspberry Pi).
Чаму гэта складана?
Папулярнасць пошукавых запытаў тлумачыцца тым, што OpenCV – самая папулярная бібліятэка кампутарнага зроку, а значыць, з ёй знаёма больш распрацоўнікаў, ды і магчымасць запускаць гатовы для дэсктопа код на мікракантролеры значна спрашчае працэс распрацоўкі. Але чаму да гэтага часу няма нейкіх папулярных гатовых рэцэптаў вырашэння гэтай праблемы?
Праблема выкарыстання OpenCV на невялікіх хустках звязана з двума асаблівасцямі:
- Калі скампіляваць бібліятэку нават з мінімальным наборам модуляў, у флэш-памяць той жа STM32F7Discovery яна проста не ўлезе (нават без уліку АС) з-за вельмі вялікага кода (некалькі мегабайт інструкцый)
- Сама бібліятэка напісана на C++, а значыць
- Патрэбна падтрымка плюсавага рантайму (выключэнні і да т.п.)
- Мала падтрымкі LibC/Posix, якія звычайна ёсць у АС для ўбудаваных сістэм – патрэбна стандартная бібліятэка плюсаў і стандартная бібліятэка шаблонаў STL (vector і т.д.)
Партаванне на Embox
Як звычайна, перад партаваннем якіх-небудзь праграм у аперацыйную сістэму нядрэнна паспрабаваць сабраць яе ў тым выглядзе, у якім гэта задумвалі распрацоўшчыкі. У нашым выпадку праблем з гэтым не ўзнікае - зыходнікі можна знайсці на
З добрых навін - 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, але пра гэта будзе расказана ўжо ў іншым артыкуле 🙂
Пасля нядоўгага высвятлення, у якім менавіта фармаце захоўваецца вынік працы дэтэктара меж, атрымліваем малюнак.
Арыгінальная карцінка
Вынік
Запуск на STM32F7Discovery
На 32F746GDISCOVERY ёсць некалькі апаратных раздзелаў памяці, якія мы можам так ці інакш выкарыстоўваць
- 320KiB аператыўнай памяці
- 1MiB флэш-памяці для выявы
- 8MiB SDRAM
- 16MiB QSPI NAND-флэшка
- Раз'ём для 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 на 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