STM32F7-Discovery 上的 OpenCV

STM32F7-Discovery 上的 OpenCV 我是操作系統的開發者之一 恩博克斯,在本文中,我將討論如何在 STM32746G 板上運行 OpenCV。

如果你在搜索引擎中輸入“OpenCV on STM32 board”之類的東西,你會發現很多人有興趣在 STM32 板或其他微控制器上使用這個庫。
有幾個視頻,從名字來看,應該演示需要什麼,但通常(在我看到的所有視頻中)在 STM32 板上,只有從相機接收到圖像並將結果顯示在屏幕上,圖像處理本身要么在普通計算機上完成,要么在功能更強大的電路板(例如 Raspberry Pi)上完成。

為什麼很難?

搜索查詢的流行是因為 OpenCV 是最流行的計算機視覺庫,這意味著更多的開發人員熟悉它,並且能夠在微控制器上運行桌面就緒代碼大大簡化了開發過程。 但是為什麼仍然沒有解決這個問題的流行的現成食譜呢?

在小披肩上使用OpenCV的問題與兩個特徵有關:

  • 如果您使用最少的模塊集編譯庫,由於代碼非常大(幾兆字節的指令),它根本不適合同一個 STM32F7Discovery 的閃存(即使不考慮操作系統)
  • 該庫本身是用 C++ 編寫的,這意味著
    • 需要支持正運行時(異常等)
    • 對 LibC/Posix 的支持很少,這通常在嵌入式系統的操作系統中找到——你需要一個標準的 plus 庫和一個標準的 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多MiB。 很明顯,如果將此靜態鏈接到特定應用程序,代碼將變得更少。

讓我們嘗試拋出盡可能多的模塊,以便組裝一個最小的示例(例如,將簡單地輸出 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 MiB 的代碼還是不少,但已經有了成功的希望。

在模擬器中運行

在模擬器上調試要容易得多,所以首先要確保庫在 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的config里肯定沒有。 事實上,QT 也可以在 STM32F7Discovery 上運行,但這將在另一篇文章中討論🙂

在簡短說明邊緣檢測器結果的存儲格式後,我們得到了一張圖像。

STM32F7-Discovery 上的 OpenCV

原圖

STM32F7-Discovery 上的 OpenCV

導致

在 STM32F7Discovery 上運行

在 32F746GDISCOVERY 上有幾個硬件內存部分,我們可以以一種或另一種方式使用

  1. 320KiB 內存
  2. 1MiB 閃存用於圖像
  3. 8MiB 內存
  4. 16MiB QSPI NAND閃存
  5. microSD 卡槽

SD 卡可用於存儲圖像,但在運行最小示例的上下文中,這不是很有用。
顯示器的分辨率為 480×272,這意味著幀緩衝存儲器在 522 位深度下為 240 字節,即這超過了 RAM 的大小,因此幀緩衝區和堆(需要,包括 OpenCV,用於存儲圖像和輔助結構的數據)將位於 SDRAM 中,其他所有內容(堆棧和其他系統需要的內存) ) 將轉到 RAM 。

如果我們採用 STM32F7Discovery 的最小配置(丟棄整個網絡、所有命令、使堆棧盡可能小等)並在其中添加帶有示例的 OpenCV,則所需內存如下:

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

對於那些不太了解哪些部分去哪裡的人,我將解釋一下:在 .text и .rodata 指令和常量(粗略地說,只讀數據)位於 .data 數據是可變的, .bss 有一些“空”變量,但它們需要一個位置(本節將“轉到”RAM)。

好消息是 .data/.bss 應該適合,但與 .text 問題是圖像只有 1MiB 的內存。 可以扔出去 .text 示例中的圖片並讀取它,例如,在啟動時從 SD 卡讀取到內存,但是 fruits.png 重約 330KiB,所以這不能解決問題: most .text 由 OpenCV 代碼組成。

總的來說,只剩下一件事了——將一部分代碼加載到 QSPI 閃存上(它有一種特殊的操作模式,可以將內存映射到系統總線,這樣處理器就可以直接訪問這些數據)。 這樣一來,問題就來了:一是QSPI閃存盤的內存在設備重啟後不能立即使用(需要單獨初始化內存映射模式),二是無法“刷入”這塊內存熟悉的引導加載程序。

因此,決定鏈接 QSPI 中的所有代碼,並使用自寫的加載程序對其進行閃存,該加載程序將通過 TFTP 接收所需的二進製文件。

導致

將這個庫移植到 Embox 的想法大約在一年前就出現了,但由於各種原因一再推遲。 其中之一是支持 libstdc++ 和標準模板庫。 Embox 中的 C++ 支持問題超出了本文的範圍,所以在這裡我只會說我們設法實現了適當數量的這種支持,以使該庫正常工作🙂

最終,這些問題都被攻克了(至少足夠讓OpenCV的例子可以運行了),例子跑起來了。 電路板使用 Canny 過濾器搜索邊界需要 40 秒。 這當然是太長了(有關於如何優化這件事的考慮,如果成功的話可以單獨寫一篇關於這個的文章)。

STM32F7-Discovery 上的 OpenCV

然而,中間目標是創建一個原型,分別展示在 STM32 上運行 OpenCV 的基本可能性,這個目標已經實現,萬歲!

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

添加評論