İçeriğe geç

COCO JSON Üzerinde YOLO'yu Dönüştürmeden Nasıl Eğitilir

Neden Doğrudan COCO JSON Üzerinde Eğitilmeli

Anotasyonlar içinde COCO JSON formatı doğrudan şunlar için kullanılabilir: Ultralytics YOLO dönüştürmeye gerek kalmadan eğitim .txt dosyalarına. Bu, alt sınıflandırılarak yapılır. YOLODataset COCO JSON'ı anında ayrıştırmak ve özel bir eğitici aracılığıyla eğitim hattına bağlamak için.

Bu yaklaşım, COCO JSON'ı tek doğruluk kaynağı olarak tutar — hiçbir convert_coco() çağrısı, dizin yeniden düzenlemesi, ara etiket dosyaları yok. YOLO26 ve diğer tüm Ultralytics YOLO detect modelleri desteklenmektedir. segmentasyon ve poz modelleri ek etiket alanları gerektirir (bkz. SSS).

Bunun yerine tek seferlik bir dönüştürme mi arıyorsunuz?

Şuna bakın: COCO'dan YOLO'ya Dönüştürme rehberi standart convert_coco() iş akışı için.

Mimariye Genel Bakış

İki sınıf gereklidir:

  1. COCODataset — COCO JSON'ı okur ve dönüştürür sınırlayıcı kutular eğitim sırasında bellekte YOLO formatına
  2. COCOTrainer — geçersiz kılar build_dataset() kullanmak için COCODataset varsayılan yerine YOLODataset

Uygulama, yerleşik olanla aynı deseni takip eder. GroundingDataset, o da JSON ek açıklamalarını doğrudan okur. Üç yöntem geçersiz kılınır: get_img_files(), cache_labels()ve get_labels().

COCO JSON Veri Kümesi Sınıfını Oluşturma

COCODataset sınıfından miras alır YOLODataset ve etiket yükleme mantığını geçersiz kılar. Okumak yerine .txt etiket dizininden dosyaları okumak yerine, COCO JSON dosyasını açar, görüntüye göre gruplandırılmış ek açıklamalar üzerinde yineler ve her sınırlayıcı kutuyu COCO piksel formatından dönüştürür [x_min, y_min, width, height] YOLO normalize edilmiş merkez formatına [x_center, y_center, width, height]. Kalabalık ek açıklamaları (iscrowd: 1) ve sıfır alanlı kutular otomatik olarak atlanır.

get_img_files() yöntemi boş bir liste döndürür çünkü görüntü yolları JSON file_name içindeki alandan çözümlenir cache_labels(). Kategori kimlikleri sıralanır ve sıfır tabanlı sınıf indekslerine yeniden eşlenir, böylece hem 1 tabanlı (standart COCO) hem de bitişik olmayan kimlik şemaları doğru çalışır.

import json
from collections import defaultdict
from pathlib import Path

import numpy as np

from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.utils import TQDM


