STM32F7-Discovery 上的 OpenCV

STM32F7-Discovery 上的 OpenCV 我是操作系统的开发者之一 信箱,在本文中我将讨论如何在 STM32746G 板上运行 OpenCV。

如果您在搜索引擎中输入“STM32 板上的 OpenCV”之类的内容,您可以找到很多有兴趣在 STM32 板或其他微控制器上使用此库的人。
有几个视频,从名称来看,应该演示需要什么,但通常(在我看到的所有视频中)在STM32板上,仅从相机接收图像并将结果显示在屏幕上,图像处理本身是在普通计算机或更强大的板上(例如,Raspberry Pi)完成的。

为什么很难?

搜索查询的流行是因为 OpenCV 是最流行的计算机视觉库,这意味着更多的开发人员熟悉它,并且在微控制器上运行桌面就绪代码的能力大大简化了开发过程。 但为什么仍然没有流行的现成食谱来解决这个问题呢?

在小披肩上使用OpenCV的问题与两个特征有关:

  • 如果即使使用最少的模块集来编译该库,由于代码非常大(几兆字节的指令),它根本无法放入同一 STM32F7Discovery 的闪存中(即使不考虑操作系统)
  • 该库本身是用 C++ 编写的,这意味着
    • 需要支持正运行时间(异常等)
    • 对 LibC/Posix 的支持很少,通常在嵌入式系统的操作系统中找到 - 你需要一个标准的 plus 库和一个标准的 STL 模板库(向量等)

移植到 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的配置中肯定不会有这些。 事实上,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 中,其他所有内容(堆栈和其他系统需求的内存)将位于 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,因此这不能解决问题:大多数 .text 由 OpenCV 代码组成。

总的来说,只剩下一件事了——将一部分代码加载到 QSPI 闪存上(它有一种特殊的操作模式,用于将内存映射到系统总线,以便处理器可以直接访问这些数据)。 这种情况下,就会出现一个问题:首先,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.conf 文件。

配置示例:

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:从要写入 QSPI 的 ELF 部分提取到 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 秒的边境搜索 🙂

如果出现问题 - 将问题写在 我们的存储库,或到邮件列表 [电子邮件保护],或在此处的评论中。

来源: habr.com

添加评论