ESP32 S3 Camera - Send Live Video to Python on PC

The ESP32 S3 is a small board. It can take pictures very well, but it cannot recognise a face, read a car number plate, or find a cat in the picture. Your computer can do all of that easily.

So we let each side do what it is good at: the ESP32 S3 sends the live video over WiFi, and a Python program on your computer receives it. Once the picture is inside Python, you can do anything with it.

The Python program is only about 20 lines.

ESP32 S3 Camera - Send Live Video to Python on PC

What you'll build:

  1. An ESP32 S3 camera that sends live video over WiFi
  2. A short Python program that shows the video in a window on your computer
  3. A key to save a picture as a file, whenever you want
  4. A second Python program that finds faces in the video, to show you how to go further

Hardware Preparation

1×ESP32 S3 N16R8 OV5640 Camera
1×Alternatively, ESP32 S3 N16R8 OV2640 Camera
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×Optionally, DC Power Jack
1×Recommended: Screw Terminal Expansion Board for ESP32 S3
1×Recommended: Breakout Expansion Board for ESP32 S3
1×Recommended: Power Splitter for ESP32 S3

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

※ NOTE THAT:

The ESP32 S3 camera board has 40 pins, but the screw terminal block and the breakout board have 44 pins. They still work together. Put the camera board in the center of the 44-pin board, so 2 pins stay free on the left side and 2 pins stay free on the right side.

The screw terminal block lets you connect wires with a screwdriver, with no soldering. The breakout board gives you easy pin headers for a breadboard.

You also need a computer with Python. Windows, Mac and Linux all work.

How It Works

The ESP32 S3 sends the video the same way a web camera does, as an MJPEG stream. That is simply many JPEG pictures, one after the other.

The good news: Python already understands it. The library OpenCV opens the video with one line, exactly like it opens a video file or a USB webcam:

camera = cv2.VideoCapture("http://192.168.0.2/stream")

Here is the whole road that one picture travels:

StepWhereWhat happens
1CameraTakes a picture and packs it as JPEG
2ESP32 S3Sends the JPEG over WiFi
3Your routerCarries it to your computer
4PythonReads it and turns it into a picture you can work with
5Your codeShows it, saves it, or looks for something in it
How the Video Travels to Python

The board also has two other addresses, which are useful while you build:

AddressWhat it gives you
http://<IP>/A web page, to test the camera in a browser first
http://<IP>/streamThe live video. This is the one Python opens.
http://<IP>/photoOne single JPEG picture, if your program only needs one

※ NOTE THAT:

Always test http://<IP>/ in your web browser before you start Python. If the browser shows the video, the board is fine, and any problem after that is in the Python part. This one habit saves a lot of time.

Wiring Diagram

There is no wiring in this project. The ESP32 S3 only needs power and WiFi.

The wiring diagram between ESP32 S3 Camera Python

This image is created using Fritzing. Click to enlarge image

※ NOTE THAT:

Your computer and your ESP32 S3 must be on the same WiFi network. If they are on different networks, Python cannot reach the board.

Arduino IDE Settings for ESP32 S3 Camera

The camera does not work with the default settings of the Arduino IDE. You must change some items in the Tools menu before you upload the code.

Tools Menu ItemSelect This
BoardESP32S3 Dev Module
Flash Size16MB (128Mb)
PSRAMOPI PSRAM
Partition SchemeHuge APP (3MB No OTA/1MB SPIFFS)
CPU Frequency240MHz (WiFi)
USB ModeHardware CDC and JTAG
Upload ModeUART0 / Hardware CDC
Upload Speed921600
USB CDC On BootEnabled

Install DIYables ESP32 WebServer Library

  1. Open the Arduino IDE.
  2. Click the Libraries icon on the left bar.
  3. Type DIYables ESP32 WebServer into the search box.
  4. Find the library by DIYables.io and click INSTALL.
  • Search for DIYables ESP32 WebServer created by DIYables.io and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Library Manager
Type:
All
Topic:
All
DIYables ESP32 WebServer by DIYables.io
DIYables ESP32 WebServer library designed for ESP32 boards. It includes multi-page web server capabilities and WebSocket support for real-time communication, perfect for IoT projects and DIYables ESP32 boards. More info
1.0.1
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32S3 Dev Module on COM15
1

ESP32 S3 Code

