2022-01-5
md
Le ESP32-CAM comme serveur vidéo
<-Présentation du module ESP32-CAM

L'élément distinctif de la carte de développement ESP32-CAM est sa caméra. Espressif a crée un serveur web, CameraWebServer, qui affiche un flux vidéo en continu ou qui peut saisir des instantanés. Cet exemple est très élaboré. En outre, de très nombreux paramètres de la caméra peuvent être ajustés en temps réel à l'aide d'une interface web. La détection et la reconnaissance des visages sont même possibles, mais ces fonctionnalités ne sont plus activées par défaut dans la dernière version du serveur datant d'octobre 2021. On retrouve dans cette version une prise en charge partielle du flash. Il semblait utile de compléter cet élément, d'ajouter un adressage IP fixe et de mettre en oeuvre le protocole mDNS pour faciliter l'accès à la caméra depuis le réseau local. Le résultat est un référentiel sur GitHub : CameraWebServer-YAF compatible avec les environnements de développement Arduino et PlatformIO.

Les impatients qui veulent essayer le plus rapidement possible le serveur vidéo pour sur ESP32 développé par Espressif peuvent se rendre immédiatement à la section 7. Projet PlatformIO/Arduino modifié. Les sections précédentes documentent

Ceux qui ont évité ces longues discussions décideront peut-être de revenir en consulter une partie quand il sera temps de se servir de la caméra. Une lecture linéaire de ce long billet n'est certainement pas nécessaire.

Table des matières

  1. Serveur vidéo
  2. Interface web de la caméra IP
  3. Utilisation de la caméra IP
    1. Visionnement du flux vidéo
    2. Scripts bash
    3. Intégration dans Domoticz
  4. Adresse IP statique ou mDNS
  5. Détection et reconnaissance des visages
  6. Prise en charge du flash
    1. Réalisation de la modulation de largeur d'impulsion
    2. Ajustement du rapport cyclique
  7. Projet PlatformIO/Arduino modifié
    1. Configuration minimale
    2. Configuration facultative
    3. Note

Serveur vidéo toc

Le projet CameraWebServer qu'on retrouve parmi les exemples proposés par Espressif sur son site GitHub arduino-esp32 n'est pas disponible dans PlatformIO ou, du moins, je n'ai pas réussi à le trouver. Ce n'est pas très grave, car les fichiers du projet sont facilement téléchargés. Pour commencer, j'ai créé le projet CameraWebServer dans le répertoire /home/michel/Documents/PlatformIO/Projects/esp32/esp32_cam/. L'EDI créera l'arborescence de base.

michel@hp:~/Documents/PlatformIO/Projects/esp32/esp32_cam/CameraWebServer$ tree --dirsfirst
. ├── include │   └── README ├── lib │   └── README ├── src │   └── main.cpp ├── test │   └── README └── platformio.ini

Il faut ouvrir un terminal, choisir le dossier /src comme répertoire de travail et éliminer le fichier main.cpp qui s'y trouve. Puis on télécharge les quatre fichiers du projet avec une seul wget très long réparti sur cinq lignes.

michel@hp:~$ cd Documents/PlatformIO/Projects/esp32/esp32_cam/CameraWebServer/src/ michel@hp:~/Documents/PlatformIO/Projects/esp32/esp32_cam/CameraWebServer/src$ rm main.cpp michel@hp:~/Documents/PlatformIO/Projects/esp32/esp32_cam/CameraWebServer/src$ wget \ https://raw.githubusercontent.com/espressif/arduino-esp32/master/libraries/ESP32/examples/Camera/CameraWebServer/CameraWebServer.ino \ https://raw.githubusercontent.com/espressif/arduino-esp32/master/libraries/ESP32/examples/Camera/CameraWebServer/app_httpd.cpp \ https://raw.githubusercontent.com/espressif/arduino-esp32/master/libraries/ESP32/examples/Camera/CameraWebServer/camera_index.h \ https://raw.githubusercontent.com/espressif/arduino-esp32/master/libraries/ESP32/examples/Camera/CameraWebServer/camera_pins.h ...

Quand le téléversement est complété, il est préférable de changer l'extension .ino à .cpp. On pourrait carrément renommer CameraWebServer.ino en main.cpp selon l'usage dans PlatformIO. Puis il faut apporter quelques changements à ce fichier.

  1. Ajouter #include Arduino.h au tout début du croquis.
  2. Sélectionner la bonne « caméra »
    • Rajouter \\ devant #define CAMERA_MODEL_WROVER_KIT // Has PSRAM à la ligne 12
    • Enlever \\ devant #define CAMERA_MODEL_AI_THINKER // Has PSRAM à la ligne 19
  3. Remplacer ********* par le nom du réseau Wi-Fi à la ligne 24.
  4. Remplacer ********* par le mot de passe du réseau Wi-Fi à la lign 25.
#include "Arduino.h" #include "esp_camera.h" #include <WiFi.h> // // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality // Ensure ESP32 Wrover Module or other board with PSRAM is selected // Partial images will be transmitted if image exceeds buffer size // // Select camera model //#define CAMERA_MODEL_WROVER_KIT // Has PSRAM //#define CAMERA_MODEL_ESP_EYE // Has PSRAM //#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM //#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM //#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM //#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM //#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM #define CAMERA_MODEL_AI_THINKER // Has PSRAM //#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM #include "camera_pins.h" const char* ssid = "mon-reseau"; const char* password = "mon-mot-de-passe"; ...

L'EDI PlatformIO affiche six avertissements concernant des variables non utilisées dans le fichier app_httpd.cpp. Ce ne sont pas des erreurs, les variables sont utilisées dans des entrées de journalisation qui ne sont pas activées par défaut. Il y a une erreur au sujet de la fonction itoa qui ne serait pas définie. Or ce n'est pas une erreur, puisque l'on peut compiler le programme qui fonctionne correctement malgré ce qu'en dit PlatformIO. Toutefois, si l'on ne veut plus voir cette mention d'erreur, il suffit d'ajouter #include "stdlib_noniso.h" au début du fichier.

La compilation du programme devrait se faire sans problème et alors on n'a qu'à téléverser le résultat vers le ESP32-CAM comme on l'a fait avec le croquis Blinks.

Interface web de la caméra IP toc

web interface Après le téléversement du nouveau micrologiciel du ESP32, il faut redémarrer le module. C'est avantageux de laisser la connexion en série en place puisque l'adresse IP du serveur Web sera affichée sur le terminal.

... rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:2 load:0x3fff0018,len:4 load:0x3fff001c,len:1044 load:0x40078000,len:10124 load:0x40080400,len:5828 entry 0x400806a8 ..... WiFi connected Camera Ready! Use 'http://192.168.1.68' to connect

Après beaucoup d'expérimentation avec le module et le programme, une erreur pouvait s'afficher.
load:0x40080400,len:3076 entry 0x400805ec E (1134) cam_hal: EV-EOF-OVF ..... WiFi connected
Le nombre entre les parenthèses pouvait être différent selon des modifications au code du serveur dont il sera question plus loin. Il n'était pas rare que le module redémarre en boucle, mais le plus souvent la séquence de démarrage se complétait après quelques itérations. Ce comportement indique un problème d'alimentation que j'ai finalement attribué aux connexions vers l'alimentation faîtes avec des câbles Dupont. Les nombreuses manipulations pour activer le bouton de démarrage et pour orienter la caméra dans diverses directions, ont eu raison du fil solide des câbles qui n'est pas très flexible ou d'un connecteur Dupont. Si vous observer ce type d'erreur, essayez de changer les câbles d'alimentation.

Si l'on se connecte à l'URL affichée avec un navigateur, on obtient l'interface web de la caméra. Initialement, on ne voit que le panneau de configuration, mais le flux vidéo sera affiché à la droite de celui-ci lorsqu'on appuie sur le bouton Start Stream (démarrer le flux vidéo). Une image fixe sera affichée au même endroit si l'on active le bouton Get Still (prendre un instantané). Le panneau peut être caché en cliquant sur l'icône du « hamburger » dans le coin supérieur gauche de la page.

Il y a un grand nombre de paramètres qui peuvent être ajusté dont les principaux sont la taille de l'image (sa résolution) et sa qualité (plus sa valeur est petite, meilleure est la qualité de l'image). Je ne prétends pas connaître le rôle de la plupart des contrôles, encore moins ceux des trois sous-menus en bas du panneau. Lorsque la caméra sera dans sa position finale et que j'aurais enlevé la pellicule plastique qui protège la lentille, j'expérimenterai pour trouver les valeurs optimales.

Utilisation de la caméra IP toc

L'interface web de la caméra est une très bonne façon d'expérimenter avec le dispositif, mais typiquement ce n'est pas ainsi que la plupart voudront exploiter la caméra. Le plus souvent, on utilisera des requêtes HTML pour soit fixer les paramètres de la caméra soit en tirer des images de capture ou un flux vidéo. Dans ce qui suit <cam-ip> dénote l'adresse IP du dispositif.

