Przedstawię tutaj uruchomienie ESP32-Cam w PlatformIO, przeanalizuję działanie streamingu video (?) z tej płytki oraz pokażę jak można dostać się do pikseli obrazu pobranego z kamery (np. w celu narysowania prostokątów).
ESP32-Cam to mała i poręczna płytka oferująca moduł ESP-32S (4MB Flash, 520KB RAM) wraz z dodatkowym 4MB zewnętrznego SRAM, oraz tytułową kamerę (OV2640) o rozdzielczości do 1600×1200px. Obok kamery jest też dioda LED którą można użyć w roli podświetlenia, a po drugiej stronie płytki mamy slot na kartę microSD, by np. zapisywać na niej zrobione zdjęcia.
ESP32-Cam jest normalnie programowany przez UART, tak jak inne moduły ESP, ale warto do niego dokupić adapter ESP32-Cam-MB, który w zasadzie jest konwerterem USB na UART opartym CH340G.
Cały taki zestaw można dostać za kilkadziesiąt złotych, w zależności od tego czy kupujemy w naszym kraju, czy sprowadzamy z Chin.
Tutaj właśnie taki zestaw przedstawię:
Schemat:
Program demonstracyjny
Płytka ma fabrycznie wgrany program demonstracyjny. Jego kopię umieściłem na moim repozytorium:
https://github.com/openshwprojects/FlashDumps/commit/8d0b74c26a26db29a91fd226e7d85909b4ca6eb4
Program tworzy otwarty access point do którego każdy może dołączyć:
Pod adresem IP 192.168.4.1 dostępna jest strona kontrolna wraz z podglądem kamery na żywo:
Coś mi nie pasowało na początku, a potem zorientowałem się, że testowałem bez zdjęcia folii ochronnej z obiektywu! To z tego powodu tak wszystko było rozmyte. To teraz jeszcze raz, ze zdjętą folią:
Wykrywanie twarzy w obu przypadkach nie działało ani na mojej osobie, ani na zdjęciach z Internetu.
Oto pobór prądu w trakcie streamowania video, 250mA mierzone przed LDO, przy 5V:
Kompilujemy demko w PlatformIO
Teraz pora coś samodzielnie skompilować i wgrać. W Internecie jest dużo tutoriali do tej płytki Arduino IDE, więc ja wziąłem PlatformIO. PlatformIO to darmowy dodatek do Visual Studio Code. Spróbowałem uruchomić w nim demonstracyjny projekt. Można go znaleźć w różnych formach w Internecie, np tutaj:
https://github.com/grimmpp/esp32-cam-example
Na początku było trochę kłopotów z brakującym fd_forward.h, ale przerobiłem kod tak, by usunąć tę dependencję.
src/app_httpd.cpp:22:10: fatal error: fd_forward.h: No such file or directory
Pełny projekt można pobrać tutaj:
https://github.com/openshwprojects/esp32cam-simplified/tree/main/esp32cam-simplified
Wystarczy otworzyć w PlatformIO i skompilować, w rezultacie dostaniemy ten sam program co wgrany fabrycznie, jedynie bez rozpoznawania twarzy.
Nie ma sensu dawać tu zrzutów ekranu, jak wszystko działa analogicznie...
Warto pamiętać, że w main.cpp mamy tutaj wybór typu płytki:
Kod: C / C++
Funkcja main również odpowiednio inicjuje kamerę w zależności od tego, czy dostępny jest SRAM:
Kod: C / C++
Minimalny przykład w PlatformIO
Może to być małym zaskoczeniem, ale do odpalenia prostego streamu wystarczy nam praktycznie jedno żądanie HTTP, trzeba tylko odpowiednio ustawić Content-Type, czyli rodzaj zawartości zwracanej przez serwer. Takie żądanie jest automatycznie wysyłane powtarzalnie przez przeglądarkę, na skutek czego mamy ruchomy obraz:
Kod: C / C++
Typ zawartości "multipart/x-mixed-replace;boundary=" w HTTP pozwala na strumieniowe przesyłanie wielu części zawartości w jednym żądaniu, gdzie każda część zastępuje poprzednią. Tutaj jest użyty w połączeniu z kompresją JPG, aby przesyłać wideo w formie sekwencji obrazów JPEG w czasie rzeczywistym, tworząc przez to prostą "kamerkę" internetową.
W tej sytuacji nie ma tu żadnego kodu HTML, mamy tu bezpośredni link do strumienia, tak samo jak można bezpośrednio otworzyć obraz w przeglądarce, czy tam plik XML...
Dodajemy HTML
Teraz można dodać dokument HTML w którym osadzimy nasz widok z kamery. Tylko uwaga - nie możemy po prostu dodać obsługi kolejnej ścieżki, bo przeglądarka ten widok z kamery cały czas odświeża. Z tego powodu wydajniej będzie utworzyć osobny mini serwerek, osobny socket TCP, itd:
Kod: C / C++
Teraz stream będzie na porcie o 1 większym, czyli jak domyślnie serwer tworzymy na 80, to stream będzie miał port 81. To dlatego, że jakoś trzeba rozróżnić te sockety.
Metoda streamu zostaje bez zmian, więc przyjrzyjmy się tylko page_handler, w której umieściłem prostą stronkę:
Kod: C / C++
Oryginalny przykład esp32-cam ma dokumenty HTML zakodowane jako hex w osobnych nagłówkach, to wpisałem całość "z palca" dla uproszczenia.
Rezultat:
REKLAMA
Pobierania JPG
Funkcję tworzącą obraz JPG już mamy w kodzie, więc wystarczy ją użyć. Dodajmy zatem przycisk do pobrania migawki z kamerki:
Kod: HTML, XML
Trzeba też dodać jego handler:
Kod: C / C++
Implementacja handlera:
Kod: C / C++
Rezultatem jest link, po wciśnięciu którego pobierze się nam obraz JPG:
Obsługa diody LED
Wspomniana dioda LED znajduje się na pinie czwartym:
Kod: C / C++
Ustawiamy ten pin w tryb wyjście tak samo jak w Arduino:
Kod: C / C++
Do obsługi pinu dodamy endpoint HTTP:
Kod: C / C++
Endpoint trzeba też zarejestrować:
Kod: C / C++
Teraz można by wysyłać ręcznie POST by kontrolować diodę, ale łatwiej jest dodać do tego przyciski na panelu kontrolnym:
Kod: HTML, XML
Od teraz można kontrolować diodę LED z poziomu strony WWW:
LED pobiera około 50mA:
Format obrazu
Teraz spróbujemy coś nanieść na przechwycony obraz. Bufor ramki możemy dostać pobrać funkcją esp_camera_fb_get:
Kod: C / C++
Struktura bufora jest następująca:
Kod: C / C++
Mamy tu przede wszystkim jego dane (bajty), wymiary (szerokość i wysokość) oraz rodzaj piksela. Wspierane są tu różne formaty pikseli o różnych rozmiarach:
Kod: C / C++
Oprócz typowych formatach (3 bajty na piksel - RGB888, 3 bajty na dwa piksele - RGB444 - po 4 bity na kolor, więc 4+4+4 = 12 bitów na piksel), mamy tu też możliwość otrzymania pikseli od razu poddanych stratnej kompresji JPG - format PIXFORMAT_JPEG.
Zatem moją pierwszą myślą była zmiana formatu pikseli ze strumienia JPG na np. RGB555 w inicjalizacji kamery:
Kod: C / C++
Niestety takie rozwiązanie nie wchodzi w grę, gdyż psuje zarówno istniejące capture obrazu, jak i stream. Nie udało mi się tego uruchomić nawet przy małych rozdzielczościach, więc podejrzewam, że użyta biblioteka może tego nie wspierać. Z tego powodu zdecydowałem się użyć funkcji fmt2rgb888 i fmt2jpg w celu najpierw zamiany strumienia JPG z ramki kamery na dane RGB888, a potem (po edycji) do zmiany tych danych z powrotem na JPG. Same moje przetwarzanie danych umieściłem w funkcji. Oto nowe wywołanie pobrania jednego zdjęcia, wraz z jego moją edycją:
Kod: C / C++
UWAGA! Powyższy kod zakłada, że ustawiona jest rozdzielczość FRAMESIZE_SVGA [800x600].
Cała operacja jest tu bardzo niewydajna, ale nie o to chodzi. Najpierw pobieram ramkę kamery funkcją esp_camera_fb_get, potem alokuję bufor na piksele (format RGB888), potem dekoduję ramkę do tych pikseli (fmt2rgb888), zwalniam ramkę kamery (esp_camera_fb_return), edytuję piksele (my_demo_rgb888), i potem ostatecznie zamieniam je z powrotem na JPG (fmt2jpg). Oczywiście zwalniam też pamięć.
Została jeszcze sama edycja pikseli - no cóż, skoro mamy już klasyczne RGB888, to można np. porysować prostokąty:
Kod: C / C++
Analogicznie można zrealizować inne operacje, w tym np. rysowanie tekstu.
Rezultat:
Jak skonfigurować OTA w PlatformIO?
OTA, czyli aktualizacja oprogramowania przez WiFi, była już omawiana w pokrewnym temacie:
Jak programować płytkę Wemos D1 (ESP8266) w kształcie Arduino? ArduinoOTA w PlatformIO
Tu analogicznie - odsyłam do dokumentacji:
https://docs.arduino.cc/libraries/arduino_esp32_ota/
Podsumowanie
Bardzo fajna płytka, wygodnie się nią mi programuje w PlatformIO. Na Githubie jest już zrealizowanych na niej nieco ciekawych projektów, więc pewnie coś jeszcze w oparciu o nią zaprezentuję. Może jakaś prosta klasyfikacja obiektów, albo jakiś odczyt tekstu lub QR w prostej formie? Zobaczymy, mam jeszcze oprócz tego ciekawy moduł podwozia od robota, tylko muszę zabezpieczyć silniczki by nie zabiły ESP przepięciami:
Z mojej strony to tyle, a czy ktoś z Was już robił jakiś projekt na ESP z kamerką? Zapraszam do dyskusji.
PS: Na naszym forum była już ta płytka prezentowana przez kolegę @ArturAVS w temacie ESP32-CAM, moduł z kamerą 2Mpix dla IoT część 1, ale użyte zostało tam Arduino IDE którego ja staram się unikać, więc i tak zdecydowałem się pokazać tu alternatywę.
Załączam projekt z PlatformIO zawierający moje eksperymenty z tego tematu. Dodatkowo projekt jest pozbawiony systemu wykrywania twarzy, więc nie będzie problemu z brakującym fd_forward.h.
Fajne? Ranking DIY Pomogłem? Kup mi kawę.