Change WIFI_SSID and WIFI_PASSWORD at the top. Nothing else needs to change.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-camera-send-live-video-to-python-on-pc */ #include <WiFi.h> #include <DIYables_ESP32_WebServer.h> // DIYables ESP32 WebServer library #include "esp_camera.h" const char *WIFI_SSID = "YOUR_WIFI_SSID"; // CHANGE IT const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD"; // CHANGE IT // The picture size. Bigger is nicer, but slower over WiFi. // FRAMESIZE_QVGA (320x240) is the fastest, FRAMESIZE_SVGA (800x600) is the biggest useful one. #define FRAME_SIZE FRAMESIZE_VGA // 640x480 #define JPEG_QUALITY 12 // 0 to 63. A bigger number makes a smaller file. // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 15 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 DIYables_ESP32_WebServer server(80); // The text that separates one picture from the next one in the stream #define BOUNDARY "diyablesframe" const char INDEX_HTML[] = R"rawliteral( <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP32 S3 Camera for Python</title> <style> body { background:#111; color:#eee; font-family:sans-serif; text-align:center; margin:0; padding:16px; } img { width:100%; max-width:640px; border-radius:8px; } code { background:#222; padding:2px 6px; border-radius:4px; } </style> </head> <body> <h2>ESP32 S3 Camera</h2> <p>The video below is the same one your Python code reads.</p> <img src="/stream"> <p>Address for Python: <code>/stream</code></p> </body> </html> )rawliteral"; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } bool initCamera() { 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; config.frame_size = FRAME_SIZE; config.jpeg_quality = JPEG_QUALITY; config.fb_count = 1; config.fb_location = CAMERA_FB_IN_DRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; if (psramFound()) { config.fb_location = CAMERA_FB_IN_PSRAM; config.fb_count = 2; // two buffers, so the video is smoother config.grab_mode = CAMERA_GRAB_LATEST; } else { config.frame_size = FRAMESIZE_QVGA; Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); } esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); // Each sensor needs slightly different settings switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } return true; } // Shows a small web page, so you can test the camera in a browser first void handleHome(WiFiClient &client, const String &method, const String &request, const QueryParams &params, const String &jsonData) { server.sendResponse(client, INDEX_HTML, "text/html"); } // Sends one JPEG picture. Good for Python code that wants single images. void handlePhoto(WiFiClient &client, const String &method, const String &request, const QueryParams &params, const String &jsonData) { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Photo capture failed"); server.sendResponse(client, "Photo capture failed", "text/plain"); return; } client.print("HTTP/1.1 200 OK\r\n"); client.print("Content-Type: image/jpeg\r\n"); client.printf("Content-Length: %u\r\n", fb->len); client.print("Connection: close\r\n\r\n"); client.write(fb->buf, fb->len); esp_camera_fb_return(fb); } // Sends the live video. This is the address the Python code opens. void handleStream(WiFiClient &client, const String &method, const String &request, const QueryParams &params, const String &jsonData) { Serial.println("A program opened the video stream"); client.print("HTTP/1.1 200 OK\r\n"); client.print("Content-Type: multipart/x-mixed-replace; boundary=" BOUNDARY "\r\n"); client.print("Access-Control-Allow-Origin: *\r\n"); client.print("Connection: close\r\n\r\n"); while (client.connected()) { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame capture failed"); break; } size_t len = fb->len; client.print("--" BOUNDARY "\r\n"); client.print("Content-Type: image/jpeg\r\n"); client.printf("Content-Length: %u\r\n\r\n", len); size_t sent = client.write(fb->buf, len); client.print("\r\n"); esp_camera_fb_return(fb); if (sent != len) { // the program on the PC stopped break; } } Serial.println("The video stream was closed"); } void setup() { Serial.begin(115200); delay(1000); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); WiFi.setSleep(false); // keep WiFi fast for the video server.addRoute("/", handleHome); server.addRoute("/photo", handlePhoto); server.addRoute("/stream", handleStream); server.begin(); Serial.print("Camera is ready. Put this address into your Python code: http://"); Serial.print(WiFi.localIP()); Serial.println("/stream"); } void loop() { server.handleClient(); }

Detailed Instructions

  • New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  • Install the DIYables ESP32 WebServer library.
  • Copy the above code and paste it into the Arduino IDE.
  • Change WIFI_SSID and WIFI_PASSWORD to your own WiFi.
  • Change the settings in the Tools menu. See the table above.
  • Compile and upload the code to the ESP32 S3 board by clicking the Upload button in Arduino IDE.
Arduino IDE Upload Code
  • Open the Serial Monitor in Arduino IDE.
