ESP32-CAM Take Photo and Display in Web Server
Build a web server with the ESP32-CAM board to take a photo and visualize the latest captured photo in your browser saved in SPIFFS. You can also rotate the image if necessary.
- Suitable forB.Tech/Diploma IoT/ESP32 students
- Build time1-2 weeks
- LevelIntermediate
- Available atAjvika Labs, Kakinada
About this project
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.
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 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:
ESP32-CAM with OV2640
(
read board overview
) – read
Best ESP32-CAM Dev Boards
Female-to-female jumper wires
FTDI programmer
5V power supply or power bank
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!
Project Overview
The following image shows the web server we’ll build in this tutorial.
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
We’ll build the web server using the following libraries:
ESPAsyncWebServer
by ESP32Async
AsyncTCP
by ESP32Async
You can install these libraries in the Arduino Library Manager. Open the Library Manager by clicking the Library icon at the left sidebar.
Search for
ESPAsyncWebServer
and install the
ESPAsyncWebServer by ESP32Async
Then, install the AsyncTCP library. Search for
AsyncTCP
and install the
AsyncTCP by ESP32Async
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.
/*********
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 <SPIFFS.h>
#include <FS.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer 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 22
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";
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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(200, "text/html", index_html);
});
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request-
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 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:
ESP32-CAM with OV2640
(
read board overview
) – read
Best ESP32-CAM Dev Boards
Female-to-female jumper wires
FTDI programmer
5V power supply or power bank
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!
Project Overview
The following image shows the web server we’ll build in this tutorial.
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
We’ll build the web server using the following libraries:
ESPAsyncWebServer
by ESP32Async
AsyncTCP
by ESP32Async
You can install these libraries in the Arduino Library Manager. Open the Library Manager by clicking the Library icon at the left sidebar.
Search for
ESPAsyncWebServer
and install the
ESPAsyncWebServer by ESP32Async
Then, install the AsyncTCP library. Search for
AsyncTCP
and install the
AsyncTCP by ESP32Async
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.
/*********
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 <SPIFFS.h>
#include <FS.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer 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 22
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";
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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(200, "text/html", index_html);
});
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request-
Technologies used
Hardware components
- ESP32
- ESP32-CAM
- Arduino
- PIR
- IR Sensor
- SD Card
Software & tools
- Arduino IDE
- Node-RED
- Home Assistant
What you get
- Working project / hardware kit, tested before delivery
- Complete source code with explanation
- Project demo and guidance for your viva
- Project report, PPT and IEEE paper support (on request)
Delivery & Payment
Delivery takes a minimum of two weeks from the date your inquiry is confirmed.
Payment is in two parts:
• 50% advance to start the project
• Remaining 50% before delivery
Note:
• Documentation and PPT are not included by default
• If you need documentation/PPT, it can be arranged for extra time and cost
We'll confirm the exact timeline and cost with you after reviewing your requirements.