Transformers documentation
Phát hiện đối tượng
Phát hiện đối tượng
Phát hiện đối tượng là tác vụ thị giác máy tính nhằm phát hiện các thực thể (chẳng hạn như con người, tòa nhà hoặc ô tô) trong một hình ảnh. Mô hình phát hiện đối tượng nhận hình ảnh làm đầu vào và xuất ra tọa độ của các khung bao (bounding box) cùng nhãn liên quan của các đối tượng được phát hiện. Một hình ảnh có thể chứa nhiều đối tượng, mỗi đối tượng có khung bao và nhãn riêng (ví dụ: có thể có một ô tô và một tòa nhà), và mỗi đối tượng có thể xuất hiện ở các phần khác nhau của hình ảnh (ví dụ: hình ảnh có thể có nhiều ô tô). Tác vụ này thường được sử dụng trong lái xe tự động để phát hiện các vật thể như người đi bộ, biển báo đường và đèn giao thông. Các ứng dụng khác bao gồm đếm số lượng đối tượng trong hình ảnh, tìm kiếm hình ảnh và nhiều hơn nữa.
Trong hướng dẫn này, bạn sẽ học cách:
- Tinh chỉnh RF-DETR trên bộ dữ liệu mobile-ui-design để phát hiện các phần tử giao diện người dùng trong ảnh chụp màn hình ứng dụng di động.
- Sử dụng mô hình đã tinh chỉnh của bạn để suy luận.
Để xem tất cả các kiến trúc và checkpoint tương thích với tác vụ này, chúng tôi khuyến nghị bạn kiểm tra trang tác vụ
Trước khi bắt đầu, hãy đảm bảo bạn đã cài đặt tất cả các thư viện cần thiết:
pip install -q datasets transformers accelerate timm trackio torchmetrics pycocotools
Bạn sẽ sử dụng 🤗 Datasets để tải một bộ dữ liệu từ Hugging Face Hub và 🤗 Transformers để huấn luyện mô hình của bạn.
Chúng tôi khuyến khích bạn chia sẻ mô hình của mình với cộng đồng. Đăng nhập vào tài khoản Hugging Face của bạn để tải lên Hub. Khi được yêu cầu, nhập token của bạn để đăng nhập:
>>> from huggingface_hub import notebook_login
>>> notebook_login()Định nghĩa các hằng số toàn cục, cụ thể là tên mô hình và kích thước ảnh. Bài hướng dẫn này sử dụng RF-DETR, nhưng bạn có thể chọn bất kỳ mô hình phát hiện đối tượng nào trong Transformers.
>>> MODEL_NAME = "Roboflow/rf-detr-medium"Tải bộ dữ liệu mobile-ui-design
Bộ dữ liệu mobile-ui-design chứa các ảnh chụp màn hình ứng dụng di động với chú thích để phát hiện các thành phần giao diện người dùng như văn bản, hình ảnh, hình chữ nhật và nhóm.
Bắt đầu bằng cách tải bộ dữ liệu và trích xuất nhãn danh mục. Bộ dữ liệu đã có các phần chia (splits).
>>> from datasets import load_dataset
>>> ds = load_dataset("merve/mobile-ui-design")
>>> CATEGORIES = sorted(set(
... cat for split in ds.values() for example in split for cat in example["objects"]["category"]
... ))
>>> label2id = {label: i for i, label in enumerate(CATEGORIES)}
>>> id2label = {i: label for label, i in label2id.items()}
>>> print(f"Categories ({len(CATEGORIES)}): {CATEGORIES}")
Categories (4): ['group', 'image', 'rectangle', 'text']Bộ dữ liệu sử dụng tên danh mục dạng chuỗi và các hộp bao quanh (bounding box) ở định dạng COCO (x, y, w, h). Hãy chuyển đổi các danh mục thành id dạng số nguyên, tính toán diện tích và lọc bỏ các hộp bao quanh suy biến trước khi huấn luyện:
>>> def prepare_example(example, idx):
... objects = example["objects"]
... in_bboxes = objects["bbox"]
... categories = objects["category"]
... img_w, img_h = example["width"], example["height"]
... bboxes, cats, areas, ids = [], [], [], []
... for i, (bbox, cat) in enumerate(zip(in_bboxes, categories)):
... x, y, w, h = bbox
... if w <= 0 or h <= 0:
... continue
... x = max(0.0, min(x, img_w))
... y = max(0.0, min(y, img_h))
... w = min(w, img_w - x)
... h = min(h, img_h - y)
... if w <= 0 or h <= 0:
... continue
... bboxes.append([x, y, w, h])
... cats.append(label2id[cat])
... areas.append(w * h)
... ids.append(i)
... return {
... "image_id": idx, "image": example["image"],
... "width": example["width"], "height": example["height"],
... "objects": {"id": ids, "bbox": bboxes, "category": cats, "area": areas},
... }
>>> ds_prepared = ds["train"].map(prepare_example, with_indices=True, remove_columns=ds["train"].column_names)
>>> ds_prepared = ds_prepared.filter(lambda x: len(x["objects"]["bbox"]) > 0)
>>> split = ds_prepared.train_test_split(test_size=0.15, seed=1337)
>>> train_ds = split["train"]
>>> val_ds = split["test"]
>>> print(f"Train: {len(train_ds)}, Validation: {len(val_ds)}")
Train: 6669, Validation: 1177Tiền xử lý dữ liệu
AutoImageProcessor đảm nhận việc xử lý dữ liệu ảnh để tạo ra pixel_values, pixel_mask và labels để mô hình có thể huấn luyện. Bộ xử lý ảnh chịu trách nhiệm thay đổi kích thước, đệm và chuẩn hóa. Ngoài ra, bạn có thể tùy chọn thêm các phép tăng cường dữ liệu ngẫu nhiên (xem bên dưới) để cải thiện khả năng tổng quát hóa.
>>> import numpy as np
>>> from functools import partial
>>> from transformers import AutoImageProcessor
>>> image_processor = AutoImageProcessor.from_pretrained(MODEL_NAME)image_processor kỳ vọng chú thích ở định dạng COCO: {'image_id': int, 'annotations': list[Dict]}. Định dạng chú thích của từng ví dụ và để bộ xử lý lo phần còn lại:
>>> def format_image_annotations_as_coco(image_id, categories, areas, bboxes):
... annotations = []
... for category, area, bbox in zip(categories, areas, bboxes):
... annotations.append({
... "image_id": image_id,
... "category_id": category,
... "iscrowd": 0,
... "area": area,
... "bbox": list(bbox),
... })
... return {"image_id": image_id, "annotations": annotations}
>>> def transform_batch(examples, image_processor):
... images = []
... annotations = []
... for image_id, image, objects in zip(examples["image_id"], examples["image"], examples["objects"]):
... images.append(np.array(image.convert("RGB")))
... formatted = format_image_annotations_as_coco(
... image_id, objects["category"], objects["area"], objects["bbox"]
... )
... annotations.append(formatted)
... result = image_processor(images=images, annotations=annotations, return_tensors="pt")
... result.pop("pixel_mask", None)
... return result
>>> transform_fn = partial(transform_batch, image_processor=image_processor)
>>> train_ds = train_ds.with_transform(transform_fn)
>>> val_ds = val_ds.with_transform(transform_fn)Tăng cường dữ liệu
Biến đổi ở trên chỉ thay đổi kích thước và chuẩn hóa ảnh. Các phép tăng dữ liệu ngẫu nhiên áp dụng cho tập đào tạo thường cải thiện khả năng tổng quát hóa, trong khi tập xác thực nên giữ nguyên không tăng dữ liệu để đảm bảo đánh giá mang tính tất định. Một lựa chọn phổ biến là Albumentations, công cụ tăng dữ liệu cho cả ảnh và các hộp bao quanh cùng lúc. Hãy định nghĩa một pipeline với bbox_params để các hộp được biến đổi nhất quán với ảnh, sau đó tính lại diện tích từ các hộp đã tăng dữ liệu:
>>> import albumentations as A
>>> train_augment = A.Compose(
... [
... A.Perspective(p=0.1),
... A.HorizontalFlip(p=0.5),
... A.RandomBrightnessContrast(p=0.5),
... A.HueSaturationValue(p=0.1),
... ],
... bbox_params=A.BboxParams(format="coco", label_fields=["category"], clip=True, min_area=25),
... )
>>> def augment_and_transform_batch(examples, image_processor, transform):
... images = []
... annotations = []
... for image_id, image, objects in zip(examples["image_id"], examples["image"], examples["objects"]):
... image = np.array(image.convert("RGB"))
... output = transform(image=image, bboxes=objects["bbox"], category=objects["category"])
... images.append(output["image"])
... areas = [w * h for (_, _, w, h) in output["bboxes"]]
... formatted = format_image_annotations_as_coco(
... image_id, output["category"], areas, output["bboxes"]
... )
... annotations.append(formatted)
... result = image_processor(images=images, annotations=annotations, return_tensors="pt")
... result.pop("pixel_mask", None)
... return resultÁp dụng biến đổi tăng cường chỉ cho phần chia huấn luyện, và giữ nguyên transform_fn cho phần kiểm tra:
>>> train_augment_fn = partial(augment_and_transform_batch, image_processor=image_processor, transform=train_augment)
>>> train_ds = train_ds.with_transform(train_augment_fn)
>>> val_ds = val_ds.with_transform(transform_fn)Tạo một collate_fn tùy chỉnh để nhóm các hình ảnh thành một lô:
>>> import torch
>>> def collate_fn(batch):
... data = {}
... data["pixel_values"] = torch.stack([x["pixel_values"] for x in batch])
... data["labels"] = [x["labels"] for x in batch]
... if "pixel_mask" in batch[0]:
... data["pixel_mask"] = torch.stack([x["pixel_mask"] for x in batch])
... return data
Chuẩn bị hàm để tính mAP
Các mô hình phát hiện đối tượng thường được đánh giá bằng một bộ các chỉ số kiểu COCO. Chúng ta sẽ sử dụng torchmetrics để tính toán các chỉ số mAP (độ chính xác trung bình) và mAR (độ hồi tưởng trung bình) và sẽ bọc chúng vào hàm compute_metrics để sử dụng trong Trainer cho việc đánh giá.
Định dạng trung gian của các hộp được sử dụng cho huấn luyện là YOLO (đã chuẩn hóa) nhưng chúng ta sẽ tính toán các chỉ số cho các hộp ở định dạng Pascal VOC (tuyệt đối) để xử lý đúng diện tích của các hộp. Hãy định nghĩa một hàm chuyển đổi các hộp bao quanh sang định dạng Pascal VOC:
>>> from transformers.image_transforms import center_to_corners_format
>>> def convert_bbox_yolo_to_pascal(boxes, image_size):
... """
... Convert bounding boxes from YOLO format (x_center, y_center, width, height) in range [0, 1]
... to Pascal VOC format (x_min, y_min, x_max, y_max) in absolute coordinates.
... Args:
... boxes (torch.Tensor): Bounding boxes in YOLO format
... image_size (tuple[int, int]): Image size in format (height, width)
... Returns:
... torch.Tensor: Bounding boxes in Pascal VOC format (x_min, y_min, x_max, y_max)
... """
... # convert center to corners format
... boxes = center_to_corners_format(boxes)
... # convert to absolute coordinates
... height, width = image_size
... boxes = boxes * torch.tensor([[width, height, width, height]])
... return boxesSau đó, trong hàm compute_metrics, chúng ta thu thập các hộp bao predicted và target, điểm số và nhãn từ kết quả của vòng lặp đánh giá, rồi truyền chúng vào hàm chấm điểm.
>>> import numpy as np
>>> from dataclasses import dataclass
>>> from torchmetrics.detection.mean_ap import MeanAveragePrecision
>>> @dataclass
>>> class ModelOutput:
... logits: torch.Tensor
... pred_boxes: torch.Tensor
>>> def _get_orig_size(image_target):
... """Robust orig_size extraction - Trainer serialization can truncate to 1 element."""
... orig = np.atleast_1d(np.asarray(image_target["orig_size"])).flatten()
... if len(orig) >= 2:
... return (int(orig[0]), int(orig[1]))
... return (int(orig[0]), int(orig[0]))
>>> @torch.no_grad()
>>> def compute_metrics(evaluation_results, image_processor, threshold=0.0, id2label=None):
... predictions, targets = evaluation_results.predictions, evaluation_results.label_ids
... image_sizes = []
... post_processed_targets = []
... post_processed_predictions = []
...
... for batch in targets:
... batch_sizes = []
... for image_target in batch:
... h, w = _get_orig_size(image_target)
... batch_sizes.append([h, w])
... boxes = torch.tensor(image_target["boxes"])
... boxes = convert_bbox_yolo_to_pascal(boxes, (h, w))
... labels = torch.tensor(image_target["class_labels"])
... post_processed_targets.append({"boxes": boxes, "labels": labels})
... image_sizes.append(torch.tensor(batch_sizes))
...
... for batch, target_sizes in zip(predictions, image_sizes):
... batch_logits, batch_boxes = batch[1], batch[2]
... output = ModelOutput(logits=torch.tensor(batch_logits), pred_boxes=torch.tensor(batch_boxes))
... post_processed_output = image_processor.post_process_object_detection(
... output, threshold=threshold, target_sizes=target_sizes
... )
... post_processed_predictions.extend(post_processed_output)
...
... metric = MeanAveragePrecision(box_format="xyxy", class_metrics=True)
... metric.update(post_processed_predictions, post_processed_targets)
... metrics = metric.compute()
...
... classes = metrics.pop("classes")
... map_per_class = metrics.pop("map_per_class")
... mar_100_per_class = metrics.pop("mar_100_per_class")
... for class_id, class_map, class_mar in zip(classes, map_per_class, mar_100_per_class):
... class_name = id2label[class_id.item()] if id2label is not None else class_id.item()
... metrics[f"map_{class_name}"] = class_map
... metrics[f"mar_100_{class_name}"] = class_mar
...
... metrics = {k: round(v.item(), 4) for k, v in metrics.items()}
... return metrics
>>> eval_compute_metrics_fn = partial(
... compute_metrics, image_processor=image_processor, id2label=id2label, threshold=0.0
... )Huấn luyện mô hình phát hiện
Bạn đã hoàn thành phần lớn công việc nặng nhọc trong các phần trước, vì vậy giờ bạn đã sẵn sàng để huấn luyện mô hình của mình! Các hình ảnh trong bộ dữ liệu này vẫn khá lớn, ngay cả sau khi đã thay đổi kích thước. Điều này có nghĩa là việc tinh chỉnh mô hình này sẽ yêu cầu ít nhất một GPU.
Quá trình huấn luyện bao gồm các bước sau:
- Tải mô hình bằng AutoModelForObjectDetection sử dụng cùng checkpoint như trong tiền xử lý.
- Định nghĩa các siêu tham số huấn luyện của bạn trong TrainingArguments.
- Truyền các đối số huấn luyện vào Trainer cùng với mô hình, tập dữ liệu, bộ xử lý ảnh và bộ sắp xếp dữ liệu.
- Gọi train() để tinh chỉnh mô hình của bạn.
Khi tải mô hình từ cùng checkpoint mà bạn đã dùng cho tiền xử lý, hãy nhớ truyền các bản đồ label2id và id2label mà bạn đã tạo trước đó từ metadata của tập dữ liệu. Ngoài ra, chúng ta chỉ định ignore_mismatched_sizes=True để thay thế đầu phân loại hiện có bằng một đầu mới.
>>> from transformers import AutoModelForObjectDetection
>>> model = AutoModelForObjectDetection.from_pretrained(
... MODEL_NAME,
... id2label=id2label,
... label2id=label2id,
... ignore_mismatched_sizes=True,
... )Trong TrainingArguments, hãy dùng output_dir để chỉ định nơi lưu mô hình của bạn, sau đó cấu hình các siêu tham số theo ý bạn. Với num_train_epochs=5, quá trình huấn luyện sẽ mất khoảng 35 phút trên GPU A100; hãy tăng số epoch để đạt kết quả tốt hơn.
Lưu ý quan trọng:
- Không xóa các cột không được sử dụng vì điều này sẽ làm mất cột ảnh. Nếu không có cột ảnh, bạn
không thể tạo
pixel_values. Vì lý do này, hãy đặtremove_unused_columnsthànhFalse. - Đặt
eval_do_concat_batches=Falseđể có kết quả đánh giá chính xác. Các hình ảnh có số lượng hộp mục tiêu khác nhau, nếu các lô được nối lại, chúng ta sẽ không thể xác định hộp nào thuộc về hình ảnh cụ thể nào.
Nếu bạn muốn chia sẻ mô hình của mình bằng cách đẩy lên Hub, hãy đặt push_to_hub thành True (bạn phải đã đăng nhập vào Hugging Face để tải lên mô hình của mình).
>>> from transformers import TrainingArguments
>>> training_args = TrainingArguments(
... output_dir="rf_detr_finetuned_mobile_ui",
... num_train_epochs=5,
... bf16=True,
... per_device_train_batch_size=8,
... dataloader_num_workers=4,
... learning_rate=5e-5,
... lr_scheduler_type="cosine",
... weight_decay=1e-4,
... max_grad_norm=0.01,
... metric_for_best_model="eval_map",
... greater_is_better=True,
... load_best_model_at_end=True,
... eval_strategy="epoch",
... save_strategy="epoch",
... save_total_limit=2,
... remove_unused_columns=False,
... report_to="trackio",
... run_name="mobile-ui-detection",
... eval_do_concat_batches=False,
... push_to_hub=True,
... )Cuối cùng, hãy kết hợp tất cả lại và gọi train():
>>> from transformers import Trainer
>>> trainer = Trainer(
... model=model,
... args=training_args,
... train_dataset=train_ds,
... eval_dataset=val_ds,
... processing_class=image_processor,
... data_collator=collate_fn,
... compute_metrics=eval_compute_metrics_fn,
... )
>>> trainer.train()| Epoch | Mất huấn luyện | Loss kiểm định | Bản đồ | Map 50 | Map 75 | Map nhỏ | Bản đồ Medium | Map Large | Ngày 1 tháng 3 | 10 tháng 3 | Mar 100 | Mar Small | Tháng Ba Trung bình | Mar Large | Nhóm Map | Nhóm Mar 100 | Bản đồ hình ảnh | Ảnh Mar 100 | Map Rectangle | Mar 100 Rectangle | Bản đồ văn bản | Mar 100 Text |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | Không có nhật ký | 9.9234 | 0.1303 | 0.2236 | 0.1478 | 0.0909 | 0.2030 | 0.2524 | 0.0421 | 0.2520 | 0.4683 | 0.3113 | 0.5607 | 0.6782 | 0.1244 | 0.5122 | 0.0958 | 0.5035 | 0.1285 | 0.4328 | 0.1725 | 0.4413 |
| 2 | Không có nhật ký | 9.8472 | 0.1893 | 0.3017 | 0.2124 | 0.1347 | 0.2789 | 0.3038 | 0.0549 | 0.2961 | 0.5140 | 0.3433 | 0.5941 | 0.7406 | 0.1305 | 0.5423 | 0.1979 | 0.5578 | 0.1964 | 0.4648 | 0.2324 | 0.4437 |
| 3 | Không có nhật ký | 9.6401 | 0.2275 | 0.3547 | 0.2657 | 0.1698 | 0.3336 | 0.3892 | 0.0611 | 0.3204 | 0.5270 | 0.3625 | 0.6143 | 0.7496 | 0.1602 | 0.5684 | 0.2617 | 0.5763 | 0.2249 | 0.4684 | 0.2631 | 0.4692 |
| 4 | Không có nhật ký | 9.5770 | 0.2733 | 0.4068 | 0.3133 | 0.2100 | 0.3867 | 0.4343 | 0.0668 | 0.3456 | 0.5593 | 0.3875 | 0.6393 | 0.7725 | 0.2013 | 0.5941 | 0.3158 | 0.6065 | 0.2733 | 0.4998 | 0.3028 | 0.4756 |
| 5 | 10.3700 | 11.0500 | 0.2827 | 0.4193 | 0.2913 | 0.2021 | 0.2814 | 0.3763 | 0.0609 | 0.3403 | 0.5668 | 0.4138 | 0.5669 | 0.7317 | 0.2092 | 0.5979 | 0.3334 | 0.6295 | 0.2793 | 0.5245 | 0.3089 | 0.5151 |
Nếu bạn đã đặt push_to_hub thành True trong training_args, các checkpoint huấn luyện sẽ được đẩy lên Hugging Face Hub. Khi quá trình huấn luyện hoàn tất, hãy đẩy mô hình cuối cùng lên Hub bằng cách gọi phương thức push_to_hub().
>>> trainer.push_to_hub()Đánh giá
>>> from pprint import pprint
>>> metrics = trainer.evaluate(eval_dataset=val_ds, metric_key_prefix="test")
>>> pprint(metrics)
{'test_loss': 11.05,
'test_map': 0.2827,
'test_map_50': 0.4193,
'test_map_75': 0.2913,
'test_map_group': 0.2092,
'test_map_image': 0.3334,
'test_map_large': 0.3763,
'test_map_medium': 0.2814,
'test_map_rectangle': 0.2793,
'test_map_small': 0.2021,
'test_map_text': 0.3089,
'test_mar_1': 0.0609,
'test_mar_10': 0.3403,
'test_mar_100': 0.5668,
'test_mar_100_group': 0.5979,
'test_mar_100_image': 0.6295,
'test_mar_100_rectangle': 0.5245,
'test_mar_100_text': 0.5151,
'test_mar_large': 0.7317,
'test_mar_medium': 0.5669,
'test_mar_small': 0.4138}Các kết quả này có thể được cải thiện thêm bằng cách tăng số lượng epoch hoặc điều chỉnh các siêu tham số khác trong TrainingArguments. Hãy thử ngay!
Suy luận
Giờ đây, khi bạn đã tinh chỉnh một mô hình, đánh giá nó và tải nó lên Hugging Face Hub, bạn có thể sử dụng nó để suy luận.
>>> import torch
>>> from PIL import Image, ImageDraw
>>> from transformers import AutoImageProcessor, AutoModelForObjectDetection
>>> from datasets import load_dataset
>>> ds = load_dataset("merve/mobile-ui-design", split="train")
>>> image = ds[5]["image"].convert("RGB")Tải model và image processor từ Hugging Face Hub (bỏ qua nếu đã được huấn luyện trong phiên này):
>>> model_repo = "merve/rf_detr_finetuned_mobile_ui"
>>> image_processor = AutoImageProcessor.from_pretrained(model_repo)
>>> model = AutoModelForObjectDetection.from_pretrained(model_repo)
>>> model.eval()Và phát hiện các hộp bao:
>>> with torch.no_grad():
... inputs = image_processor(images=[image], return_tensors="pt")
... outputs = model(**inputs)
... target_sizes = torch.tensor([[image.size[1], image.size[0]]])
... results = image_processor.post_process_object_detection(outputs, threshold=0.5, target_sizes=target_sizes)[0]
>>> for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
... box = [round(i, 2) for i in box.tolist()]
... print(
... f"Detected {model.config.id2label[label.item()]} with confidence "
... f"{round(score.item(), 3)} at location {box}"
... )
Detected text with confidence 0.727 at location [324.02, 340.55, 339.52, 359.12]
Detected rectangle with confidence 0.717 at location [39.97, 705.14, 335.93, 753.54]
Detected text with confidence 0.702 at location [199.94, 473.66, 213.41, 490.6]
Detected text with confidence 0.678 at location [153.14, 474.81, 165.33, 491.0]
Detected text with confidence 0.675 at location [262.67, 718.28, 281.44, 740.81]
Detected rectangle with confidence 0.655 at location [143.57, 242.51, 214.32, 274.26]
Detected text with confidence 0.653 at location [298.68, 637.77, 345.68, 656.26]Hãy vẽ kết quả:
>>> draw = ImageDraw.Draw(image)
>>> colors = {"group": "blue", "image": "green", "rectangle": "red", "text": "orange"}
>>> for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
... box = [round(i, 2) for i in box.tolist()]
... x, y, x2, y2 = tuple(box)
... label_name = model.config.id2label[label.item()]
... color = colors.get(label_name, "red")
... draw.rectangle((x, y, x2, y2), outline=color, width=2)
... draw.text((x, y), f"{label_name} {score:.2f}", fill=color)
>>> image
