Buckets:

hf-doc-build/doc / transformers /main /ja /tasks /object_detection.md
mishig's picture
|
download
raw
24.6 kB
# オブジェクト検出
Object detectionは、画像内のインスタンス(人間、建物、車など)を検出するコンピュータビジョンのタスクです。Object detectionモデルは画像を入力として受け取り、検出されたオブジェクトのbounding boxの座標と関連するラベルを出力します。1つの画像には複数のオブジェクトが含まれることがあり、それぞれに独自のbounding boxとラベル(例:車と建物)が付与されます。また、各オブジェクトは画像内の異なる部分に存在する可能性があります(例:複数の車)。このタスクは、歩行者、道路標識、信号機などの検出のために自動運転で一般的に使用されています。その他の応用としては、画像内のオブジェクトの個数カウント、画像検索などが挙げられます。
このガイドでは、次のことを学びます。
1. [mobile-ui-design](https://huggingface.co/datasets/merve/mobile-ui-design) データセットで [RF-DETR](https://huggingface.co/Roboflow/rf-detr-medium) をファインチューニングし、モバイルアプリのスクリーンショット内のUI要素を検出します。
2. ファインチューニングしたモデルを推論に使用します。
このタスクと互換性のあるすべてのアーキテクチャとチェックポイントを確認するには、[タスクページ](https://huggingface.co/tasks/object-detection)を参照することをお勧めします。
開始前に、必要なライブラリがすべてインストールされていることを確認してください。
```bash
pip install -q datasets transformers accelerate timm trackio torchmetrics pycocotools
```
🤗 Datasets を使用して Hugging Face Hub からデータセットを読み込み、🤗 Transformers を使用してモデルを学習します。
コミュニティにモデルを共有することをお勧めします。Hubにアップロードするには、Hugging Faceアカウントにログインしてください。プロンプトが表示されたら、ログイン用のトークンを入力してください:
```py
>>> from huggingface_hub import notebook_login
>>> notebook_login()
```
グローバル定数、すなわちモデル名と画像サイズを定義します。このチュートリアルではRF-DETRを使用していますが、Transformersのオブジェクト検出モデルであれば何でも選択できます。
```py
>>> MODEL_NAME = "Roboflow/rf-detr-medium"
```
## mobile-ui-design データセットを読み込みます
[mobile-ui-design データセット](https://huggingface.co/datasets/merve/mobile-ui-design)には、テキスト、画像、長方形、グループなどの UI 要素を検出するためのアノテーション付きのモバイルアプリのスクリーンショットが含まれています。
データセットを読み込み、カテゴリラベルを抽出することから始めます。データセットにはすでにスプリットが含まれています。
```py
>>> 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']
```
データセットは、文字列のカテゴリ名とCOCO形式のバウンディングボックス`(x, y, w, h)`を使用しています。学習前に、カテゴリを整数IDに変換し、面積を計算し、退化したバウンディングボックスを除外してください:
```py
>>> 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: 1177
```
## データを前処理する
[AutoImageProcessor](/docs/transformers/main/ja/model_doc/auto#transformers.AutoImageProcessor)は、モデルの学習に使用できる`pixel_values`、`pixel_mask`、`labels`を生成するために、画像データの処理を行います。画像プロセッサは、リサイズ、パディング、正規化を処理します。さらに、汎化性能を向上させるために、ランダムなデータ拡張を任意で追加できます([下記](#data-augmentation)を参照)。
```py
>>> import numpy as np
>>> from functools import partial
>>> from transformers import AutoImageProcessor
>>> image_processor = AutoImageProcessor.from_pretrained(MODEL_NAME)
```
`image_processor` は COCO 形式の注釈を期待します: `{'image_id': int, 'annotations': list[Dict]}`。各サンプルの注釈をこの形式に整形し、残りはプロセッサに委ねてください:
```py
>>> 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)
```
### データ拡張
上記のtransformは、画像のリサイズと正規化のみを行います。**training**スプリットに適用されるランダムなデータ拡張は通常、汎化性能の向上に寄与しますが、評価を決定論的に保つため、validationスプリットには拡張を適用しないようにします。一般的な選択肢として、画像とバウンディングボックスを同時に拡張する[Albumentations](https://albumentations.ai/)があります。`bbox_params`を指定してパイプラインを定義し、ボックスが画像と一貫して変換されるようにしてから、拡張されたボックスから面積を再計算します。
```py
>>> 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
```
拡張変換はトレーニングスプリットにのみ適用し、検証にはプレーンな`transform_fn`を使用します:
```py
>>> 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)
```
画像をバッチ処理するために、カスタムの`collate_fn`を作成します:
```py
>>> 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
```
## mAPを計算する関数の準備
オブジェクト検出モデルは、COCOスタイルの指標のセットで評価されることが一般的です。`torchmetrics` を使用して `mAP`(平均平均精度)と `mAR`(平均平均再現率)の指標を計算し、[Trainer](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer) で評価に使用するために `compute_metrics` 関数にラップします。
学習に使用されるボックスの中間形式は`YOLO`(正規化)ですが、ボックスの面積を正しく処理するために、`Pascal VOC`(絶対値)形式のボックスでメトリクスを計算します。以下に、バウンディングボックスを`Pascal VOC`形式に変換する関数を定義します。
```py
>>> 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 boxes
```
次に、`compute_metrics` 関数では、評価ループの結果から`predicted` と `target` のバウンディングボックス、スコア、ラベルを収集し、スコアリング関数に渡します。
```py
>>> 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
... )
```
## 検出モデルの学習
前節で大部分の作業を完了したので、いよいよモデルの学習を開始できます。このデータセットの画像は、リサイズ後も依然としてかなり大きいため、このモデルのファインチューニングには少なくとも1つのGPUが必要です。
トレーニングには、以下の手順が含まれます。
1. 前処理で使用したのと同じチェックポイントで、[AutoModelForObjectDetection](/docs/transformers/main/ja/model_doc/auto#transformers.AutoModelForObjectDetection)を使ってモデルを読み込みます。
2. [TrainingArguments](/docs/transformers/main/ja/main_classes/trainer#transformers.TrainingArguments) で、学習ハイパーパラメータを定義します。
3. モデル、データセット、画像プロセッサ、データコラテーターとともに、学習引数を[Trainer](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer)に渡します。
4. [train()](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer.train)を呼び出して、モデルをファインチューニングします。
前処理で使用したのと同じチェックポイントからモデルを読み込む際は、データセットのメタデータから作成した`label2id`と`id2label`のマップを渡すことを忘れないでください。また、既存の分類ヘッドを新しいものに置き換えるため、`ignore_mismatched_sizes=True`を指定します。
```py
>>> from transformers import AutoModelForObjectDetection
>>> model = AutoModelForObjectDetection.from_pretrained(
... MODEL_NAME,
... id2label=id2label,
... label2id=label2id,
... ignore_mismatched_sizes=True,
... )
```
[TrainingArguments](/docs/transformers/main/ja/main_classes/trainer#transformers.TrainingArguments) で `output_dir` を使用してモデルの保存先を指定し、その後、ハイパーパラメータを必要に応じて設定します。`num_train_epochs=5` の場合、A100 GPU では約35分かかります。より良い結果を得るには、エポック数を増やしてください。
重要な注意事項:
- 未使用の列を削除しないでください。そうすると画像の列が削除されてしまいます。画像の列がないと
`pixel_values` を作成できません。このため、`remove_unused_columns` を `False` に設定してください。
- 適切な評価結果を得るには、`eval_do_concat_batches=False` を設定してください。画像ごとにターゲットボックスの数が異なるため、バッチを連結すると、どのボックスがどの画像に属しているかを特定できなくなります。
モデルを Hub にプッシュして共有する場合は、`push_to_hub` を `True` に設定してください(モデルをアップロードするには、Hugging Face にサインインしている必要があります)。
```py
>>> 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,
... )
```
最後に、すべての要素をまとめ、[train()](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer.train)を呼び出します。
```py
>>> 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()
```
[2085/2085 38:39, Epoch 5/5]
Epoch Training Loss Validation Loss Map Map 50 Map 75 Map Small Map Medium Map Large Mar 1 Mar 10 Mar 100 Mar Small Mar Medium Mar Large Map Group Mar 100 Group Map Image Mar 100 Image Map Rectangle Mar 100 Rectangle Map Text Mar 100 Text
1 No log 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 No log 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 No log 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 No log 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
`training_args` で `push_to_hub` を `True` に設定している場合、トレーニングのチェックポイントは Hugging Face Hub にプッシュされます。トレーニングが完了したら、[push_to_hub()](/docs/transformers/main/ja/main_classes/trainer#transformers.Trainer.push_to_hub) メソッドを呼び出して、最終モデルも Hub にプッシュしてください。
```py
>>> trainer.push_to_hub()
```
## 評価
```py
>>> 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}
```
[TrainingArguments](/docs/transformers/main/ja/main_classes/trainer#transformers.TrainingArguments) でエポック数を増やす、または他のハイパーパラメータを調整することで、これらの結果をさらに改善できます。ぜひ試してみてください!
## 推論
モデルのファインチューニング、評価、そして Hugging Face Hub へのアップロードが完了したので、推論に使用できます。
```py
>>> 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")
```
Hugging Face Hubからモデルと画像プロセッサを読み込みます(このセッションで既に学習済みの場合はスキップしてください):
```py
>>> model_repo = "merve/rf_detr_finetuned_mobile_ui"
>>> image_processor = AutoImageProcessor.from_pretrained(model_repo)
>>> model = AutoModelForObjectDetection.from_pretrained(model_repo)
>>> model.eval()
```
バウンディングボックスを検出します:
```py
>>> 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]
```
結果をプロットしてみましょう:
```py
>>> 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
```

Xet Storage Details

Size:
24.6 kB
·
Xet hash:
be2709eb958aba332fb7882ee898ab114c7161938be3429438b64cfe660c2eb8

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.