FonctionURLnotes
Flux vidéohttp://<cam-ip>:81/streamUn seul client peut se connecter à la fois
Capture d'imagehttp://<cam-ip>:80/captureFormat : JPEG, résolution par défaut : 320x240 pixels
http://<cam-ip>:80/bmpFormat : BMP, résolution par défaut : 320x240 pixels
Statushttp://<cam-ip>:80/status
Résultat : une chaîne au format JSON
{"0xd3":12,"0x111":0,"0x132":54,"xclk":20,"pixformat":3,"framesize":5,"quality":10,"brightness":0,"contrast":0,"saturation":0,"sharpness":0,"special_effect":0,"wb_mode":0,"awb":1,"awb_gain":1,"aec":1,"aec2":0,"ae_level":0,"aec_value":168,"agc":1,"agc_gain":0,"gainceiling":0,"bpc":0,"wpc":1,"raw_gma":1,"lenc":0,"hmirror":0,"dcw":1,"colorbar":0}
Paramétrerhttp://<cam-ip>:80/control?requête
Format de la requête : var=paramètre&val=valeur tel
var=quality&val=2 ou
var=framesize&val=9
http://<cam-ip>:80/regà documenter
http://<cam-ip>:80/gregà documenter
http://<cam-ip>:80/pllà documenter
http://<cam-ip>:80/resolutionà documenter

