YOLO

YOLO: You Only Look Once

YOLO (You Only Look Once) là một trong những thuật toán phát hiện đối tượng hiệu quả và phổ biến nhất hiện nay. Nổi tiếng với tốc độ cao và khả năng xử lý thời gian thực, YOLO đã cách mạng hóa lĩnh vực phát hiện đối tượng kể từ khi ra mắt vào năm 2015.

YOLO: You Only Look Once

Nguyên lý cơ bản của YOLO

Điểm khác biệt chính của YOLO so với các thuật toán phát hiện đối tượng truyền thống là cách tiếp cận "một lần nhìn" (one-look). YOLO xử lý toàn bộ hình ảnh trong một lần truyền qua mạng nơ-ron, đồng thời dự đoán nhiều bounding box và xác suất lớp cho các box này.

Cách hoạt động:

  1. Phân chia lưới (Grid Division): Chia hình ảnh thành lưới S×S (ví dụ: 7×7 trong YOLOv1, 13×13 trong YOLOv3)

  2. Dự đoán đồng thời: Mỗi ô lưới dự đoán:

    • B bounding boxes (thường là 2-3 boxes)

    • Độ tin cậy (confidence) cho mỗi box

    • C xác suất lớp (với C là số lượng lớp cần phát hiện)

  3. Thông tin dự đoán box: Mỗi bounding box bao gồm:

    • Tọa độ trung tâm (x, y) - tương đối trong ô lưới

    • Chiều rộng và chiều cao (w, h) - tương đối với kích thước hình ảnh

    • Confidence score biểu thị khả năng box chứa đối tượng

  4. Lọc kết quả: Áp dụng ngưỡng confidence và Non-Maximum Suppression (NMS) để loại bỏ các box dư thừa

Tiến hóa của YOLO

YOLO đã trải qua nhiều cải tiến qua các phiên bản, mỗi phiên bản đều nâng cao hiệu suất và tốc độ:

YOLOv1 (2015)

  • Phiên bản đầu tiên do Joseph Redmon giới thiệu

  • Mạng CNN đơn giản với 24 lớp tích chập và 2 lớp fully connected

  • Tốc độ 45 FPS, không đủ chính xác cho các đối tượng nhỏ

YOLOv2/YOLO9000 (2016)

  • Thêm Batch Normalization

  • Sử dụng Anchor Boxes (điểm neo) thay vì dự đoán trực tiếp

  • Darknet-19 backbone nhẹ hơn

  • YOLO9000 có thể phát hiện hơn 9000 lớp đối tượng

YOLOv3 (2018)

  • Sử dụng Darknet-53 làm backbone

  • Dự đoán ở ba tỷ lệ khác nhau (scales) giúp phát hiện đối tượng ở nhiều kích cỡ

  • Feature pyramid network để kết hợp thông tin từ các tỷ lệ khác nhau

  • Thay thế Softmax bằng hàm Sigmoid để hỗ trợ phân loại đa nhãn

YOLOv4 (2020)

  • Phát triển bởi Alexey Bochkovskiy, không phải nhóm ban đầu

  • Sử dụng CSPDarknet53 làm backbone

  • Thêm nhiều kỹ thuật augmentation như Mosaic, CutMix

  • Bag of Freebies và Bag of Specials để cải thiện độ chính xác

  • Path Aggregation Network (PAN) để kết hợp feature maps

YOLOv5 (2020)

  • Phát triển bởi Ultralytics, viết bằng PyTorch

  • Nhiều kích cỡ mô hình: nano, small, medium, large, xlarge

  • Tích hợp tốt vào các pipeline triển khai

YOLOv6 (2022)

  • Phát triển bởi Meituan

  • Sử dụng RepVGG cho backbone

  • Thiết kế đầu phát hiện đối xứng hiệu quả

YOLOv7 (2022)

  • Cải tiến bởi WongKinYiu và Alexey Bochkovskiy

  • Tập trung vào hiệu suất trên nhiều phần cứng khác nhau

  • E-ELAN (Extended Efficient Layer Aggregation Network)

  • Auxiliary head và lead head đặc biệt

