В этой статье я расскажу, как я получил приложение OpenCV, работающее на STM32746G-Discovery и STM32F769I-Discovery. Если вам нужна только краткая инструкция по его воспроизведению, перейдите на соответствующую страницу wiki на Github.

Я один из разработчиков Embox. Эта ОСРВ позволяет запускать некоторое тяжелое программное обеспечение Linux (QT, OpenGL, PJSIP и т. Д.) Без ядра Linux (т.е. вам нужно гораздо меньше ресурсов и получить более прямой контроль над периферийными устройствами). Запуск OpenCV на микроконтроллерах кажется довольно популярным запросом, но кажется, что этого еще никто не делал (есть несколько видеороликов с такими именами, как OpenCV + STM32, но, насколько я вижу, они используют плату STM32 в качестве камеры, а фактическое изображение обработка ведется на настольном компьютере), поэтому я решил перенести его на плату STM32F7Discovery.

В чем проблема?

У OpenCV есть две основные проблемы, когда вы пытаетесь запустить его на MCU:

  • Скомпилированный код занимает слишком много памяти (~ 4 МБ при минимальном включенном количестве модулей)
  • OpenCV написан на C ++, поэтому вы не можете просто запустить его как простой код (требуются исключения и libstdc ++)

Перенос OpenCV на Embox

Когда вы переносите что-то на новую платформу, рекомендуется собрать это из исходного кода обычным способом, то есть скомпилировать для системы GNU / Linux. С OpenCV это не проблема: исходный код доступен на Github, и его легко собрать с помощью cmake.
Хорошие новости: OpenCV может быть статически связан, поэтому его будет намного проще перенести на MCU. Давайте создадим его с конфигурацией по умолчанию и посмотрим, сколько кода он производит:

> 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 занимают менее 1 МБ каждый, в то время как раздел кода составляет ~ 70 МБ (конечно, со статической компоновкой для конкретного приложения это займет гораздо меньше, но в любом случае это слишком много).

Теперь попробуем сделать минимальную сборку. Позвоните 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)

Запустить OpenCV в QEMU

Лучше начать с эмулятора, чем с реального оборудования, поэтому давайте попробуем QEMU для запуска OpenCV на эмулированной плате Integrator / CP (это просто случайная плата ARM с поддержкой видео в QEMU).

Минимальный рабочий пример использования OpenCV выглядит так:

version.cpp:

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

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

    return 0;
}

Эта программа печатает некоторую информацию OpenCV:

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
< other build info follows >

Следующим шагом является запуск базового примера реальной обработки изображения. На официальном сайте есть несколько примеров, я выбрал Canny edge Detector.

OpenCV поддерживает QT, GTK и Window API, которые слишком тяжелы для работы на микроконтроллерах, поэтому мне пришлось переписать некоторые части этого примера для прямого рисования в буфер кадра. После некоторой работы с внутренними форматами изображений OpenCV я получил следующие результаты:

Запустите OpenCV на STM32F7Discovery

32F746GDISCOVERY имеет следующие ресурсы памяти:

  1. 1 мегабайт флэш-памяти
  2. 340 КБ ОЗУ
  3. 128-мегабитная флэш-память Quad-SPI
  4. 128 Мбит SDRAM (доступно 64 Мбит)
  5. Разъем для карты microSD

MicroSD можно использовать для хранения изображений, но обрабатывать большой участок кода не очень удобно.

Разрешение дисплея 480x272, поэтому буфер кадра займет 522240 байт (с 32-битными цветами), то есть не умещается в оперативной памяти. Однако можно использовать SDRAM для буфера кучи и кадра; остальная часть оперативной памяти будет использована для других нужд ОС (стек, ресурсы процесса и т. д.).

Минимальная конфигурация Embox с OpenCV имеет следующие разделы:

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

Краткое отступление по разделам: разделы .text и .rodata содержат инструкции и константы (т.е. неизменяемые данные), .data содержат изменяемые данные, .bss содержат «обнуленные» переменные, которые фактически не помещаются в образ ядра, но эта память будет использоваться при выполнении время.

_12 _ / _ 13_ в порядке, они точно подойдут для RAM / SDRAM, но .text слишком большой (код помещается во флеш-память - 1MiB).

Единственный способ справиться с такой большой частью кода - использовать флэш-память QSPI. Он имеет режим отображения памяти, который обеспечивает доступ только для чтения через системную шину, поэтому там можно разместить код. Однако с этим есть несколько проблем:

  1. QSPI недоступен после перезагрузки, т.е. перед выполнением кода из этой памяти необходимо выполнить некоторую инициализацию программного обеспечения.
  2. Как обычно, прошить с openocd и gdb не получится.

В конце концов я решил написать небольшой загрузчик, который получал данные с главного компьютера через TFTP и записывал данные в QSPI с помощью функций stm32cube.

Результаты

Наконец-то все заработало! Однако на обработку и отрисовку изображения уходит слишком много времени: 40 секунд.

Затем я попытался запустить ту же программу на плате STM32F769I-Discovery с немного другой конфигурацией (она использует другие контакты для UART и тому подобное). Эта плата имеет 2 МБ флеш-памяти, поэтому с -O2 она отлично работает без уловки QSPI и обрабатывает это изображение за 3 секунды.

Я надеюсь, что эта статья поможет вам запустить свои собственные проекты на основе OpenCV. Не стесняйтесь создавать проблемы в репозитории Embox или писать мне, если вам понадобится помощь.

Ресурсы Embox: