Building a webcam streaming web interface with Flask and Raspberry Pi is one of the most practical home server projects. A local streaming server lets you monitor any room from a browser on your phone or laptop without any cloud service. This tutorial covers Flask-based MJPEG streaming, authentication for security, mobile-responsive UI, and deployment tips for Indian home networks including Airtel Fiber, Jio Fiber, and BSNL broadband environments.
Table of Contents
- Streaming Approaches
- Flask Setup on Raspberry Pi
- Camera Capture with Picamera2
- MJPEG Streaming Endpoint
- Complete Web Interface
- Adding Password Authentication
- Remote Access via Tailscale
- FAQ
Streaming Approaches
Three main methods for browser-based camera streaming:
- MJPEG (Motion JPEG): Server pushes JPEG frames continuously. Universal browser support, simple to implement, moderate bandwidth. Best for LAN streaming.
- HLS (HTTP Live Streaming): Segmented video over HTTP. Better compression (H.264), higher latency (5-15s). Best for internet streaming.
- WebRTC: Peer-to-peer, lowest latency (sub-second), complex setup. Ideal for live monitoring requiring instant response.
Flask MJPEG is the fastest to implement and works reliably on Indian home networks with 25+ Mbps broadband.
Flask Setup on Raspberry Pi
sudo apt update
sudo apt install -y python3-flask python3-picamera2
# Or install via pip
pip3 install flask
# Create project directory
mkdir ~/pi-camera-server
cd ~/pi-camera-server
Camera Capture with Picamera2
from picamera2 import Picamera2
import cv2, threading, time
class CameraStream:
def __init__(self, width=640, height=480, fps=30):
self.picam2 = Picamera2()
config = self.picam2.create_preview_configuration(
main={'size': (width, height), 'format': 'BGR888'}
)
self.picam2.configure(config)
self.frame = None
self.lock = threading.Lock()
self.running = False
def start(self):
self.picam2.start()
time.sleep(2)
self.running = True
self.thread = threading.Thread(target=self._capture_loop, daemon=True)
self.thread.start()
def _capture_loop(self):
while self.running:
frame = self.picam2.capture_array()
_, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
with self.lock:
self.frame = encoded.tobytes()
def get_frame(self):
with self.lock:
return self.frame
def stop(self):
self.running = False
self.picam2.stop()
MJPEG Streaming Endpoint
from flask import Flask, Response, render_template_string
import time
app = Flask(__name__)
camera = CameraStream(width=640, height=480)
camera.start()
def generate_frames():
while True:
frame = camera.get_frame()
if frame is not None:
yield (b'--framern'
b'Content-Type: image/jpegrnrn' + frame + b'rn')
time.sleep(0.033) # ~30 FPS
@app.route('/video_feed')
def video_feed():
return Response(generate_frames(),
mimetype='multipart/x-mixed-replace; boundary=frame')
Complete Web Interface
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>Pi Camera</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin:0; background:#111; display:flex; flex-direction:column; align-items:center; min-height:100vh; }
h1 { color:#fff; font-family:sans-serif; margin:10px 0; }
img { max-width:100%; border:2px solid #333; border-radius:4px; }
.info { color:#aaa; font-family:monospace; font-size:12px; margin-top:8px; }
</style>
</head>
<body>
<h1>Pi Camera Live</h1>
<img src="/video_feed">
<p class="info">Raspberry Pi Camera Server | Zbotic.in</p>
</body>
</html>
'''
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
Access at http://[pi-ip-address]:5000 from any device on your local network. Find your Pi’s IP with hostname -I.
Arducam IMX219 8MP Camera Module
8MP IMX219 sensor for Raspberry Pi. High resolution streaming at 1080p. Excellent low-light performance for indoor monitoring. Works with Picamera2 out of the box on Raspberry Pi OS Bullseye/Bookworm.
Adding Password Authentication
from functools import wraps
from flask import request, Response
USERNAME = 'admin'
PASSWORD = 'your_secure_password' # Change this!
def check_auth(username, password):
return username == USERNAME and password == PASSWORD
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return Response(
'Authentication required.',
401,
{'WWW-Authenticate': 'Basic realm="Pi Camera"'}
)
return f(*args, **kwargs)
return decorated
# Apply to both routes:
@app.route('/')
@requires_auth
def index(): ...
@app.route('/video_feed')
@requires_auth
def video_feed(): ...
Remote Access via Tailscale
Indian ISPs (Jio, Airtel, BSNL) typically use CGNAT which prevents port forwarding. Tailscale (free for personal use) creates a VPN mesh network:
# Install Tailscale on Raspberry Pi
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Install Tailscale on your phone (Android/iOS - free)
# Both devices appear on same virtual network
# Access Pi camera at: http://100.x.x.x:5000 (Tailscale IP)
Tailscale works through CGNAT, double-NAT, and most corporate firewalls. Works on Jio Fiber, Airtel Xstream, BSNL FTTH, and mobile data. No router configuration needed.
Waveshare IMX219-160 Wide Angle Camera
160-degree wide angle IMX219 for comprehensive room coverage in a single camera stream. Wide FOV means less panning and adjusting for home monitoring. Compatible with Picamera2 on Raspberry Pi.
FAQ
What frame rate can I achieve with Flask MJPEG on Pi 4?
At 640×480 JPEG quality 80: 25-30 FPS. At 1280×720: 15-20 FPS. At 1920×1080: 10-15 FPS. Actual rate depends on network bandwidth and client browser rendering.
Can multiple people view the stream simultaneously?
Yes, Flask serves each client with a separate generator. With threading=True, each client gets its own thread. Pi 4 comfortably handles 4-5 simultaneous viewers at 720p.
How do I make the Flask server start on boot?
Create a systemd service file at /etc/systemd/system/picamera.service with your Flask app details. Enable with sudo systemctl enable picamera. Server starts automatically after boot.
Can I add motion detection to the Flask stream?
Yes. In the capture loop, compare consecutive frames with cv2.absdiff(). If the difference exceeds a threshold, trigger an alert or save the frame. Integrate with the Telegram bot approach from the MotionEye article for instant alerts.
Add comment