ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (2024)

Learn how to build a web server with the ESP32-CAM board that allows you to send a command to take a photo and visualize the latest captured photo in your browser saved in SPIFFS. We also added the option to rotate the image if necessary.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (1)

We have other ESP32-CAM projects in our blog that you might like. In fact you can take this project further, by adding a PIR sensor to take a photo when motion is detected, a physical pushbutton to take a photo, or also include video streaming capabilities in another URL path.

Other ESP32-CAM projects and tutorials:

  • ESP32-CAM PIR Motion Detector with Photo Capture (saves to microSD card)
  • ESP32-CAM Video Streaming and Face Recognition with Arduino IDE
  • ESP32-CAM Video Streaming Web Server (Home Assistant, Node-RED, etc…)
  • ESP32-CAM Take Photo and Save to MicroSD Card
  • ESP32-CAM Troubleshooting Guide

Watch the Video Demonstration

Watch the following video demonstration to see what you’re going to build throughout this tutorial.

Parts Required

To follow this project, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (2)

Project Overview

The following image shows the web server we’ll build in this tutorial.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (3)

When you access the web server, you’ll see three buttons:

  • ROTATE: depending on your ESP32-CAM orientation, you might need to rotate the photo;
  • CAPTURE PHOTO: when you click this button, the ESP32-CAM takes a new photo and saves it in the ESP32 SPIFFS. Please wait at least 5 seconds before refreshing the web page to ensure the ESP32-CAM takes and stores the photo;
  • REFRESH PAGE: when you click this button, the web page refreshes and it’s updated with the latest photo.

Note: as mentioned previously the latest photo captured is stored in the ESP32 SPIFFS, so even if you restart your board, you can always access the last saved photo.

Installing the ESP32 add-on

We’ll program the ESP32 board using Arduino IDE. So, you need the Arduino IDE installed as well as the ESP32 add-on:

  • Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux)

Installing Libraries

To build the web server, we’ll use the ESPAsyncWebServer library. This library also requires the AsyncTCP Library to work properly. Follow the next steps to install those libraries.

Installing the ESPAsyncWebServer library

Follow the next steps to install theESPAsyncWebServerlibrary:

  1. Click here to downloadthe ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should getESPAsyncWebServer-masterfolder
  3. Rename your folder fromESPAsyncWebServer-mastertoESPAsyncWebServer
  4. Move theESPAsyncWebServerfolder to your Arduino IDE installation libraries folder

Alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you’ve just downloaded.

Installing theAsync TCP Library for ESP32

TheESPAsyncWebServerlibrary requires theAsyncTCPlibrary to work. Follow the next steps to install that library:

  1. Click here to download the AsyncTCP library. You should have a .zip folder in your Downloads folder
  2. Unzip the .zip folder and you should getAsyncTCP-masterfolder
  3. Rename your folder fromAsyncTCP-mastertoAsyncTCP
  4. Move theAsyncTCPfolder to your Arduino IDE installation libraries folder
  5. Finally, re-open your Arduino IDE

Alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you’ve just downloaded.

ESP32-CAM Take and Display Photo Web Server Sketch

Copy the following code to your Arduino IDE. This code builds a web server that allows you to take a photo with your ESP32-CAM and display the last photo taken. Depending on the orientation of your ESP32-CAM, you may want to rotate the picture, so we also included that feature.