Le paramètre framesize (qui correspond au contrôle Resolution de l'interface web) prend une valeur de 0 à 13 qui représente les différentes résolutions supportées par la caméra OV2640.

NomRésolutionvaleur
UXGA1600×120013
SXGA1280×102412
HD1280×72011
XGA1024×76810
SVGA800×6009
VGA640×4808
HVGA480×3207
CIF400×2966
QVGA320×2405
240X240240×2404
HQVGA240×1763
QCIF176×1442
QQVGA160×1201
96X9696×960

Le port par défaut du protocole HTTP est 80. Donc il n'est pas nécessaire de le spécifier explicitement dans les URLs autre que celle qui utilise le port 81 pour la diffusion en continu du flux de vidéo. Une capture d'image peut être initiée avec la requête http://<cam-ip>:80/capture ou http://<cam-ip>/capture.

Visionnement du flux vidéo toc

On peut visionner le flux vidéo en continu du ESP32-CAM dans un navigateur web ou un lecteur vidéo comme VLC de VideoLAN. Il suffit d'utiliser la URL http://<cam-ip>:81/stream comme emplacement. Bien sur <cam-ip> représente l'adresse IP du module. À titre d'exemple, voici comment faire dans VLC. À partir du menu principal Média, on sélectionne Ouvrir un flux réseau... La fenêtre qui surgit contient un mélange un peu surprenant de français et d'anglais, du moins dans la version que je possède, mais qu'importe, il suffit de saisir l'URL du flux dans le contrôle étiquetté Please enter a network URL. Entering network URL in VLC

On peut faire la même chose dans Celluloid. En fait ce programme est une interface graphique à mpv qu'on peut utiliser directement depuis un terminal.

michel@hp:~$ mpv http://192.168.1.68:81/stream [lavf] This format is marked by FFmpeg as having no timestamps! [lavf] FFmpeg will likely make up its own broken timestamps. For [lavf] video streams you can correct this with: [lavf] --no-correct-pts --fps=VALUE [lavf] with VALUE being the real framerate of the stream. You can [lavf] expect seeking and buffering estimation to be generally [lavf] broken as well. (+) Video --vid=1 (mjpeg 320x240) VO: 800x600 yuvj422p V: 00:00:28 / 00:00:32 (88%) Cache: 3.5s/1MB ... michel@hp:~$ mpv --no-correct-pts --fps=12 http://192.168.1.68:81/stream [lavf] This format is marked by FFmpeg as having no timestamps! [lavf] FFmpeg will likely make up its own broken timestamps. For [lavf] video streams you can correct this with: [lavf] --no-correct-pts --fps=VALUE [lavf] with VALUE being the real framerate of the stream. You can [lavf] expect seeking and buffering estimation to be generally [lavf] broken as well. (+) Video --vid=1 (mjpeg 800x600) FPS forced to 12.000. Use --no-correct-pts to force FPS based timing. VO: [gpu] 800x600 yuvj422p V: 00:00:17 / 00:00:09 (100%) Cache: 1.0s/794KB

Le résultat n'est pas très convaincant. Le flux vidéo ne contient pas de code temporel selon l'avertissement. J'ai suivi le conseil et expérimenté avec le nombre d'images à la seconde (fps) pour le fixer finalement à 12 ce qui est faible pour un résolution de 800x600 pixels. Le lecteur FFPlay de FFmpeg se pleint du format.

michel@hp:~$ ffplay http://esp32-cam.local:81/stream ffplay version 4.2.4-1ubuntu0.1 Copyright (c) 2003-2020 the FFmpeg developers built with gcc 9 (Ubuntu 9.3.0-10ubuntu2) configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened ... Input #0, mpjpeg, from 'http://esp32-cam.local:81/stream':B f=0/0 Duration: N/A, bitrate: N/A Stream #0:0: Video: mjpeg (Baseline), yuvj422p(pc, bt470bg/unknown/unknown), 800x600, 25 tbr, 25 tbn, 25 tbc [swscaler @ 0x7f91f4123f00] deprecated pixel format used, make sure you did set range correctly ...

Si l'on autorise la journalisation avec cet utilitaire, une quantité imposante d'information est affichée alors que le flux vidéo est joué.

michel@hp:~$ ffplay -loglevel 'verbose' http://192.168.1.68:81/stream ffplay version 4.2.4-1ubuntu0.1 Copyright (c) 2003-2020 the FFmpeg developers built with gcc 9 (Ubuntu 9.3.0-10ubuntu2) ... michel@hp:~$ ffplay -loglevel 'debug' http://192.168.1.68:81/stream ffplay version 4.2.4-1ubuntu0.1 Copyright (c) 2003-2020 the FFmpeg developers built with gcc 9 (Ubuntu 9.3.0-10ubuntu2) configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared WARNING: library configuration mismatch avcodec configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared --enable-version3 --disable-doc --disable-programs --enable-libaribb24 --enable-liblensfun --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libtesseract --enable-libvo_amrwbenc libavutil 56. 31.100 / 56. 31.100 libavcodec 58. 54.100 / 58. 54.100 libavformat 58. 29.100 / 58. 29.100 libavdevice 58. 8.100 / 58. 8.100 libavfilter 7. 57.100 / 7. 57.100 libavresample 4. 0. 0 / 4. 0. 0 libswscale 5. 5.100 / 5. 5.100 libswresample 3. 5.100 / 3. 5.100 libpostproc 55. 5.100 / 55. 5.100 Initialized opengl renderer. [NULL @ 0x7f4c18000bc0] Opening 'http://192.168.1.68:81/stream' for reading [http @ 0x7f4c18001780] Setting default whitelist 'http,https,tls,rtp,tcp,udp,crypto,httpproxy' [tcp @ 0x7f4c18003600] Original list of addresses:sq= 0B f=0/0 [tcp @ 0x7f4c18003600] Address 192.168.0.140 port 81 [tcp @ 0x7f4c18003600] Interleaved list of addresses: [tcp @ 0x7f4c18003600] Address 192.168.0.140 port 81 [tcp @ 0x7f4c18003600] Starting connection attempt to 192.168.0.140 port 81 [tcp @ 0x7f4c18003600] Successfully connected to 192.168.0.140 port 81 [http @ 0x7f4c18001780] request: GET /stream HTTP/1.1 User-Agent: Lavf/58.29.100 Accept: */* Range: bytes=0- Connection: close Host: 192.168.1.68:81 Icy-MetaData: 1 Probing mpjpeg score:0 increased to 75 due to MIME type 0B f=0/0 [mpjpeg @ 0x7f4c18000bc0] Format mpjpeg probed with size=2048 and score=75 [mpjpeg @ 0x7f4c18000bc0] Before avformat_find_stream_info() pos: 0 bytes read:2900 seeks:0 nb_streams:1 [mjpeg @ 0x7f4c18005c00] marker=d8 avail_size_in_buf=48334B f=0/0 [mjpeg @ 0x7f4c18005c00] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18005c00] marker=e0 avail_size_in_buf=48332 [mjpeg @ 0x7f4c18005c00] marker parser used 16 bytes (128 bits) [mjpeg @ 0x7f4c18005c00] marker=db avail_size_in_buf=48314 [mjpeg @ 0x7f4c18005c00] index=0 [mjpeg @ 0x7f4c18005c00] qscale[0]: 1 [mjpeg @ 0x7f4c18005c00] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18005c00] marker=db avail_size_in_buf=48245 [mjpeg @ 0x7f4c18005c00] index=1 [mjpeg @ 0x7f4c18005c00] qscale[1]: 3 [mjpeg @ 0x7f4c18005c00] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18005c00] marker=c4 avail_size_in_buf=48176 [mjpeg @ 0x7f4c18005c00] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18005c00] marker=c4 avail_size_in_buf=48143 [mjpeg @ 0x7f4c18005c00] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18005c00] marker=c4 avail_size_in_buf=47960 [mjpeg @ 0x7f4c18005c00] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18005c00] marker=c4 avail_size_in_buf=47927 [mjpeg @ 0x7f4c18005c00] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18005c00] marker=c0 avail_size_in_buf=47744 [mjpeg @ 0x7f4c18005c00] Changing bps from 0 to 8 [mjpeg @ 0x7f4c18005c00] sof0: picture: 800x600 [mjpeg @ 0x7f4c18005c00] component 0 2:1 id: 0 quant:0 [mjpeg @ 0x7f4c18005c00] component 1 1:1 id: 1 quant:1 [mjpeg @ 0x7f4c18005c00] component 2 1:1 id: 2 quant:1 [mjpeg @ 0x7f4c18005c00] pix fmt id 21111100 [mjpeg @ 0x7f4c18005c00] Format yuvj422p chosen by get_format(). [mjpeg @ 0x7f4c18005c00] marker parser used 17 bytes (136 bits) [mjpeg @ 0x7f4c18005c00] escaping removed 154 bytes [mjpeg @ 0x7f4c18005c00] marker=da avail_size_in_buf=47725 [mjpeg @ 0x7f4c18005c00] marker parser used 47571 bytes (380568 bits) [mjpeg @ 0x7f4c18005c00] marker=d9 avail_size_in_buf=0 [mjpeg @ 0x7f4c18005c00] decode frame unused 0 bytes [mpjpeg @ 0x7f4c18000bc0] All info found [mpjpeg @ 0x7f4c18000bc0] After avformat_find_stream_info() pos: 48448 bytes read:48448 seeks:0 frames:1 Input #0, mpjpeg, from 'http://192.168.1.68:81/stream': Duration: N/A, bitrate: N/A Stream #0:0, 1, 1/25: Video: mjpeg (Baseline), 1 reference frame, yuvj422p(pc, bt470bg/unknown/unknown, center), 800x600, 0/1, 25 tbr, 25 tbn, 25 tbc [mjpeg @ 0x7f4c18007100] marker=d8 avail_size_in_buf=48334 [mjpeg @ 0x7f4c18007100] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18007100] marker=e0 avail_size_in_buf=48332 [mjpeg @ 0x7f4c18007100] marker parser used 16 bytes (128 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48314 [mjpeg @ 0x7f4c18007100] index=0 [mjpeg @ 0x7f4c18007100] qscale[0]: 1 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48245 [mjpeg @ 0x7f4c18007100] index=1 [mjpeg @ 0x7f4c18007100] qscale[1]: 3 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48176 [mjpeg @ 0x7f4c18007100] class=0 index=0 nb_codes=12 [mjpeg @ 0x7f4c18007100] marker parser used 31 bytes (248 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48143 [mjpeg @ 0x7f4c18007100] class=1 index=0 nb_codes=251 [mjpeg @ 0x7f4c18007100] marker parser used 181 bytes (1448 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=47960 [mjpeg @ 0x7f4c18007100] class=0 index=1 nb_codes=12 [mjpeg @ 0x7f4c18007100] marker parser used 31 bytes (248 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=47927 [mjpeg @ 0x7f4c18007100] class=1 index=1 nb_codes=251 [mjpeg @ 0x7f4c18007100] marker parser used 181 bytes (1448 bits) [mjpeg @ 0x7f4c18007100] marker=c0 avail_size_in_buf=47744 [mjpeg @ 0x7f4c18007100] sof0: picture: 800x600 [mjpeg @ 0x7f4c18007100] component 0 2:1 id: 0 quant:0 [mjpeg @ 0x7f4c18007100] component 1 1:1 id: 1 quant:1 [mjpeg @ 0x7f4c18007100] component 2 1:1 id: 2 quant:1 [mjpeg @ 0x7f4c18007100] pix fmt id 21111100 [mjpeg @ 0x7f4c18007100] Format yuvj422p chosen by get_format(). [mjpeg @ 0x7f4c18007100] marker parser used 17 bytes (136 bits) [mjpeg @ 0x7f4c18007100] escaping removed 154 bytes [mjpeg @ 0x7f4c18007100] marker=da avail_size_in_buf=47725 [mjpeg @ 0x7f4c18007100] component: 0 [mjpeg @ 0x7f4c18007100] component: 1 [mjpeg @ 0x7f4c18007100] component: 2 [mjpeg @ 0x7f4c18007100] marker parser used 47570 bytes (380553 bits) [mjpeg @ 0x7f4c18007100] marker=d9 avail_size_in_buf=0 [mjpeg @ 0x7f4c18007100] decode frame unused 0 bytes Video frame changed from size:0x0 format:none serial:-1 to size:800x600 format:yuvj422p serial:1 detected 8 logical cores [ffplay_buffer @ 0x7f4c1c00e7c0] Setting 'video_size' to value '800x600' [ffplay_buffer @ 0x7f4c1c00e7c0] Setting 'pix_fmt' to value '13' [ffplay_buffer @ 0x7f4c1c00e7c0] Setting 'time_base' to value '1/25' [ffplay_buffer @ 0x7f4c1c00e7c0] Setting 'pixel_aspect' to value '0/1' [ffplay_buffer @ 0x7f4c1c00e7c0] Setting 'frame_rate' to value '25/1' [ffplay_buffer @ 0x7f4c1c00e7c0] w:800 h:600 pixfmt:yuvj422p tb:1/25 fr:25/1 sar:0/1 sws_param: [auto_scaler_0 @ 0x7f4c1c121ac0] Setting 'flags' to value 'bicubic' [auto_scaler_0 @ 0x7f4c1c121ac0] w:iw h:ih flags:'bicubic' interl:0 [ffplay_buffersink @ 0x7f4c1c120b40] auto-inserting filter 'auto_scaler_0' between the filter 'ffplay_buffer' and the filter 'ffplay_buffersink' [AVFilterGraph @ 0x7f4c1c012500] query_formats: 2 queried, 0 merged, 1 already done, 0 delayed [auto_scaler_0 @ 0x7f4c1c121ac0] picking rgb0 out of 5 ref:yuvj422p alpha:0 [swscaler @ 0x7f4c1c122fc0] deprecated pixel format used, make sure you did set range correctly [auto_scaler_0 @ 0x7f4c1c121ac0] w:800 h:600 fmt:yuvj422p sar:0/1 -> w:800 h:600 fmt:rgb0 sar:0/1 flags:0x4 Created 800x600 texture with SDL_PIXELFORMAT_BGR888. [mjpeg @ 0x7f4c18007100] marker=d8 avail_size_in_buf=48798B f=0/0 [mjpeg @ 0x7f4c18007100] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18007100] marker=e0 avail_size_in_buf=48796 [mjpeg @ 0x7f4c18007100] marker parser used 16 bytes (128 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48778 [mjpeg @ 0x7f4c18007100] index=0 [mjpeg @ 0x7f4c18007100] qscale[0]: 1 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48709 [mjpeg @ 0x7f4c18007100] index=1 [mjpeg @ 0x7f4c18007100] qscale[1]: 3 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48640 [mjpeg @ 0x7f4c18007100] class=0 index=0 nb_codes=12 [mjpeg @ 0x7f4c18007100] marker parser used 31 bytes (248 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48607 [mjpeg @ 0x7f4c18007100] class=1 index=0 nb_codes=251 [mjpeg @ 0x7f4c18007100] marker parser used 181 bytes (1448 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48424 [mjpeg @ 0x7f4c18007100] class=0 index=1 nb_codes=12 [mjpeg @ 0x7f4c18007100] marker parser used 31 bytes (248 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48391 [mjpeg @ 0x7f4c18007100] class=1 index=1 nb_codes=251 [mjpeg @ 0x7f4c18007100] marker parser used 181 bytes (1448 bits) [mjpeg @ 0x7f4c18007100] marker=c0 avail_size_in_buf=48208 [mjpeg @ 0x7f4c18007100] sof0: picture: 800x600 [mjpeg @ 0x7f4c18007100] component 0 2:1 id: 0 quant:0 [mjpeg @ 0x7f4c18007100] component 1 1:1 id: 1 quant:1 [mjpeg @ 0x7f4c18007100] component 2 1:1 id: 2 quant:1 [mjpeg @ 0x7f4c18007100] pix fmt id 21111100 [mjpeg @ 0x7f4c18007100] marker parser used 17 bytes (136 bits) [mjpeg @ 0x7f4c18007100] escaping removed 306 bytes [mjpeg @ 0x7f4c18007100] marker=da avail_size_in_buf=48189 [mjpeg @ 0x7f4c18007100] component: 0 [mjpeg @ 0x7f4c18007100] component: 1 [mjpeg @ 0x7f4c18007100] component: 2 [mjpeg @ 0x7f4c18007100] marker parser used 47882 bytes (383049 bits) [mjpeg @ 0x7f4c18007100] marker=d9 avail_size_in_buf=145 [mjpeg @ 0x7f4c18007100] decode frame unused 145 bytes [mjpeg @ 0x7f4c18007100] marker=d8 avail_size_in_buf=48843B f=0/0 [mjpeg @ 0x7f4c18007100] marker parser used 0 bytes (0 bits) [mjpeg @ 0x7f4c18007100] marker=e0 avail_size_in_buf=48841 [mjpeg @ 0x7f4c18007100] marker parser used 16 bytes (128 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48823 [mjpeg @ 0x7f4c18007100] index=0 [mjpeg @ 0x7f4c18007100] qscale[0]: 1 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=db avail_size_in_buf=48754 [mjpeg @ 0x7f4c18007100] index=1 [mjpeg @ 0x7f4c18007100] qscale[1]: 3 [mjpeg @ 0x7f4c18007100] marker parser used 67 bytes (536 bits) [mjpeg @ 0x7f4c18007100] marker=c4 avail_size_in_buf=48685 [mjpeg @ 0x7f4c18007100] class=0 index=0 nb_codes=12 [mjpeg @ 0x7f4c18007100] marker parser used 31 bytes (248 bits) ...

Que veut dire tout cela ? Encore des choses à apprendre.

Il y a une contrainte importante : le serveur web du ESP32-CAM ne peut accepter qu'un seul client du flux vidéo à la fois. Toutefois, on peut simultanément ouvrir la page d'accueil du serveur http://<cam-ip>:80/ (avec ou sans le numéro de port) et modifier en temps réel les paramètres du flux. Ou, comme dans le script, on pourrait lui envoyer des requêtes HTTP avec curl, wget ou un navigateur web pour modifier la résolution, la qualité, l'orientation de l'image et ainsi de suite.

Script bash pour prendre un cliché toc

Voici un tout petit script bash qui fixe la résolution de la caméra à 1280×1024 pixels et la qualité de l'image à 4.

#!/bin/bash curl "http://192.168.1.68/control?var=framesize&val=12" curl "http://192.168.1.68/control?var=quality&val=4"

Èvidemment il faudra ajuster l'adresse IP en fonction de l'allocation faite par le serveur DHCP du réseau local. Ci-dessous on verra qu'il est facile de donner une adresse IP fixe au ESP32-CAM ou de lui donner un nom d'hôte local ce qui facilite les choses. Ce script peut être invoqué dès que le serveur vidéo est en marche. On peut aussi l'utiliser pour modifier la qualité ou la résolution de flux vidéo qu'on regarde sur un navigateur. Le script pourait être plus rapide que d'ouvrir l'interface web du serveur.

On ajoute quelques lignes au script pour faire une capture d'image au format JPEG qu'on sauvegarde sur le disque de l'ordinateur sur lequel est activé le script. On note le délai qui est nécessaire avant que le ESP32-CAM puisse compléter les ajustements des paramètres de la caméra et que celle-ci soit en mesure de prendre un cliché.

#!/bin/bash curl "http://192.168.1.68/control?var=framesize&val=12" curl "http://192.168.1.68/control?var=quality&val=4" sleep 1 date=`date +"%Y-%m-%d-%H:%M:%S"` fname="capture-${date}.jpg" if curl -o "${fname}" "http://192.168.1.68/capture" >/dev/null 2>&1; then echo "Image captured to file \"${fname}\"" else echo "Could not capture image" fi

Intégration dans Domoticz toc

Dès que l’on connait l'URL pour réaliser une capture d'image avec la caméra, il devient très simple de l'intégrer dans un programme de domotique comme Domoticz. Il suffit de cliquer sur Configuration, Plus d'options, Caméras puis d'activer le bouton Ajouter une caméra > pour aboutir dans l'écran de définition d'une caméra.

Définition d'une caméra dans Domoticz

C'est assez évident ce qu'il faut saisir comme valeurs dans les champs de cette fenêtre. N'oubliez pas d'ajuster l'adresse IP et de donner un nom unique au dispositif.

Adresse IP statique ou mDNS toc

Clairement, il est presque obligatoire que l'adresse IP du ESP32-CAM soit statique pour que l'ajout de la caméra dans Domoticz et que le script fonctionnent. On peut faire cela avec le serveur DHCP du réseau local qui attribuera une adresse IP statique au ESP32 en fonction de son adresse MAC. Avec cette approche il n'y a rien à changer dans le croquis. La façon de procéder est différente selon le dispositif où se trouve le serveur DHCP qui est souvent un routeur provenant d'un fournisseur d'accès à Internet. Si modifier les paramètres du serveur DHCP n'est pas possible, alors on peut manuellement fixer l'adresse IP du ESP32-CAM dans le croquis CameraWebServer.cpp (ou main.cpp).

Les adresses IP données ci-dessus ne sont qu'indicatives et doivent être ajustées pour chaque réseau domestique. Pour connaître la passerelle (gateway), qui sera le routeur le plus souvent, on peut utiliser la commande ip ou route dans Linux.

michel@hp:~$ ip route show default via 192.168.1.1 dev enp4s0 proto dhcp metric 100 169.254.1.0/16 dev enp4s0 scope link metric 1000 192.168.1.0/24 dev enp4s0 proto kernel scope link src 192.168.1.100 metric 100 michel@hp:~$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 enp4s0 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 enp4s0 192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 enp4s0

On peut éviter de fixer une adresse IP statique à l'aide du protocole mDNS. Pour ce faire il faut ajouter une bibliothèque et quelques lignes dans la fonction setup() du programme CameraWebServer.cpp (ou main.cpp). C'est assez simple.

Dès qu'on redémarre le module, on devrait pouvoir repérer le ESP32-CAM avec un outils comme avahi dans Linux et ainsi obtenir son adresse IP.

michel@hp:~$ avahi-browse --all + enp4s0 IPv4 esp32-cam-01 Web Site local michel@hp:~$ avahi-resolve -n esp32-cam-01.local esp32-cam-01.local 192.168.0.101

En fait, il n'est souvent pas nécessaire de connaître l'adresse IP, car on peut utiliser le nom d'hôte avec plusieurs logiciels, dont curl.

michel@hp:~$ curl "http://esp32-cam-01.local/status" {"0xd3":8,"0x111":0,"0x132":137,"xclk":20,"pixformat":3,"framesize":5,"quality":10,"brightness":0,"contrast":0,"saturation":0,"sharpness":0,"special_effect":0,"wb_mode":0,"awb":1,"awb_gain":1,"aec":1,"aec2":0,"ae_level":0,"aec_value":168,"agc":1,"agc_gain":0,"gainceiling":0,"bpc":0,"wpc":1,"raw_gma":1,"lenc":1,"hmirror":0,"dcw":1,"colorbar":0}

Firefox, Celluloid, Domoticz etc. peuvent afficher la vidéo en continu ou télécharger des captures en utilisant une URL contenant le nom d'hôte local.

flux vidéo depuis http://esp32-cam-01.local:81/stream

Détection et reconnaissance des visages toc

Le fichier app_httpd.cpp contient deux directives au sujet de la détection et la reconnaissance des visages.

#if CONFIG_ESP_FACE_DETECT_ENABLED #include "fd_forward.h" #if CONFIG_ESP_FACE_RECOGNITION_ENABLED #include "fr_forward.h" #define ENROLL_CONFIRM_TIMES 5 #define FACE_ID_SAVE_NUMBER 7 #endif #define FACE_COLOR_WHITE 0x00FFFFFF ... #endif

Si la directive CONFIG_ESP_FACE_DETECT_ENABLED est définie alors une bibliothèque, fd_forward est ajouté au micrologiciel. Dès lors, le paramètre Face Detection de l'interface web devient fonctionnel. Si activé et si la résolution de la vidéo est assez faible, alors le serveur vidéo identifie les visages qu'il peut détecter avec un cadre jaune. L'interface affiche le message suivant si l'on tente d'activer la détection des visages avec une résolution telle SXGA (1280x1024)

Please select CIF or lower resolution before enabling this feature!

Nonobstant cet avertissement, il faut que la résolution soit inférieure à CFI (400x296 = 118 400 pixels), donc égale à QVGA (320x240 = 76 800 pixels) ou moins pour pouvoir activer la détection des visages. C'est clairement quelque chose qui demande tellement de calculs qu'il ne serait pas possible de détecter des visages en temps réels s'il fallait analyser 50% de plus de pixels.

Le micrologiciel est capable de faire encore plus, soit la reconnaissance de visages déjà « enrôller ». Il faut définir les deux directives CONFIG_ESP_FACE_DETECT_ENABLED et CONFIG_ESP_FACE_RECOGNITION_ENABLED, compiler le projet et puis téléverser le micrologiciel pour y incorporer cette fonctionnalité. Dans l'interface web, la reconnaissance des visages est activée avec le contrôle Face Recognition. Cette activation entraine l'activation de la détection des visages et le bouton Enroll Face devient fonctionnel. Avant de reconnaitre un visage, le micrologiciel doit enregistrer le visage à reconnaître. Pour ce faire on présente le visage à la caméra, on attend que le visage soit détecté et l'on clique sur le bouton Enroll Face. Il faut faire cela cinq fois si je comprends bien la macro ENROLL_CONFIRM_TIMES tout en modifiant l'angle et peut-être la distance du visage par rapport à la caméra. Dorénavant, quand le microcologiciel detecte un visage il indiquera s'il est reconnu ou non. Je n'ai peu expérimenté ces fonctions, car elles ne sont pas d'un grand intérêt pratique. On est épaté qu'un microcontrôleur puisse réaliser ces exploits, mais les résolutions auxquels ceci est faisable sont bien trop faibles.

Il reste à expliquer comment définir des directives pour les néophytes. Le plus simple, surtout pour l'environnement Arduino, est d'ajouter les directives au début du fichier avant la liste des bibliothèques à inclure.

#define CONFIG_ESP_FACE_DETECT_ENABLED #define CONFIG_ESP_FACE_RECOGNITION_ENABLED #include "esp_http_server.h" #include "esp_timer.h" ...

Depuis la version 2.0.1 de ESP32 Arduino, les fichiers fd_forward.h et fr_formward.h et les autres composants de la bibliothèque de la reconnaissance des visages ne sont plus présents. Cette dernière a été remplacée par la bibliothèque dl (deep learning) qui semble bien plus avancée. Le fichier app_httpd.cpp ne prend pas en charge cette nouvelle bibliothèque. Espressif a protégé l'ancien code de reconnaissance faciale avec les directives CONFIG_ESP_FACE_DETECT_ENABLED et CONFIG_ESP_FACE_RECOGNITION_ENABLED qui ne sont jamais définies. C'est pour cette raison que l'on peut encore activer cette fonctionnalité pour autant qu'on utilise une version plus ancienne de noyau Arduino, comme les versions 2.0.0 or 1.0.6. D'ailleurs la reconnaissance des visages était incorporée dans CameraWebServer dans les versions 1.0.5 et 1.0.4 et peut-être avant, mais je n'ai rien testé de si vieux.

La version stable de la plateforme Espressif 32 dans PlatformIO présentement utilisée (v.34.0 - 495c689) est encore basée sur le noyau 1.0.6 ce qui veut dire qu'on peut activer la reconnaissance des visages sans problèmes dans cet environnement. Puisque c'est une version bien plus récente du noyau qui est présentement utilisée dans l'EDI Arduino, il faut rétrader vers la version 2.0.0 de esp32 avec le gestionnaire de carte si l'on veut expérimenter avec cet aspect du serveur vidéo.


Prise en charge du flash toc

Dans le même fichier app_httpd.cpp il y a une autre directive qui est pertinente pour le ESP32-CAM de Ai Thinker. Si la directive CONFIG_LED_ILLUMINATOR_ENABLED est définie alors un contrôle additionnel, LED Intensity est affiché dans l'interface web du serveur vidéo.

LED Intensity in web interface

L'objectif visé est de fixer l'intensité de la DEL utilisée comme flash. La variable, led_duty, est clairement créée dans ce but.

#ifdef CONFIG_LED_ILLUMINATOR_ENABLED int led_duty = 0; bool isStreaming = false; #ifdef CONFIG_LED_LEDC_LOW_SPEED_MODE #define CONFIG_LED_LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE #else #define CONFIG_LED_LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE #endif #endif

En plus de créer des variables, ce fragment de code fixe la valeur de la macro CONFIG_LED_LEDC_SPEED_MODE à LEDC_HIGH_SPEED_MODE puisque CONFIG_LED_LEDC_LOW_SPEED_MODE n'est pas définie. Il est préférable de ne rien changer à ceci comme on verra plus tard. Un peu plus loin dans le fichier, on observe que des fonctions de gestion des requêttes HTTP associent l'étiquette led_instensity à la variable led_duty.

static esp_err_t cmd_handler(httpd_req_t *req) { ... #ifdef CONFIG_LED_ILLUMINATOR_ENABLED else if (!strcmp(variable, "led_intensity")) { led_duty = val; if (isStreaming) enable_led(true); } #endif ... static esp_err_t status_handler(httpd_req_t *req) { ... #ifdef CONFIG_LED_ILLUMINATOR_ENABLED p += sprintf(p, ",\"led_intensity\":%u", led_duty); #else p += sprintf(p, ",\"led_intensity\":%d", -1); #endif ...

Ainsi, lorsqu'on ajuste la valeur du contrôle LED Intensity dans l'interface web, c'est la valeur de la variable led_duty qui est modifiée. Ultimement, la fonction enable_led contrôle l'activation de la DEL.

#ifdef CONFIG_LED_ILLUMINATOR_ENABLED void enable_led(bool en) { // Turn LED On or Off int duty = en ? led_duty : 0; if (en && isStreaming && (led_duty > CONFIG_LED_MAX_INTENSITY)) { duty = CONFIG_LED_MAX_INTENSITY; } ledc_set_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL, duty); ledc_update_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL); ESP_LOGI(TAG, "Set LED intensity to %d", duty); } #endif

Cette fonction contient plusieurs macros dont CONFIG_LED_LEDC_CHANNEL et CONFIG_LED_MAX_INTENSITY dont il faut fixer les valeurs puisqu'elles ne sont pas définies dans les fichiers du projet. Le nom de la première macro et la fonction ledc_set_duty dévoilent que l'intensité de la DEL flash est contrôlée par la modulation de largeur d'impulsions (en court, MLI ou PWM pour Pulse Width Modulation selon Shakespeare). La MLI est réalisée par des composants matériels du ESP32 et non par émulation comme sur le ESP8286. Il y a un choix de 8 canaux LEDC (LED Control) pour engendrer un signal MLI sur presque n'importe quelle entrée/sortie du ESP (voir LED Control (LEDC)). Clairement CONFIG_LED_MAX_INTENSITY est un plafond pour protéger la DEL qui, allumée à pleine puissance, dégage beaucoup de chaleur pouvant diminuer sa durée de vie et nuire au bon fonctionnement du dispositif. Quant à la macro CONFIG_LED_LEDC_SPEED_MODE égale à LEDC_HIGH_SPEED_MODE, elle signifie que la MLI est réalisé par un périphérique matériel et pas par émulation.

Pour terminer cette exploration du code de prise en charge du flash, soulignons que ce dernier est activé automatiquement lors de la capture de clichés au format JPEG et lors de la diffusion du flux vidéo en continu.

static esp_err_t capture_handler(httpd_req_t *req) { ... #ifdef CONFIG_LED_ILLUMINATOR_ENABLED enable_led(true); vTaskDelay(150 / portTICK_PERIOD_MS); // The LED needs to be turned on ~150ms before the call to esp_camera_fb_get() fb = esp_camera_fb_get(); // or it won't be visible in the frame. A better way to do this is needed. enable_led(false); #else fb = esp_camera_fb_get(); #endif ... static esp_err_t stream_handler(httpd_req_t *req) { ... #ifdef CONFIG_LED_ILLUMINATOR_ENABLED enable_led(true); isStreaming = true; #endif ...

Malheureusement, tout cet outillage ne donne aucun résultat pratique, car le canal LEDC n'est jamais initialisé et l'entrée/sortie connectée à la DEL flash n'est jamais spécifiée. Grâce a Owen Carter, il a été relativement facile de voir comment initialiser le canal LEDC dans la fonction setup.

#define CONFIG_LED_ILLUMINATOR_ENABLED // Enables the flash LED #define CONFIG_FLASH_PWM_FREQ 50000 // 50 KHz pulse width modulation frequency #define CONFIG_FLASH_PWM_BITS 8 // Duty cycle bit range 0-255 (= 2^8-1) #define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_7 // Channel < 8 but 0 is used by camera #define CONFIG_FLASH_LED 4 // White flash LED is controlled with GPIO 4 on ESP32-CAM void setup() { ... #if defined(CONFIG_LED_ILLUMINATOR_ENABLED) ledcSetup(CONFIG_LED_LEDC_CHANNEL, CONFIG_FLASH_PWM_FREQ, CONFIG_FLASH_PWM_BITS); ledcAttachPin(CONFIG_FLASH_LED, CONFIG_LED_LEDC_CHANNEL); Serial.println("Flash LED configured."); #endif // defined(CONFIG_LED_ILLUMINATOR_ENABLED) Serial.println("Camera Ready!"); ...

Il faut aussi modifier app_httpd.cpp. On remplace les fonctions ledc_set_duty et ledc_update_duty (déclarés dans ledc.h)) avec ledcWrite (déclarée dans esp32-hal-ledc.h) et on y ajoute trois macros. Il faut aussi remplacer ledc.h par esp32-hal-ledc.h parmi les fichiers d'en-têtes à inclure.

... //#include "driver/ledc.h" #include "esp32-hal-ledc.h" ... #define CONFIG_LED_ILLUMINATOR_ENABLED // Enables the flash LED #define CONFIG_LED_MAX_INTENSITY 255 // Must less or equal to 255 using 8 bit duty counter #define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_7 // Channel < 8 but 0 is used by camera ... #ifdef CONFIG_LED_ILLUMINATOR_ENABLED void enable_led(bool en) { // Turn LED On or Off int duty = en ? led_duty : 0; if (en && isStreaming && (led_duty > CONFIG_LED_MAX_INTENSITY)) { duty = CONFIG_LED_MAX_INTENSITY; } ledcWrite(CONFIG_LED_LEDC_CHANNEL, duty); ESP_LOGI(TAG, "Set LED intensity to %d", duty); } #endif ...

Après tout ce bricolage, le flash fonctionne.

Mais comme c'est laid. Des macros répétées sont dans deux fichiers et la valeur maximum d'une macro dépend d'une macro dans un autre fichier. Un programmeur serait horrifié, on n'a qu'à penser aux problèmes pour un tiers qui voudrait changer une de ces valeurs. Un utilisateur serait aussi déçu. Le règlement linéaire de l'intensité du flash n'est pas naturel. En plus, si l'on fixe CONFIG_LED_MAX_INTENSITY sur une valeur plus faible comme 128 par exemple, alors la moitié du déplacement du contrôle dans l'interface web n'au aucun effet ce qui est très déconcertant.

Le projet final élimine ces problèmes, espérons, de façon élégante. Avant de le présenter, voici un détour ou deux au sujet de la façon de régler l'intensité du flash.

Réalisation de la modulation de largeur d'impulsion toc

De prime abord, il ne semble pas qu'il ne soit pas possible de moduler l'intensité lumineuse d'une DEL alimentée par un signal digital qui ne peut avoir que l'une de deux valeurs : 0 volts (OFF ou 0) ou 3,3 volts (ON ou 1). Or la DEL est perçue comme allumée en permanence, mais à une intensité plus faible, s'il y a une alternance rapide des valeurs du signal digital. En fait, on exploite la persistance rétinienne qui fait qu'à partir d'une certaine fréquence on ne distingue plus les périodes allumées des périodes éteintes. L'oeil demeure quand même sensible à la quantité totale de lumière qui atteint la rétine et on peut donc modifier l'intensité perçue de la DEL en variant le ratio des durées des états allumés et éteints.

rapport cyclique 75, 50 et 25%

Le rapport cyclique (duty cycle) est le ratio de la durée de la partie active du signal à la durée totale d'un cycle. En d'autres mots c'est la durée de l'état allumé divisée par la somme des durées de l'état allumé et de l'état éteint qui le suit.

réalisation matérielle d'une sortie MLI

Le schéma ci-dessus est une représentation simplifiée de la réalisation matérielle d'une sortie MLI. L'horloge est un signal périodique à haute fréquence (supérieur à 20 KHz pour éviter la gamme audible) qui alimente le circuit. Ce signal est généralement dérivé de l'horologe du microcontrôleur ou d'un oscillateur externe. La valeur du compteur augmente d'un à chaque descente du signal de l'horloge. Un compteur de 10 bits, comme dans la figure, peut compter de 0 à 1023 (= [2^10]-1), avant de recommencer à 0. La sortie du compteur est connectée à deux comparateurs. Le premier est activé quand le compte est 0. Ce signal est connecté à l'entrée S d'une bascule dont la sortie sera alors égale à 1. Le second comparateur le second est activé quand le compte est égal à la valeur dans le registre. La sortie de ce comparateur est connectée à l'entrée R de la bascule et quand cette dernière est activée la sortie de la bascule descend à 0. On contrôle la durée de la partie active de la sortie MLI en modifiant la valeur dans le registre du second comparateur. Pour obtenir un rapport cyclique de 50% on stocke 512 dans le registre (512/1024 = 0.5), alors qu'un rapport de 25% est obtenu avec 1024/4 = 256 et ainsi de suite. Clairement, plus le nombre de bits du compteur est élevé plus la fixation du rapport cyclique sera précise. Plus l'horloge est rapide, plus précise sera la période du signal MLI.

Voici une bonne référence sur le principe de fonctionnement PWM : Modulation de Largeur d’Impulsion de Pierre-Yves Rochat, École Polytechnique Fédérale de Lausanne (2015-07-19, consulté le 2021-12-28).

On retrouve tous ces éléments dans la configuration du canal LEDC du ESP32 utilisé pour le flash.

ledcSetup(CONFIG_LED_LEDC_CHANNEL, CONFIG_FLASH_PWM_FREQ, CONFIG_FLASH_PWM_BITS); ledcAttachPin(CONFIG_FLASH_LED, CONFIG_LED_LEDC_CHANNEL);

Il y a huit canaux ou périphériques LEDC, il faut en choisir un, mais on ne peut pas utiliser le canal 0 qui est utilisé par la caméra. Le canal 7 est disponible sur le ESP32-CAM.

#define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_7

Quand à la fréquence de l'horloge elle a été fixée à 50 KHz.

#define CONFIG_FLASH_PWM_FREQ 50000

Un compteur de 8 bits qui correspond à l'intensité du flash (0 à 255) dans l'interface web a été choisi.

#define CONFIG_FLASH_PWM_BITS 8

Il ne restait plus qu'à associer la bonne entrée/sortie, celle qui est reliée à la DEL au périphérique LEDC. Enfin, quand on fixe le rapport cyclique dans la fonction enable_led, c'est la valeur du registre du comparateur qui est spécifié avec la fonction

ledcWrite(CONFIG_LED_LEDC_CHANNEL, duty);

Si duty = 0 alors la DEL demeure éteinte et si duty = 255 alors la DEL est a pleine intensité. Voilà pour la « mécanique », mais il y a un problème de perception. Si duty est à 128, la tension moyenne de la DEL est réduite de la moitié par rapport à la tension maximum, mais à l'œil on n'a pas la sensation que l'intensité est réduite de la moyenne. C'est une caractéristique de la physiologie humaine que la perception n'est pas une fonction linéaire de l'intensité d'un stimulus.

Ajustement du rapport cyclique toc

Encore une fois, Owen Carter (easytarget) est notre guide. Voici comment il ajuste la valeur du paramètre de la fonction ledcWrite qui fixe rapport cyclique du signal MLI.

// Lamp Control void setLamp(int newVal) { if (newVal != -1) { // Apply a logarithmic function to the scale. int brightness = round((pow(2,(1+(newVal*0.02)))-2)/6*pwmMax); ledcWrite(lampChannel, brightness); Serial.print("Lamp: "); Serial.print(newVal); Serial.print("%, pwm = "); Serial.println(brightness); } }

Source : esp32-cam-webserver.ino, lignes 247-257.

La fonction qui transforme NewVal (un chiffre entre 0 et 100) en pour obtenir le rapport cyclique brightness est plutôt rébarbative. En plus, il faut faire attention, car c'est l'échelle qui est transformée de façon logarithmique, brightness est une fonction exponentielle de newVal. Cependant, j'ai bien vu que cette transformation est bien supérieure à une fonction linéaire en testant la version du serveur vidéo d'easytarget. Toutefois, y a-t-il une façon plus simple d'arriver à un résultat semblable? Dans la section Finding the not-so-ideal-but-still-quite-good curve d'un article intitulé Programming Volume Controls, Alexander Thomas dit que la transformation y = x4 est très proche de l'idéal qui serait y = abx + c (où a, b et c sont des paramètres à déterminer, mais que clairement il faut que c = -1 si la courbe doit passer par l'origine) quand il s'agit de fixer le volume. J'ai essayé cette approximation, mais le résultat était décevant, car le flash était presque éteint sur plus d'un quart de la plage du contrôle web de l'intensité. Dr. Lex dit qu'on pourrait préférér x3 ou x5. Il était temps de s'amuser un peu avec les mathématiques.

graphes de x^2, x^3 et x^3graphes de la transformation de Carter

Les puissances de x figurent sur le premier graphique. La diagonale bleue est la transformation linéaire y=x qu'on veut remplacer. On peut comparer ces puissances à la fonction de Owen Carter sur la figure à droite (ou dessous selon la largeur de l'écran). En fait le graphe qu'on voit est la fonction est normalisée (car newVal prend des valeurs entre 0 et 100 et x des valeurs entre 0 et 1). On voit clairement que la valeur de y=x4 est beaucoup trop proche de 0 lorsque x est inférieur à 0,3. La fonction y=x4 s'approche le plus de la fonction de Carter, mais elle augmente encore trop lentement au début. La simplicité du carré est difficile à battre. J'ai donc décidé de faire une translation vers la gauche pour se servir d'une partie un peu plus abrupte de la courbe. Il faut aussi faire une translation vers le bas pour que la courbe passe par l'origine et enfin il faut appliquer une constante multiplicative pour que la courbe passe par le point (1; 1) :

