Raspberry Pi 5 là một nền tảng edge AI mạnh mẽ với CPU ARM Cortex-A76 tốc độ 2.4GHz. Bài viết này hướng dẫn chi tiết cách chạy TensorFlow Lite, MediaPipe, và YOLO object detection trên Pi 5 — từ cài đặt, code mẫu, đến benchmark hiệu năng thực tế.
Tổng quan
| Framework | Mục đích | Hiệu năng |
|---|---|---|
| TensorFlow Lite | Object detection, classification, segmentation | ~2-15 FPS tùy model |
| MediaPipe | Hand tracking, pose detection, face mesh | ~10-30 FPS |
| YOLOv8n (CPU) | Real-time object detection | ~2 FPS |
Lưu ý: Các con số benchmark trên là chạy CPU thuần (4 cores). Với Hailo-8L NPU (AI Kit), YOLOv8 đạt ~30 FPS — tham khảo bài Pi 5 AI Kit: Chạy YOLOv8 với Hailo-8L.
1. Cài đặt môi trường
Yêu cầu
- Raspberry Pi 5 (4GB/8GB)
- Raspberry Pi OS Bookworm (64-bit)
- Camera Module 3 hoặc USB webcam
- Nguồn 5V/5A
Cài đặt TensorFlow Lite Runtime
# 1. Kiểm tra Python version
python3 --version
# Phải là Python 3.11 (Bookworm mặc định)
# 2. Cài dependencies
sudo apt update
sudo apt install -y python3-pip python3-opencv python3-picamera2 libatlas-base-dev
# 3. TensorFlow Lite Runtime cho ARM64
pip3 install tflite-runtime --upgrade
# Hoặc cài từ wheel cụ thể
wget https://github.com/nicktajzs/TensorFlow-Lite-Runtime-ARM64/releases/download/v2.17.0/tflite_runtime-2.17.0-cp311-cp311-linux_aarch64.whl
pip3 install tflite_runtime-2.17.0-cp311-cp311-linux_aarch64.whl
# 4. Kiểm tra
python3 -c "import tflite_runtime.interpreter as tflite; print('TFLite OK:', tflite.__version__)"
Cài đặt MediaPipe
# MediaPipe cho ARM64
pip3 install mediapipe --upgrade
# Kiểm tra
python3 -c "import mediapipe as mp; print('MediaPipe OK:', mp.__version__)"
Cài đặt thêm
# Pillow, numpy, các thư viện hỗ trợ
pip3 install numpy pillow matplotlib
# picamera2 (đã có trên Bookworm)
python3 -c "from picamera2 import Picamera2; print('Picamera2 OK')"
2. TensorFlow Lite — Object Detection
Tải model COCO SSD MobileNet
mkdir -p ~/tflite && cd ~/tflite
# Model quantized (tối ưu cho ARM)
wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip
unzip coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip
# Labels
wget -O labels.txt https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_complete_label_map.pbtxt
Code Python — Real-time object detection
# tflite_detection.py — TensorFlow Lite object detection
import tflite_runtime.interpreter as tflite
import numpy as np
import cv2
import time
from picamera2 import Picamera2
# ════════════ Cấu hình ════════════
MODEL_PATH = "coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.tflite"
LABELS_PATH = "labels.txt"
CONF_THRESHOLD = 0.5
# ════════════ Load model ════════════
print("🔄 Đang nạp model TFLite...")
interpreter = tflite.Interpreter(model_path=MODEL_PATH)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
_, height, width, _ = input_details[0]['shape']
print(f"✅ Model loaded: {width}x{height}")
print(f" Input tensor: {input_details[0]['shape']}")
print(f" Output tensors: {len(output_details)}")
# Load labels
def load_labels(path):
labels = {}
with open(path, 'r') as f:
for line in f:
if 'id:' in line:
id_val = int(line.strip().split(':')[1].strip())
elif 'display_name:' in line:
name = line.strip().split('"')[1]
labels[id_val] = name
return labels
labels = load_labels(LABELS_PATH)
print(f" {len(labels)} classes")
# ════════════ Camera ════════════
picam2 = Picamera2()
config = picam2.create_video_configuration(
main={"size": (640, 480)},
controls={"FrameRate": 30}
)
picam2.configure(config)
# ════════════ Inference ════════════
def detect_objects(frame):
"""Chạy TFLite inference trên frame"""
# Resize về input size của model
img = cv2.resize(frame, (width, height))
img = np.expand_dims(img, axis=0).astype(np.uint8)
# Set input
interpreter.set_tensor(input_details[0]['index'], img)
# Run inference
interpreter.invoke()
# Get output
boxes = interpreter.get_tensor(output_details[0]['index'])[0]
classes = interpreter.get_tensor(output_details[1]['index'])[0]
scores = interpreter.get_tensor(output_details[2]['index'])[0]
num_detections = int(interpreter.get_tensor(output_details[3]['index'])[0])
results = []
for i in range(num_detections):
if scores[i] > CONF_THRESHOLD:
label_id = int(classes[i]) + 1
label = labels.get(label_id, f"Class {label_id}")
y1, x1, y2, x2 = boxes[i]
results.append({
"label": label,
"score": float(scores[i]),
"box": (int(x1 * frame.shape[1]),
int(y1 * frame.shape[0]),
int(x2 * frame.shape[1]),
int(y2 * frame.shape[0]))
})
return results
def draw_detections(frame, detections):
"""Vẽ bounding box lên frame"""
for d in detections:
x1, y1, x2, y2 = d["box"]
color = (0, 255, 0)
if d["label"] == "person":
color = (0, 0, 255)
elif d["label"] in ["car", "truck", "bus"]:
color = (255, 0, 0)
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
label = f"{d['label']} {d['score']:.0%}"
cv2.putText(frame, label, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
return frame
# ════════════ Main loop ════════════
print("✅ Sẵn sàng! Start detection...")
picam2.start()
fps_history = []
while True:
start = time.time()
# Capture
frame = picam2.capture_array()
# Detect
detections = detect_objects(frame)
# Draw
frame = draw_detections(frame, detections)
# FPS
fps = 1.0 / (time.time() - start)
fps_history.append(fps)
avg_fps = sum(fps_history[-30:]) / min(len(fps_history), 30)
cv2.putText(frame, f"TFLite CPU: {avg_fps:.1f} FPS", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.putText(frame, f"Detections: {len(detections)}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# Show
cv2.imshow("TFLite Object Detection", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
picam2.stop()
cv2.destroyAllWindows()
print(f"\n📊 Benchmark: Avg FPS = {sum(fps_history)/len(fps_history):.1f}")
Hiệu năng mong đợi
| Model | Độ phân giải | FPS (Pi 5 CPU) |
|---|---|---|
| SSD MobileNet V1 (quantized) | 300×300 | 8-12 FPS |
| SSD MobileNet V2 (quantized) | 300×300 | 10-15 FPS |
| EfficientDet-Lite0 | 320×320 | 5-8 FPS |
| EfficientDet-Lite2 | 448×448 | 2-4 FPS |
3. MediaPipe — Hand Tracking
MediaPipe Hands phát hiện 21 landmarks trên bàn tay với độ chính xác cao.
Code Python — Hand tracking
# mediapipe_hands.py — Hand tracking với MediaPipe
import mediapipe as mp
import cv2
import time
from picamera2 import Picamera2
# ════════════ Khởi tạo MediaPipe ════════════
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5,
model_complexity=1 # 0=light, 1=full
)
# ════════════ Camera ════════════
picam2 = Picamera2()
config = picam2.create_video_configuration(
main={"size": (640, 480)},
controls={"FrameRate": 30}
)
picam2.configure(config)
# ════════════ Main loop ════════════
print("🖐️ MediaPipe Hand Tracking — Press 'q' to quit")
picam2.start()
while True:
start = time.time()
frame = picam2.capture_array()
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Process với MediaPipe
result = hands.process(frame_rgb)
# Draw landmarks
if result.multi_hand_landmarks:
for hand_landmarks in result.multi_hand_landmarks:
mp_drawing.draw_landmarks(
frame,
hand_landmarks,
mp_hands.HAND_CONNECTIONS,
mp_drawing_styles.get_default_hand_landmarks_style(),
mp_drawing_styles.get_default_hand_connections_style()
)
# Lấy số ngón tay
for hand_landmarks in result.multi_hand_landmarks:
# Đếm ngón tay xòe
fingers = []
tips = [4, 8, 12, 16, 20] # Landmark index của đầu ngón
# Ngón cái
if hand_landmarks.landmark[4].x < hand_landmarks.landmark[3].x:
fingers.append(1)
else:
fingers.append(0)
# 4 ngón còn lại
for tip in tips[1:]:
if hand_landmarks.landmark[tip].y < hand_landmarks.landmark[tip - 2].y:
fingers.append(1)
else:
fingers.append(0)
count = sum(fingers)
cv2.putText(frame, f"Fingers: {count}", (10, 90),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# Tọa độ cổ tay
wrist = hand_landmarks.landmark[0]
h, w, _ = frame.shape
cx, cy = int(wrist.x * w), int(wrist.y * h)
cv2.circle(frame, (cx, cy), 5, (255, 0, 0), -1)
# FPS
fps = 1.0 / (time.time() - start)
cv2.putText(frame, f"FPS: {fps:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow("Hand Tracking", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
picam2.stop()
cv2.destroyAllWindows()
hands.close()
MediaPipe — Pose Detection
# mediapipe_pose.py — Human pose detection
import mediapipe as mp
import cv2
import time
from picamera2 import Picamera2
mp_pose = mp.solutions.pose
mp_drawing = mp.solutions.drawing_utils
pose = mp_pose.Pose(
static_image_mode=False,
model_complexity=1, # 0=light, 1=full, 2=heavy
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
picam2 = Picamera2()
config = picam2.create_video_configuration(
main={"size": (640, 480)},
controls={"FrameRate": 30}
)
picam2.configure(config)
print("🧍 Pose Detection — Press 'q' to quit")
picam2.start()
while True:
start = time.time()
frame = picam2.capture_array()
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
result = pose.process(frame_rgb)
if result.pose_landmarks:
mp_drawing.draw_landmarks(
frame,
result.pose_landmarks,
mp_pose.POSE_CONNECTIONS,
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2)
)
# Lấy tọa độ mũi (landmark 0)
nose = result.pose_landmarks.landmark[0]
h, w, _ = frame.shape
cx, cy = int(nose.x * w), int(nose.y * h)
cv2.circle(frame, (cx, cy), 5, (255, 0, 0), -1)
# Phát hiện tư thế raise hands
left_wrist = result.pose_landmarks.landmark[15]
right_wrist = result.pose_landmarks.landmark[16]
if left_wrist.y < nose.y and right_wrist.y < nose.y:
cv2.putText(frame, "BOTH HANDS UP!", (150, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
fps = 1.0 / (time.time() - start)
cv2.putText(frame, f"FPS: {fps:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow("Pose Detection", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
picam2.stop()
cv2.destroyAllWindows()
pose.close()
Benchmark MediaPipe trên Pi 5
| Feature | Model Complexity | FPS (640×480) | Ghi chú |
|---|---|---|---|
| Hand Tracking | 0 (light) | ~25-30 FPS | Phát hiện 1 tay |
| Hand Tracking | 1 (full) | ~15-20 FPS | Phát hiện 2 tay |
| Pose Detection | 0 (light) | ~20-25 FPS | 33 landmarks |
| Pose Detection | 1 (full) | ~12-18 FPS | 33 landmarks |
| Pose Detection | 2 (heavy) | ~6-10 FPS | Chính xác nhất |
| Face Mesh | Default | ~15-20 FPS | 468 landmarks |
4. YOLOv8 trên CPU (ONNX Runtime)
Chạy YOLOv8 trên CPU Pi 5 dùng ONNX Runtime hoặc OpenCV DNN.
Code Python — YOLOv8 với OpenCV DNN
# yolov8_cpu.py — YOLOv8 trên CPU Pi 5
import cv2
import numpy as np
import time
from picamera2 import Picamera2
import urllib.request
import os
# ════════════ Tải model ════════════
YOLO_URL = "https://github.com/ultralytics/assets/releases/download/v0.0.0/yolov8n.onnx"
YOLO_PATH = "yolov8n.onnx"
if not os.path.exists(YOLO_PATH):
print("⬇️ Đang tải YOLOv8n ONNX model...")
urllib.request.urlretrieve(YOLO_URL, YOLO_PATH)
print("✅ OK")
# Load model
net = cv2.dnn.readNetFromONNX(YOLO_PATH)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
# COCO labels
COCO_LABELS = [
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
"boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",
"bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra",
"giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
"skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove",
"skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup",
"fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange",
"broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
"potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse",
"remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",
"refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier",
"toothbrush"
]
CONF_THRESHOLD = 0.5
IOU_THRESHOLD = 0.45
# ════════════ Camera ════════════
picam2 = Picamera2()
config = picam2.create_video_configuration(
main={"size": (640, 640)},
controls={"FrameRate": 15}
)
picam2.configure(config)
# ════════════ Hàm xử lý ════════════
def detect_yolov8(frame):
"""YOLOv8 inference với OpenCV DNN"""
h, w = frame.shape[:2]
# Preprocess
blob = cv2.dnn.blobFromImage(frame, 1/255.0, (640, 640), swapRB=True, crop=False)
net.setInput(blob)
# Inference
outputs = net.forward()[0] # (84, 8400)
# Parse output
boxes = []
for i in range(outputs.shape[1]):
scores = outputs[4:, i]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence < CONF_THRESHOLD:
continue
cx, cy, bw, bh = outputs[:4, i]
x1 = int((cx - bw/2) * w / 640)
y1 = int((cy - bh/2) * h / 640)
x2 = int((cx + bw/2) * w / 640)
y2 = int((cy + bh/2) * h / 640)
boxes.append({
"label": COCO_LABELS[int(class_id)],
"score": float(confidence),
"box": (x1, y1, x2, y2)
})
# NMS
boxes.sort(key=lambda b: b["score"], reverse=True)
final = []
for b in boxes:
if not any(iou(b["box"], fb["box"]) > IOU_THRESHOLD for fb in final):
final.append(b)
return final
def iou(b1, b2):
x1 = max(b1[0], b2[0]); y1 = max(b1[1], b2[1])
x2 = min(b1[2], b2[2]); y2 = min(b1[3], b2[3])
inter = max(0, x2-x1) * max(0, y2-y1)
a1 = (b1[2]-b1[0]) * (b1[3]-b1[1])
a2 = (b2[2]-b2[0]) * (b2[3]-b2[1])
return inter / (a1 + a2 - inter) if (a1 + a2 - inter) > 0 else 0
# ════════════ Main ════════════
print("🟢 YOLOv8 CPU Detection — Press 'q' to quit")
picam2.start()
while True:
start = time.time()
frame = picam2.capture_array()
detections = detect_yolov8(frame)
for d in detections:
x1, y1, x2, y2 = d["box"]
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f"{d['label']} {d['score']:.0%}", (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
fps = 1.0 / (time.time() - start)
cv2.putText(frame, f"YOLOv8 CPU: {fps:.1f} FPS", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
cv2.imshow("YOLOv8 on Pi 5 CPU", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
picam2.stop()
cv2.destroyAllWindows()
⚠️ Lưu ý: YOLOv8n chạy CPU Pi 5 chỉ đạt ~2 FPS do model 8.9M parameters. Để chạy real-time (≥30 FPS), cần Hailo NPU (AI Kit).
So sánh hiệu năng YOLO inference
| Model | CPU Pi 5 | Hailo-8L NPU | Tỉ lệ tăng |
|---|---|---|---|
| YOLOv5n 640px | ~3 FPS | ~35 FPS | 11.7× |
| YOLOv8n 640px | ~2 FPS | ~30 FPS | 15× |
| YOLOv8s 640px | ~0.8 FPS | ~15 FPS | 18.8× |
| YOLOv8m 640px | ~0.3 FPS | ~8 FPS | 26.7× |
5. Tối ưu hiệu năng
Giảm độ phân giải
# Thay vì 640×480, dùng 320×240
config = picam2.create_video_configuration(
main={"size": (320, 240)}, # Nhỏ hơn = nhanh hơn
controls={"FrameRate": 30}
)
| Resolution | TFLite FPS | MediaPipe Hands FPS |
|---|---|---|
| 640×480 | 8-12 | 15-20 |
| 320×240 | 20-25 | 25-30 |
| 160×120 | 30+ | 30+ |
Dùng model complexity thấp
# MediaPipe: model_complexity=0 cho speed
hands = mp_hands.Hands(
model_complexity=0, # 0=fast, 1=balanced, 2=accurate
max_num_hands=1 # Chỉ detect 1 tay
)
Threading
# Tách capture và inference trên 2 threads
from threading import Thread
from queue import Queue
frame_queue = Queue(maxsize=2)
result_queue = Queue(maxsize=2)
def capture_thread():
picam2 = Picamera2()
picam2.configure(config)
picam2.start()
while True:
frame = picam2.capture_array()
if not frame_queue.full():
frame_queue.put(frame)
Xử lý bỏ qua frame (skip frame)
# Chỉ inference mỗi frame thứ N
frame_count = 0
while True:
frame = picam2.capture_array()
frame_count += 1
if frame_count % 3 == 0: # Inference mỗi 3 frame ~ 10 FPS
detections = detect_objects(frame)
6. Common Mistakes
| Sai lầm | Hậu quả | Giải pháp |
|---|---|---|
| Dùng TensorFlow full (không phải TFLite) | Chiếm 1GB+ RAM, crash | Dùng tflite-runtime (nhẹ) |
| Không cài libatlas-base-dev | Lỗi import numpy/opencv | sudo apt install libatlas-base-dev |
| MediaPipe model_complexity=2 | Pose detection ~6 FPS | Dùng complexity=0 hoặc 1 |
| Quên swap khi chạy model lớn | OOM (Out of Memory) | Tăng swap: sudo dphys-swapfile swapoff && edit /etc/dphys-swapfile CONF_SWAPSIZE=2048 |
| Dùng USB 2.0 webcam | Camera chậm, FPS thấp | Dùng Camera Module 3 (CSI) |
| Độ phân giải quá cao (1080p) | AI chạy <1 FPS | Dùng 640×480 hoặc 320×240 |
| Quên tản nhiệt | CPU throttle, FPS giảm | Heatsink + fan case |
| Chạy nhiều model cùng lúc | CPU 100%, crash | Chạy tuần tự, mỗi model cách nhau |
7. Ứng dụng thực tế
Smart mirror
# MediaPipe hand tracking + pose detection
# Điều khiển smart mirror bằng cử chỉ tay
# Hiển thị thời tiết, lịch, tin tức
Gesture-controlled robot
# Đếm số ngón tay → gửi lệnh điều khiển
# 1 ngón = tiến, 2 ngón = lùi, 3 ngón = trái, 4 ngón = phải
# Nắm tay = dừng
Fitness tracking
# MediaPipe Pose phát hiện tư thế tập luyện
# Đếm số lần squat, push-up, jumping jack
# Đánh giá kỹ thuật dựa trên angle của khớp
People counter
# YOLO object detection đếm người qua camera
# Hiển thị trên web dashboard (Flask/Node-RED)
# Dùng cho smart retail, office occupancy
8. Chạy tự động với systemd
# Tạo service cho object detection
sudo tee /etc/systemd/system/edge-ai.service << 'EOF'
[Unit]
Description=Edge AI Object Detection on Pi 5
After=network.target
[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/tflite
ExecStart=/usr/bin/python3 tflite_detection.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Kích hoạt
sudo systemctl enable edge-ai
sudo systemctl start edge-ai
# Logs
sudo journalctl -u edge-ai -f
Tổng kết
Raspberry Pi 5 là một nền tảng edge AI cực kỳ linh hoạt:
- TensorFlow Lite — Object detection, classification ~8-15 FPS với model nhẹ
- MediaPipe — Hand tracking ~20 FPS, pose detection ~15 FPS
- YOLOv8 (CPU) — ~2 FPS, cần NPU để đạt real-time
Với CPU mạnh hơn 2-3× so với Pi 4, Pi 5 có thể chạy nhiều tác vụ AI cơ bản mà không cần thêm phần cứng. Khi cần hiệu năng cao hơn (≥30 FPS), kết hợp với Hailo-8L AI Kit là giải pháp tối ưu.
| Framework | Use case | Hiệu năng | Khó |
|---|---|---|---|
| TFLite | Object detection cơ bản | 8-15 FPS | Thấp |
| MediaPipe | Hand/pose/face tracking | 15-30 FPS | Thấp |
| YOLOv8 (CPU) | Detection chính xác | ~2 FPS | Trung bình |
| YOLOv8 + Hailo NPU | Detection real-time | ~30 FPS | Trung bình |