class COCODataset(YOLODataset):
    """Dataset that reads COCO JSON annotations directly without conversion to .txt files."""

    def __init__(self, *args, json_file="", **kwargs):
        self.json_file = json_file
        super().__init__(*args, data={"channels": 3}, **kwargs)

    def get_img_files(self, img_path):
        """Image paths are resolved from the JSON file, not from scanning a directory."""
        return []

    def cache_labels(self, path=Path("./labels.cache")):
        """Parse COCO JSON and convert annotations to YOLO format. Results are saved to a .cache file."""
        x = {"labels": []}
        with open(self.json_file) as f:
            coco = json.load(f)

        images = {img["id"]: img for img in coco["images"]}

        # Sort categories by ID and map to 0-indexed classes
        categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}

        img_to_anns = defaultdict(list)
        for ann in coco["annotations"]:
            img_to_anns[ann["image_id"]].append(ann)

        for img_info in TQDM(coco["images"], desc="reading annotations"):
            h, w = img_info["height"], img_info["width"]
            im_file = Path(self.img_path) / img_info["file_name"]
            if not im_file.exists():
                continue

            self.im_files.append(str(im_file))
            bboxes = []
            for ann in img_to_anns.get(img_info["id"], []):
                if ann.get("iscrowd", False):
                    continue
                # COCO: [x, y, w, h] top-left in pixels -> YOLO: [cx, cy, w, h] center normalized
                box = np.array(ann["bbox"], dtype=np.float32)
                box[:2] += box[2:] / 2  # top-left to center
                box[[0, 2]] /= w  # normalize x
                box[[1, 3]] /= h  # normalize y
                if box[2] <= 0 or box[3] <= 0:
                    continue
                cls = categories[ann["category_id"]]
                bboxes.append([cls, *box.tolist()])

            lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
            x["labels"].append(
                {
                    "im_file": str(im_file),
                    "shape": (h, w),
                    "cls": lb[:, 0:1],
                    "bboxes": lb[:, 1:],
                    "segments": [],
                    "normalized": True,
                    "bbox_format": "xywh",
                }
            )
        x["hash"] = get_hash([self.json_file, str(self.img_path)])
        save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
        return x

    def get_labels(self):
        """Load labels from .cache file if available, otherwise parse JSON and create the cache."""
        cache_path = Path(self.json_file).with_suffix(".cache")
        try:
            cache = load_dataset_cache_file(cache_path)
            assert cache["version"] == DATASET_CACHE_VERSION
            assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
            self.im_files = [lb["im_file"] for lb in cache["labels"]]
        except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
            cache = self.cache_labels(cache_path)
        cache.pop("hash", None)
        cache.pop("version", None)
        return cache["labels"]

Ayrıştırılmış etiketler bir .cache dosyasına JSON'ın yanına kaydedilir (örn. instances_train.cache). Sonraki eğitim çalıştırmalarında, önbellek doğrudan yüklenir ve JSON ayrıştırması atlanır. JSON dosyası değişirse, hash kontrolü başarısız olur ve önbellek otomatik olarak yeniden oluşturulur.

Veri Kümesini Eğitim Hattına Bağlama

Eğiticide yapılması gereken tek değişiklik, şunu geçersiz kılmaktır: build_dataset(). Varsayılan DetectionTrainer bir YOLODataset tarayan bir .txt etiket dosyalarını. Bunu şununla değiştirerek COCODataset, eğitici bunun yerine COCO JSON'dan okur.

JSON dosya yolu veri yapılandırmasındaki özel bir train_json / val_json alandan çekilir (bkz. Adım 3). Eğitim sırasında, mode="train" şuna çözümlenir: train_json; doğrulama sırasında ise, mode="val" şuna çözümlenir: val_json. Eğer val_json ayarlanmamışsa, şuna geri döner: train_json.

from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import colorstr


class COCOTrainer(DetectionTrainer):
    """Trainer that uses COCODataset for direct COCO JSON training."""

    def build_dataset(self, img_path, mode="train", batch=None):
        json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
        return COCODataset(
            img_path=img_path,
            json_file=json_file,
            imgsz=self.args.imgsz,
            batch_size=batch,
            augment=mode == "train",
            hyp=self.args,
            rect=self.args.rect or mode == "val",
            cache=self.args.cache or None,
            single_cls=self.args.single_cls or False,
            stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
            pad=0.0 if mode == "train" else 0.5,
            prefix=colorstr(f"{mode}: "),
            task=self.args.task,
            classes=self.args.classes,
            fraction=self.args.fraction if mode == "train" else 1.0,
        )

COCO JSON için dataset.yaml Yapılandırma

dataset.yaml standart olanı kullanır path, trainve val görüntü dizinlerini bulmak için alanlar. İki ek alan, train_json ve val_json, COCO ek açıklama dosyalarını belirtir. COCOTrainer okur. Bu nc ve names alanları, sınıf sayısını ve adlarını tanımlar, JSON'daki sıralı düzeniyle eşleşir. categories JSON'da.

