Thu thập dữ liệu chất lượng cao, đại diện
Lấy ảnh từ điều kiện triển khai thực tế, đưa metadata thu thập vào từng tên tệp, theo dõi độ bao phủ so với bản đặc tả và lấy mẫu video mà không làm tràn dataset.
Phần lớn thất bại của dataset bắt nguồn từ khâu thu thập. Một nhóm ghi lại các ví dụ rõ nét từ một camera trong một tuần, kết quả validation rất tốt, nhưng model lại hoạt động kém ở địa điểm thứ hai. Khâu thu thập đặt ra giới hạn tối đa: chất lượng nhãn, augmentation hay hyperparameter đều không thể giúp model học một cảnh chưa từng có trong dataset.
Thực hành
Độ đa dạng quan trọng hơn số lượng#
So sánh hai dataset hướng đến cùng một môi trường triển khai:
| Dataset | Kết quả validation | Trong môi trường production |
|---|---|---|
| 10.000 khung hình gần như giống hệt nhau, một camera, chỉ vào buổi trưa | Cao: khung hình validation giống khung hình train | Hiệu suất giảm khi dùng camera khác và vào lúc chạng vạng |
| 1.500 khung hình trải đều trên 12 kịch bản trong bản đặc tả | Khiêm tốn hơn | Hoạt động ổn định vì môi trường production giống dữ liệu |
Điểm validation của dataset đầu tiên chỉ cao vì validation đo lường đúng điều kiện duy nhất mà dataset bao phủ. Trong production, model chỉ tốt bằng kịch bản được bao phủ kém nhất mà model gặp phải. Hãy ưu tiên thu thập dữ liệu để bao phủ bản đặc tả trước, rồi mới tăng số lượng.
Chọn nguồn theo thứ tự phù hợp#
Phần lớn dataset nên đến từ thiết lập production thực tế. Hướng dẫn thu thập và gán nhãn dữ liệu trình bày chi tiết hơn về từng nguồn:
| Nguồn | Khi nào hữu ích | Khi nào gây bất lợi |
|---|---|---|
| Camera production, trực tiếp | Gần với môi trường triển khai nhất | Chậm; có thể cần thỏa thuận chia sẻ dữ liệu |
| Camera production, lưu trữ | Nhanh chóng có được nhiều tình huống đa dạng | Video cũ có thể không phù hợp với thiết lập hiện tại |
| Dataset công khai (COCO, Open Images, VOC) | Khởi tạo với các lớp phổ biến | Khác biệt miền dữ liệu so với cảnh của bạn |
| Ảnh chụp dàn dựng | Tạo các trường hợp biên cụ thể theo yêu cầu | Quá sạch; dễ phụ thuộc quá mức |
| Ảnh tổng hợp hoặc ảnh được tạo | Các mối nguy và sự cố hiếm gặp mà bạn không thể chờ đợi để ghi lại | Model có thể học phong cách của bộ tạo ảnh |
| Thu thập dữ liệu web | Đa dạng với chi phí thấp | Chất lượng không đồng nhất và rủi ro về giấy phép |
Dùng ảnh dàn dựng, công khai và tổng hợp để lấp các khoảng thiếu cụ thể đã nêu trong bản đặc tả, đồng thời theo dõi tỷ lệ của chúng trong dataset để đo lường hiệu quả. Trên Ultralytics Platform, Tạo ảnh tương tự bổ sung các biến thể được tạo từ một ảnh hiện có vào dataset; ảnh chưa được gán nhãn và sẵn sàng để chú thích.
Đưa metadata vào tên tệp#
Mỗi ảnh cần có ít nhất ID camera, ngày thu thập và số khung hình hoặc clip. Nếu thiếu những thông tin này, bạn không thể chia dữ liệu theo nhóm sau này, truy nguyên hồi quy về nguồn hoặc thu thập thêm dữ liệu cho một kịch bản chưa được đại diện đầy đủ. Tên tệp là nơi lưu trữ bền vững nhất: cam2_2026-05-01_000124.jpg tồn tại qua mọi công cụ, lần xuất và lần tải lên. Tính năng tìm kiếm dataset của Ultralytics Platform hỗ trợ tìm theo tên tệp và metadata tùy chỉnh, vì vậy các khóa này vẫn có thể tìm kiếm sau khi tải lên.
Script này tạo dataset thực hành được dùng trong phần còn lại của khóa học. Script sao chép các ảnh đã gán nhãn của COCO128 và đổi tên chúng như thể bốn camera đã ghi lại ảnh trong bốn ngày, trong đó cam1 được thu thập quá mức như cách một camera tiện dụng thường gặp:
import shutil
from pathlib import Path
from ultralytics.data.utils import check_det_dataset, img2label_paths
data = check_det_dataset("coco128.yaml") # 128 COCO images stand in for your camera captures
out = Path("datasets/practice")
(out / "images").mkdir(parents=True, exist_ok=True)
(out / "labels").mkdir(parents=True, exist_ok=True)
for i, image in enumerate(sorted(Path(data["train"]).glob("*.jpg"))):
camera = ("cam1", "cam1", "cam2", "cam3", "cam4")[i % 5] # cam1 over-collected, like a real site
name = f"{camera}_2026-05-0{i // 32 + 1}_{i:06d}" # camera, capture day, frame number
shutil.copy(image, out / "images" / f"{name}.jpg")
label = Path(img2label_paths([str(image)])[0])
if label.exists():
shutil.copy(label, out / "labels" / f"{name}.txt")
print(len(list((out / "images").glob("*.jpg"))), "images written to", out)128 images written to datasets/practice
Tải lên Ultralytics Platform vẫn giữ nguyên các tên tệp đó. Hộp thoại tải lên cũng chấp nhận video và toàn bộ dataset, đồng thời có tùy chọn làm mờ khuôn mặt để bảo vệ quyền riêng tư.
Theo dõi độ bao phủ so với bản đặc tả#
Khi metadata đã có trong tên tệp, độ bao phủ có thể được biểu thị bằng số lượng. Đặt chỉ tiêu theo bản đặc tả và để script cho biết cần thu thập gì tiếp theo:
from collections import Counter
from pathlib import Path
QUOTA = {"cam1": 30, "cam2": 30, "cam3": 30, "cam4": 30} # images per camera, from your spec
images = sorted(Path("datasets/practice/images").glob("*.jpg"))
cameras = Counter(p.stem.split("_")[0] for p in images)
days = Counter(p.stem.split("_")[1] for p in images)
for camera, quota in QUOTA.items():
gap = quota - cameras[camera]
print(f"{camera}: {cameras[camera]:3d} / {quota} {'ok' if gap <= 0 else f'collect {gap} more'}")
print("days:", dict(sorted(days.items())))cam1: 52 / 30 ok
cam2: 26 / 30 collect 4 more
cam3: 25 / 30 collect 5 more
cam4: 25 / 30 collect 5 more
days: {'2026-05-01': 32, '2026-05-02': 32, '2026-05-03': 32, '2026-05-04': 32}Quy tắc ra quyết định: ngừng thu thập từ mọi nguồn đã vượt chỉ tiêu và dành công sức cho các khoảng thiếu. Ở đây, cam1 đã chiếm 40% số ảnh. Thu thập thêm video từ nguồn này trong một tuần nữa sẽ làm tăng số lượng nhưng không cải thiện độ bao phủ.
Lấy mẫu video mà không làm tràn dataset#
Video là nguồn có chi phí thấp nhất và cũng dễ bị sử dụng sai nhất. Ở tốc độ 30 khung hình/giây, một phút tạo ra 1.800 khung hình gần như giống hệt nhau; gán nhãn cùng một khoảnh khắc hai lần vừa tốn tiền vừa làm tăng chỉ số. Chỉ giữ một khung hình khi cảnh đã thay đổi so với khung hình được giữ gần nhất:
from pathlib import Path
import cv2
from ultralytics.utils.downloads import safe_download
video = "solutions_ci_demo.mp4" # a short public demo clip; swap in your own footage
safe_download(f"https://github.com/ultralytics/assets/releases/download/v0.0.0/{video}")
Path("frames").mkdir(exist_ok=True)
cap = cv2.VideoCapture(video)
fps = cap.get(cv2.CAP_PROP_FPS)
read = kept = 0
last = None
while True:
ok, frame = cap.read()
if not ok:
break
thumb = cv2.cvtColor(cv2.resize(frame, (64, 64)), cv2.COLOR_BGR2GRAY).astype(float)
if last is None or abs(thumb - last).mean() > 10: # keep a frame only when the scene has changed
cv2.imwrite(f"frames/clip01_{read:06d}.jpg", frame) # the clip ID stays in the filename
kept, last = kept + 1, thumb
read += 1
print(f"{read} frames at {fps:.0f} FPS -> {kept} kept")62 frames at 30 FPS -> 6 keptClip dài hai giây có 62 khung hình nhưng chỉ chứa 6 khoảnh khắc khác biệt về mặt hình ảnh. Script so sánh các thumbnail thang xám có kích thước nhỏ nên xử lý nhanh cả video dài hàng giờ. Tăng ngưỡng 10 để giữ ít khung hình hơn từ camera tĩnh; giảm ngưỡng cho cảnh chuyển động nhanh. Đồng thời phân bổ ngân sách trên nhiều clip: 20 khung hình từ mỗi clip trong 50 clip tốt hơn 1.000 khung hình từ một clip.
Hai khung hình cách nhau một giây có chung điều kiện ánh sáng, phông nền và thường là cùng các đối tượng. Nếu chúng cùng xuất hiện trong train và val, model sẽ được đánh giá trên những ảnh mà thực chất nó đã thấy. Giữ ID clip trong mọi tên file để Chia dataset đúng cách có thể giữ các khung hình của một clip cùng nhau.
Tránh những sai lầm khi thu thập dữ liệu#
| Sai lầm | Triệu chứng | Cách khắc phục |
|---|---|---|
| Thu thập dữ liệu trong một tuần duy nhất | mAP trên tập validation cao nhưng bỏ sót dữ liệu ở các mùa khác hoặc khi có thay đổi | Trải đều việc thu thập dữ liệu trong nhiều tuần hoặc nhiều tháng |
| Chỉ dùng một camera | Không hoạt động tốt với camera thứ hai | Thu thập dữ liệu từ mọi loại camera mà bạn triển khai |
| Chỉ dùng dữ liệu dàn dựng | Không hoạt động tốt trong môi trường lộn xộn thực tế | Giữ số ảnh dàn dựng ở mức thiểu số nhỏ và theo dõi tỷ lệ này |
| Các frame trùng lặp | Chỉ số bị thổi phồng, lãng phí công gán nhãn | Lấy mẫu theo thay đổi cảnh; Ultralytics Platform cũng bỏ qua các bản sao chính xác khi tải lên |
| Chỉ một người vận hành hoặc một ca làm việc | Phân bố ngoại hình không có kế hoạch và mất cân bằng lớp | Lấy mẫu qua nhiều ca làm việc, địa điểm và người khác nhau |
| Thiếu các trường hợp hiếm gặp | Bỏ sót những sự cố an toàn quan trọng nhất | Thu thập có mục tiêu, dàn dựng hoặc bổ sung dữ liệu tổng hợp |
Thiên lệch và sự đồng thuận#
Thiên lệch là vấn đề thu thập dữ liệu, không phải vấn đề mô hình: không có siêu tham số nào có thể khắc phục việc dữ liệu chưa bao giờ chứa một nhóm nào đó. Hướng dẫn thu thập dữ liệu liệt kê bốn biện pháp: đa dạng hóa nguồn dữ liệu, cân bằng mức độ đại diện, thường xuyên kiểm tra lại độ bao phủ và áp dụng biện pháp giảm thiểu như lấy mẫu quá mức các nhóm ít được đại diện.
Nếu ảnh có khuôn mặt, biển số xe hoặc dữ liệu cá nhân khác, bạn cần có sự đồng thuận và chính sách lưu giữ dữ liệu trước khi bắt đầu thu thập. Mục từ quyền riêng tư dữ liệu trong bảng thuật ngữ là điểm khởi đầu phù hợp, còn Quyền riêng tư, Khu vực và Tuân thủ đề cập đến việc lưu trữ dữ liệu trong đúng khu vực pháp lý bằng dataRegion.
Hiển thị lời giải
Với tập dữ liệu thực hành, báo cáo độ bao phủ đưa ra kế hoạch cụ thể:
| Nguồn | Số lượng / hạn ngạch | Hành động |
|---|---|---|
cam1 | 52 / 30 | Dừng thu thập; nguồn này đã chiếm 40% tổng số ảnh |
cam2 | 26 / 30 | Thu thập thêm 4 ảnh |
cam3 | 25 / 30 | Thu thập thêm 5 ảnh |
cam4 | 25 / 30 | Thu thập thêm 5 ảnh |
Mỗi camera ghi nhận dữ liệu trong cả bốn ngày, nên các khoảng thiếu được tính theo camera chứ không phải theo ngày. Phân bổ 14 ảnh mới cho ít nhất hai ngày mới để chúng bổ sung điều kiện mới thay vì thêm nhiều dữ liệu từ cùng một buổi chiều. Với camera không thể cung cấp đủ phần còn thiếu (camera chỉ ghi hình ban đêm), hãy thêm một hàng vào thông số kỹ thuật cho điều kiện còn thiếu và ghi lại xem ảnh dàn dựng hay ảnh được tạo tổng hợp sẽ bù vào, để có thể đo lường tác động sau này.