How to open serial monitor on Arduino IDE
  • Press the RESET button one time.
  • Write down the address that the Serial Monitor prints. You need it in the Python code.
  • Open that address in your web browser and check that you see the video.

Serial Monitor

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-09-28 16:00:01] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-28 16:00:01] Connecting to WiFi... [2026-09-28 16:00:04] Camera is ready. Put this address into your Python code: http://192.168.0.2/stream [2026-09-28 16:00:31] A program opened the video stream [2026-09-28 16:01:58] The video stream was closed
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

※ NOTE THAT:

The line A program opened the video stream appears when your Python code starts. If you never see it, Python is not reaching the board, and the problem is the address or the network.

Install Python and OpenCV

  1. Install Python from python.org. On Windows, tick the box Add python.exe to PATH during the install.
  2. Open a terminal, and install OpenCV with one command:
pip install opencv-python
  1. Check that it worked:
python -c "import cv2; print(cv2.__version__)"

You should see a version number, for example 4.12.0.

※ NOTE THAT:

OpenCV is the most used library in the world for working with pictures. It is free, and it already knows how to read an MJPEG stream, so you do not have to write anything about JPEG or WiFi yourself.

Python Code - Example 1: Show the Video

This is the whole program. It opens the video, shows it in a window, and saves a picture when you press the s key.

""" This ESP32 S3 script was developed by newbiely.com This ESP32 S3 script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/esp32-s3/esp32-s3-camera-send-live-video-to-python-on-pc """ import cv2 CAMERA_URL = "http://192.168.0.2/stream" # CHANGE IT to the address of your ESP32 S3 camera = cv2.VideoCapture(CAMERA_URL) if not camera.isOpened(): print("Cannot open the camera.") print("Check the address, and try it in your web browser first.") exit() print("The video is open.") print("Press ESC to stop, or press s to save a picture.") photo_number = 1 while True: ok, frame = camera.read() if not ok: print("No picture. Is the ESP32 S3 still on?") break # frame is a normal OpenCV picture. You can do anything you like with it here. cv2.imshow("ESP32 S3 Camera", frame) key = cv2.waitKey(1) & 0xFF if key == 27: # the ESC key break if key == ord("s"): file_name = "photo_%05d.jpg" % photo_number cv2.imwrite(file_name, frame) print("Saved", file_name) photo_number += 1 camera.release() cv2.destroyAllWindows() print("Stopped.")

Change one line to the address from your Serial Monitor:

CAMERA_URL = "http://192.168.0.2/stream" # CHANGE IT

Detailed Instructions

  • Save the code as camera_pc_view.py on your computer.
  • Change CAMERA_URL to the address of your board.
  • Open a terminal in the same folder and run it:
python camera_pc_view.py
  • A window opens and shows the live video.
  • Press s to save a picture. Press ESC to stop.
The video is open. Press ESC to stop, or press s to save a picture. Saved photo_00001.jpg Saved photo_00002.jpg Stopped.
ESP32 S3 Camera Video in a Python Window

The Important Line

Everything interesting happens here:

ok, frame = camera.read()

frame is now the picture, as a table of numbers. This is the same kind of picture that every OpenCV example on the internet uses. So from this line on, you are no longer doing an ESP32 project. You are doing a normal computer vision project, and every tutorial, every example and every AI library works with it.

Python Code - Example 2: Find Faces

To show what "you can do anything now" means, here is the same program with face detection. It needs no extra download, because OpenCV brings a face finder with it.

""" This ESP32 S3 script was developed by newbiely.com This ESP32 S3 script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/esp32-s3/esp32-s3-camera-send-live-video-to-python-on-pc """ import cv2 CAMERA_URL = "http://192.168.0.2/stream" # CHANGE IT to the address of your ESP32 S3 # OpenCV brings a ready-made face finder with it. Nothing to download. face_finder = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") camera = cv2.VideoCapture(CAMERA_URL) if not camera.isOpened(): print("Cannot open the camera.") print("Check the address, and try it in your web browser first.") exit() print("Looking for faces. Press ESC to stop.") while True: ok, frame = camera.read() if not ok: print("No picture. Is the ESP32 S3 still on?") break # The face finder works on a black and white picture gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_finder.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60)) # Draw a green box around every face for (x, y, width, height) in faces: cv2.rectangle(frame, (x, y), (x + width, y + height), (0, 255, 0), 2) cv2.putText(frame, "%d face(s)" % len(faces), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2) cv2.imshow("ESP32 S3 Camera - Faces", frame) if cv2.waitKey(1) & 0xFF == 27: # the ESC key break camera.release() cv2.destroyAllWindows() print("Stopped.")

