我是操作系统的开发者之一
如果您在搜索引擎中输入“STM32 板上的 OpenCV”之类的内容,您可以找到很多有兴趣在 STM32 板或其他微控制器上使用此库的人。
有几个视频,从名称来看,应该演示需要什么,但通常(在我看到的所有视频中)在STM32板上,仅从相机接收图像并将结果显示在屏幕上,图像处理本身是在普通计算机或更强大的板上(例如,Raspberry Pi)完成的。
为什么很难?
搜索查询的流行是因为 OpenCV 是最流行的计算机视觉库,这意味着更多的开发人员熟悉它,并且在微控制器上运行桌面就绪代码的能力大大简化了开发过程。 但为什么仍然没有流行的现成食谱来解决这个问题呢?
在小披肩上使用OpenCV的问题与两个特征有关:
- 如果即使使用最少的模块集来编译该库,由于代码非常大(几兆字节的指令),它根本无法放入同一 STM32F7Discovery 的闪存中(即使不考虑操作系统)
- 该库本身是用 C++ 编写的,这意味着
- 需要支持正运行时间(异常等)
- 对 LibC/Posix 的支持很少,通常在嵌入式系统的操作系统中找到 - 你需要一个标准的 plus 库和一个标准的 STL 模板库(向量等)
移植到 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多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 上运行,但这将在另一篇文章中讨论 🙂
在简短说明边缘检测器的结果以何种格式存储后,我们得到了一张图像。
原图
导致
在 STM32F7Discovery 上运行
在 32F746GDISCOVERY 上,有几个我们可以以某种方式使用的硬件内存部分
- 320KiB内存
- 1MiB 图像闪存
- 8MiB内存
- 16MiB QSPI NAND 闪存
- 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 秒的时间。 当然这个太长了(有考虑如何优化这个问题,如果成功的话可以单独写一篇文章)。
然而,中间目标是创建一个原型,以展示在 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