/********* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-take-photo-display-web-server/ IMPORTANT!!! - Select Board "AI Thinker ESP32-CAM" - GPIO 0 must be connected to GND to upload a sketch - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.*********/#include "WiFi.h"#include "esp_camera.h"#include "esp_timer.h"#include "img_converters.h"#include "Arduino.h"#include "soc/soc.h" // Disable brownour problems#include "soc/rtc_cntl_reg.h" // Disable brownour problems#include "driver/rtc_io.h"#include <ESPAsyncWebServer.h>#include <StringArray.h>#include <SPIFFS.h>#include <FS.h>// Replace with your network credentialsconst char* ssid = "REPLACE_WITH_YOUR_SSID";const char* password = "REPLACE_WITH_YOUR_PASSWORD";// Create AsyncWebServer object on port 80AsyncWebServer server(80);boolean takeNewPhoto = false;// Photo File Name to save in SPIFFS#define FILE_PHOTO "/photo.jpg"// OV2640 camera module pins (CAMERA_MODEL_AI_THINKER)#define PWDN_GPIO_NUM 32#define RESET_GPIO_NUM -1#define XCLK_GPIO_NUM 0#define SIOD_GPIO_NUM 26#define SIOC_GPIO_NUM 27#define Y9_GPIO_NUM 35#define Y8_GPIO_NUM 34#define Y7_GPIO_NUM 39#define Y6_GPIO_NUM 36#define Y5_GPIO_NUM 21#define Y4_GPIO_NUM 19#define Y3_GPIO_NUM 18#define Y2_GPIO_NUM 5#define VSYNC_GPIO_NUM 25#define HREF_GPIO_NUM 23#define PCLK_GPIO_NUM 22const char index_html[] PROGMEM = R"rawliteral(<!DOCTYPE HTML><html><head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { text-align:center; } .vert { margin-bottom: 10%; } .hori{ margin-bottom: 0%; } </style></head><body> <div id="container"> <h2>ESP32-CAM Last Photo</h2> <p>It might take more than 5 seconds to capture a photo.</p> <p> <button onclick="rotatePhoto();">ROTATE</button> <button onclick="capturePhoto()">CAPTURE PHOTO</button> <button onclick="location.reload();">REFRESH PAGE</button> </p> </div> <div><img src="saved-photo" id="photo" width="70%"></div></body><script> var deg = 0; function capturePhoto() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/capture", true); xhr.send(); } function rotatePhoto() { var img = document.getElementById("photo"); deg += 90; if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; } else{ document.getElementById("container").className = "hori"; } img.style.transform = "rotate(" + deg + "deg)"; } function isOdd(n) { return Math.abs(n % 2) == 1; }</script></html>)rawliteral";void setup() { // Serial port for debugging purposes Serial.begin(115200); // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } if (!SPIFFS.begin(true)) { Serial.println("An Error has occurred while mounting SPIFFS"); ESP.restart(); } else { delay(500); Serial.println("SPIFFS mounted successfully"); } // Print ESP32 Local IP Address Serial.print("IP Address: http://"); Serial.println(WiFi.localIP()); // Turn-off the 'brownout detector' WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // OV2640 camera module camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; if (psramFound()) { config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } // Camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); ESP.restart(); } // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", index_html); }); server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) { takeNewPhoto = true; request->send_P(200, "text/plain", "Taking Photo"); }); server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) { request->send(SPIFFS, FILE_PHOTO, "image/jpg", false); }); // Start server server.begin();}void loop() { if (takeNewPhoto) { capturePhotoSaveSpiffs(); takeNewPhoto = false; } delay(1);}// Check if photo capture was successfulbool checkPhoto( fs::FS &fs ) { File f_pic = fs.open( FILE_PHOTO ); unsigned int pic_sz = f_pic.size(); return ( pic_sz > 100 );}// Capture Photo and Save it to SPIFFSvoid capturePhotoSaveSpiffs( void ) { camera_fb_t * fb = NULL; // pointer bool ok = 0; // Boolean indicating if the picture has been taken correctly do { // Take a photo with the camera Serial.println("Taking a photo..."); fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); return; } // Photo file name Serial.printf("Picture file name: %s\n", FILE_PHOTO); File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE); // Insert the data in the photo file if (!file) { Serial.println("Failed to open file in writing mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO); Serial.print(" - Size: "); Serial.print(file.size()); Serial.println(" bytes"); } // Close the file file.close(); esp_camera_fb_return(fb); // check if file has been correctly saved in SPIFFS ok = checkPhoto(SPIFFS); } while ( !ok );}

View raw code

How the Code Works

First, include the required libraries to work with the camera, to build the web server and to use SPIFFS.

#include "WiFi.h"#include "esp_camera.h"#include "esp_timer.h"#include "img_converters.h"#include "Arduino.h"#include "soc/soc.h" // Disable brownout problems#include "soc/rtc_cntl_reg.h" // Disable brownout problems#include "driver/rtc_io.h"#include <ESPAsyncWebServer.h>#include <StringArray.h>#include <SPIFFS.h>#include <FS.h>

Next, write your network credentials in the following variables, so that the ESP32-CAM can connect to your local network.

const char* ssid = "REPLACE_WITH_YOUR_SSID";const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Create an AsyncWebServer object on port 80.

AsyncWebServer server(80);

The takeNewPhoto boolean variable indicates when it’s time to take a new photo.

boolean takeNewPhoto = false;

Then, define the path and name of the photo to be saved in SPIFFS.

#define FILE_PHOTO "/photo.jpg"

Next, define the camera pins for the ESP32-CAM AI THINKER module.