Only three things were added:

# 1. Load the ready-made face finder face_finder = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") # 2. Look for faces in the picture gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_finder.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60)) # 3. Draw a green box around every face for (x, y, width, height) in faces: cv2.rectangle(frame, (x, y), (x + width, y + height), (0, 255, 0), 2)

Run it the same way:

python camera_pc_face.py
ESP32 S3 Camera Face Detection in Python

※ NOTE THAT:

This face finder is old and simple, but it is fast and it needs nothing extra. It only finds faces that look straight at the camera. For better results, see the list below.

What Else Can You Do in Python?

Once the picture is in frame, the ESP32 S3 part of your project is finished. Here is what people usually add next:

What you wantWhat to useHow hard
Find facesOpenCV, already installedEasy
Find people, cars, animals, 80 other thingsYOLO (pip install ultralytics)Easy
Recognise who a person isface_recognition libraryMedium
Read text and numbers in the pictureTesseract OCR (pip install pytesseract)Medium
Read a QR code or a bar codeOpenCV, already installedEasy
Follow hands, bodies and facesMediaPipe (pip install mediapipe)Easy
Ask an AI what it seesAn AI service with an image APIMedium
Record the video to a fileOpenCV VideoWriterEasy
Send an alert to Telegram or emailPython requests or smtplibEasy

For example, finding people with YOLO is about five lines:

from ultralytics import YOLO model = YOLO("yolov8n.pt") # downloads itself the first time results = model(frame) # look at the picture frame = results[0].plot() # draw the boxes

※ NOTE THAT:

Your computer does the hard work, not the ESP32 S3. That is why this way is so strong: a small cheap board becomes the eye, and your computer becomes the brain. The board does not even know that somebody is looking for faces.

Troubleshooting

Python says Cannot open the camera.

  • Open http://<IP>/ in your web browser first. No video there means the problem is the board, not Python.
  • Your computer and the board must be on the same WiFi network.
  • Check that the address in CAMERA_URL ends with /stream.
  • Use http://, not https://.

The window opens but stays grey or black.

Wait a few seconds. The first picture can take a moment. If it stays black, the room may be too dark.

Python says ModuleNotFoundError: No module named 'cv2'.

OpenCV is not installed. Run pip install opencv-python again, and make sure you use the same Python that runs your file.

The video in Python is behind the video in the browser.

OpenCV keeps a few pictures in a queue. Make sure your loop has no time.sleep(), and keep your own work short. A smaller picture also helps: change FRAME_SIZE to FRAMESIZE_QVGA in the ESP32 S3 code.

The video is slow, only a few pictures per second.

  • Change FRAME_SIZE to FRAMESIZE_QVGA (320 x 240) in the ESP32 S3 code.
  • Change JPEG_QUALITY from 12 to 16, so the pictures are smaller.
  • Move the board closer to the router.

The video stops after some minutes.

Only one program can read the video at a time. Close the browser tab that shows the video, then run Python again.

The Serial Monitor never shows A program opened the video stream.

Python is not reaching the board. This is a network problem, not a code problem. Check the address and the WiFi.

The Serial Monitor shows nothing after the boot messages.

If your USB cable is in the port named UART, set USB CDC On Boot to Disabled. If it is in the port named USB, set it to Enabled.

Applications

A cheap board as the eye, and a computer as the brain. Here are practical projects you can build with this code:

  1. Face detection door camera: Your computer sees a face and opens the door, or writes the time in a file.
  2. People counter for a shop: Count how many people walk past during the day.
  3. Car number plate reader: Read the plate with OCR and open a gate for known cars.
  4. Parcel detector: Let YOLO see when a parcel is left at your door, then send a message to your phone.
  5. Pet watcher: Know when your cat or your dog comes into the room, and record only those moments.
  6. Machine and 3D printer watcher: Stop a print when the picture shows that something went wrong.
  7. Quality check on a work bench: Look at every part that passes the camera and mark the bad ones.
  8. Bird feeder camera: Find out which birds come, with an animal recognition model.
  9. Hand gesture remote control: Wave your hand and let the computer switch a lamp on.
  10. Learning computer vision: A very cheap way to get a real live camera into your Python lessons.

Video Tutorial

Watch the step-by-step video walkthrough for this ESP32 S3 project below.

