OpenCV บน STM32F7-Discovery

OpenCV บน STM32F7-Discovery ฉันเป็นหนึ่งในผู้พัฒนาระบบปฏิบัติการ เอ็มบ็อกซ์และในบทความนี้ฉันจะพูดถึงวิธีที่ฉันจัดการเพื่อเรียกใช้ OpenCV บนบอร์ด STM32746G

หากคุณพิมพ์บางอย่างเช่น “OpenCV บนบอร์ด STM32” ลงในเครื่องมือค้นหา คุณจะพบผู้คนไม่กี่คนที่สนใจใช้ไลบรารีนี้บนบอร์ด STM32 หรือไมโครคอนโทรลเลอร์อื่นๆ
มีวิดีโอหลายรายการที่พิจารณาจากชื่อแล้วควรแสดงให้เห็นถึงสิ่งที่จำเป็น แต่โดยปกติ (ในวิดีโอทั้งหมดที่ฉันเห็น) บนบอร์ด STM32 จะได้รับเฉพาะภาพจากกล้องและผลลัพธ์แสดงบนหน้าจอ และการประมวลผลภาพนั้นทำได้ทั้งบนคอมพิวเตอร์ทั่วไปหรือบนบอร์ดที่ทรงพลังกว่า (เช่น Raspberry Pi)

ทำไมมันยาก?

ความนิยมของข้อความค้นหาอธิบายได้จากข้อเท็จจริงที่ว่า OpenCV เป็นไลบรารีการมองเห็นของคอมพิวเตอร์ที่ได้รับความนิยมสูงสุด ซึ่งหมายความว่านักพัฒนาจำนวนมากคุ้นเคยกับมัน และความสามารถในการรันโค้ดที่พร้อมใช้งานบนเดสก์ท็อปบนไมโครคอนโทรลเลอร์ทำให้กระบวนการพัฒนาง่ายขึ้นอย่างมาก แต่ทำไมจึงยังไม่มีสูตรสำเร็จรูปยอดนิยมสำหรับแก้ปัญหานี้?

ปัญหาของการใช้ OpenCV บนผ้าคลุมไหล่ขนาดเล็กนั้นเกี่ยวข้องกับคุณสมบัติสองประการ:

  • หากคุณคอมไพล์ไลบรารีแม้จะมีชุดโมดูลเพียงเล็กน้อย ก็จะไม่พอดีกับหน่วยความจำแฟลชของ STM32F7Discovery เดียวกัน (แม้ว่าจะไม่คำนึงถึงระบบปฏิบัติการก็ตาม) เนื่องจากมีโค้ดขนาดใหญ่มาก (คำสั่งหลายเมกะไบต์)
  • ไลบรารี่เขียนด้วยภาษา C++ ซึ่งหมายความว่า
    • ต้องการการสนับสนุนสำหรับรันไทม์เชิงบวก (ข้อยกเว้น ฯลฯ)
    • การสนับสนุนเพียงเล็กน้อยสำหรับ LibC/Posix ซึ่งมักจะพบในระบบปฏิบัติการสำหรับระบบฝังตัว คุณต้องมีไลบรารีมาตรฐานและไลบรารีแม่แบบ STL มาตรฐาน (เวกเตอร์ ฯลฯ)

ย้ายไปยัง Embox

ตามปกติ ก่อนที่จะย้ายโปรแกรมใดๆ ไปยังระบบปฏิบัติการ คุณควรพยายามสร้างโปรแกรมในรูปแบบที่นักพัฒนาตั้งใจไว้ ในกรณีของเราไม่มีปัญหากับสิ่งนี้ - สามารถพบซอร์สโค้ดได้ GitHubห้องสมุดถูกสร้างขึ้นภายใต้ 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 ได้ด้วย แต่จะกล่าวถึงในบทความอื่น 🙂

หลังจากการชี้แจงสั้น ๆ ว่ารูปแบบใดที่จัดเก็บผลลัพธ์ของเครื่องตรวจจับขอบเราจะได้รับภาพ

OpenCV บน STM32F7-Discovery

ภาพต้นฉบับ

OpenCV บน STM32F7-Discovery

ผล

ทำงานบน STM32F7Discovery

ใน 32F746GDISCOVERY มีส่วนหน่วยความจำฮาร์ดแวร์หลายส่วนที่เราสามารถใช้ได้ไม่ทางใดก็ทางหนึ่ง

  1. แรม 320KiB
  2. แฟลช 1MiB สำหรับภาพ
  3. SDRAM 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 มีตัวแปร "nulled" ซึ่งต้องการสถานที่ (ส่วนนี้จะ "ไป" ที่ RAM)

