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
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 picamera2
và OpenCV
, 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 picamera2
và OpenCV
, 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.