Przymierzam się do projektu, w którym chciałbym mieć pewne możliwości obróbki obrazu uzyskanego z kamery. Zanim dojdę do etapu wykonania prototypu, chciałbym wstępnie zweryfikować przyjęte założenia i odpowiedzieć sobie na serię pytań: Jaki sprzęt wybrać? Jakie parametry jestem w stanie osiągnąć? Czy to ma szansę funkcjonować?
Do testów wybrałem następujące zestawy sprzętu:
- STM32F746G-DISCO i kamerę z przetwornikiem OV9655 1,3Mpix
- NUCLEO-F767ZI i kamerę z przetwornikiem OV5640 5Mpix
- NUCLEO-H755ZI-Q$AT3 i kamerę z przetwornikiem OV5642 5Mpix
Pamięć
Jednym z podstawowych pytań jest ilość wymaganej pamięci do przechowania obrazu. Na każdy piksel kolorowego obrazu z kamery potrzebne są 2 bajty pamięci w przypadku formaty RGB565 lub nawet 3 w przypadku YUV. Dobrze było by móc przechwytywać obraz do jednego bufora a w drugim go obrabiać i do tego mieć jeszcze kolejne bufory dla pośrednich wyników obróbki. Dopóki nie spróbuję przejść tej ścieżki, to trudno jest szacować ile pamięci jest potrzebne. Każdy mikrokontroler ma do dyspozycji pewną ilość szybkiej pamięci statycznej ale jest to za mało aby zmieścić obraz w maksymalnej rozdzielczości. Zestaw Discovery ma dodatkowo dużą ilość pamięci dynamicznej, jednak jest ona wolniejsza od statycznej, ale jak bardzo wolniejsza? Istotna jest też moc obliczeniowa kontrolera. W wymienionych wyżej zestawach kontrolery mają następujące kluczowe parametry:
- STM32F746 ma 320kB SRAM i 8MB DRAM a pracuje z zegarem 200MHz (maksymalnie do 216MHz)
- STM32F767 ma 512kB SRAM i pracuje z zegarem 200MHz (maksymalnie do 216MHz)
- STM32H755 ma 1MB SRAM i pracuje z zegarem 400MHz (maksymalnie do 480MHz)
Zegary celowo obniżone są do okrągłych wartości aby łatwiej było je porównywać. Najszybszy kontroler ma specyficzne wymagania co do zasilania gdy pracuje na maksymalnej częstotliwości 480MHz, natomiast na 400MHz pracuje bez problemu w każdych warunkach.
Test wydajnościowy - generowanie fraktala Julii i Mandelbrota
Zestawy Nucleo mają złącze zgodne z arduino, więc zainstalowałem tam kupiony kiedyś wyświetlacz TFT o rozdzielczości 320x240 pikseli. Zestaw Discovery ma wyświetlacz o rozdzielczości 480x272, ale w celu zgodności wszystkie testy generuję w oknie 320x240. W zestawach Nucleo fraktal jest generowany w pamięci SRAM, w Discovery w DRAM. Pomiar dotyczy czasu obliczania fraktala. Czas generowania kolejnych iteracji fraktala Mandelbrota o stałym powiększeniu jest stały, natomiast dla fraktala Julii złożoność obliczeń zależy od wartości przyjętej potęgi. Ponieważ algorytm mam napisany tak żeby generował animację zmieniając potęgę, do pomiaru biorę pierwszą klatkę animacji.
Wyniki pomiaru wyglądają następująco:
| Mikrokontroler / czas obliczeń [ms] | fraktal Julii | fraktal Mandelbrota |
| STM32F746 @200MHz, bufor w DRAM | 1251 | 5510 |
| STM32F767 @200MHz, bufor w SRAM1 | 270 | 299 |
| STM32H755 @400MHz, bufor w SRAM D1+D2 @0x30000000 | 138 | 149 |
| STM32H755 @400MHz, bufor w AXI-SRAM @0x24000000 | 138 | 148 |
Wpływ rodzaju pamięci
W tabeli widać że czas obliczeń kontrolerów pracującymi z pamięcią SRAM taktowanych z częstotliwością 200 i 400MHz różnią się niemalże dwukrotnie. Drobne różnice można wytłumaczyć skończoną rozdzielczością pomiaru. Natomiast różnice pomiędzy pamięcią dynamiczną (DRAM) i statyczną (SRAM) są bardzo duże. Przy tej samej częstotliwości taktowania, czas generowania fraktala Julii w DRAM jest ok. 4,6 raza dłuższy. Na kontrolerze STM32H755 gdzie jest dużo pamięci próbowałem umieszczać bufor w AXI_SRAM (512k) oraz w połączonych obszarach SRAM1 (128k) i SRAM2 (128k). Różnica w czasie liczenia fraktala mieści się w granicach błędu pomiaru.
Wpływ rodzaju koprocesora
Na kontrolerze F746 fraktal Mandelbrota liczy się w DRAM niemalże 18,4 razy dłuższy niż w przypadku generowania w pamięci SRAM. Dla fraktala Julii różnica była tylko 4,6 raza. To sugeruje, że coś jeszcze ma wpływ na szybkość działania. W obu fraktalach na każdy piksel przypada jeden zapis do pamięci, reszta to obliczenia matematyczne na zmiennych w lokalnej pamięci kontrolera. Oba kontrolery mają włączony koprocesor matematyczny, jednak koprocesory trochę się różnią. Ten w F746 ma w nazwie "SP" co znaczy "Single Precision", czyli wspiera obliczenia na liczbach float a Mandelbrot jest liczony na liczbach double, więc obliczenia nie są robione na na sprzęcie, ale w sposób programowy. Koprocesory w F767 i H755 są takie same i wspierają obliczenia na liczbach o podwójnej precyzji.