path: /path/to/images # root directory with train/ and val/ subfolders
train: train
val: val

# COCO JSON annotation files
train_json: /path/to/annotations/instances_train.json
val_json: /path/to/annotations/instances_val.json

nc: 80
names:
    0: person
    1: bicycle
    # ... remaining class names

Beklenen dizin yapısı:

my_dataset/
  images/
    train/
      img_001.jpg
      ...
    val/
      img_100.jpg
      ...
  annotations/
    instances_train.json
    instances_val.json
  dataset.yaml

COCO JSON Üzerinde Eğitim Çalıştırma

Veri kümesi sınıfı, eğitici sınıfı ve yaml yapılandırması mevcut olduğunda, eğitim standart model.train() çağrısı aracılığıyla çalışır. Normal bir eğitim çalışmasından tek fark, trainer=COCOTrainer argümanıdır; bu, Ultralytics'e varsayılan yerine özel veri kümesi yükleyicisini kullanmasını söyler.

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)

Tam eğitim hattı, doğrulama, kontrol noktası kaydetme ve metrik günlükleme dahil olmak üzere beklendiği gibi çalışır.

Tam Uygulama

Kolaylık sağlamak için, tüm uygulama aşağıda tek bir kopyala-yapıştır betiği olarak sunulmuştur. Bu, özel veri kümesini, özel eğiticiyi ve eğitim çağrısını içerir. Bunu kendi dataset.yaml yanına kaydedin ve doğrudan çalıştırın.

import json
from collections import defaultdict
from pathlib import Path

import numpy as np

from ultralytics import YOLO
from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import TQDM, colorstr


class COCODataset(YOLODataset):
    """Dataset that reads COCO JSON annotations directly without conversion to .txt files."""

    def __init__(self, *args, json_file="", **kwargs):
        self.json_file = json_file
        super().__init__(*args, data={"channels": 3}, **kwargs)

    def get_img_files(self, img_path):
        return []

    def cache_labels(self, path=Path("./labels.cache")):
        x = {"labels": []}
        with open(self.json_file) as f:
            coco = json.load(f)

        images = {img["id"]: img for img in coco["images"]}
        categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}

        img_to_anns = defaultdict(list)
        for ann in coco["annotations"]:
            img_to_anns[ann["image_id"]].append(ann)

        for img_info in TQDM(coco["images"], desc="reading annotations"):
            h, w = img_info["height"], img_info["width"]
            im_file = Path(self.img_path) / img_info["file_name"]
            if not im_file.exists():
                continue

            self.im_files.append(str(im_file))
            bboxes = []
            for ann in img_to_anns.get(img_info["id"], []):
                if ann.get("iscrowd", False):
                    continue
                box = np.array(ann["bbox"], dtype=np.float32)
                box[:2] += box[2:] / 2
                box[[0, 2]] /= w
                box[[1, 3]] /= h
                if box[2] <= 0 or box[3] <= 0:
                    continue
                cls = categories[ann["category_id"]]
                bboxes.append([cls, *box.tolist()])

            lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
            x["labels"].append(
                {
                    "im_file": str(im_file),
                    "shape": (h, w),
                    "cls": lb[:, 0:1],
                    "bboxes": lb[:, 1:],
                    "segments": [],
                    "normalized": True,
                    "bbox_format": "xywh",
                }
            )
        x["hash"] = get_hash([self.json_file, str(self.img_path)])
        save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
        return x

    def get_labels(self):
        cache_path = Path(self.json_file).with_suffix(".cache")
        try:
            cache = load_dataset_cache_file(cache_path)
            assert cache["version"] == DATASET_CACHE_VERSION
            assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
            self.im_files = [lb["im_file"] for lb in cache["labels"]]
        except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
            cache = self.cache_labels(cache_path)
        cache.pop("hash", None)
        cache.pop("version", None)
        return cache["labels"]