#define PWDN_GPIO_NUM 32#define RESET_GPIO_NUM -1#define XCLK_GPIO_NUM 0#define SIOD_GPIO_NUM 26#define SIOC_GPIO_NUM 27#define Y9_GPIO_NUM 35#define Y8_GPIO_NUM 34#define Y7_GPIO_NUM 39#define Y6_GPIO_NUM 36#define Y5_GPIO_NUM 21#define Y4_GPIO_NUM 19#define Y3_GPIO_NUM 18#define Y2_GPIO_NUM 5#define VSYNC_GPIO_NUM 25#define HREF_GPIO_NUM 23#define PCLK_GPIO_NUM 22

Building the Web Page

Next, we have the HTML to build the web page:

const char index_html[] PROGMEM = R"rawliteral(<!DOCTYPE HTML><html><head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { text-align:center; } .vert { margin-bottom: 10%; } .hori{ margin-bottom: 0%; } </style></head><body> <div id="container"> <h2>ESP32-CAM Last Photo</h2> <p>It might take more than 5 seconds to capture a photo.</p> <p> <button onclick="rotatePhoto();">ROTATE</button> <button onclick="capturePhoto()">CAPTURE PHOTO</button> <button onclick="location.reload();">REFRESH PAGE</button> </p> </div> <div><img src="saved-photo" id="photo" width="70%"></div></body><script> var deg = 0; function capturePhoto() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/capture", true); xhr.send(); } function rotatePhoto() { var img = document.getElementById("photo"); deg += 90; if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; } else{ document.getElementById("container").className = "hori"; } img.style.transform = "rotate(" + deg + "deg)"; } function isOdd(n) { return Math.abs(n % 2) == 1; }</script></html>)rawliteral";

We won’t go into much detail on how this HTML works. We’ll just take a quick overview.

Basically, create three buttons: ROTATE; CAPTURE PHOTO and REFRESH PAGE. Each photo calls a different JavaScript function: rotatePhoto(), capturePhoto() and reload().

<button onclick="rotatePhoto();">ROTATE</button><button onclick="capturePhoto()">CAPTURE PHOTO</button><button onclick="location.reload();">REFRESH PAGE</button>

The capturePhoto() function sends a request on the /capture URL to the ESP32, so it takes a new photo.

function capturePhoto() { var xhr = new XMLHttpRequest(); xhr.open('GET', "/capture", true); xhr.send();}

The rotatePhoto() function rotates the photo.

function rotatePhoto() { var img = document.getElementById("photo"); deg += 90; if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; } else{ document.getElementById("container").className = "hori"; } img.style.transform = "rotate(" + deg + "deg)";}function isOdd(n) { return Math.abs(n % 2) == 1; }

We’re not sure what’s the “best” way to rotate a photo with JavaScript. This method works perfectly, but there may be better ways to do this. If you have any suggestion please share with us.

Finally, the following section displays the photo.

<div><img src="saved-photo" id="photo" width="70%"></div>

When, you click the REFRESH button, it will load the latest image.

setup()

In the setup(), initialize a Serial communication:

Serial.begin(115200);

Connect the ESP32-CAM to your local network:

WiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi...");}

Initialize SPIFFS:

if (!SPIFFS.begin(true)) { Serial.println("An Error has occurred while mounting SPIFFS"); ESP.restart();}else { delay(500); Serial.println("SPIFFS mounted successfully");}

Print the ESP32-CAM local IP address:

Serial.print("IP Address: http://");Serial.println(WiFi.localIP());

The lines that follow, configure and initialize the camera with the right settings.

Handle the Web Server

Next, we need to handle what happens when the ESP32-CAM receives a request on a URL.

When the ESP32-CAM receives a request on the root / URL, we send the HTML text to build the web page.

server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { request->send_P(200, "text/html", index_html);});

When we press the “CAPTURE” button on the web server, we send a request to the ESP32 /capture URL. When that happens, we set the takeNewPhoto variable to true, so that we know it is time to take a new photo.

server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) { takeNewPhoto = true; request->send_P(200, "text/plain", "Taking Photo");});

In case there’s a request on the /saved-photo URL, send the photo saved in SPIFFS to a connected client:

server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) { request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);});

Finally, start the web server.

server.begin();

loop()

In the loop(), if the takeNewPhoto variable is True, we call the capturePhotoSaveSpiffs() to take a new photo and save it to SPIFFS. Then, set the takeNewPhoto variable to false.

void loop() { if (takeNewPhoto) { capturePhotoSaveSpiffs(); takeNewPhoto = false; } delay(1);}

Take a Photo

There are two other functions in the sketch: checkPhoto() and capturePhotoSaveSpiffs().

The checkPhoto() function checks if the photo was successfully saved to SPIFFS.