Wpływ obecności cache
Kontrolery Cortex-7 mają wbudowaną pamięć cache. STM32H755 ma 16kB I-cache do buforowania instrukcji i 16kB D-cache do buforowania danych. Kiedy po raz pierwszy zaobserwowałem jaki to ma wpływ na prędkość działania programu, to słowo "zaskoczony" do określenia stanu emocjonalnego jest stanowczo zbyt delikatne. Zresztą zobaczcie sami jak to wygląda w przypadku najszybszego kontrolera STM32F755 pracującego na 400MHz. Cache ma tym większy sens im większa jest częstotliwość zegara.
| I-cache | D-cache | Czas liczenia Julii [ms] | Czas liczenia Mandelbrota [ms] |
| wyłączony | wyłączony | 483 | 379 |
| wyłączony | włączony | 267 (x1,81) | 173 (x2,19) |
| włączony | wyłączony | 442 (x1,09) | 369 (x1,02) |
| włączony | włączony | 138 (x3,50) | 149 (x2,54) |
Test wykrywania krawędzi
Jedną z funkcjonalności jaką chciałbym uzyskać jest śledzenie wskazanego obiektu. Żeby móc coś śledzić, trzeba wyznaczyć w obrazie charakterystyczny układ punktów i próbować go odnaleźć w kolejnych klatkach obrazu. Zwykle śledzimy coś co ma swój początek i koniec, a wiec ma krawędzie. Porównajmy próby kolejnych etapów obróbki kolorowego obrazu o rozdzielczości 320x240 pikseli.
Pierwszym etapem jest konwersja na obraz monochromatyczny, później wykonanie splotu z macierzą detekcji krawędzi, później progowanie aby przejść na 1-bitowy obraz czerń i biel, później dylatacja i konwersja aby uzyskać domknięcie krawędzi i usunąć jedno pikselowy szum. Finalnie progowanie wbudowałem w algorytm splotu, aby zoptymalizować kolejny dostęp do pamięci. Do funkcji splotu podaję parametr wartości progu i zeruję wyniki poniżej progu. Podając tam zero mogę wyłączyć progowanie. Splot z macierzą detekcji krawędzi zaimplementowałem dla najprostszej i najszybszej macierzy Robertsa o rozmiarach 2x(2x2) oraz dla macierzy Sobela 2x(3x3).
| Mikrokontroler / czas obliczeń [ms] | konwersja na mono | splot 2x2 Robertsa | splot 3x3 Sobela | odszumianie 3x3 | dylatacja | domykanie (dylatacja+erozja) |
| STM32F746 @200MHz, bufor w DRAM | 71 | 395 | 630 | 651 | ||
| STM32F767 @200MHz, bufor w SRAM | 15 | 37 | ||||
| STM32H755 @400MHz, bufor w SRAM | 6 | 16 | 71 | 29 | 25 | 43 |
Repozytoria programów testowych
Gdybyście chcieli porównać obecnie używany kontroler z powyższym przykładem, możecie wkompilować użyte tutaj funkcje w swój program. W szczególności fraktal zawsze cieszy oko. Repozytoria do wszystkich 3 projektów znajdują się na moim Githubie i można z nich korzystać na zasadach open source:
https://github.com/pitlab/Kamera-OV9655-na-STM32F746G-DISCO