y=[ (x+a)<sup>2</sup> - a<sup>2</sup> ] / [ (1 + a)<sup>2</sup> - a<sup>2</sup>]

Clairement, qu'importe a, y=0 si x=0 et y=1 quand x=1. Le truc est de trouver la valeur optimale de a. J'ai choisi a=0,3 et comme on peut voir le résultat est assez proche de la fonction désirée tout en étant plus simple.

comparaison graphique des deux fonctions

Malheureusement, en éliminant l'inclusion de la bibliothèque math.h la taille du micrologiciel n'est pas réduite. C'était une des motivations pour éviter la fonction d'Owen Carter. L'autre objectif était que la transformation soit plus rapidement calculée. Un petit test a montré sans aucun doute que la nouvelle fonction est beaucoup plus rapide, mais ce calcul est fait si peu souvent que tout gain d'efficacité n'est probablement pas significatif.

Si la question de la transformation d'un ajustement linéaire vers une fonction exponentielle qui correspond mieux aux perceptions humaines est d'intérêt, il y a une autre famille de fonctions qui vaut la peine d'être examinée:

   y = (ax - 1)/(a - 1),   a > 1.

Évidemment quand a = 4, on a la fonction de Carter, mais avec des valeurs supérieures à 4, on obtient une courbe plus bombée qui, néanmoins, se comporte relativement bien autour de l'origine.