ข่าวดีก็คือว่า .data/.bss น่าจะพอดีแต่กับ .text ปัญหาคือมีหน่วยความจำเพียง 1MiB สำหรับรูปภาพ สามารถโยนออกไปได้ .text รูปภาพจากตัวอย่างและอ่านตัวอย่างเช่น จากการ์ด SD ไปยังหน่วยความจำเมื่อเริ่มต้น แต่ Fruit.png มีน้ำหนักประมาณ 330KiB ดังนั้นสิ่งนี้จะไม่แก้ปัญหา: ส่วนใหญ่ .text ประกอบด้วยรหัส OpenCV

โดยทั่วไปแล้ว มีเพียงสิ่งเดียวที่เหลืออยู่ - การโหลดส่วนหนึ่งของโค้ดลงในแฟลช QSPI (มีโหมดการทำงานพิเศษสำหรับการแมปหน่วยความจำกับบัสระบบ เพื่อให้โปรเซสเซอร์สามารถเข้าถึงข้อมูลนี้ได้โดยตรง) ในกรณีนี้ เกิดปัญหา: ประการแรก หน่วยความจำของแฟลชไดรฟ์ QSPI ไม่สามารถใช้งานได้ทันทีหลังจากรีบูตอุปกรณ์ (คุณต้องเริ่มต้นโหมดการแมปหน่วยความจำแยกต่างหาก) และประการที่สอง คุณไม่สามารถ "แฟลช" หน่วยความจำนี้ด้วย bootloader ที่คุ้นเคย

เป็นผลให้มีการตัดสินใจที่จะเชื่อมโยงรหัสทั้งหมดใน QSPI และแฟลชด้วยตัวโหลดที่เขียนขึ้นเองซึ่งจะได้รับไบนารีที่จำเป็นผ่าน TFTP

ผล

แนวคิดในการย้ายไลบรารีนี้ไปยัง Embox ปรากฏขึ้นเมื่อประมาณหนึ่งปีที่แล้ว แต่ถูกเลื่อนออกไปครั้งแล้วครั้งเล่าเนื่องจากเหตุผลหลายประการ หนึ่งในนั้นคือการสนับสนุน libstdc++ และไลบรารีเทมเพลตมาตรฐาน ปัญหาของการสนับสนุน C++ ใน Embox นั้นอยู่นอกเหนือขอบเขตของบทความนี้ ดังนั้นฉันจะบอกเพียงว่าเราจัดการเพื่อให้ได้รับการสนับสนุนนี้ในปริมาณที่เหมาะสมเพื่อให้ไลบรารีนี้ทำงานได้ 🙂

ในที่สุด ปัญหาเหล่านี้ก็หมดไป (อย่างน้อยก็เพียงพอสำหรับตัวอย่าง OpenCV ที่จะใช้งานได้) และตัวอย่างก็ดำเนินต่อไป บอร์ดใช้เวลานาน 40 วินาทีในการค้นหาขอบเขตโดยใช้ตัวกรอง Canny แน่นอนว่านี่ยาวเกินไป (มีข้อควรพิจารณาเกี่ยวกับวิธีเพิ่มประสิทธิภาพในเรื่องนี้ เป็นไปได้ที่จะเขียนบทความแยกต่างหากเกี่ยวกับเรื่องนี้ในกรณีที่ประสบความสำเร็จ)

OpenCV บน STM32F7-Discovery

อย่างไรก็ตาม เป้าหมายขั้นกลางคือการสร้างต้นแบบที่จะแสดงความเป็นไปได้พื้นฐานของการเรียกใช้ OpenCV บน STM32 ตามลำดับ เป้าหมายนี้สำเร็จแล้ว ไชโย!

tl; dr: คำแนะนำทีละขั้นตอน

0: ดาวน์โหลดแหล่งที่มาของ Embox เช่นนี้:

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

1: เริ่มต้นด้วยการประกอบ bootloader ที่จะ "แฟลช" แฟลชไดรฟ์ 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 - ที่อยู่ของคณะกรรมการ

หลังจากนั้นเรารวบรวม bootloader:

    make

2: การโหลด bootloader ตามปกติ (ขออภัยสำหรับการเล่นสำนวน) บนกระดาน - ไม่มีอะไรเฉพาะเจาะจงที่นี่ คุณต้องทำเหมือนแอปพลิเคชันอื่นสำหรับ 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 (เช่นใน bootloader) คุณต้องดำเนินการคำสั่งต่อไปนี้ (เราคิดว่าเซิร์ฟเวอร์มีที่อยู่ 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 วินาที 🙂

หากมีข้อผิดพลาด - เขียนปัญหาใน พื้นที่เก็บข้อมูลของเราหรือไปยังรายชื่อผู้รับจดหมาย [ป้องกันอีเมล]หรือในความคิดเห็นที่นี่

ที่มา: will.com

เพิ่มความคิดเห็น