OpenCV trên STM32F7-Discovery

OpenCV trên STM32F7-Discovery Tôi là một trong những nhà phát triển hệ điều hành Hộp thư, và trong bài viết này, tôi sẽ nói về cách tôi chạy OpenCV trên bo mạch STM32746G.

Nếu bạn nhập nội dung như “OpenCV trên bảng STM32” vào công cụ tìm kiếm, bạn có thể tìm thấy khá nhiều người quan tâm đến việc sử dụng thư viện này trên bảng STM32 hoặc các bộ vi điều khiển khác.
Có một số video, xét theo tiêu đề, sẽ thể hiện những gì cần thiết, nhưng thông thường (trong tất cả các video tôi đã xem) trên bảng STM32, chỉ nhận được hình ảnh từ máy ảnh và hiển thị kết quả trên màn hình, và Quá trình xử lý hình ảnh được thực hiện trên máy tính thông thường hoặc trên các bo mạch mạnh hơn (ví dụ: Raspberry Pi).

Tại sao điều này lại khó khăn?

Sự phổ biến của các truy vấn tìm kiếm được giải thích là do OpenCV là thư viện thị giác máy tính phổ biến nhất, có nghĩa là nhiều nhà phát triển quen thuộc với nó hơn và khả năng chạy mã sẵn sàng cho máy tính để bàn trên bộ vi điều khiển giúp đơn giản hóa đáng kể quá trình phát triển. Nhưng tại sao vẫn chưa có công thức làm sẵn phổ biến nào để giải quyết vấn đề này?

Vấn đề khi sử dụng OpenCV trên bảng nhỏ có liên quan đến hai tính năng:

  • Nếu bạn biên dịch thư viện ngay cả với một bộ mô-đun tối thiểu, thì đơn giản là nó sẽ không vừa với bộ nhớ flash của cùng một STM32F7Discovery (thậm chí không tính đến hệ điều hành) do mã rất lớn (vài megabyte hướng dẫn)
  • Bản thân thư viện được viết bằng C++, có nghĩa là
    • Chúng tôi cần hỗ trợ cho thời gian chạy tích cực (ngoại lệ, v.v.)
    • Có rất ít hỗ trợ cho LibC/Posix, thường được tìm thấy trong các hệ điều hành dành cho hệ thống nhúng - bạn cần một thư viện cộng tiêu chuẩn và thư viện mẫu STL tiêu chuẩn (vectơ, v.v.)

Chuyển sang Embox

Như thường lệ, trước khi chuyển bất kỳ chương trình nào sang hệ điều hành, bạn nên cố gắng biên dịch nó theo dạng mà các nhà phát triển dự định. Trong trường hợp của chúng tôi, không có vấn đề gì với điều này - bạn có thể tìm thấy mã nguồn tại github, thư viện được biên dịch theo GNU/Linux với cmake thông thường.

Tin vui là OpenCV có thể được xây dựng ngay lập tức dưới dạng thư viện tĩnh, giúp việc chuyển đổi dễ dàng hơn. Chúng tôi tập hợp thư viện với cấu hình tiêu chuẩn và xem nó chiếm bao nhiêu dung lượng. Mỗi mô-đun được tập hợp thành một thư viện riêng biệt.

> 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)

Như bạn có thể thấy ở dòng cuối cùng, .bss và .data không chiếm nhiều dung lượng nhưng mã lớn hơn 70 MiB. Rõ ràng là nếu điều này được liên kết tĩnh với một ứng dụng cụ thể thì sẽ có ít mã hơn.

Hãy thử loại bỏ càng nhiều mô-đun càng tốt để tạo một ví dụ tối thiểu (ví dụ: sẽ chỉ hiển thị phiên bản OpenCV), vì vậy hãy xem cmake .. -LA và vô hiệu hóa mọi thứ bị vô hiệu hóa trong tùy chọn.

        -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)

Một mặt, đây chỉ là một mô-đun thư viện, mặt khác, đây không phải là mô-đun được trình biên dịch tối ưu hóa cho kích thước mã (-Os). ~3 MiB mã vẫn còn khá nhiều, nhưng nó đã mang lại hy vọng thành công.

Chạy trong trình giả lập

Việc gỡ lỗi trên trình mô phỏng dễ dàng hơn nhiều, vì vậy trước tiên chúng tôi sẽ đảm bảo rằng thư viện hoạt động trên qemu. Tôi đã chọn Integrator/CP làm nền tảng mô phỏng vì... thứ nhất, đó cũng là ARM, thứ hai, Embox hỗ trợ đầu ra đồ họa cho nền tảng này.

Embox có một cơ chế để xây dựng các thư viện bên ngoài, với sự trợ giúp của nó, chúng tôi thêm OpenCV dưới dạng một mô-đun (chuyển tất cả các tùy chọn tương tự để tập hợp “tối thiểu” dưới dạng thư viện tĩnh), sau đó tôi thêm một ứng dụng đơn giản trông như thế này:

version.cpp:

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

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

    return 0;
}

Chúng tôi lắp ráp hệ thống, chạy nó và nhận được kết quả như mong đợi.

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 включены в сборку и т.п.>

Bước tiếp theo là chạy một số ví dụ, tốt nhất là một số ví dụ tiêu chuẩn mà chính các nhà phát triển đưa ra trên trang web của bạn. Tôi chọn Máy dò biên giới Canny.

Ví dụ này phải được viết lại một chút để hiển thị hình ảnh kết quả trực tiếp vào bộ đệm khung. Tôi phải làm việc này vì... chức năng imshow() có thể vẽ hình ảnh qua giao diện QT, GTK và Windows, tất nhiên giao diện này chắc chắn sẽ không có trong config cho STM32. Trên thực tế, QT cũng có thể chạy trên STM32F7Discovery, nhưng điều này sẽ được thảo luận trong một bài viết khác :)

Sau khi nhanh chóng tìm ra chính xác định dạng mà kết quả của bộ dò cạnh được lưu trữ, chúng ta sẽ có được một hình ảnh.

OpenCV trên STM32F7-Discovery

Ảnh gốc

OpenCV trên STM32F7-Discovery

Kết quả

Chạy trên STM32F7Discovery

32F746GDISCOVERY có một số phần bộ nhớ phần cứng mà chúng ta có thể sử dụng theo cách này hay cách khác

  1. RAM 320KiB
  2. Bộ nhớ flash 1MiB cho hình ảnh
  3. SDRAM 8MiB
  4. Ổ đĩa flash 16MiB QSPI NAND
  5. Khe cắm thẻ MicroSD

Thẻ SD có thể được sử dụng để lưu trữ hình ảnh, nhưng trong bối cảnh chạy một ví dụ tối thiểu thì điều này không hữu ích lắm.
Màn hình có độ phân giải 480x272, nghĩa là bộ nhớ dành cho bộ đệm khung sẽ là 522 byte với độ sâu 240 bit, tức là. kích thước này lớn hơn kích thước của RAM, do đó, bộ đệm khung và vùng heap (sẽ được yêu cầu, trong số những thứ khác, để OpenCV lưu trữ dữ liệu cho hình ảnh và cấu trúc phụ trợ) sẽ được đặt trong SDRAM, mọi thứ khác (bộ nhớ cho ngăn xếp và hệ thống khác nhu cầu) sẽ chuyển đến RAM .

Nếu chúng tôi lấy cấu hình tối thiểu cho STM32F7Discovery (loại bỏ toàn bộ mạng, tất cả các lệnh, tạo các ngăn xếp nhỏ nhất có thể, v.v.) và thêm OpenCV với các ví dụ ở đó, bộ nhớ cần thiết sẽ như sau:

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

Đối với những người không quen thuộc với phần nào đi đâu, hãy để tôi giải thích: trong .text и .rodata chứa các hướng dẫn và hằng số (nói đại khái là dữ liệu chỉ đọc), trong .data có dữ liệu có thể thay đổi trong .bss tuy nhiên, có các biến "không", tuy nhiên, cần có dung lượng (phần này sẽ "chuyển" sang RAM).

Tin tốt là .data/.bss nên phù hợp, nhưng với .text Vấn đề là chỉ có 1MiB bộ nhớ cho hình ảnh. Có thể ném ra ngoài .text hình ảnh từ ví dụ và đọc nó, chẳng hạn như từ thẻ SD vào bộ nhớ khi khởi động, nhưng Fruits.png nặng khoảng 330KiB, vì vậy điều này sẽ không giải quyết được vấn đề: hầu hết .text bao gồm chính xác mã OpenCV.

Nhìn chung, chỉ còn một việc duy nhất - tải một phần mã vào ổ flash QSPI (nó có chế độ vận hành đặc biệt để ánh xạ bộ nhớ tới bus hệ thống, để bộ xử lý có thể truy cập trực tiếp vào dữ liệu này). Trong trường hợp này, một vấn đề nảy sinh: thứ nhất, bộ nhớ của ổ flash QSPI không khả dụng ngay sau khi khởi động lại thiết bị (chế độ ánh xạ bộ nhớ phải được khởi tạo riêng) và thứ hai, không thể “flash” bộ nhớ này bằng bộ nạp khởi động thông thường.

Do đó, người ta đã quyết định liên kết tất cả mã vào QSPI và flash nó bằng bộ tải khởi động tự viết, bộ tải khởi động này sẽ nhận được tệp nhị phân cần thiết thông qua TFTP.

Kết quả

