How do I stream an openCV video to an HTML webpage?
Categories:
Stream OpenCV Video to an HTML Webpage: A Comprehensive Guide

Learn how to capture video with OpenCV and stream it efficiently to a web browser using Python or C++ for real-time display.
Streaming video from an OpenCV application to an HTML webpage is a common requirement for various projects, including surveillance systems, robotics, and interactive web applications. This guide will walk you through the process, covering both Python and C++ implementations, focusing on efficient methods to deliver real-time video frames to a web browser. We'll explore the core concepts of video capture, frame processing, and web server integration.
Understanding the Core Concepts of Video Streaming
Before diving into code, it's crucial to understand the fundamental components involved in streaming video from a backend application to a frontend webpage. The process typically involves a video source (e.g., webcam, video file), an OpenCV application to capture and process frames, a web server to host the stream, and an HTML page to display it. The key challenge is efficiently transmitting a continuous stream of image data over HTTP.
flowchart TD A[Video Source] --> B[OpenCV Application] B --> C{Frame Processing (e.g., encoding)} C --> D[Web Server] D --> E[HTTP Request from Browser] E --> D D --> F[Streamed Frames (e.g., MJPEG)] F --> G[HTML Webpage] G --> H[Display Video]
High-level architecture for streaming OpenCV video to a webpage
The most common approach for browser-compatible video streaming without dedicated streaming protocols (like WebRTC for peer-to-peer) is to use Motion JPEG (MJPEG). MJPEG works by sending a continuous stream of JPEG images, each representing a single video frame, encapsulated within a multipart/x-mixed-replace HTTP response. The browser then interprets this stream as a video.
Python Implementation with Flask
Python, with its rich ecosystem of libraries like OpenCV and Flask, provides a straightforward way to set up a video streaming server. Flask is a lightweight web framework that's perfect for this task. We'll capture frames using cv2.VideoCapture
, encode them to JPEG, and then serve them via a Flask route.
pip install opencv-python flask
.import cv2
from flask import Flask, Response
app = Flask(__name__)
camera = cv2.VideoCapture(0) # Use 0 for default webcam
def generate_frames():
while True:
success, frame = camera.read()
if not success:
break
else:
ret, buffer = cv2.imencode('.jpg', frame)
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
@app.route('/video_feed')
def video_feed():
return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/')
def index():
return """
<html>
<head>
<title>OpenCV Video Stream</title>
</head>
<body>
<h1>Live Video Stream</h1>
<img src="/video_feed" width="640" height="480" />
</body>
</html>
"""
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Python Flask application for MJPEG video streaming
In this Python example:
cv2.VideoCapture(0)
initializes the default webcam.generate_frames()
is a generator function that continuously reads frames, encodes them to JPEG usingcv2.imencode
, and yields them in themultipart/x-mixed-replace
format.- The
/video_feed
route usesResponse
with the correctmimetype
to serve the MJPEG stream. - The root
/
route serves a simple HTML page containing an<img>
tag whosesrc
points to the/video_feed
endpoint. The browser automatically handles the MJPEG stream from this<img>
tag.
C++ Implementation with Boost.Beast (Advanced)
For C++ developers, streaming OpenCV video to a webpage typically involves a more low-level approach, often using a library for HTTP server capabilities. Boost.Beast is a powerful and modern C++ library for HTTP and WebSocket, making it suitable for this task. This implementation will be more complex due to manual HTTP handling and threading.
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;
// Global frame buffer and mutex
std::vector<uchar> global_frame_buffer;
std::mutex frame_mutex;
void capture_frames() {
cv::VideoCapture cap(0); // Open the default camera
if (!cap.isOpened()) {
std::cerr << "Error: Could not open camera." << std::endl;
return;
}
cv::Mat frame;
std::vector<int> compression_params;
compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
compression_params.push_back(90); // JPEG quality
while (true) {
cap >> frame;
if (frame.empty()) {
std::cerr << "Error: Blank frame grabbed." << std::endl;
break;
}
std::vector<uchar> buffer;
cv::imencode(".jpg", frame, buffer, compression_params);
std::lock_guard<std::mutex> lock(frame_mutex);
global_frame_buffer = buffer;
std::this_thread::sleep_for(std::chrono::milliseconds(30)); // Simulate frame rate
}
}
// Handles an HTTP server connection
void do_session(tcp::socket& socket) {
beast::error_code ec;
// This buffer is used to store the incoming request.
beast::flat_buffer buffer;
// Read a request
http::request<http::string_body> req;
http::read(socket, buffer, req, ec);
if (ec == http::error::end_of_stream) return; // Client closed connection
if (ec) { std::cerr << "read: " << ec.message() << std::endl; return; }
// Handle the request
if (req.target() == "/video_feed") {
http::response<http::string_body> res;
res.version(req.version());
res.result(http::status::ok);
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "multipart/x-mixed-replace; boundary=frame");
res.keep_alive(true);
// Write the header to the client
http::write(socket, res, ec);
if (ec) { std::cerr << "write header: " << ec.message() << std::endl; return; }
while (true) {
std::vector<uchar> current_frame;
{ // Lock scope
std::lock_guard<std::mutex> lock(frame_mutex);
current_frame = global_frame_buffer;
}
if (current_frame.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
std::string boundary = "--frame\r\n";
std::string content_type = "Content-Type: image/jpeg\r\n\r\n";
std::string end_line = "\r\n";
beast::flat_buffer frame_buffer;
std::ostream os(&frame_buffer);
os << boundary << content_type;
os.write(reinterpret_cast<const char*>(current_frame.data()), current_frame.size());
os << end_line;
http::write(socket, frame_buffer.data(), ec);
if (ec) {
if (ec != beast::errc::broken_pipe && ec != beast::errc::connection_aborted) {
std::cerr << "write frame: " << ec.message() << std::endl;
}
break; // Client disconnected
}
std::this_thread::sleep_for(std::chrono::milliseconds(30)); // Control frame rate
}
} else if (req.target() == "/") {
http::response<http::string_body> res;
res.version(req.version());
res.result(http::status::ok);
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.body() = """
<html>
<head>
<title>OpenCV C++ Video Stream</title>
</head>
<body>
<h1>Live Video Stream (C++)</h1>
<img src=\"/video_feed\" width=\"640\" height=\"480\" />
</body>
</html>
""";
res.prepare_payload();
http::write(socket, res, ec);
if (ec) { std::cerr << "write html: " << ec.message() << std::endl; return; }
} else {
http::response<http::string_body> res;
res.version(req.version());
res.result(http::status::not_found);
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/plain");
res.body() = "The resource '" + std::string(req.target()) + "' was not found.";
res.prepare_payload();
http::write(socket, res, ec);
if (ec) { std::cerr << "write not found: " << ec.message() << std::endl; return; }
}
socket.shutdown(tcp::socket::shutdown_send, ec);
if (ec && ec != beast::errc::not_connected) {
std::cerr << "shutdown: " << ec.message() << std::endl;
}
}
int main() {
std::thread frame_capture_thread(capture_frames);
try {
auto const address = net::ip::make_address("0.0.0.0");
auto const port = static_cast<unsigned short>(5000);
net::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
std::cout << "Server listening on http://" << address << ":" << port << std::endl;
while (true) {
tcp::socket socket{ioc};
acceptor.accept(socket);
std::thread(do_session, std::move(socket)).detach();
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
frame_capture_thread.join();
return 0;
}
C++ Boost.Beast application for MJPEG video streaming
The C++ example is significantly more involved:
capture_frames()
function: Runs in a separate thread, continuously captures frames from the webcam, encodes them to JPEG, and stores them in aglobal_frame_buffer
protected by astd::mutex
.do_session()
function: Handles individual client connections. It reads HTTP requests and, for/video_feed
, constructs amultipart/x-mixed-replace
response, continuously sending JPEG frames from theglobal_frame_buffer
.main()
function: Initializes the Boost.Asioio_context
andtcp::acceptor
, then enters a loop to accept incoming client connections. Each connection is handled in a new detached thread bydo_session()
.
This setup ensures that frame capture and HTTP serving happen concurrently, preventing blocking issues.
1. Set up your environment
Install OpenCV and the chosen web framework (Flask for Python, Boost for C++). For Python: pip install opencv-python flask
. For C++: Ensure Boost and OpenCV are correctly linked in your build system (e.g., CMake).
2. Implement video capture and encoding
Write the code to initialize cv2.VideoCapture
(Python) or cv::VideoCapture
(C++), read frames, and encode them into JPEG format using cv2.imencode
or cv::imencode
.
3. Create the web server endpoint
Develop a web server route (e.g., /video_feed
) that continuously sends the encoded JPEG frames as a multipart/x-mixed-replace
HTTP response. This involves setting the correct Content-Type
header and formatting each frame with the boundary string.
4. Design the HTML page
Create a simple HTML file with an <img>
tag whose src
attribute points to your video feed endpoint (e.g., <img src="/video_feed" />
). The browser will automatically display the MJPEG stream.
5. Run the application
Start your Python Flask application (python your_app.py
) or compile and run your C++ executable. Then, open your web browser and navigate to the server's address (e.g., http://localhost:5000
).