Challenges

  1. Beginner: Make the picture black and white in Python with cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) before you show it.
  2. Intermediate: Save a picture by itself every time a face is found, with the time in the file name.
  3. Advanced: Send a command back to the ESP32 S3 when Python sees something. Add an address like /led_on to the board, and call it from Python with requests.get(). See the ESP32 S3 - Telegram Bot tutorial for how to add your own addresses.

Advanced Knowledge - Why HTTP is Good Enough Here

Many people look at this project and ask a fair question: Python uses HTTP. Does it not ask the board for a new picture again and again? That must be slow.

The answer is no, and it is worth understanding why.

There is Only One Request

Python asks one time, and the connection then stays open for ever. The board keeps writing new pictures into that same open connection until you stop the program.

This works because of one special answer from the board:

Content-Type: multipart/x-mixed-replace; boundary=diyablesframe

multipart/x-mixed-replace tells the other side: *"more pictures are coming on this same connection, and each new one replaces the one before"*. So the board simply writes picture after picture:

while (client.connected()) { // one connection, it stays open camera_fb_t *fb = esp_camera_fb_get(); client.print("--" BOUNDARY "\r\n"); // a line to separate the pictures client.write(fb->buf, len); // the picture itself }

You can see this yourself in the Serial Monitor. The line A program opened the video stream appears one time when Python starts, not ten times per second. If HTTP really asked again for every picture, that line would run down your screen.

How Much is Wasted?

Between two pictures the board sends only a separator and two short lines:

--diyablesframe 17 bytes Content-Type: image/jpeg 26 bytes Content-Length: 30000 25 bytes (empty line) 2 bytes = 70 bytes

One picture at 640 × 480 is about 30000 bytes. So the extra text is about 0.2% of what is sent. Two bytes in every thousand. There is nothing to win by removing it.

※ NOTE THAT:

A different way of sending, with no HTTP at all, would save those 70 bytes per picture. On a 10 pictures per second stream that is 700 bytes per second, out of about 300000. You would never see the difference.

What Really Costs Something

The waste is not in HTTP. It is in two other places:

Real costWhyCan you fix it?
Every picture is a full JPEGThe ESP32 S3 has no video encoder, so it cannot send only "what changed" like a film doesNo. This is the hardware.
Your computer keeps old pictures in a queueOpenCV holds a few pictures, so what you see can be a moment behindYes, see below
Only one program can watchThe small web server answers one program at a timeYes, with a bigger server

The second one is the only problem people really notice. The fix is on the Python side, not on the board: read the pictures in the background and always take the newest one.

import cv2, threading class Camera: def __init__(self, url): self.capture = cv2.VideoCapture(url) self.frame = None threading.Thread(target=self.loop, daemon=True).start() def loop(self): while True: ok, frame = self.capture.read() # keep reading, never stop if ok: self.frame = frame # always keep only the newest one

※ NOTE THAT:

Your own code then reads camera.frame whenever it is ready. Old pictures are thrown away instead of waiting in a queue, so the video feels live again. This helps a lot when your program does slow work, for example object detection.

Why HTTP is a Good Choice Here

Beside being fast enough, HTTP gives you things that a private way of sending never would:

Good thingWhy it matters
Your web browser can open itYou can test the camera before you write one line of Python
VLC can open itA free video player shows the stream with no code at all
OpenCV opens it with one linecv2.VideoCapture(url) and you are done
Home Assistant and other programs know itYour camera fits into a smart home with no extra work
It goes through your routerNo port forwarding, no special settings
It is easy to look at when it breaksAny problem is visible in a browser

※ NOTE THAT:

This is the real reason to choose HTTP. Not speed, but that everything already speaks it. A faster way that only your own program understands is usually a bad trade.

When Should You Use Something Else?

There are real reasons to change, but they are not about the 70 bytes:

Way of sendingChoose it whenCost
MJPEG over HTTP (this tutorial)Almost alwaysNothing
Raw TCP, your own formatThe PC is the brain and you want full controlYou write both sides yourself
UDPYou need the lowest possible delay, and a broken picture is better than a late onePictures arrive broken or not at all
RTSPYou want to put the camera into a security system or an NVRA library and more code
WebSocketA web page must show the video and send commands backMore code on both sides

※ NOTE THAT:

For a project where a computer looks at the pictures, MJPEG over HTTP is the right answer. Change it only when you have measured a real problem, not because HTTP sounds slow.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!