簡介
靜態影像的偵測很容易。但現實世界需要即時:監視器、自動駕駛汽車、乘客數。本文涵蓋:視訊偵測、物件追蹤(透過幀追蹤物件)、計數(計數通道)和 FPS 優化。
🎯 目標: 建立一個具有追蹤和計數功能的 30+ FPS 即時檢測系統。
1. 視訊串流上的 YOLO
1.1 即時網路攝影機
"""YOLO real-time trên webcam"""
import cv2
from ultralytics import YOLO
import time
model = YOLO("yolo11n.pt")
cap = cv2.VideoCapture(0) # 0 = webcam
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
fps_counter = 0
fps_start = time.time()
fps_display = 0
while True:
ret, frame = cap.read()
if not ret:
break
# Detection
results = model(frame, verbose=False, conf=0.5)
annotated = results[0].plot()
# FPS counter
fps_counter += 1
elapsed = time.time() - fps_start
if elapsed >= 1.0:
fps_display = fps_counter / elapsed
fps_counter = 0
fps_start = time.time()
cv2.putText(annotated, f"FPS: {fps_display:.0f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("YOLO Real-time", annotated)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
1.2 RTSP / IP 攝影機串流
"""YOLO trên IP camera (RTSP stream)"""
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
# RTSP stream
rtsp_url = "rtsp://admin:[email protected]:554/stream1"
# Stream mode — xử lý frame-by-frame, tiết kiệm RAM
results = model(rtsp_url, stream=True, conf=0.25)
for result in results:
annotated = result.plot()
cv2.imshow("RTSP Stream", annotated)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
2. 物件追蹤
2.1 為什麼我們需要追蹤?
Detection only: Frame 1: person A, person B
Frame 2: person ?, person ? ← KHÔNG BIẾT ai là ai!
Detection + Tracking: Frame 1: person A (ID=1), person B (ID=2)
Frame 2: person A (ID=1), person B (ID=2) ← BIẾT!
2.2 追蹤演算法
| 演算法 | 它是如何運作的 | 速度 | 準確度 | 使用案例 |
|---|---|---|---|---|
| 排序 | 卡爾曼濾波器+匈牙利 | ⚡ 非常快 | ⭐⭐⭐ | 即時、簡單 |
| 深度排序 | 排序+外觀特徵(Re-ID) | 🔥 快速 | ⭐⭐⭐⭐ | 人員追蹤 |
| 位元組追蹤 | 追蹤低置信度檢測 | ⚡ 非常快 | ⭐⭐⭐⭐⭐ | SOTA,建議 |
| 機器人排序 | ByteTrack + 相機運動補償 | 🔥 快速 | ⭐⭐⭐⭐⭐ | 移動相機 |
2.3 YOLO + 3行程式碼追蹤
"""YOLO Tracking — cực kỳ đơn giản với Ultralytics"""
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
# Track trên video — 1 dòng!
results = model.track(
source="people_walking.mp4",
tracker="bytetrack.yaml", # Hoặc "botsort.yaml"
show=True, # Hiển thị real-time
conf=0.3,
persist=True, # Giữ tracking IDs giữa frames
)
2.4 詳細追蹤-獲取信息
"""Phân tích tracking results"""
import cv2
from ultralytics import YOLO
from collections import defaultdict
model = YOLO("yolo11n.pt")
cap = cv2.VideoCapture("crosswalk.mp4")
# Lưu trajectory (đường đi) của mỗi object
track_history = defaultdict(list)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
results = model.track(frame, persist=True, verbose=False)
result = results[0]
if result.boxes.id is not None:
boxes = result.boxes.xywh.cpu().numpy()
track_ids = result.boxes.id.int().cpu().numpy()
classes = result.boxes.cls.int().cpu().numpy()
for box, track_id, cls in zip(boxes, track_ids, classes):
x, y, w, h = box
class_name = result.names[cls]
# Lưu vị trí center
track_history[track_id].append((float(x), float(y)))
# Vẽ trajectory (đường đi)
track = track_history[track_id]
if len(track) > 1:
points = [(int(p[0]), int(p[1])) for p in track[-30:]]
for i in range(1, len(points)):
cv2.line(frame, points[i-1], points[i], (0, 255, 0), 2)
# Info
cv2.putText(frame, f"ID:{track_id} {class_name}",
(int(x-w/2), int(y-h/2)-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.imshow("Tracking", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
print(f"Total unique objects tracked: {len(track_history)}")
3. 物件計數
3.1 行計數 — 透過行進行計數
"""Đếm objects đi qua 1 đường kẻ"""
import cv2
import numpy as np
from ultralytics import YOLO
from collections import defaultdict
model = YOLO("yolo11n.pt")
cap = cv2.VideoCapture("highway.mp4")
# Đường đếm (ngang giữa frame)
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
count_line_y = frame_height // 2
# Tracking state
track_history = defaultdict(list)
counted_ids = set()
count_up = 0
count_down = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
results = model.track(frame, persist=True, verbose=False, classes=[2]) # class 2 = car
# Vẽ counting line
cv2.line(frame, (0, count_line_y), (frame_width, count_line_y),
(0, 0, 255), 2)
if results[0].boxes.id is not None:
for box, track_id in zip(results[0].boxes.xywh.cpu(),
results[0].boxes.id.int().cpu()):
x, y = float(box[0]), float(box[1])
tid = int(track_id)
track_history[tid].append(y)
# Check nếu đi qua line
if tid not in counted_ids and len(track_history[tid]) >= 2:
prev_y = track_history[tid][-2]
curr_y = track_history[tid][-1]
if prev_y < count_line_y <= curr_y:
count_down += 1
counted_ids.add(tid)
elif prev_y > count_line_y >= curr_y:
count_up += 1
counted_ids.add(tid)
# Display counts
cv2.putText(frame, f"Up: {count_up} | Down: {count_down}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("Vehicle Counter", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
print(f"Final count — Up: {count_up}, Down: {count_down}")
3.2 基於區域的偵測-區域內計數
"""Đếm objects trong 1 vùng (polygon zone)"""
import numpy as np
# Định nghĩa vùng (polygon)
zone_polygon = np.array([
[200, 300],
[600, 300],
[700, 500],
[100, 500],
], dtype=np.int32)
def point_in_polygon(point, polygon):
"""Kiểm tra point có nằm trong polygon không"""
return cv2.pointPolygonTest(polygon, point, False) >= 0
# Trong tracking loop:
objects_in_zone = 0
for box in results[0].boxes.xywh.cpu():
center = (float(box[0]), float(box[1]))
if point_in_polygon(center, zone_polygon):
objects_in_zone += 1
# Vẽ zone
cv2.polylines(frame, [zone_polygon], True, (255, 255, 0), 2)
cv2.putText(frame, f"In Zone: {objects_in_zone}",
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)
4.優化FPS
4.1 優化速度的技巧
"""Các kỹ thuật tăng FPS"""
# 1. Dùng model nhỏ
model = YOLO("yolo11n.pt") # nano: nhanh nhất
# 2. Giảm input size
results = model(frame, imgsz=320) # 320 thay vì 640
# 3. Half precision (FP16)
model = YOLO("yolo11n.pt")
results = model(frame, half=True) # FP16 trên GPU
# 4. Export sang TensorRT
model.export(format="engine", half=True)
trt_model = YOLO("yolo11n.engine") # 2-3x faster
# 5. Skip frames — không xử lý mọi frame
frame_skip = 2 # Xử lý 1 frame, skip 1
frame_count = 0
while cap.isOpened():
ret, frame = cap.read()
frame_count += 1
if frame_count % frame_skip != 0:
continue # Skip frame
results = model(frame)
# 6. Chỉ detect classes cần thiết
results = model(frame, classes=[0, 2]) # Chỉ person + car
4.2 基準 FPS
| 配置 | 第一人稱射擊 (RTX 3060) | 第一人稱射擊 (T4) | FPS(CPU) |
|---|---|---|---|
| yolo11n,640 | 180 | 180 120 | 120 25 |
| yolo11n,320 | 300+ | 200 | 200 45 |
| yolo11s,640 | 120 | 120 80 | 12 |
| yolo11m, 640 | 70 | 70 45 | 45 5 |
| yolo11n TRT,640 | 350+ | 250 | 250 — |
總結
| 概念 | 記住 |
|---|---|
| 視頻檢測 | YOLO + OpenCV,對於長視頻,stream=True |
| 物件追蹤 | ByteTrack(建議),為每個物件分配一個唯一的ID |
| 行計數 | 線路追蹤+檢查→計數 |
| 區域偵測 | pointPolygonTest 檢查區域中的物件 |
| FPS 最佳化 | 小模型、小imgsz、TensorRT、跳幀 |
一般練習
- 網路攝影機偵測器: YOLO + 網路攝影機 + FPS 顯示。
- 人數統計器: 從影片中統計通過門的人數(行計數)。
- 速度比較: 在 1 分鐘影片上對 yolo11n 與 yolo11s 進行基準測試。第一人稱射擊?
- 軌跡可見: 繪製每個人最近30幀的軌跡。
下一篇文章: 影像分割 - 逐像素分類、語意與實例與全景。