YOLOv8 (2023)

  • Phiên bản mới nhất từ Ultralytics

  • Anchorless detection

  • Cải tiến head detection với C2f module

  • Hỗ trợ nhiều nhiệm vụ: Detection, Segmentation, Classification, Pose

  • Hiệu suất cao nhất trong các phiên bản YOLO

Kiến trúc chi tiết

Hãy xem xét kiến trúc của YOLOv8, phiên bản mới nhất và phổ biến hiện nay:

1. Backbone

YOLOv8 sử dụng CSPDarknet làm backbone, với cấu trúc được cải tiến:

  • Lớp tích chập (Convolution) → Batch Normalization → SiLU activation

  • C2f block (Cross Stage Partial với nhiều bottleneck)

  • Kết nối tắt (skip connections) giữa các khối

# Ví dụ về C2f block trong YOLOv8
class C2f(nn.Module):
    # CSP Bottleneck with 2 convolutions
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))

2. Neck

YOLOv8 sử dụng cấu trúc neck dạng SPPF (Spatial Pyramid Pooling - Fast) và PAN (Path Aggregation Network):

  • SPPF: Tổng hợp thông tin không gian ở các tỷ lệ khác nhau

  • PAN: Kết hợp thông tin từ feature map có độ phân giải thấp với độ phân giải cao

# SPPF block trong YOLOv8
class SPPF(nn.Module):
    # Spatial Pyramid Pooling - Fast (SPPF) layer
    def __init__(self, c1, c2, k=5):
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        y1 = self.m(x)
        y2 = self.m(y1)
        return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))

3. Head

YOLOv8 sử dụng Decoupled Head, tách biệt dự đoán phân loại và dự đoán bounding box:

  • Một nhánh cho phân loại (classification)

  • Một nhánh cho định vị (bbox regression)

  • Dự đoán không sử dụng anchor boxes (anchorless)

