Gestion de la caméra et finalisation du programme
La bibliothèque de gestion de la caméra a déjà été installée dans l’environnement de développement lors de l’installation de la carte ESP32-CAM pour l’Arduino IDE. Il reste simplement à la déclarer par l’instruction:
#include <esp_camera.h>
La prise de photo avec l’ESP32-CAM passe par 3 étapes:
- Configuration de la caméra au démarrage de programme
- Capture de l’image
- Libération de la mémoire
Configuration de la caméra
La configuration de la caméra pour la photo est similaire à la configuration pour la vidéo précédemment décrite dans le tutoriel Caméra Wi-Fi ESP32-CAM avec Arduino. Nous changeons simplement la résolution pour le format UXGA (1600×1200) qui est le maximum de la caméra OV2640, soit:
ConfigurationCamera_L.frame_size = FRAMESIZE_UXGA;
Le code logiciel obtenu pour la configuration de la caméra est placé dans une nouvelle fonction nommée “camera_demarre”:
esp_err_t camera_demarre() { // Variables locales de la fonction esp_err_t Retour_L; camera_config_t ConfigurationCamera_L; // Cablage de la caméra sur l'ESP32-CAM du fabricant AI-Thinker ConfigurationCamera_L.pin_d0 = 5; ConfigurationCamera_L.pin_d1 = 18; ConfigurationCamera_L.pin_d2 = 19; ConfigurationCamera_L.pin_d3 = 21; ConfigurationCamera_L.pin_d4 = 36; ConfigurationCamera_L.pin_d5 = 39; ConfigurationCamera_L.pin_d6 = 34; ConfigurationCamera_L.pin_d7 = 35; ConfigurationCamera_L.pin_xclk = 0; ConfigurationCamera_L.pin_pclk = 22; ConfigurationCamera_L.pin_vsync = 25; ConfigurationCamera_L.pin_href = 23; ConfigurationCamera_L.pin_sccb_sda = 26; ConfigurationCamera_L.pin_sccb_scl = 27; ConfigurationCamera_L.pin_pwdn = 32; ConfigurationCamera_L.pin_reset = -1; // La génération du signal d'horloge ConfigurationCamera_L.ledc_channel = LEDC_CHANNEL_0; ConfigurationCamera_L.ledc_timer = LEDC_TIMER_0; ConfigurationCamera_L.xclk_freq_hz = 20000000; // Compression jpeg ConfigurationCamera_L.pixel_format = PIXFORMAT_JPEG; ConfigurationCamera_L.fb_count = 2; ConfigurationCamera_L.jpeg_quality = 10; // Résolution de l'image ConfigurationCamera_L.frame_size = FRAMESIZE_UXGA; // Lancement de l'initialisation de la caméra Retour_L=esp_camera_init(&ConfigurationCamera_L); if (Retour_L == ESP_OK) { Serial.println("La camera est initialiseen"); } else { sprintf(tampon_traces_g, "Erreur 0x%x lors de l'initialisation de la cameran", Retour_L); Serial.println(tampon_traces_g); } return(Retour_L); }
Capture de l’image
La prise d’une photo par l’ESP32-CAM est permise par l’instruction “esp_camera_fb_get” qui s’utilise sans paramètre. Elle retourne l’adresse de la zone mémoire dans laquelle a été enregistrée la photo:
pTamponVideo_L = esp_camera_fb_get();
Il est donc nécessaire d’avoir déclaré au préalable un pointeur pour mémoriser l’adresse de cette zone mémoire.
camera_fb_t *pTamponVideo_L = NULL;
Libération de la mémoire
La mémoire vive de l’ESP32-CAM est limitée. Une fois la photo copiée dans un fichier jpg sur la carte Micro SD il est important de libérer cette espace par l’instruction “esp_camera_fb_return” avant de prendre la photo suivant:
esp_camera_fb_return(pTamponVideo_L);
Enregistrement des photos en fichiers .jpg.
Pour l’enregistrement des photos en fichiers « .jpg », plusieurs modifications du code logiciel sont à réaliser.
La première est la modification de l’extension du fichier dans la fonction donner_nom_fichier.
void donner_nom_fichier(char* nom_fichier_P, int index_P) { sprintf(nom_fichier_P, "/photo_%05d.jpg", index_P); }
Ensuite la fonction « photographier_et_enregistrer » est créée pour y ajouter:
- Les instructions de prises de photos vues ci-dessus.
- Les instructions d’écriture de fichiers.
Pour l’enregistrement dans un fichier de la photo stockée en mémoire, la méthode “write“ est utilisée. Cette méthode prend en paramètre l’adresse de début et la taille des données de la zone mémoire à copier.
Les instructions …
sprintf(contenu_fichier_l, "Contenu du fichier \'%s\'.", nom_fichier_l+1); fichier_l.println(contenu_fichier_l);
… sont remplacées par:
fichier_l.write(pTamponVideo_L->buf, pTamponVideo_L->len)
Mesure des performances
Pour mesurer la performance d’écriture des photos par protocole SPI, nous allons simplement:
- Démarrer un chronomètre après l’initialisation de la caméra.
- Relever sa valeur à l’issue de la prise des 15 photos et de leur écriture sur carte Micro SD.
La bibliothèque Arduino “elapsedMillis” de “Paul Stoffregen” offre le type “elapsedMillis” qui permet de répondre simplement à ce besoin. Il est nécessaire de l’installer dans l’environnement Arduino IDE avant de l’utiliser dans ce programme.
Une fois déclarée la bibliothèque
#include <elapsedMillis.h>
et le chronomètre,
static elapsedMillis chronometre_g;
on démarre celui-ci par une mise à zéro.
chronometre_g = 0;
Son relevé s’effectue par une simple lecture de la variable déclarée:
sprintf(tampon_traces_g, "---\n%d photos prises en %lu ms.", NOMBRE_PHOTOS, (unsigned long )chronometre_g); Serial.println(tampon_traces_g);
Code complet
Nous arrivons à la fin du tutoriel. Voici le code complet:
/* Déclaration des librairies utilisées */ #include <SD.h> #include <esp_camera.h> #include <elapsedMillis.h> /* Définition des constantes globaless */ // Brochage lecteur de cartes: #define PORT_CARD_SCLK 14 #define PORT_CARD_MISO 2 #define PORT_CARD_MOSI 15 #define PORT_CARD_CS 13 // Autres constantes #define NOMBRE_PHOTOS 15 /* Déclaration des fonctions */ bool SD_demarre(int port_CLK_p, int port_MISO_p, int port_MOSI_p, int port_CS_p); void donner_nom_fichier(char* nom_fichier_P, int index_P); int donner_prochain_index_fichier(void); esp_err_t camera_demarre(void); bool photographier_et_enregistrer(int index_fichier_P); /* Déclaration globales */ SPIClass hspi_G(HSPI); char tampon_traces_g[120]; static elapsedMillis chronometre_g; // Fonction de démarrage, s'exécute une seule fois: void setup() { int ii_l; int prochain_index_fichier_l; bool resultat_l = true; File fichier_l; // Pour le debug Serial.begin(115200); Serial.println("-----\n"); // Initialisation de la carte SD while(!SD_demarre(PORT_CARD_SCLK, PORT_CARD_MISO, PORT_CARD_MOSI, PORT_CARD_CS)) { // On bloque la poursuite du programme tant qu'une carte SD correcte n'a pas été insérée delay(1000); } // Initialisation de la caméra camera_demarre(); // On laisse la caméra chauffer 1 seconde delay(1000); // Parcourt les fichiers d'enregistrement déjà présents prochain_index_fichier_l = donner_prochain_index_fichier(); // Demarrage du chrono chronometre_g = 0; // Lancement de la série de prise de photos et d'enregistrement sur carte SD for(ii_l=0; (ii_l<NOMBRE_PHOTOS)&&resultat_l; ii_l++) { resultat_l = photographier_et_enregistrer(prochain_index_fichier_l+ii_l); } // Fermeture de la carte SD SD.end(); // PErformances sprintf(tampon_traces_g, "---\n%d photos prises en %lu ms.", NOMBRE_PHOTOS, (unsigned long )chronometre_g); Serial.println(tampon_traces_g); } // Fonction principale du programme, s'exécute en boucle: void loop() { } bool SD_demarre(int port_CLK_p, int port_MISO_p, int port_MOSI_p, int port_CS_p) { bool Retour_L; uint8_t type_carte_L; size_t nombre_secteurs_l, taille_secteur_l; // Valeur par defaut Retour_L = false; // Réassigne les ports SPI du bus HSPI hspi_G.begin(port_CLK_p, PORT_CARD_MISO, port_MOSI_p, port_CS_p); if (!SD.begin(port_CS_p, hspi_G, 80000000)) { Serial.println("Carte SD absente, mal formatée ou lecteur mal câblé."); } else { // Identification du type de carte type_carte_L = SD.cardType(); if(type_carte_L == CARD_NONE) { Serial.println("Carte SD illisible"); } else { Serial.print("Type de carte SD: "); switch(type_carte_L) { case CARD_MMC: Serial.println("MMC"); break; case CARD_SD: Serial.println("SDSC"); break; case CARD_SDHC: Serial.println("SDHC"); break; default: Serial.println("Inconnu"); break; } // Caractéristiques de la carte nombre_secteurs_l = SD.numSectors(); taille_secteur_l = SD.sectorSize(); sprintf(tampon_traces_g, "Découpée en %u secteurs de %u octets, soit une capacité de %.2f Go ou de %.2f Gio", nombre_secteurs_l, taille_secteur_l, ((float)nombre_secteurs_l * (float)taille_secteur_l)/(1000.0*1000.0*1000.0), ((float)nombre_secteurs_l * (float)taille_secteur_l)/(1024.0*1024.0*1024.0)); Serial.println(tampon_traces_g); sprintf(tampon_traces_g, "Il reste %.2f Gio de disponibles.", ((float)SD.totalBytes()-(float)SD.usedBytes())/(1024.0*1024.0*1024.0)); Serial.println(tampon_traces_g); Retour_L = true; } } return(Retour_L); } void donner_nom_fichier(char* nom_fichier_P, int index_P) { sprintf(nom_fichier_P, "/photo_%05d.jpg", index_P); } int donner_prochain_index_fichier(void) { char nom_fichier_l[30]; int index_fichier_l; index_fichier_l = 0; do { index_fichier_l++; donner_nom_fichier(nom_fichier_l, index_fichier_l); } while(SD.exists(nom_fichier_l)); return(index_fichier_l); } esp_err_t camera_demarre() { // Variables locales de la fonction esp_err_t Retour_L; camera_config_t ConfigurationCamera_L; // Cablage de la caméra sur l'ESP32-CAM du fabricant AI-Thinker ConfigurationCamera_L.pin_d0 = 5; ConfigurationCamera_L.pin_d1 = 18; ConfigurationCamera_L.pin_d2 = 19; ConfigurationCamera_L.pin_d3 = 21; ConfigurationCamera_L.pin_d4 = 36; ConfigurationCamera_L.pin_d5 = 39; ConfigurationCamera_L.pin_d6 = 34; ConfigurationCamera_L.pin_d7 = 35; ConfigurationCamera_L.pin_xclk = 0; ConfigurationCamera_L.pin_pclk = 22; ConfigurationCamera_L.pin_vsync = 25; ConfigurationCamera_L.pin_href = 23; ConfigurationCamera_L.pin_sccb_sda = 26; ConfigurationCamera_L.pin_sccb_scl = 27; ConfigurationCamera_L.pin_pwdn = 32; ConfigurationCamera_L.pin_reset = -1; // La génération du signal d'horloge ConfigurationCamera_L.ledc_channel = LEDC_CHANNEL_0; ConfigurationCamera_L.ledc_timer = LEDC_TIMER_0; ConfigurationCamera_L.xclk_freq_hz = 20000000; // Compression jpeg ConfigurationCamera_L.pixel_format = PIXFORMAT_JPEG; ConfigurationCamera_L.fb_count = 2; ConfigurationCamera_L.jpeg_quality = 10; // Résolution de l'image ConfigurationCamera_L.frame_size = FRAMESIZE_UXGA; // Lancement de l'initialisation de la caméra Retour_L=esp_camera_init(&ConfigurationCamera_L); if (Retour_L == ESP_OK) { Serial.println("La caméra est initialisée\n"); } else { sprintf(tampon_traces_g, "Erreur 0x%x lors de l'initialisation de la cameran", Retour_L); Serial.println(tampon_traces_g); } return(Retour_L); } bool photographier_et_enregistrer(int index_fichier_P) { bool Retour_L; char nom_fichier_l[30]; File fichier_l; camera_fb_t *pTamponVideo_L = NULL; // Valeur par defaut Retour_L = false; // Récupèration d'une image depuis la caméra pTamponVideo_L = esp_camera_fb_get(); // Ecriture de l'image dans un fichier jpg donner_nom_fichier(nom_fichier_l, index_fichier_P); fichier_l = SD.open(nom_fichier_l, FILE_WRITE); if(!fichier_l) { sprintf(tampon_traces_g, "Erreur de creation du fichier '%s'.", nom_fichier_l); Serial.println(tampon_traces_g); } else { if(!fichier_l.write(pTamponVideo_L->buf, pTamponVideo_L->len)) { sprintf(tampon_traces_g, "Erreur d'enregistrement de l'image dans le fichier '%s'.", nom_fichier_l); Serial.println(tampon_traces_g); } else { sprintf(tampon_traces_g, "Photo '%s' enregistrée.", nom_fichier_l); Serial.println(tampon_traces_g); fichier_l.close(); Retour_L = true; } } // Réamorcage du tampon video, indispensable pour sa réutilisation if (pTamponVideo_L) { esp_camera_fb_return(pTamponVideo_L); pTamponVideo_L = NULL; } return(Retour_L); }
et les traces affichées sur le Moniteur série lors de son exécution :

Un exemple de photo présente sur la carte Micro SD :

La résolution de la caméra a été paramétrée à son maximum, aussi il est important d’avoir un bon éclairage pour avoir des photos nettes. Ce tutoriel ayant été réalisé au mois de novembre j’ai ajouté une lampe directionnelle.
Nous sommes à la fin du tutoriel. J’espère que cet article vous a donné des idées pour vos prochains projets !
Super article, cela va beaucoup m’aider dans mon projet. Merci beaucoup.
Je suis content que cela vous soit utile…
De quel projet s’agit-il si ce n’est pas secret ? 😉