OpenCV در STM32F7-Discovery

OpenCV در STM32F7-Discovery من یکی از توسعه دهندگان این سیستم عامل هستم صندوق پستیو در این مقاله در مورد نحوه اجرای OpenCV بر روی برد STM32746G صحبت خواهم کرد.

اگر چیزی مانند "OpenCV در برد STM32" را در موتور جستجو تایپ کنید، می توانید افراد زیادی را پیدا کنید که علاقه مند به استفاده از این کتابخانه در بردهای STM32 یا سایر میکروکنترلرها هستند.
چندین ویدیو وجود دارد که، با قضاوت بر اساس نام، باید نشان دهد که چه چیزی لازم است، اما معمولا (در تمام ویدیوهایی که من دیدم) روی برد STM32، فقط تصویر از دوربین دریافت می شود و نتیجه روی صفحه نمایش داده می شود. و پردازش تصویر خود یا بر روی یک کامپیوتر معمولی یا روی بردهای قدرتمندتر (به عنوان مثال، Raspberry Pi) انجام می شود.

چرا مشکل است؟

محبوبیت پرس و جوهای جستجو با این واقعیت توضیح داده می شود که OpenCV محبوب ترین کتابخانه بینایی کامپیوتری است، به این معنی که توسعه دهندگان بیشتری با آن آشنا هستند و توانایی اجرای کدهای آماده دسکتاپ روی یک میکروکنترلر، روند توسعه را بسیار ساده می کند. اما چرا هنوز هیچ دستور العمل آماده محبوبی برای حل این مشکل وجود ندارد؟

مشکل استفاده از OpenCV در شال های کوچک به دو ویژگی مربوط می شود:

  • اگر کتابخانه را حتی با حداقل مجموعه ای از ماژول ها کامپایل کنید، به دلیل کد بسیار بزرگ (چند مگابایت دستورالعمل) به سادگی در حافظه فلش همان STM32F7Discovery (حتی بدون در نظر گرفتن سیستم عامل) جا نمی شود.
  • خود کتابخانه به زبان C++ نوشته شده است، یعنی
    • نیاز به پشتیبانی برای زمان اجرا مثبت (استثناء و غیره)
    • پشتیبانی کمی از LibC/Posix، که معمولاً در سیستم‌عامل برای سیستم‌های تعبیه‌شده یافت می‌شود - به یک کتابخانه استاندارد پلاس و یک کتابخانه الگوی استاندارد STL (بردار و غیره) نیاز دارید.

انتقال به Embox

طبق معمول، قبل از انتقال هر برنامه به سیستم عامل، ایده خوبی است که سعی کنید آن را به شکلی که توسعه دهندگان آن را در نظر گرفته اند، بسازید. در مورد ما، هیچ مشکلی با این وجود ندارد - کد منبع را می توان در آن یافت github، کتابخانه تحت گنو/لینوکس با 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. رم 320 کیلوبایت
  2. فلاش 1 مگابایت برای تصویر
  3. 8 مگابایت SDRAM
  4. فلش 16 مگابایتی QSPI NAND
  5. اسلات کارت microSD

از کارت SD می توان برای ذخیره تصاویر استفاده کرد، اما در زمینه اجرای یک مثال حداقلی، این کار چندان مفید نیست.
صفحه نمایش دارای وضوح 480×272 است، به این معنی که حافظه فریم بافر 522 بایت در عمق 240 بیت خواهد بود. این بیشتر از اندازه RAM است، بنابراین فریم بافر و پشته (که از جمله برای OpenCV، برای ذخیره داده‌ها برای تصاویر و ساختارهای کمکی مورد نیاز است) در SDRAM قرار خواهند گرفت، هر چیز دیگری (حافظه برای پشته‌ها و سایر نیازهای سیستم). ) به رم می رود.

اگر حداقل پیکربندی را برای STM32F7Discovery در نظر بگیریم (کل شبکه، همه دستورات را بیرون بیاوریم، پشته ها را تا حد امکان کوچک کنید، و غیره) و OpenCV را با مثال هایی در آنجا اضافه کنیم، حافظه مورد نیاز به صورت زیر خواهد بود:

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

برای کسانی که خیلی آشنا نیستند که کدام بخش ها کجا می روند، توضیح می دهم: در .text и .rodata دستورالعمل ها و ثابت ها (به طور تقریبی، داده های فقط خواندنی) در آن قرار دارند .data داده ها قابل تغییر هستند، .bss متغیرهای "nulled" وجود دارد که با این وجود به یک مکان نیاز دارند (این بخش به RAM "می رود").

خبر خوب این است که .data/.bss باید مناسب باشد، اما با .text مشکل اینجاست که فقط 1 مگابایت حافظه برای تصویر وجود دارد. می توان بیرون انداخت .text تصویر را از مثال ببینید و آن را بخوانید، به عنوان مثال، از کارت SD در هنگام راه اندازی، اما fruits.png حدود 330 کیلوبایت وزن دارد، بنابراین این مشکل را حل نمی کند: بیشتر .text از کد OpenCV تشکیل شده است.

به طور کلی، تنها یک چیز باقی مانده است - بارگذاری بخشی از کد بر روی یک فلش QSPI (یک حالت عملکرد ویژه برای نگاشت حافظه به گذرگاه سیستم دارد، به طوری که پردازنده می تواند مستقیماً به این داده ها دسترسی داشته باشد). در این مورد، یک مشکل ایجاد می شود: اولاً، حافظه فلش مموری QSPI بلافاصله پس از راه اندازی مجدد دستگاه در دسترس نیست (شما باید حالت نقشه برداری حافظه را به طور جداگانه مقداردهی اولیه کنید)، و ثانیاً نمی توانید این حافظه را با "فلش" کنید. یک بوت لودر آشنا

در نتیجه، تصمیم گرفته شد که تمام کدها را در QSPI پیوند داده و آن را با یک لودر خودنویس فلش کنیم که باینری مورد نیاز را از طریق TFTP دریافت کند.

نتیجه

ایده انتقال این کتابخانه به Embox حدود یک سال پیش ظاهر شد، اما بارها و بارها به دلایل مختلف به تعویق افتاد. یکی از آنها پشتیبانی از libstdc++ و کتابخانه قالب استاندارد است. مشکل پشتیبانی از C++ در Embox خارج از حوصله این مقاله است، بنابراین در اینجا فقط می گویم که ما توانستیم به میزان مناسبی به این پشتیبانی دست پیدا کنیم تا این کتابخانه کار کند.

در نهایت، این مشکلات برطرف شد (حداقل به اندازه کافی برای مثال OpenCV کار کند)، و مثال اجرا شد. 40 ثانیه طول می کشد تا برد با استفاده از فیلتر Canny به جستجوی مرزها بپردازد. البته این خیلی طولانی است (مطالعاتی در مورد نحوه بهینه سازی این موضوع وجود دارد، در صورت موفقیت می توان مقاله جداگانه ای در این مورد نوشت).

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 ثانیه ای لذت ببرید 🙂

اگر مشکلی پیش آمد - یک مشکل را در آن بنویسید مخزن ما، یا به لیست پستی [ایمیل محافظت شده]، یا در یک نظر در اینجا.

منبع: www.habr.com

اضافه کردن نظر