ฉันเป็นหนึ่งในผู้พัฒนาระบบปฏิบัติการ
หากคุณพิมพ์บางอย่างเช่น “OpenCV บนบอร์ด STM32” ลงในเครื่องมือค้นหา คุณจะพบผู้คนไม่กี่คนที่สนใจใช้ไลบรารีนี้บนบอร์ด STM32 หรือไมโครคอนโทรลเลอร์อื่นๆ
มีวิดีโอหลายรายการที่พิจารณาจากชื่อแล้วควรแสดงให้เห็นถึงสิ่งที่จำเป็น แต่โดยปกติ (ในวิดีโอทั้งหมดที่ฉันเห็น) บนบอร์ด STM32 จะได้รับเฉพาะภาพจากกล้องและผลลัพธ์แสดงบนหน้าจอ และการประมวลผลภาพนั้นทำได้ทั้งบนคอมพิวเตอร์ทั่วไปหรือบนบอร์ดที่ทรงพลังกว่า (เช่น Raspberry Pi)
ทำไมมันยาก?
ความนิยมของข้อความค้นหาอธิบายได้จากข้อเท็จจริงที่ว่า OpenCV เป็นไลบรารีการมองเห็นของคอมพิวเตอร์ที่ได้รับความนิยมสูงสุด ซึ่งหมายความว่านักพัฒนาจำนวนมากคุ้นเคยกับมัน และความสามารถในการรันโค้ดที่พร้อมใช้งานบนเดสก์ท็อปบนไมโครคอนโทรลเลอร์ทำให้กระบวนการพัฒนาง่ายขึ้นอย่างมาก แต่ทำไมจึงยังไม่มีสูตรสำเร็จรูปยอดนิยมสำหรับแก้ปัญหานี้?
ปัญหาของการใช้ OpenCV บนผ้าคลุมไหล่ขนาดเล็กนั้นเกี่ยวข้องกับคุณสมบัติสองประการ:
- หากคุณคอมไพล์ไลบรารีแม้จะมีชุดโมดูลเพียงเล็กน้อย ก็จะไม่พอดีกับหน่วยความจำแฟลชของ STM32F7Discovery เดียวกัน (แม้ว่าจะไม่คำนึงถึงระบบปฏิบัติการก็ตาม) เนื่องจากมีโค้ดขนาดใหญ่มาก (คำสั่งหลายเมกะไบต์)
- ไลบรารี่เขียนด้วยภาษา C++ ซึ่งหมายความว่า
- ต้องการการสนับสนุนสำหรับรันไทม์เชิงบวก (ข้อยกเว้น ฯลฯ)
- การสนับสนุนเพียงเล็กน้อยสำหรับ LibC/Posix ซึ่งมักจะพบในระบบปฏิบัติการสำหรับระบบฝังตัว คุณต้องมีไลบรารีมาตรฐานและไลบรารีแม่แบบ 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 สำหรับภาพ
- SDRAM 8MiB
- 16MiB QSPI NAND แฟลช
- ช่องเสียบการ์ด 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 บน 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