Ý tưởng chuyển thư viện này sang Embox đã xuất hiện khoảng một năm trước, nhưng nhiều lần nó bị trì hoãn vì nhiều lý do. Một trong số đó là hỗ trợ libstdc++ và thư viện mẫu tiêu chuẩn. Vấn đề hỗ trợ C++ trong Embox nằm ngoài phạm vi của bài viết này, vì vậy ở đây tôi sẽ chỉ nói rằng chúng tôi đã cố gắng đạt được sự hỗ trợ này ở mức cần thiết để thư viện này hoạt động :)

Cuối cùng, những vấn đề này đã được khắc phục (ít nhất là đủ để ví dụ OpenCV hoạt động) và ví dụ đã chạy. Bảng phải mất 40 giây dài để tìm kiếm ranh giới bằng bộ lọc Canny. Tất nhiên, điều này quá dài (có những cân nhắc về cách tối ưu hóa vấn đề này; một bài viết riêng có thể được viết về vấn đề này nếu thành công).

OpenCV trên STM32F7-Discovery

Tuy nhiên, mục tiêu trung gian là tạo ra một nguyên mẫu cho thấy khả năng cơ bản của việc chạy OpenCV trên STM32, vậy là mục tiêu này đã đạt được, hoan hô!

tl;dr: hướng dẫn từng bước

0: Tải nguồn Embox về, ví dụ như thế này:

    git clone https://github.com/embox/embox && cd ./embox

1: Hãy bắt đầu bằng cách lắp ráp bộ tải khởi động sẽ flash ổ flash QSPI.

    make confload-arm/stm32f7cube

Bây giờ bạn cần phải cấu hình mạng, bởi vì... Chúng ta sẽ tải hình ảnh qua TFTP. Để đặt địa chỉ IP của bo mạch và máy chủ, bạn cần chỉnh sửa tệp conf/rootfs/network.

Ví dụ về cấu hình:

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 — địa chỉ máy chủ nơi hình ảnh sẽ được tải, address - địa chỉ bảng.

Sau đó chúng ta lắp ráp bootloader:

    make

2: Tải bộ nạp khởi động bình thường (xin lỗi vì chơi chữ) lên bảng - không có gì cụ thể ở đây, bạn cần thực hiện giống như đối với bất kỳ ứng dụng nào khác cho STM32F7Discovery. Nếu bạn không biết cách thực hiện việc này, bạn có thể đọc về nó đây.
3: Biên dịch hình ảnh với cấu hình cho OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Trích xuất các phần từ ELF cần ghi vào QSPI vào 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

Có một đoạn script trong thư mục conf thực hiện việc này nên bạn có thể chạy nó

    ./conf/qspi_objcopy.sh # Нужный бинарник -- build/base/bin/qspi.bin

5: Sử dụng tftp, tải qspi.bin.bin vào ổ flash QSPI. Trên máy chủ, để thực hiện việc này, bạn cần sao chép qspi.bin vào thư mục gốc của máy chủ tftp (thường là /srv/tftp/ hoặc /var/lib/tftpboot/; các gói dành cho máy chủ tương ứng đều có sẵn ở hầu hết các bản phân phối phổ biến , thường được gọi là tftpd hoặc tftp-hpa, đôi khi bạn phải làm systemctl start tftpd.service để bắt đầu).

    # вариант для tftpd
    sudo cp build/base/bin/qspi.bin /srv/tftp
    # вариант для tftp-hpa
    sudo cp build/base/bin/qspi.bin /var/lib/tftpboot

Trên Embox (tức là trong bộ nạp khởi động), bạn cần chạy lệnh sau (chúng tôi giả sử rằng máy chủ có địa chỉ 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Sử dụng lệnh goto bạn cần phải “nhảy” vào bộ nhớ QSPI. Vị trí cụ thể sẽ khác nhau tùy vào cách liên kết hình ảnh, bạn có thể xem địa chỉ này bằng lệnh mem 0x90000000 (địa chỉ bắt đầu khớp với từ 32 bit thứ hai của hình ảnh); bạn cũng sẽ cần đặt cờ ngăn xếp -s, địa chỉ ngăn xếp là 0x90000000, ví dụ:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              это адрес    это  адрес 
                стэка        первой
                           инструкции

    embox>goto -i 0x9000c27f -s 0x20023200 # Флаг -i нужен чтобы запретить прерывания во время инициализации системы

    < Начиная отсюда будет вывод не загрузчика, а образа с OpenCV >

7: Khởi động

    embox> edges 20

và tận hưởng việc tìm kiếm ranh giới trong 40 giây :)

Nếu có sự cố xảy ra, hãy viết một vấn đề cho kho lưu trữ của chúng tôi, hoặc đến bản tin [email được bảo vệ], hoặc trong một bình luận ở đây.

Nguồn: www.habr.com

Thêm một lời nhận xét