Chapter 30 Camera Web Server
In this section, we’ll use ESP32’s video function as an example to study.
Project 30.1 Camera Web Server
Connect ESP32 using USB and check its IP address through serial monitor. Use web page to access IP address to obtain video and image data.
Component List
ESP32-WROVER x1
|
Micro USB Wire x1
|
Circuit
Connect Freenove ESP32 to the computer using the USB cable.
Code
Move the program folder “ Freenove_Ultimate_Starter_Kit_for_ESP32/Python/Python_Codes “ to disk(D) in advance with the path of “ D:/Micropython_Codes “.
Since Micropython does not provide firmware including camera module, in this chapter, we will use the camera based on the firmware in lemariva’s Github project, micropython-camera-driver.
Project link: https://github.com/lemariva/micropython-camera-driver
Before starting the project, we need to re-upload the firmware with the camera module via steps below.
Open Thonny, click “run” and select “Select interpreter…””
Select “Micropython (ESP32)”, select “USB-SERIAL CH340 (COM4)”, and then click the long button under “Firmware”.
In the new popup window, select corresponding “USB-SERIAL CH340 (COM3)” for port. Click “Browse”, select “ 30.1_Camera_WebServerfirmwaremicropython_camera_feeeb5ea3_esp32_idf4_4.bin “.
Select “Erase…” and click “Install”。
Wait for completion.
Open “Thonny”, click “This computer” -> “D:” -> “Micropython_Codes” -> “30.1_Camera_WebServer”. Select folder “lib”, right click your mouse to select “Upload to /”, wait for “lib” to be uploaded to ESP32-WROVER and then double click “picoweb_video.py”.
30.1_Camera_WebServer
Before running the program, please modify your router’s name and password in the box shown in the illustration above to make sure that your code can compile and work successfully.
Click “run” to run the code “picoweb_video.py”, then you can see the following content in the shell area.
If your ESP32 has been in the process of connecting to router, but the information above has not been printed out, please re-check whether the router name and password have been entered correctly and press the reset key on ESP32-WROVER to wait for a successful connection prompt.
Open a web browser, enter the IP address printed by the serial monitor in the address bar, and access it.
Taking the Google browser as an example, here’s what the browser prints out after successful access to ESP32’s IP.
The effect is shown in the image below.
Note
If the shell area prompts an error when you click to run the code, please press the rst button on the esp32, wait for the system reset to complete, and then re-run the code.
The following is the program code.
1import network
2import socket
3import time
4import gc
5from camera import Camera, FrameSize, PixelFormat
6
7cam = Camera(
8 frame_size=FrameSize.QQVGA,
9 pixel_format=PixelFormat.RGB565,
10 xclk_freq=20000000,
11 init=True
12)
13
14ssid = '********'
15password = '********'
16
17station = network.WLAN(network.STA_IF)
18station.active(True)
19station.connect(ssid, password)
20
21while not station.isconnected():
22 time.sleep(1)
23
24ip = station.ifconfig()[0]
25print(f'Connected! Open in browser: http://{ip}')
26
27addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
28s = socket.socket()
29s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
30s.bind(addr)
31s.listen(5)
32
33print('Server running on:', addr)
34
35html = """<!DOCTYPE html>
36<html>
37<head>
38 <meta charset="UTF-8">
39 <title>ESP32 Camera Stream</title>
40 <style>
41 body {
42 display: flex;
43 justify-content: center;
44 align-items: center;
45 height: 100vh;
46 margin: 0;
47 background-color: #f0f0f0;
48 font-family: Arial, sans-serif;
49 }
50 .video-container {
51 text-align: center;
52 width: 100%;
53 max-width: 1000px;
54 }
55 canvas {
56 width: auto;
57 height: auto;
58 image-rendering: pixelated;
59 border: 2px solid #ccc;
60 background-color: #000;
61 }
62 #status {
63 margin-top: 10px;
64 font-size: 16px;
65 color: #333;
66 }
67 </style>
68</head>
69<body>
70 <div class="video-container">
71 <h1>ESP32 Camera Stream</h1>
72 <canvas id="videoCanvas" width="640" height="480"></canvas>
73 <div id="status">Connecting to camera...</div>
74 </div>
75
76 <script>
77 const canvas = document.getElementById('videoCanvas');
78 const ctx = canvas.getContext('2d');
79 const statusDiv = document.getElementById('status');
80
81 function fetchImage() {
82 fetch('/frame')
83 .then(response => {
84 if (!response.ok) throw new Error('Network request error');
85 return response.arrayBuffer();
86 })
87 .then(data => {
88 if (!data || data.byteLength === 0) {
89 statusDiv.textContent = 'Error: Empty data received';
90 setTimeout(fetchImage, 500);
91 return;
92 }
93
94 let uint8Array = new Uint8Array(data);
95 if (data.byteLength % 2 !== 0) {
96 uint8Array = new Uint8Array(data.slice(0, data.byteLength - 1));
97 }
98
99 const swappedArray = swapRgb565Bytes(uint8Array);
100 const rgb565Array = new Uint16Array(swappedArray.buffer);
101 const rgbaArray = convertRGB565ToRGBA(rgb565Array);
102
103 const actualPixelCount = rgb565Array.length;
104
105 let width, height;
106 if (actualPixelCount === 640 * 480) {
107 width = 640; height = 480;
108 } else if (actualPixelCount === 320 * 240) {
109 width = 320; height = 240;
110 } else if (actualPixelCount === 160 * 120) {
111 width = 160; height = 120;
112 } else {
113 width = Math.floor(Math.sqrt(actualPixelCount));
114 while (actualPixelCount % width !== 0 && width > 1) { width--; }
115 height = actualPixelCount / width;
116 }
117
118 if (canvas.width !== width || canvas.height !== height) {
Import picoweb、utime、camera、gc modules.
1import time
2import gc
3from camera import Camera, FrameSize, PixelFormat
4
Before running the code, please modify the WiFi name and password in the code to ensure that the ESP32 can connect to the network.
1frame_size=FrameSize.QQVGA,
2pixel_format=PixelFormat.RGB565,
Define the WiFi connection function, set the ESP32 to STA mode, and let the ESP32 connect to the nearby WiFi. If the connection is successful, the WiFi configuration information of the ESP32 will be printed; if the connection fails, the connection timeout will be printed.
1)
2
3ssid = '********'
4password = '********'
5
6station = network.WLAN(network.STA_IF)
7station.active(True)
8station.connect(ssid, password)
9
10while not station.isconnected():
11 time.sleep(1)
12
13ip = station.ifconfig()[0]
14print(f'Connected! Open in browser: http://{ip}')
15
The deinit() is used to disable the configuration of the camera to prevent the previous configuration from interfering with the following configuration.
The init() is used to configure the camera’s pin driver, image data format, resolution and other information. By default, please do not modify this function, otherwise the camera initialization fails and the image cannot be obtained.
1s.listen(5)
2
3print('Server running on:', addr)
4
5html = """<!DOCTYPE html>
6<html>
7<head>
This function can set the resolution of the camera individually, you can refer to the notes below to select the appropriate resolution size.
1<title>ESP32 Camera Stream</title>
2<style>
3 body {
4 display: flex;
5 justify-content: center;
6 align-items: center;
7 height: 100vh;
The following functions can modify the image information obtained by the camera.
1 background-color: #f0f0f0;
2 font-family: Arial, sans-serif;
3}
4.video-container {
5 text-align: center;
6 width: 100%;
7 max-width: 1000px;
8}
9canvas {
10 width: auto;
11 height: auto;
12 image-rendering: pixelated;
This is the code for a simple web interface, used here as an example.
1 }
2 #status {
3 margin-top: 10px;
4 font-size: 16px;
5 color: #333;
6 }
7 </style>
8</head>
9<body>
10 <div class="video-container">
11 <h1>ESP32 Camera Stream</h1>
12 <canvas id="videoCanvas" width="640" height="480"></canvas>
Web page response function. When a user visits the webpage “/” built by ESP32, ESP32 calls this function, allowing the user to observe a display interface in the browser.
1
2<script>
3 const canvas = document.getElementById('videoCanvas');
send_frame() can send the image obtained by ESP32 in web page format. When someone visits the webpage “/video” built by the ESP32, the video(req, resp) function is used to continuously fetch images and send them to the browser.
1const statusDiv = document.getElementById('status');
2
3function fetchImage() {
4 fetch('/frame')
5 .then(response => {
6 if (!response.ok) throw new Error('Network request error');
7 return response.arrayBuffer();
8 })
9 .then(data => {
10 if (!data || data.byteLength === 0) {
11 statusDiv.textContent = 'Error: Empty data received';
12 setTimeout(fetchImage, 500);
13 return;
14 }
15
Create two route decorators and declare their listening strings and corresponding response handlers respectively.
1 uint8Array = new Uint8Array(data.slice(0, data.byteLength - 1));
2}
3
4const swappedArray = swapRgb565Bytes(uint8Array);
5const rgb565Array = new Uint16Array(swappedArray.buffer);
This is the main part of the program. First initialize the ESP32 camera, and then configure WiFi to connect the ESP32 to the network. Call the picoweb library, build a webserver, and run it.
Reference
Image resolution |
Sharpness |
Image resolution |
Sharpness |
|---|---|---|---|
FRAMESIZE_96x96 |
96x96 |
FRAMESIZE_HVGA |
480x320 |
FRAMESIZE_QQVGA |
160x120 |
FRAMESIZE_VGA |
640x480 |
FRAMESIZE_QCIF |
176x144 |
FRAMESIZE_SVGA |
800x600 |
FRAMESIZE_HQVGA |
240x176 |
FRAMESIZE_XGA |
1024x768 |
FRAMESIZE_240x240 |
240x240 |
FRAMESIZE_HD |
1280x720 |
FRAMESIZE_QVGA |
320x240 |
FRAMESIZE_SXGA |
1280x1024 |
FRAMESIZE_CIF |
400x296 |
FRAMESIZE_UXGA |
1600x1200 |
We recommend that the resolution not exceed VGA(640x480).

