
Raspberry Pi Camera: Quét mã QR/Barcode tự động
Dùng Raspberry Pi 4 + Camera Module v3 với thư viện pyzbar và OpenCV để quét tự động mã QR và các loại barcode phổ biến (EAN-13, Code128...) theo thời gian thực, log kết quả kèm thời gian quét.
Project này biến Raspberry Pi 4 và Camera Module v3 thành một đầu đọc mã QR/barcode thời gian thực, dùng thư viện pyzbar (bọc quanh ZBar) kết hợp OpenCV để giải mã.
Camera liên tục chụp khung hình, pyzbar quét toàn bộ khung tìm mọi vùng chứa mã (QR Code, EAN-13, Code128, v.v.), vẽ khung bao quanh mã tìm được và in nội dung giải mã ra console/log.
Một bộ lọc chống trùng (debounce) đảm bảo cùng một mã đứng yên trước camera không bị log lặp lại liên tục — chỉ log lại khi mã biến mất rồi xuất hiện lại, hoặc sau một khoảng thời gian tối thiểu.
Hướng dẫn chi tiết
Dùng Raspberry Pi 4 + Camera Module v3 với pyzbar và OpenCV để quét tự động mã QR và barcode phổ biến (EAN-13, Code128...) theo thời gian thực, chống trùng và log kết quả ra CSV.
1. Giới thiệu
Project này biến Raspberry Pi 4 và Camera Module v3 thành một đầu đọc mã QR/barcode thời gian thực, dùng thư viện pyzbar (bọc quanh thư viện giải mã ZBar) kết hợp OpenCV để lấy khung hình.
Camera liên tục chụp khung hình, pyzbar quét toàn bộ khung tìm mọi vùng chứa mã (QR Code, EAN-13, Code128, và nhiều định dạng khác mà ZBar hỗ trợ), vẽ khung bao quanh mã tìm được và in nội dung giải mã ra console/log.
Một bộ lọc chống trùng (debounce) đảm bảo cùng một mã đứng yên trước camera không bị log lặp lại liên tục — chỉ log lại khi mã biến mất rồi xuất hiện lại, hoặc sau một khoảng thời gian tối thiểu (mặc định 3 giây).
Đây là một pipeline nhẹ, không cần huấn luyện mô hình gì — pyzbar/ZBar là bộ giải mã tất định (deterministic), hoạt động tốt trong hầu hết điều kiện ánh sáng bình thường. Phù hợp làm trạm quét check-in, quét mã sản phẩm, hoặc mã QR truy cập trên Raspberry Pi.
2. Linh kiện cần có
Linh kiện | Số lượng | Giá tham khảo |
|---|---|---|
Raspberry Pi 4 Model B (2/4/8GB RAM) | 1 | ~1.500.000 – 2.200.000đ |
Raspberry Pi Camera Module v3 (IMX708) | 1 | ~700.000 – 900.000đ |
Thẻ nhớ microSD 32GB (Class 10 trở lên, cài Raspberry Pi OS) — phụ kiện, không nằm trong sơ đồ điện | 1 | ~150.000đ |
Nguồn USB-C 5V/3A — phụ kiện, không nằm trong sơ đồ điện | 1 | ~150.000đ |
3. Sơ đồ kết nối
RPi Camera Module v3 | Raspberry Pi 4 |
|---|---|
CSI (Ribbon connector) | CSI Camera Port |
SDA (I2C control) | GPIO2 / SDA1 (Pin 3) |
SCL (I2C control) | GPIO3 / SCL1 (Pin 5) |
3V3 (Power) | 3V3 (Pin 1/17) |
GND | GND |
Kết nối chính là cáp ribbon CSI 15-chân đi kèm camera — cắm thẳng vào cổng CSI trên Raspberry Pi 4 (nằm giữa cổng HDMI và jack audio).
Lưu ý chiều cáp: mặt có các đường tiếp xúc kim loại phải hướng về phía cổng HDMI; cắm ngược chiều là nguyên nhân phổ biến nhất khiến hệ thống báo "no cameras available". Mở khoá chốt nhựa trên đầu nối CSI trước khi lắp cáp, luồn cáp vào rồi ấn khoá lại chắc chắn.
Bốn dây SDA/SCL/3V3/GND chỉ áp dụng cho board có route thêm chân I2C điều khiển autofocus ra ngoài — nếu module của bạn chỉ có đầu nối ribbon CSI đơn thuần, bỏ qua phần này, chỉ cần cáp CSI là đủ.
4. Code ví dụ #1 — Kiểm tra camera hoạt động (Camera Self-Test)
Chạy script này đầu tiên sau khi lắp camera để xác nhận phần cứng nhận diện đúng trước khi chạy ứng dụng quét mã. Script in banner khởi động, thử mở camera qua picamera2, chụp thử vài khung hình trong 3 giây và báo FPS ước tính.
"""
camera_test.py
Kiem tra nhanh Camera Module v3 truoc khi chay bo quet QR/Barcode.
In banner khoi dong, mo camera qua picamera2, chup vai khung hinh, in
kich thuoc + FPS uoc tinh. Bao loi ro rang neu thieu picamera2 hoac
khong mo duoc phan cung, khong treo may.
"""
import sys
import time
def main() -> int:
print("[BOOT] Raspberry Pi QR/Barcode Scanner - Camera Self-Test")
print("[BOOT] Dang khoi tao picamera2...")
try:
from picamera2 import Picamera2
except ImportError:
print("[ERROR] Khong tim thay thu vien picamera2.")
print(" Cai dat: sudo apt install -y python3-picamera2")
return 1
try:
picam2 = Picamera2()
config = picam2.create_preview_configuration(
main={"size": (640, 480), "format": "RGB888"}
)
picam2.configure(config)
picam2.start()
time.sleep(1.0)
except Exception as exc: # noqa: BLE001
print(f"[ERROR] Khong the khoi dong camera: {exc}")
print(" Kiem tra: cap ribbon CSI cam dung chieu, dung chan,")
print(" camera duoc bat trong raspi-config (Interface Options).")
return 1
frame_count = 0
start = time.monotonic()
test_duration_s = 3.0
while time.monotonic() - start < test_duration_s:
frame = picam2.capture_array()
frame_count += 1
if frame_count == 1:
print(f"[OK] Khung hinh dau tien: shape={frame.shape}, dtype={frame.dtype}")
elapsed = time.monotonic() - start
fps = frame_count / elapsed if elapsed > 0 else 0.0
print(f"[OK] Da chup {frame_count} khung hinh trong {elapsed:.2f}s (~{fps:.1f} FPS)")
picam2.stop()
print("[DONE] Camera hoat dong binh thuong.")
return 0
if __name__ == "__main__":
sys.exit(main())
5. Code ví dụ #2 — Ứng dụng chính: quét QR/Barcode thời gian thực (qr_barcode_scanner.py)
Vòng lặp chính: đọc khung hình từ camera, đưa cho pyzbar.decode() để tìm và giải mã mọi mã QR/barcode trong khung, áp dụng DedupeWindow để không log lặp cùng một mã đứng yên trước camera, ghi kết quả ra CSV qua ScanCsvLogger.
Script tuân thủ các quy tắc mô phỏng bắt buộc: banner khởi động, retry có giới hạn khi mở camera thất bại (không lặp vô hạn), bỏ qua khung hình lỗi thay vì crash, và in telemetry JSON định kỳ mỗi 5 giây.
"""
qr_barcode_scanner.py
Ung dung chinh: quet QR Code va cac loai barcode pho bien (EAN-13, Code128,
...) theo thoi gian thuc, dung Raspberry Pi 4 + Camera Module v3 + pyzbar +
OpenCV (chi dung OpenCV de ve khung bao, giai ma hoan toan boi pyzbar/ZBar).
Hanh vi mo phong quan sat duoc:
- In banner ngay sau khi console san sang.
- Retry co gioi han khi mo camera that bai (khong while vo han).
- Khung hinh loi -> bo qua, khong crash.
- Debounce chong log lap (scan_logger.DedupeWindow).
- In telemetry JSON dinh ky.
"""
from __future__ import annotations
import json
import time
from typing import Optional
from scan_logger import DedupeWindow, ScanCsvLogger
FRAME_WIDTH = 640
FRAME_HEIGHT = 480
TELEMETRY_INTERVAL_S = 5.0
CAMERA_RETRY_INTERVAL_S = 3.0
CAMERA_MAX_RETRIES = 5
DEDUPE_WINDOW_S = 3.0
CSV_LOG_PATH = "qr_barcode_scans.csv"
def open_camera():
from picamera2 import Picamera2
for attempt in range(1, CAMERA_MAX_RETRIES + 1):
try:
picam2 = Picamera2()
config = picam2.create_preview_configuration(
main={"size": (FRAME_WIDTH, FRAME_HEIGHT), "format": "RGB888"}
)
picam2.configure(config)
picam2.start()
time.sleep(0.5)
print(f"[BOOT] Camera san sang sau {attempt} lan thu.")
return picam2
except Exception as exc: # noqa: BLE001
print(f"[WARN] Lan thu {attempt}/{CAMERA_MAX_RETRIES} mo camera that bai: {exc}")
if attempt < CAMERA_MAX_RETRIES:
time.sleep(CAMERA_RETRY_INTERVAL_S)
print("[ERROR] Khong mo duoc camera sau nhieu lan thu, dung chuong trinh.")
return None
def scan_frame(frame):
"""Quet 1 khung hinh, tra ve list cac ket qua pyzbar. [] neu khong co ma."""
from pyzbar.pyzbar import decode
if frame is None or frame.size == 0:
return []
return decode(frame)
def build_telemetry(total_scanned: int, last_type: Optional[str]) -> str:
payload = {
"ts": time.strftime("%Y-%m-%dT%H:%M:%S"),
"total_scanned": total_scanned,
"last_code_type": last_type,
}
return json.dumps(payload, ensure_ascii=False)
def main() -> int:
print("[BOOT] Raspberry Pi QR/Barcode Scanner khoi dong...")
print(f"[BOOT] Do phan giai khung hinh: {FRAME_WIDTH}x{FRAME_HEIGHT}")
try:
import cv2 # noqa: F401 - dung de ve khung bao khi debug truc quan
except ImportError:
print("[ERROR] Thieu thu vien opencv-python. Cai dat: pip install opencv-python")
return 1
try:
from pyzbar.pyzbar import decode # noqa: F401
except ImportError:
print("[ERROR] Thieu thu vien pyzbar. Cai dat: pip install pyzbar")
print(" Yeu cau them thu vien he thong: sudo apt install -y libzbar0")
return 1
picam2 = open_camera()
if picam2 is None:
return 1
dedupe = DedupeWindow(window_s=DEDUPE_WINDOW_S)
logger = ScanCsvLogger(CSV_LOG_PATH)
total_scanned = 0
last_type: Optional[str] = None
last_telemetry_at = time.monotonic()
try:
while True:
try:
frame = picam2.capture_array()
except Exception as exc: # noqa: BLE001
print(f"[WARN] Loi doc khung hinh, bo qua: {exc}")
frame = None
results = scan_frame(frame) if frame is not None else []
for result in results:
code_type = result.type
try:
data = result.data.decode("utf-8", errors="replace")
except Exception: # noqa: BLE001
data = repr(result.data)
if dedupe.should_log(code_type, data):
x, y, w, h = result.rect
logger.log(code_type, data, (x, y, w, h))
total_scanned += 1
last_type = code_type
print(f"[SCAN] type={code_type} data={data!r} box=({x},{y},{w},{h})")
now = time.monotonic()
if now - last_telemetry_at >= TELEMETRY_INTERVAL_S:
print(build_telemetry(total_scanned, last_type))
last_telemetry_at = now
time.sleep(0.05)
except KeyboardInterrupt:
print("[STOP] Nhan Ctrl+C, dang dung...")
finally:
logger.close()
picam2.stop()
print(f"[DONE] Tong so ma da quet: {total_scanned}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
6. Code ví dụ #3 — Module hỗ trợ: chống trùng và ghi log (scan_logger.py)
Module này tách riêng hai tiện ích mà qr_barcode_scanner.py import: DedupeWindow (chỉ cho phép log lại cùng một nội dung mã sau khi qua một khoảng thời gian tối thiểu, tránh spam khi mã đứng yên nhiều khung hình liên tiếp) và ScanCsvLogger (ghi mỗi lần quét thành công ra file CSV kèm loại mã, nội dung, và toạ độ khung bao, tự tạo file + header nếu chưa có).
"""
scan_logger.py
Tien ich dung chung cho qr_barcode_scanner.py:
- DedupeWindow: chan log lap lai cung mot noi dung ma trong khoang thoi
gian toi thieu (debounce), tranh spam khi ma dung yen truoc camera.
- ScanCsvLogger: ghi moi lan quet thanh cong ra file CSV (timestamp,
loai ma, noi dung, vi tri khung bao).
"""
from __future__ import annotations
import csv
import os
import time
from typing import Dict, Tuple
class DedupeWindow:
"""Chi cho phep log lai cung 1 (type, data) sau khi qua window_s giay."""
def __init__(self, window_s: float = 3.0) -> None:
self.window_s = window_s
self._last_seen: Dict[Tuple[str, str], float] = {}
def should_log(self, code_type: str, data: str) -> bool:
key = (code_type, data)
now = time.monotonic()
last = self._last_seen.get(key)
if last is None or (now - last) >= self.window_s:
self._last_seen[key] = now
return True
return False
class ScanCsvLogger:
def __init__(self, csv_path: str) -> None:
self.csv_path = csv_path
is_new_file = not os.path.exists(csv_path)
self._file = open(csv_path, "a", newline="", encoding="utf-8")
self._writer = csv.writer(self._file)
if is_new_file:
self._writer.writerow(["timestamp_iso", "code_type", "data", "box_x", "box_y", "box_w", "box_h"])
self._file.flush()
def log(self, code_type: str, data: str, box) -> None:
x, y, w, h = box
timestamp_iso = time.strftime("%Y-%m-%dT%H:%M:%S")
self._writer.writerow([timestamp_iso, code_type, data, x, y, w, h])
self._file.flush()
def close(self) -> None:
self._file.close()
7. Lỗi thường gặp
Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
"no cameras available" / camera không nhận | Cáp ribbon CSI cắm ngược chiều hoặc chưa cài chặt khoá chốt | Rút cáp, kiểm tra mặt tiếp xúc kim loại hướng về phía cổng HDMI, cắm lại và khoá chốt chắc chắn |
ImportError: No module named pyzbar | Chưa cài thư viện pyzbar hoặc thiếu thư viện hệ thống libzbar | Cài bằng |
ImportError: No module named picamera2 | Chưa cài thư viện picamera2 | Cài bằng |
Mã không được nhận diện dù nằm trong khung hình | Mã quá nhỏ, mờ, phản chiếu ánh sáng gắt, hoặc góc chụp quá nghiêng | Đưa mã gần camera hơn hoặc tăng độ phân giải khung hình, tránh ánh sáng phản chiếu trực tiếp lên bề mặt mã, giữ mã vuông góc với ống kính |
Một mã bị log lặp lại liên tục dù không di chuyển | Thời gian debounce ( | Tăng giá trị |
FPS thấp khi có nhiều mã trong khung hình cùng lúc | pyzbar phải giải mã tuần tự từng mã, tốn CPU hơn khi số lượng mã tăng | Giảm độ phân giải khung hình, giới hạn số mã kỳ vọng trong một khung, hoặc tăng khoảng nghỉ giữa các vòng lặp |
8. Tổng kết
Với Raspberry Pi 4, Camera Module v3, pyzbar và một vòng lặp Python gọn nhẹ, bạn đã có một trạm quét QR/barcode thời gian thực, tự động log kết quả ra CSV kèm timestamp.
Vì pyzbar/ZBar là bộ giải mã cố định (không phải machine learning), kết quả ổn định và có thể dự đoán được trong điều kiện ánh sáng và khoảng cách hợp lý — đây là điểm mạnh so với các phương pháp thị giác máy tính khác trong bài viết cùng series.
Bạn có thể mở rộng project này bằng cách gửi dữ liệu quét được lên MQTT/webhook, tích hợp với hệ thống chấm công, hoặc thêm màn hình nhỏ để hiển thị phản hồi tức thì cho người dùng.