Pour ceux qui préféraient la fonction d'Owen Carter (easytarget), elle est activée en définissant la directive CONFIG_EASYTARGET_INTENSITY_SCALING au début du fichier app_httpd.cpp. En fin de compte, la bibliothèque math.h est incluse car la fonction pow facilite grandement le calcul de pwmMax sans accroître la taille du micrologiciel.

Projet PlatformIO/Arduino modifié toc

Le référentiel CameraWebServer-YAF sur GitHub contient toutes les modifications décrites ici. Mon objectif était de modifier le moins possible les fichiers originaux d'Espressif, mais il y a quand même d'assez nombreux changements dont la plupart ne sont pas très importants. Quant aux ajouts dont il est question ci-dessus je voulais qu'ils soient facultatifs. Cet objectif est réalisé avec l'addition d'un fichier de configuration, nommé config.h. C'est le seul fichier où l'on doit apporter des modifications, le reste du code est fixe. Après quelques faux départs, j'ai décidé que la meilleure façon d'accommoder les deux environnements était de créer un projet PlatformIO dont le répertoire src était remplacé par un répertoire nommé CameraWebServer.

| ├── CameraWebServer │ ├── app_httpd.cpp - serveur web │ ├── camera_index.h - pages HTML de l'interface web │ ├── camera_pins.h - définitions du brochage de la caméra selon la carte de développement │ ├── CameraWebServer.ino - croquis Arduino, maintenant vide, voir main.cpp │ ├── config.h - configuration à ajuster │ └── main.cpp - source réelle du croquis avec les fonctions setup() et loop() ├── include │ └── README ├── lib │ └── README ├── test │ └── README ├── CHANGELOG.md ├── LICENSE ├── platformio.ini - sélection de la carte ESP32. Se fait avec le menu Outils dans l'EDI Arduio └── README.md

