Soy uno de los desarrolladores del sistema operativo.
Si escribe algo como "OpenCV en placa STM32" en un motor de búsqueda, puede encontrar bastantes personas interesadas en usar esta biblioteca en placas STM32 u otros microcontroladores.
Hay varios videos que, a juzgar por el nombre, deberían demostrar lo que se necesita, pero generalmente (en todos los videos que vi) en la placa STM32, solo se recibía la imagen de la cámara y se mostraba el resultado en la pantalla, y el procesamiento de la imagen en sí se realizó en una computadora normal o en placas más potentes (por ejemplo, Raspberry Pi).
¿Por qué es difícil?
La popularidad de las consultas de búsqueda se explica por el hecho de que OpenCV es la biblioteca de visión por computadora más popular, lo que significa que más desarrolladores están familiarizados con ella, y la capacidad de ejecutar código listo para escritorio en un microcontrolador simplifica enormemente el proceso de desarrollo. Pero, ¿por qué todavía no hay recetas populares preparadas para resolver este problema?
El problema de usar OpenCV en chales pequeños está relacionado con dos características:
- Si compila la biblioteca incluso con un conjunto mínimo de módulos, simplemente no cabrá en la memoria flash del mismo STM32F7Discovery (incluso sin tener en cuenta el sistema operativo) debido a un código muy grande (varios megabytes de instrucciones)
- La biblioteca en sí está escrita en C++, lo que significa
- Necesita soporte para tiempo de ejecución positivo (excepciones, etc.)
- Poco soporte para LibC/Posix, que generalmente se encuentra en el sistema operativo para sistemas integrados: necesita una biblioteca estándar plus y una biblioteca de plantillas STL estándar (vector, etc.)
Portar a Embox
Como de costumbre, antes de portar cualquier programa al sistema operativo, es una buena idea intentar compilarlo en la forma en que los desarrolladores lo concibieron. En nuestro caso, no hay problemas con esto: el código fuente se puede encontrar en
La buena noticia es que OpenCV se puede compilar como una biblioteca estática lista para usar, lo que facilita la portabilidad. Recopilamos una biblioteca con una configuración estándar y vemos cuánto espacio ocupan. Cada módulo se recopila en una biblioteca separada.
> 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)
Como puede ver en la última línea, .bss y .data no ocupan mucho espacio, pero el código tiene más de 70 MiB. Está claro que si esto está vinculado estáticamente a una aplicación específica, el código será menor.
Intentemos tirar tantos módulos como sea posible para que se ensamble un ejemplo mínimo (que, por ejemplo, simplemente generará la versión de OpenCV), así que buscamos cmake .. -LA
y apaga en las opciones todo lo que apaga.
-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)
Por un lado, este es solo un módulo de la biblioteca, por otro lado, esto no tiene optimización del compilador para el tamaño del código (-Os
). ~3 MiB de código sigue siendo bastante, pero ya da esperanzas de éxito.
Ejecutar en el emulador
Es mucho más fácil depurar en el emulador, así que primero asegúrese de que la biblioteca funcione en qemu. Como plataforma emulada, elegí Integrator/CP, porque en primer lugar, también es ARM y, en segundo lugar, Embox admite la salida de gráficos para esta plataforma.
Embox tiene un mecanismo para construir bibliotecas externas, al usarlo agregamos OpenCV como un módulo (pasando todas las mismas opciones para la compilación "mínima" en forma de bibliotecas estáticas), luego de eso agrego una aplicación simple que se ve así:
version.cpp:
#include <stdio.h>
#include <opencv2/core/utility.hpp>
int main() {
printf("OpenCV: %s", cv::getBuildInformation().c_str());
return 0;
}
Ensamblamos el sistema, lo ejecutamos, obtenemos el resultado esperado.
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 включены в сборку и т.п.>
El siguiente paso es ejecutar algún ejemplo, preferiblemente uno de los estándar que ofrecen los propios desarrolladores.
El ejemplo tuvo que reescribirse ligeramente para mostrar la imagen con el resultado directamente en el búfer de fotogramas. Tuve que hacer esto, porque. función imshow()
puede dibujar imágenes a través de las interfaces QT, GTK y Windows, que, por supuesto, definitivamente no estarán en la configuración de STM32. De hecho, QT también se puede ejecutar en STM32F7Discovery, pero esto se discutirá en otro artículo 🙂
Después de una breve aclaración en qué formato se almacena el resultado del detector de bordes, obtenemos una imagen.
foto original
resultado
Ejecutándose en STM32F7Discovery
En 32F746GDISCOVERY hay varias secciones de memoria de hardware que podemos usar de una forma u otra
- RAM de 320 KiB
- Flash de 1MiB para imagen
- SDRAM de 8 MB
- Flash NAND QSPI de 16MiB
- ranura para tarjeta microSD
Se puede usar una tarjeta SD para almacenar imágenes, pero en el contexto de ejecutar un ejemplo mínimo, esto no es muy útil.
La pantalla tiene una resolución de 480×272, lo que significa que la memoria framebuffer será de 522 bytes a una profundidad de 240 bits, es decir esto es más que el tamaño de la RAM, por lo que el framebuffer y el montón (que serán necesarios, incluso para OpenCV, para almacenar datos para imágenes y estructuras auxiliares) se ubicarán en SDRAM, todo lo demás (memoria para pilas y otras necesidades del sistema ) irá a la RAM .
Si tomamos la configuración mínima para STM32F7Discovery (eliminar toda la red, todos los comandos, hacer las pilas lo más pequeñas posible, etc.) y agregar OpenCV con ejemplos allí, la memoria requerida será la siguiente:
text data bss dec hex filename
2876890 459208 312736 3648834 37ad42 build/base/bin/embox
Para aquellos que no están muy familiarizados con qué secciones van a dónde, les explico: en .text
и .rodata
instrucciones y constantes (en términos generales, datos de solo lectura) se encuentran en .data
los datos son mutables, .bss
hay variables "anuladas" que, sin embargo, necesitan un lugar (esta sección "irá" a la RAM).
La buena noticia es que .data
/.bss
debe encajar, pero con .text
el problema es que solo hay 1MiB de memoria para la imagen. se puede tirar .text
la imagen del ejemplo y la lea, por ejemplo, desde la tarjeta SD a la memoria al inicio, pero fruit.png pesa alrededor de 330 KiB, por lo que esto no resolverá el problema: la mayoría .text
consiste en el código OpenCV.
En general, solo queda una cosa: cargar una parte del código en un flash QSPI (tiene un modo de operación especial para asignar memoria al bus del sistema, de modo que el procesador pueda acceder a estos datos directamente). En este caso, surge un problema: en primer lugar, la memoria de una unidad flash QSPI no está disponible inmediatamente después de reiniciar el dispositivo (debe inicializar por separado el modo de asignación de memoria) y, en segundo lugar, no puede "flashear" esta memoria con un gestor de arranque familiar.
Como resultado, se decidió vincular todo el código en QSPI y actualizarlo con un cargador autoescrito que recibirá el binario requerido a través de TFTP.
resultado
La idea de portar esta biblioteca a Embox apareció hace aproximadamente un año, pero una y otra vez fue pospuesta por varias razones. Uno de ellos es el soporte para libstdc++ y la biblioteca de plantillas estándar. El problema del soporte de C++ en Embox está más allá del alcance de este artículo, así que aquí solo diré que logramos lograr este soporte en la cantidad adecuada para que esta biblioteca funcione 🙂
Al final, estos problemas se superaron (al menos lo suficiente para que funcionara el ejemplo de OpenCV) y se ejecutó el ejemplo. El tablero tarda 40 largos segundos en buscar límites usando el filtro Canny. Esto, por supuesto, es demasiado largo (hay consideraciones sobre cómo optimizar este asunto, será posible escribir un artículo separado sobre esto en caso de éxito).
Sin embargo, el objetivo intermedio era crear un prototipo que mostrara la posibilidad fundamental de ejecutar OpenCV en STM32, respectivamente, este objetivo se logró, ¡hurra!
tl;dr: instrucciones paso a paso
0: descarga las fuentes de Embox, así:
git clone https://github.com/embox/embox && cd ./embox
1: Comencemos ensamblando un gestor de arranque que "flasheará" una unidad flash QSPI.
make confload-arm/stm32f7cube
Ahora necesita configurar la red, porque. Subiremos la imagen a través de TFTP. Para configurar las direcciones IP de la placa y del host, debe editar el archivo conf/rootfs/network.
Ejemplo de configuración:
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
- dirección de host desde donde se cargará la imagen, address
- dirección de la junta.
Después de eso, recopilamos el gestor de arranque:
make
2: La carga habitual del cargador de arranque (perdón por el juego de palabras) en la placa: no hay nada específico aquí, debe hacerlo como cualquier otra aplicación para STM32F7Discovery. Si no sabes cómo hacerlo, puedes leer al respecto.
3: Compilar una imagen con una configuración para OpenCV.
make confload-platform/opencv/stm32f7discovery
make
4: Extracto de las secciones ELF para escribir en QSPI en 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
Hay un script en el directorio conf que hace esto, por lo que puede ejecutarlo
./conf/qspi_objcopy.sh # Нужный бинарник -- build/base/bin/qspi.bin
5: Con tftp, descargue qspi.bin.bin en una unidad flash QSPI. En el host, para hacer esto, copie qspi.bin a la carpeta raíz del servidor tftp (generalmente /srv/tftp/ o /var/lib/tftpboot/; los paquetes para el servidor correspondiente están disponibles en las distribuciones más populares, generalmente llamados tftpd o tftp-hpa, a veces tienes que hacer systemctl start tftpd.service
para comenzar).
# вариант для tftpd
sudo cp build/base/bin/qspi.bin /srv/tftp
# вариант для tftp-hpa
sudo cp build/base/bin/qspi.bin /var/lib/tftpboot
En Embox (es decir, en el gestor de arranque), debe ejecutar el siguiente comando (asumimos que el servidor tiene la dirección 192.168.2.1):
embox> qspi_loader qspi.bin 192.168.2.1
6: Con comando goto
necesita "saltar" a la memoria QSPI. La ubicación específica variará dependiendo de cómo se vincule la imagen, puede ver esta dirección con el comando mem 0x90000000
(la dirección de inicio cabe en la segunda palabra de 32 bits de la imagen); también deberá marcar la pila -s
, la dirección de la pila está en 0x90000000, ejemplo:
embox>mem 0x90000000
0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275
↑ ↑
это адрес это адрес
стэка первой
инструкции
embox>goto -i 0x9000c27f -s 0x20023200 # Флаг -i нужен чтобы запретить прерывания во время инициализации системы
< Начиная отсюда будет вывод не загрузчика, а образа с OpenCV >
7: lanzamiento
embox> edges 20
y disfruta de la búsqueda de fronteras de 40 segundos 🙂
Si algo sale mal, escriba un problema en
Fuente: habr.com