bool checkPhoto( fs::FS &fs ) { File f_pic = fs.open( FILE_PHOTO ); unsigned int pic_sz = f_pic.size(); return ( pic_sz > 100 );}

The capturePhotoSaveSpiffs() function takes a photo and saves it to SPIFFS.

void capturePhotoSaveSpiffs( void ) { camera_fb_t * fb = NULL; // pointer bool ok = 0; // Boolean indicating if the picture has been taken correctly do { // Take a photo with the camera Serial.println("Taking a photo..."); fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); return; } // Photo file name Serial.printf("Picture file name: %s\n", FILE_PHOTO); File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE); // Insert the data in the photo file if (!file) { Serial.println("Failed to open file in writing mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO); Serial.print(" - Size: "); Serial.print(file.size()); Serial.println(" bytes"); } // Close the file file.close(); esp_camera_fb_return(fb); // check if file has been correctly saved in SPIFFS ok = checkPhoto(SPIFFS); } while ( !ok );}

This function was based on this sketch by dualvim.

ESP32-CAM Upload Code

To upload code to the ESP32-CAM board, connect it to your computer using anFTDI programmer. Follow the next schematic diagram:

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (4)

Important: GPIO 0needs to be connected toGNDso that you’re able to upload code.

Many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make sure the jumper is in the right place to select 5V.

ESP32-CAMFTDI Programmer
GNDGND
5VVCC (5V)
U0RTX
U0TRX
GPIO 0GND

To upload the code, follow the next steps:

1) Go to Tools > Board and select AI-Thinker ESP32-CAM.

2) Go to Tools > Port and select the COM port the ESP32 is connected to.

3) Then, click the upload button to upload the code.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (5)

4) When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (6)

After a few seconds, the code should be successfully uploaded to your board.

Demonstration

Open your browser and type the ESP32-CAM IP Address. Then, click the “CAPTURE PHOTO” to take a new photo and wait a few seconds for the photo to be saved in SPIFFS.

Then, if you press the “REFRESH PAGE” button, the page will update with the latest saved photo. If you need to adjust the image orientation, you can always use the “ROTATE” button to do it so.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (7)

In your Arduino IDE Serial Monitor window, you should see similar messages:

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (8)

Troublehsooting

If you’re getting any of the following errors, read ourESP32-CAM Troubleshooting Guide: Most Common Problems Fixed

  • Failed to connect to ESP32: Timed out waiting for packet header
  • Camera init failed with error 0x20001 or similar
  • Brownout detector or Guru meditation error
  • Sketch too big error – Wrong partition scheme selected
  • Board at COMX is not available – COM Port Not Selected
  • Psram error: GPIO isr service is not installed
  • Weak Wi-Fi Signal
  • No IP Address in Arduino IDE Serial Monitor
  • Can’t open web server
  • The image lags/shows lots of latency

[eBook] Build ESP32-CAM Projects using Arduino IDE

Learn how to program and build 17 projects with the ESP32-CAM using Arduino IDEDOWNLOAD »

Learn how to program and build 17 projects with the ESP32-CAM using Arduino IDE DOWNLOAD »

Wrapping Up

We hope you’ve found this example useful. We’ve tried to keep it as simple as possible so it is easy for you to modify and include it in your own projects. You can combine this example with the ESP32-CAM PIR Motion Detector with Photo Capture to capture and display a new photo when motion is detected.

For more ESP32-CAM projects you cansubscribe to our newsletter. If you don’t have an ESP32-CAM yet, you canget one for approximately $6.

If you like this project, you may also like other projects with the ESP32-CAM:

  • ESP32-CAM AI-Thinker Pinout Guide: GPIOs Usage Explained
  • Video Streaming, Face Detection and Face Recognition
  • Build ESP32-CAM Projects (eBook)
  • Read all our ESP32-CAM Projects, Tutorials and Guides

Thank you for reading.

ESP32-CAM Take Photo and Display in Web Server | Random Nerd Tutorials (2024)
Top Articles
Latest Posts
Article information

Author: Dan Stracke

Last Updated:

Views: 6067

Rating: 4.2 / 5 (63 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Dan Stracke

Birthday: 1992-08-25

Address: 2253 Brown Springs, East Alla, OH 38634-0309

Phone: +398735162064

Job: Investor Government Associate

Hobby: Shopping, LARPing, Scrapbooking, Surfing, Slacklining, Dance, Glassblowing

Introduction: My name is Dan Stracke, I am a homely, gleaming, glamorous, inquisitive, homely, gorgeous, light person who loves writing and wants to share my knowledge and understanding with you.