class COCOTrainer(DetectionTrainer):
    """Trainer that uses COCODataset for direct COCO JSON training."""

    def build_dataset(self, img_path, mode="train", batch=None):
        json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
        return COCODataset(
            img_path=img_path,
            json_file=json_file,
            imgsz=self.args.imgsz,
            batch_size=batch,
            augment=mode == "train",
            hyp=self.args,
            rect=self.args.rect or mode == "val",
            cache=self.args.cache or None,
            single_cls=self.args.single_cls or False,
            stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
            pad=0.0 if mode == "train" else 0.5,
            prefix=colorstr(f"{mode}: "),
            task=self.args.task,
            classes=self.args.classes,
            fraction=self.args.fraction if mode == "train" else 1.0,
        )


model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)

Hiperparametre önerileri için Model Eğitim İpuçları kılavuzuna bakın.

SSS

Bunun ile convert_coco() arasındaki fark nedir?

convert_coco() yazar .txt etiket dosyalarını tek seferlik bir dönüştürme olarak diske yazar. Bu yaklaşım, her eğitim çalışmasının başlangıcında JSON'ı ayrıştırır ve ek açıklamaları bellekte dönüştürür. Şunu kullanın: convert_coco() kalıcı YOLO formatında etiketler tercih edildiğinde; ek dosyalar oluşturmadan COCO JSON'ı tek doğruluk kaynağı olarak tutmak için bu yaklaşımı kullanın.

YOLO, özel kod olmadan COCO JSON üzerinde eğitim yapabilir mi?

Mevcut Ultralytics hattıyla değil, bu varsayılan olarak YOLO .txt etiketlerini bekler. Bu kılavuz, gereken minimum özel kodu sağlar — bir veri kümesi sınıfı ve bir eğitici sınıfı. Tanımlandıktan sonra, eğitim yalnızca standart bir model.train() çağrısı gerektirir.

Bu, segmentasyon ve poz tahmini destekliyor mu?

Bu kılavuz şunları kapsar: nesne tespiti. Eklemek için örnek segmentasyonu desteği için, segmentation COCO ek açıklamalarından poligon verilerini segments her etiket sözlüğünün alanına ekleyin. İçin poz tahmini, ekleyin keypoints. Şunu GroundingDataset kaynak kodu segmentleri işlemek için bir referans uygulama sağlar.

Artırmalar bu özel veri setiyle çalışıyor mu?

Evet. COCODataset genişletir YOLODataset, bu nedenle tüm yerleşik veri artırmalarımozaik, mixup, kopyala-yapıştır, ve diğerleri — değişiklik yapmadan çalışır.

Kategori kimlikleri sınıf indekslerine nasıl eşlenir?

Kategoriler şuna göre sıralanır: id ve 0'dan başlayan sıralı indekslere eşlenir. Bu, 1 tabanlı kimlikleri (standart COCO), 0 tabanlı kimlikleri ve bitişik olmayan kimlikleri işler. names sözlüğü içinde dataset.yaml COCO ile aynı sıralı düzeni takip etmelidir. categories dizisi.

Önceden dönüştürülmüş etiketlere kıyasla bir performans yükü var mı?

COCO JSON, ilk eğitim çalıştırmasında bir kez ayrıştırılır. Ayrıştırılan etiketler bir dosyaya kaydedilir. .cache dosyasına kaydedilir, böylece sonraki çalıştırmalar yeniden ayrıştırma yapmadan anında yüklenir. Ek açıklamalar bellekte tutulduğu için eğitim hızı standart YOLO eğitimine benzerdir. JSON dosyası değişirse önbellek otomatik olarak yeniden oluşturulur.



📅 7 gün önce oluşturuldu ✏️ 6 gün önce güncellendi
raimbekovmglenn-jocher

Yorumlar