Giải pháp camera giám sát với Raspberry Pi

Raspberry Pi không chỉ là một chiếc máy tính mini thú vị để học lập trình, mà còn có thể trở thành một giải pháp giám sát từ xa cực kỳ tiện lợi. Trong bài viết này, chúng ta sẽ cùng xây dựng một hệ thống camera giám sát đơn giản nhưng hiệu quả, sử dụng Raspberry Pi và Camera Module 2. Đây là một giải pháp lý tưởng cho các dự án DIY, lớp học STEM, hoặc để theo dõi không gian tại nhà/nhà xưởng nhỏ.

Linh kiện sử dụng trong bài

Giảm 14%
Giá gốc là: 350.000 ₫.Giá hiện tại là: 300.000 ₫.
Giảm 30%
Giá từ: 1.720.000 
Sản phẩm này có nhiều biến thể. Các tùy chọn có thể được chọn trên trang sản phẩm
690.000 

Ngoài ra, bạn có thể chuẩn bị thêm màn hình, bàn phím, chuột để sử dụng Raspberry Pi hoặc bạn cũng có thể sử dụng Pi thông qua VNC Viewer.

Viết chương trình giám sát

Với một chiếc Raspberry Pi và Camera Module, chúng ta hoàn toàn có thể tự xây dựng một hệ thống camera giám sát đơn giản, tiết kiệm và dễ triển khai. Thay vì phải dùng phần mềm bên thứ ba hoặc dịch vụ đám mây phức tạp, bài viết này sẽ hướng dẫn bạn cách livestream hình ảnh từ camera trực tiếp lên trình duyệt web. Bằng cách sử dụng Python với thư viện picamera2OpenCV, chúng ta sẽ thu thập ảnh từ camera, xử lý nếu cần (chẳng hạn như đảo màu, lật hình) và truyền tới trình duyệt dưới dạng MJPEG – một chuỗi ảnh JPEG được hiển thị liên tục như video.

Mã Python

import io
import cv2
import time
import threading
import socket
import socketserver
from http import server
import numpy as np
from picamera2 import Picamera2

# Trang HTML
PAGE = """\
<html>
<head>
    <title>Camera Stream with OpenCV</title>
</head>
<body>
    <h1>Live Stream from Raspberry Pi Camera</h1>
    <img src="/stream.mjpg" width="640" height="480" />
</body>
</html>
"""

def get_ip_address():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    finally:
        s.close()

class FrameBuffer:
    def __init__(self):
        self.frame = None
        self.condition = threading.Condition()

    def update(self, new_frame):
        with self.condition:
            self.frame = new_frame
            self.condition.notify_all()

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/' or self.path == '/index.html':
            content = PAGE.encode('utf-8')
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', len(content))
            self.end_headers()
            self.wfile.write(content)
        elif self.path == '/stream.mjpg':
            self.send_response(200)
            self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            try:
                while True:
                    with output.condition:
                        output.condition.wait()
                        frame = output.frame
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', str(len(frame)))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                print(f"Client disconnected: {e}")
        else:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)}))
picam2.start()

output = FrameBuffer()

def capture_loop():
    while True:
        frame = picam2.capture_array()

        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

        ret, jpeg = cv2.imencode('.jpg', frame)
        if ret:
            output.update(jpeg.tobytes())

thread = threading.Thread(target=capture_loop)
thread.daemon = True
thread.start()

try:
    address = ('', 8000)
    server = StreamingServer(address, StreamingHandler)
    print(f"Streaming started: http://{get_ip_address()}:8000")
    server.serve_forever()
except KeyboardInterrupt:
    print("Stopping...")
finally:
    picam2.stop()

Trong cách làm này, chúng ta không truyền video theo chuẩn nén phức tạp như H.264, mà sử dụng một cách tiếp cận đơn giản hơn: truyền liên tục các ảnh JPEG (ảnh chụp từ camera) tới trình duyệt. Mỗi khung hình (frame) từ camera sẽ được mã hóa thành một file JPEG rồi gửi đi nối tiếp nhau. Trình duyệt khi nhận được chuỗi ảnh này sẽ hiển thị chúng một cách tuần tự, tạo cảm giác như đang xem video.

Cơ chế này gọi là MJPEG (Motion JPEG) – tức là video được hình thành từ chuỗi các ảnh JPEG độc lập. Mỗi ảnh được phân tách bằng một đoạn mã đặc biệt gọi là --FRAME, giúp trình duyệt hiểu đâu là điểm đầu/điểm cuối của một ảnh.

Về phía máy chủ, chúng ta sử dụng một HTTPServer tự xây dựng bằng Python. Khi người dùng truy cập vào đường dẫn /stream.mjpg, server sẽ giữ kết nối mở và liên tục gửi các ảnh JPEG mới tới trình duyệt.

Ưu điểm của phương pháp này là:

  • Rất dễ triển khai bằng Python thuần, không cần plugin hay thư viện nặng.

  • Tương thích với hầu hết các trình duyệt hiện nay.

  • Cho phép xử lý ảnh từng frame bằng phần mềm (như đảo màu, nhận diện chuyển động).

Sau khi chạy chương trình Python, địa chỉ web để truy cập luồng video sẽ được hiển thị ngay trên màn hình terminal (ví dụ: http://192.168.0.134:8000). Đây chính là địa chỉ IP nội bộ của Raspberry Pi, kết hợp với cổng 8000 mà chương trình đã mở sẵn để truyền dữ liệu.

Bạn chỉ cần mở trình duyệt trên máy tính hoặc điện thoại (miễn là cùng kết nối với mạng nội bộ) và truy cập vào địa chỉ này. Ngay lập tức, hình ảnh từ camera sẽ được hiển thị trực tiếp trong trình duyệt dưới dạng livestream.

Trường hợp bạn muốn sử dụng bên ngoài Internet, có thể kết hợp với bài viết cài đặt Dynamic DNS qua FreeDNS hoặc cài đặt Dynamic DNS qua Cloudflare để sử dụng với domain.

Kết luận

Với chi phí thấp và khả năng mở rộng cao, Raspberry Pi kết hợp cùng Camera Module đã trở thành một lựa chọn lý tưởng để xây dựng hệ thống camera giám sát đơn giản nhưng hiệu quả. Thông qua việc sử dụng Python cùng thư viện picamera2OpenCV, chúng ta có thể dễ dàng thu thập hình ảnh từ camera, xử lý từng khung hình nếu cần, và truyền trực tiếp lên trình duyệt web dưới dạng luồng MJPEG. Giải pháp này phù hợp cho nhiều mục đích như: theo dõi trong nhà, quan sát lớp học, giám sát xưởng nhỏ hoặc phục vụ cho các dự án học tập và nghiên cứu. Hy vọng bài viết sẽ giúp bạn có được một khởi đầu thuận lợi trong việc xây dựng hệ thống giám sát cá nhân hóa với Raspberry Pi.