# Head detection của YOLOv8
class Detect(nn.Module):
    # YOLOv8 Detect head
    def __init__(self, nc=80, ch=()):
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch)  # number of detection layers
        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.stride = torch.zeros(self.nl)  # strides computed during build
        
        # Classification and Box Regression heads
        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc)
        self.cv2 = nn.ModuleList(nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
        
    def forward(self, x):
        for i in range(self.nl):
            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
        return x

Các ưu điểm của YOLO

1. Tốc độ cao

  • YOLOv8-nano: 155+ FPS trên GPU RTX 3090

  • YOLOv8-small: 100+ FPS

  • Phù hợp cho các ứng dụng thời gian thực

2. Cân bằng tốc độ và độ chính xác

  • YOLOv8-small: 37.4% mAP trên COCO val2017

  • YOLOv8-large: 43.4% mAP trên COCO val2017

3. Khả năng triển khai đa nền tảng

  • Dễ dàng triển khai trên nhiều thiết bị: GPU, CPU, edge devices

  • Hỗ trợ ONNX, TensorRT, CoreML, TFLite

4. Cộng đồng mạnh mẽ

  • Mã nguồn mở

  • Tài liệu và hướng dẫn phong phú

  • Nhiều pre-trained models

5. Đa nhiệm vụ

  • Object Detection

  • Instance Segmentation

  • Pose Estimation

  • Classification

Triển khai YOLO trong thực tế

1. Cài đặt và sử dụng YOLOv8

# Cài đặt
pip install ultralytics

# Sử dụng cơ bản
from ultralytics import YOLO

# Tải mô hình pre-trained
model = YOLO('yolov8n.pt')  # nano model
# Các lựa chọn khác: yolov8s.pt, yolov8m.pt, yolov8l.pt, yolov8x.pt

# Inference
results = model('path/to/image.jpg')

# Xử lý kết quả
for r in results:
    boxes = r.boxes  # Bounding boxes
    
    for box in boxes:
        x1, y1, x2, y2 = box.xyxy[0]  # Lấy tọa độ box (format xyxy)
        confidence = box.conf[0]      # Độ tin cậy
        class_id = box.cls[0]         # ID lớp
        class_name = model.names[int(class_id)]  # Tên lớp
        
        print(f"Phát hiện {class_name} với độ tin cậy {confidence:.2f}")

2. Huấn luyện YOLO với dữ liệu tùy chỉnh

# Định nghĩa file cấu hình (data.yaml)
"""
path: /path/to/dataset
train: train/images
val: val/images

# Định nghĩa các lớp
names:
  0: person
  1: car
  2: bicycle
"""

# Bắt đầu huấn luyện
from ultralytics import YOLO

# Tải mô hình cơ sở
model = YOLO('yolov8n.pt')

# Huấn luyện
results = model.train(
    data='data.yaml',
    epochs=100,
    imgsz=640,
    batch=16,
    patience=50,
    device=0  # GPU id
)

# Đánh giá sau huấn luyện
metrics = model.val()

3. Ví dụ ứng dụng thời gian thực với webcam

from ultralytics import YOLO
import cv2
import numpy as np
import time

# Tải mô hình
model = YOLO('yolov8n.pt')  # yolov8 nano

# Mở webcam
cap = cv2.VideoCapture(0)

# Thiết lập kích thước khung hình
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

# Danh sách màu cho các lớp khác nhau
colors = np.random.uniform(0, 255, size=(80, 3))

while True:
    # Đọc frame từ webcam
    ret, frame = cap.read()
    if not ret:
        break
    
    # Bắt đầu đo thời gian
    start_time = time.time()
    
    # Thực hiện phát hiện
    results = model(frame)
    
    # Đo thời gian hoàn thành phát hiện
    fps = 1.0 / (time.time() - start_time)
    
    # Vẽ kết quả lên frame
    for r in results:
        boxes = r.boxes
        
        for box in boxes:
            # Lấy tọa độ
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            
            # Lấy thông tin lớp và độ tin cậy
            conf = float(box.conf[0])
            cls_id = int(box.cls[0])
            cls_name = model.names[cls_id]
            
            # Chỉ hiển thị kết quả có độ tin cậy > 0.5
            if conf > 0.5:
                # Vẽ bounding box
                color = colors[cls_id]
                cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
                
                # Vẽ nhãn
                label = f'{cls_name}: {conf:.2f}'
                t_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[0]
                c2 = x1 + t_size[0], y1 - t_size[1] - 3
                cv2.rectangle(frame, (x1, y1), c2, color, -1)
                cv2.putText(frame, label, (x1, y1 - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, [255, 255, 255], 1)
    
    # Hiển thị FPS
    cv2.putText(frame, f'FPS: {fps:.2f}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
    
    # Hiển thị frame
    cv2.imshow('YOLOv8 Detection', frame)
    
    # Thoát khi nhấn phím 'q'
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Giải phóng tài nguyên
cap.release()
cv2.destroyAllWindows()

4. Tinh chỉnh và tối ưu hóa

# Ví dụ tinh chỉnh tham số khi huấn luyện
from ultralytics import YOLO

model = YOLO('yolov8s.pt')

# Tinh chỉnh tham số nâng cao
results = model.train(
    data='data.yaml',
    epochs=300,
    imgsz=640,
    batch=16,
    patience=50,
    optimizer='AdamW',  # Optimizer
    lr0=0.001,         # Tốc độ học ban đầu
    weight_decay=0.0005, # Weight decay
    warmup_epochs=3,    # Warmup epochs
    cos_lr=True,        # Cosine LR scheduler
    augment=True,       # Augmentation
    mixup=0.1,          # MixUp augmentation
    degrees=0.1,        # Xoay ảnh
    translate=0.1,      # Dịch chuyển ảnh
    scale=0.1,          # Thay đổi tỷ lệ ảnh
    fliplr=0.5,         # Lật ngang
    mosaic=1.0,         # Mosaic augmentation
    device=0            # GPU id
)

5. Triển khai trên thiết bị nhúng (Raspberry Pi, Jetson Nano)

# Cài đặt trên Raspberry Pi/Jetson Nano
# 1. Cài đặt dependencies
# $ sudo apt update
# $ sudo apt install python3-pip libopenblas-dev libopenmpi-dev libomp-dev

# 2. Cài đặt PyTorch phiên bản dành cho thiết bị ARM
# $ pip3 install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu

# 3. Cài đặt Ultralytics
# $ pip3 install ultralytics

# Sử dụng
from ultralytics import YOLO
import cv2

# Tải mô hình nhỏ nhất (nano)
model = YOLO('yolov8n.pt')

# Chạy inference với cấu hình thấp
# - Giảm kích thước ảnh
# - Sử dụng độ chính xác thấp hơn (FP16)
results = model('test.jpg', imgsz=320, half=True)  # half=True cho FP16

# Xử lý kết quả
for r in results:
    boxes = r.boxes
    for box in boxes:
        if box.conf[0] > 0.3:  # Ngưỡng thấp hơn
            print(f"Phát hiện {model.names[int(box.cls[0])]} với độ tin cậy {box.conf[0]:.2f}")

Nâng cao hiệu suất YOLO

1. Tối ưu hóa tốc độ

# Chuyển đổi model sang ONNX để tăng tốc độ
from ultralytics import YOLO

model = YOLO('yolov8n.pt')
model.export(format='onnx', dynamic=True, simplify=True)  # Xuất ra yolov8n.onnx

# Sử dụng model ONNX
import onnxruntime as ort
import numpy as np
import cv2

# Tải model ONNX
session = ort.InferenceSession('yolov8n.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])

# Xử lý ảnh đầu vào
img = cv2.imread('test.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (640, 640))
img = img.transpose((2, 0, 1)).astype(np.float32) / 255.0
img = np.expand_dims(img, axis=0)

# Lấy tên input/output
input_name = session.get_inputs()[0].name
output_names = [output.name for output in session.get_outputs()]

# Thực hiện inference
outputs = session.run(output_names, {input_name: img})

# Xử lý kết quả
# ...

2. Cải thiện độ chính xác

# 1. Ensemble - Kết hợp nhiều mô hình
from ultralytics import YOLO

# Tải nhiều mô hình
model1 = YOLO('yolov8n.pt')
model2 = YOLO('yolov8s.pt')
model3 = YOLO('yolov8m.pt')

# Thực hiện phát hiện với từng mô hình
results1 = model1('test.jpg')
results2 = model2('test.jpg')
results3 = model3('test.jpg')

# Kết hợp kết quả bằng Weighted Box Fusion (WBF)
# (Cần sử dụng thư viện ensemble-boxes)
from ensemble_boxes import weighted_boxes_fusion

# 2. Test Time Augmentation (TTA)
results = model('test.jpg', augment=True)  # Bật TTA

3. Theo dõi đối tượng (Object Tracking)

from ultralytics import YOLO
import cv2
from collections import defaultdict

# Tải mô hình
model = YOLO('yolov8n.pt')

# Mở video
cap = cv2.VideoCapture('video.mp4')

# Theo dõi đối tượng
track_history = defaultdict(lambda: [])

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        break

    # Chạy YOLOv8 tracking với các lớp người và xe
    results = model.track(frame, persist=True, classes=[0, 2])  # 0: person, 2: car
    
    # Nếu có kết quả
    if results[0].boxes.id is not None:
        boxes = results[0].boxes
        
        for box in boxes:
            # Lấy ID
            track_id = int(box.id[0])
            
            # Lấy tọa độ tâm
            x1, y1, x2, y2 = box.xyxy[0]
            x_center = (x1 + x2) / 2
            y_center = (y1 + y2) / 2
            center = (int(x_center), int(y_center))
            
            # Lưu vị trí vào lịch sử
            track_history[track_id].append(center)
            
            # Giữ kích thước lịch sử hợp lý
            if len(track_history[track_id]) > 30:
                track_history[track_id].pop(0)
            
            # Vẽ bounding box và ID
            cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
            cv2.putText(frame, f"ID: {track_id}", (int(x1), int(y1) - 10), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            
            # Vẽ quỹ đạo
            points = track_history[track_id]
            for i in range(1, len(points)):
                cv2.line(frame, points[i - 1], points[i], (0, 255, 0), 2)
    
    # Hiển thị
    cv2.imshow("YOLOv8 Tracking", frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

YOLO so với các thuật toán khác

1. YOLO vs SSD

Tiêu chí
YOLO
SSD

Tốc độ

Cao hơn (45-155 FPS)

Trung bình (22-46 FPS)

Độ chính xác

Cải thiện qua các phiên bản

Tốt cho đối tượng nhiều kích cỡ

Cách tiếp cận

Lưới phân chia

Feature maps đa tỷ lệ

Dễ triển khai

Đơn giản, nhiều framework

Phức tạp hơn một chút

2. YOLO vs Faster R-CNN

Tiêu chí
YOLO
Faster R-CNN

Tốc độ

Rất nhanh (45-155 FPS)

Chậm (5-8 FPS)

Độ chính xác

Tốt (33-44% mAP)

Xuất sắc (42-48% mAP)

Kiến trúc

Một bước

Hai bước (RPN + Classifier)

Ứng dụng

Thời gian thực

Độ chính xác cao

Ứng dụng thực tế của YOLO

1. Giám sát an ninh

  • Phát hiện xâm nhập

  • Theo dõi người và phương tiện

  • Phát hiện hành vi bất thường

2. Xe tự lái

  • Phát hiện người đi bộ, xe cộ, biển báo

  • Hỗ trợ lái xe

  • Phát hiện và tránh vật cản

3. Bán lẻ

  • Theo dõi hàng hóa trong kệ

  • Phân tích luồng khách hàng

  • Hệ thống thanh toán tự động

4. Nông nghiệp

  • Theo dõi vật nuôi

  • Phát hiện bệnh cây trồng

  • Đếm và phân loại sản phẩm

5. Y tế

  • Phân tích chuyển động

  • Hỗ trợ phẫu thuật

  • Theo dõi bệnh nhân

Những thách thức và hạn chế

1. Đối tượng nhỏ

  • YOLO gặp khó khăn với các đối tượng nhỏ và đông đúc

  • YOLOv8 đã cải thiện đáng kể vấn đề này, nhưng vẫn chưa hoàn hảo

2. Yêu cầu tài nguyên

  • Mô hình lớn như YOLOv8-X yêu cầu GPU mạnh

  • Các phiên bản nhỏ hơn có hiệu suất thấp hơn

3. Tính tổng quát

  • Cần huấn luyện lại cho các miền dữ liệu đặc biệt

  • Hiệu suất phụ thuộc vào chất lượng dữ liệu huấn luyện

Tương lai của YOLO

1. Kết hợp với Transformer

  • Hướng đi mới là kết hợp CNN và Transformer

  • YOLOv8 đã bắt đầu tích hợp các ý tưởng từ Transformer

2. Mở rộng đa nhiệm vụ

  • Phát triển thêm các tác vụ như phân đoạn 3D

  • Kết hợp với xử lý ngôn ngữ tự nhiên

3. Tối ưu hóa thiết bị nhúng

  • Tiếp tục tối ưu cho thiết bị có tài nguyên hạn chế

  • Mô hình lượng tử hóa (quantized) và nhẹ hơn

Kết luận

YOLO đã và đang là một trong những thuật toán phát hiện đối tượng có ảnh hưởng lớn nhất trong thị giác máy tính. Từ YOLOv1 đến YOLOv8, mỗi phiên bản đều mang đến những cải tiến đáng kể về tốc độ và độ chính xác. Sự đơn giản, hiệu quả và cộng đồng hỗ trợ mạnh mẽ đã giúp YOLO trở thành lựa chọn hàng đầu cho các ứng dụng phát hiện đối tượng thời gian thực.

Với tốc độ phát triển nhanh chóng của lĩnh vực học sâu, chúng ta có thể mong đợi các phiên bản YOLO trong tương lai sẽ tiếp tục phá vỡ những ranh giới về hiệu suất và khả năng ứng dụng. Đồng thời, việc tiếp tục tối ưu hóa cho các thiết bị có tài nguyên hạn chế sẽ mở rộng phạm vi ứng dụng của YOLO đến nhiều lĩnh vực hơn nữa.

Last updated