Le fichier CameraWebServer.ino ne contient plus qu'un commentaire, car son contenu original a été versé dans le fichier main.cpp. Avec ce stratagème, les exigences de l'EDI Arduino concernant le nom du croquis et du répertoire le contenant sont satisfaites. Puisqu'il n'y a aucune raison d'ouvrir CameraWebServer.ino dans PlatformIO, on ne verra plus le message d'avertissement du service C/C++ IntelliSense au sujet des problèmes associés aux fichiers .ino.

Ceux qui préfèrent l'EDI Arduino n'ont besoin que du dossier CameraWebServer. On peut obtenir la plus récente archive .zip contenant uniquement ce répertoire depuis la page Releases du référentiel. On obtient facilement l'archive avec la commande wget, c'est un peu plus laborieux avec curl

michel@hp:~$ wget "https://github.com/sigmdel/CameraWebServer-YAF/releases/download/v1.2.1/CameraWebServer-arduino-1-2-1.zip" michel@hp:~$ curl -L "https://github.com/sigmdel/CameraWebServer-YAF/releases/download/v1.2.1/CameraWebServer-arduino-1-2-1.zip" -o CameraWebServer-arduino-1-2-1.zip

Les personnes qui sont plus à l'aise dans l'environnement PlatformIO trouveront l'arborescence complète du projet dans la même page. Pour obtenir la plus récente version du référentiel, elles devraient plutôt créer un clone du projet ou obtenir une archive zip selon les instructions affichées quand on active le bouton Code sur la page d'acceuil du référentiel.

Dès que le projet est ajouté à l'environnement de programmation de son choix, on peut le compiler. Il y a quelques explications sur la procédure à suivre dans PlatformIO et dans l'EDI Arduino. La compilation devrait se compléter sans erreurs, mais le binaire résultant ne fonctionnera pas tant que le projet n'est pas configuré correctement. Puisque e fichier de configuration est un peu long les explications à son sujet sont scindées en deux.

Configuration minimale toc

Il y a trois éléments qui doivent obligatoirement être spécifiés correctement.

  1. Le modèle de la carte de développement avec caméra.
  2. Le nom du réseau Wi-Fi auquel se branchera le serveur Web.
  3. Le mot de passe du réseau Wi-Fi.

Il faut choisir une et seulement une carte de développement parmi la liste ce qui est fait en enlevant les deux obliques // devant la directive #define appropriée.

// Sélectionner un et seulement un module // Obligatoire // //#define CAMERA_MODEL_WROVER_KIT //has PSRAM //#define CAMERA_MODEL_ESP_EYE //has PSRAM #define CAMERA_MODEL_AI_THINKER //has PSRAM //#define CAMERA_MODEL_M5STACK_PSRAM //has PSRAM (M5Camera version A?) //#define CAMERA_MODEL_M5STACK_V2_PSRAM //has PSRAM (M5Camera version B?) //#define CAMERA_MODEL_M5STACK_WIDE //has PSRAM (M5CameraF? ) //#define CAMERA_MODEL_M5STACK_ESP32CAM //no PSRAM //#define CAMERA_MODEL_M5STACK_UNITCAM //no PSRAM //#define CAMERA_MODEL_TTGO_T_JOURNAL //no PSRAM //#define CAMERA_MODEL_CUSTOM_CAM

Le but de cette sélection, qui se faisait dans le fichier CameraWebServer dans l'exemple initial, est de définir les entrées/sorties du ESP32 affectées à la caméra. Les connexions sont définies dans le fichier camera_pins.h. Cette liste contient des modules qui ne sont plus disponibles. Sans doute existe-t-il de nouveaux modèles qui ne sont pas dans cette liste. Peut-être que le brochage de la caméra sur une nouvelle carte est le même que sur l'une de ces anciennes cartes et on pourra alors utiliser ce modèle. Autrement on peut choisir CAMERA_MODEL_CUSTOM_CAM et définir plus loin ses entrées/sorties.

Après avoir sélectionné le modèle, il faut définir des paramètres particuliers pour celui-ci. Voici ces valeurs pour le ESP32-CAM de AI Thinker et plusieurs de ses imitateurs.

#if defined(CAMERA_MODEL_AI_THINKER) #define LED_BUILTIN 33 #define LED_BUILTIN_ON LOW // Flash LED configuration #define CONFIG_FLASH_LED 4 #define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_7 // Channel 0 is used by camera // Paramètres de la caméra au lancement #define CONFIG_DEFAULT_RESOLUTION FRAMESIZE_SVGA // 800x600 #define CONFIG_DEFAULT_QUALITY 4 //#define CONFIG_V_FLIP //#define CONFIG_H_MIRROR #endif

La macro LED_BUILTIN qui identifie l'entrée/sortie affectée à une DEL témoin est souvent déjà spécifiée dans la définition de la carte. Ce n'est pas le cas pour le ESP32-CAM. On allume la DEL avec un signal LOW. À noter que ces deux macros ne sont pas utilisées dans le croquis. Après viennent les paramètres pour la DEL qui sert de flash photographique. Tout cela a été expliqué ci-dessus, inutile d'y revenir. Enfin il y a quatre directives qui contrôlent la résolution de la caméra, la qualité de l'image et l'orientation physique de la caméra. Voici la liste des valeurs possibles de la macro CONFIG_DEFAULT_RESOLUTION.

Résolutions avec la caméra OV2640 Autres résolutions avec capteurs 3MP FRAMESIZE_96X96, // 96x96 FRAMESIZE_FHD, // 1920x1080 FRAMESIZE_QQVGA, // 160x120 FRAMESIZE_P_HD, // 720x1280 FRAMESIZE_QCIF, // 176x144 FRAMESIZE_P_3MP, // 864x1536 FRAMESIZE_HQVGA, // 240x176 FRAMESIZE_QXGA, // 2048x1536 FRAMESIZE_240X240, // 240x240 FRAMESIZE_QVGA, // 320x240 Autres résolutions avec capteurs 5MP FRAMESIZE_CIF, // 400x296 FRAMESIZE_QHD, // 2560x1440 FRAMESIZE_HVGA, // 480x320 FRAMESIZE_WQXGA, // 2560x1600 FRAMESIZE_VGA, // 640x480 FRAMESIZE_P_FHD, // 1080x1920 FRAMESIZE_SVGA, // 800x600 FRAMESIZE_QSXGA, // 2560x1920 FRAMESIZE_XGA, // 1024x768 FRAMESIZE_HD, // 1280x720 FRAMESIZE_SXGA, // 1280x1024 FRAMESIZE_UXGA, // 1600x1200

Je n'ai pas d'autre type de modules ESP32 avec caméra alors je n'ai pas ajouté des blocs de configuration spécifique pour chaque modèle. Toutefois, il y a celui-ci

// Configuration spécifique à chaque carte #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM) // Paramètres de la caméra au lancement //#define CONFIG_DEFAULT_RESOLUTION FRAMESIZE_SVGA // 800x600 //#define CONFIG_DEFAULT_QUALITY 4 #define CONFIG_V_FLIP #define CONFIG_H_MIRROR #endif

parce qu'il était auparavant dans CameraWebServer.ino, et il y a aussi celui pour la carte personnalisée.

#if defined(CAMERA_MODEL_CUSTOM_CAM) // configuration personnalisée des broches de la caméra, celle de l'ESP32-CAM est montrée #define CONFIG_CAMERA_PIN_PWDN 32 #define CONFIG_CAMERA_PIN_RESET -1 #define CONFIG_CAMERA_PIN_XCLK 0 #define CONFIG_CAMERA_PIN_SIOD 26 #define CONFIG_CAMERA_PIN_SIOC 27 #define CONFIG_CAMERA_PIN_Y9 35 #define CONFIG_CAMERA_PIN_Y8 34 #define CONFIG_CAMERA_PIN_Y7 39 #define CONFIG_CAMERA_PIN_Y6 36 #define CONFIG_CAMERA_PIN_Y5 21 #define CONFIG_CAMERA_PIN_Y4 19 #define CONFIG_CAMERA_PIN_Y3 18 #define CONFIG_CAMERA_PIN_Y2 5 #define CONFIG_CAMERA_PIN_VSYNC 25 #define CONFIG_CAMERA_PIN_HREF 23 #define CONFIG_CAMERA_PIN_PCLK 22 // Configuration de la DEL flash LED //#define CONFIG_FLASH_LED 4 //#define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_7 // channel 0 is used by camera // Paramètres de la caméra au lancement //#define CONFIG_DEFAULT_RESOLUTION FRAMESIZE_SVGA // 800x600 //#define CONFIG_DEFAULT_QUALITY 4 //#define CONFIG_V_FLIP //#define CONFIG_H_MIRROR #endif

Pour vérifier cette définition et l'ajout fait à camera_pin.h, le brochage de ESP32-CAM est utilisé ci-dessus.

Après le choix de la carte de développement, il reste à définir les coordonnées du réseau Wi-Fi.

// Tronc commun de la configuration #define CONFIG_WIFI_SSID "nom-du-réseau-wifi" // Obligatoire #define CONFIG_WIFI_PWD "mot-de-passe-du-wifi" // Obligatoire

Après ces trois étapes, il devrait être possible de compiler et de téléverser le micrologiciel sur le ESP32 et d'accéder à l'interface web. On aura alors le même micrologiciel que si l'on avait compilé l'exemple initial. Les directives qui contrôlent les ajouts à l'exemple initial sont présentées dans la section suivante.

Configuration facultative toc

On pourrait certainement modifier la vitesse de transmission du port sériel du ESP32, mais presque toujours vitesse par défaut est un 115200 bauds. Dans l'environnement PlatformIO il faut fixer la valeur du paramètre monitor_port sur la même valeur dans le fichier de configuration platformio.ini alors que dans le moniteur série de l'EDI Arduino cette valeur est choisie dans une liste déroulante au bas de la fenêtre. La deuxième directive du bloc de configuration facultative fixe la priorité des messages journalisés. Par défaut, seuls les erreurs et les messages d'une plus haute priorité sont envoyés vers le port sériel du ESP32. En temps d'utilisation normale de la caméra, il n'y a pas de raison de changer cette valeur, mais j'ai trouvé utile d'abaisser la priorité d'un niveau pour mieux saisir ce qui se produisait quand le flash était activé. On a vu qu'il était possible de prendre des clichés au format JPEG et au format BMP. N'ayant aucune raison d'utiliser ce deuxième format, j'ai rajouté la directive CONFIG_BMP_CAPTURE_DISABLED pour éliminer cette possibilité. Par défaut elle n'est pas activée et la capture au format BMP demeure possible.

#define CONFIG_BAUD 115200 // PlatformIO: fixer monitor_speed à la même valeur //#define CORE_DEBUG_LEVEL ARDUHAL_LOG_LEVEL_INFO // Facultatif. Valeur par défaut: ARDUHAL_LOG_LEVEL_ERROR, voir https://thingpulse.com/esp32-logging/ //#define CONFIG_BMP_CAPTURE_DISABLED // Facultatif. Par défaut la capture de clichés au format .BMP est activée //#define CONFIG_STATIC_IP_ENABLED // Facultatif. Autrement l'adresse IP est obtenue du serveur DHCP //#define CONFIG_SHOW_NETWORK_PARAMS // Facultatif //#define CONFIG_MDNS_ADVERTISE_ENABLED // Facultatif. Si mDNS est activé, le nom local par défaut est "esp32-cam.local" //#define CONFIG_ESP_FACE_DETECT_ENABLED // Facultatif. Fonctionne avec un résolution ≤ 320x240 //#define CONFIG_ESP_FACE_RECOGNITION_ENABLED // Facultatif. Fonctionne avec un résolution ≤ 320x240

Les autres directives correspondent aux ajouts dont il a été question ci-dessus.

Enfin, si l'on utilise un module caméra avec une DEL flash et que la macro CONFIG_FLASH_LED a été définie dans un bloc de configuration spécifique à la carte alors on peut modifier les paramètres du signal MLI qui contrôle l'intensité de la DEL.

#if defined(CONFIG_FLASH_LED) #define CONFIG_LED_ILLUMINATOR_ENABLED #define CONFIG_FLASH_PWM_FREQ 50000 // Fréquence du signal MLI de la DEL flash #define CONFIG_FLASH_PWM_BITS 9 // Résolution du compteur du rapport cyclique #define CONFIG_LED_MAX_INTENSITY 100 // Un percentage (0..100) de l'intensité maximum #if !defined(CONFIG_LED_LEDC_CHANNEL) #error "Must specify LEDC channel" #endif #endif

Bien sûr que la fréquence du signal ainsi que la résolution du compteur de la durée de la phase allumée de la DEL peuvent être ajustés, mais je n'ai pas encore senti le besoin de le faire. En revanche, on voudra peut-être diminuer la valeur de la macro CONFIG_LED_MAX_INTENSITY si le flash est allumé pendant la diffusion d'un flux vidéo de longue durée pour éviter la surchauffe. Il y a une longue discussion au sujet du flash ci-dessus: 6. Prise en charge du flash.

Note toc

Dans un encadré de la section sur la détection et la reconnaissance des visages, il est clairement souligné que la bibliothèque utilisée pour ces fonctions n'est plus inclue dans les versions plus récentes du noyau ESP32-Arduino. Il y a un autre changement du noyau dont il faut tenir compte. Depuis la version 2.0.0 un nouveau champ a été ajouté à la structure camera_config_t définie dans esp_camera.h. Appelé grab_mode, il contrôle la façon dont les images sont extraites du tampon d'images. Par défaut, il est défini sur CAMERA_GRAP_WHEN_EMPTY, ce qui signifie vraisemblablement qu'une capture d'image n'est effectuée qu'une fois le tampon vide. Étant donné que le tampon contient deux images par défaut, obtenir une prise d'image avec la caméra peut demandé jusqu'à trois clics du bouton Get Still dans l'interface web ou trois requêtes de type http://<cam-ip>:80/capture ou http://<cam-ip>:80/bmp. Ce problème est réglé en modifiant la valeur de grab_mode vers CAMERA_GRAB_LATEST (voir la réponse de me-no-dev).

Puisque le noyau encore utilisé dans PlatformIO est la version 1.0.6 et que le champ grab_mode n'existait pas alors, il fallait appliquer la solution de façon sélective.

void setup() { ... config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; #if !defined(NO_GRAB_MODE) config.grab_mode = CAMERA_GRAB_LATEST; // https://github.com/espressif/arduino-esp32/issues/5805#issuecomment-951861112 Serial.println("Camera buffer grab mode set to latest image"); #endif ...

La paresse était de rigueur lorsqu'est venu le temps de définir la macro NO_GRAB_MODE dans config.h.

#if defined(ARDUINO_ESP32_RELEASE_1_0_6) #define NO_GRAB_MODE #endif

Cette définition suppose que ce projet ne sera jamais compilé avec une version du noyau ESP32-Arduino plus ancien que 1.0.6. Or CameraWebServer existait dans les versions 1.0.5 et 1.0.4 du noyau. L'exemple était peut-être disponible dans des noyaux encore plus vieux; je n'ai pas vérifié. Si la compilation du projet échoue avec un message disant que grab_mode n'existe pas, il faudra ajuster la directive NO_GRAB_MODE ou passer à un noyau plus récent.

<-Présentation du module ESP32-CAM