Dönüştürme Yapmadan COCO JSON ile YOLO Nasıl Eğitilir#
Ek açıklamalar, COCO JSON biçiminde olduklarında, önce .txt dosyalarına dönüştürülmeleri gerekmeden doğrudan Ultralytics YOLO eğitimi için kullanılabilir. Bu işlem, COCO JSON'u anında ayrıştırmak için YOLODataset sınıfından türetme yaparak ve bunu özel bir eğitici aracılığıyla eğitim işlem hattına bağlayarak gerçekleştirilir.
Neden Doğrudan COCO JSON ile Eğitim Yapılmalı?#
Bu yaklaşım, COCO JSON'u tek doğruluk kaynağı olarak korur — convert_coco() çağrısı, dizin yeniden düzenlemesi veya ara etiket dosyaları yoktur. YOLO26 ve diğer tüm Ultralytics YOLO nesne algılama modelleri desteklenir. Segmentasyon ve poz modelleri ek etiket alanları gerektirir (bkz. SSS).
Standart convert_coco() iş akışı için COCO'dan YOLO'ya Dönüştürme kılavuzuna bak.
Mimariye Genel Bakış#
İki sınıf gerekir:
COCODataset— COCO JSON'u okur ve sınırlayıcı kutuları eğitim sırasında bellekte YOLO biçimine dönüştürürCOCOTrainer— varsayılanYOLODatasetyerineCOCODatasetkullanmak içinbuild_dataset()öğesini geçersiz kılar
Uygulama, JSON ek açıklamalarını doğrudan okuyan yerleşik GroundingDataset sınıfının basitleştirilmiş bir sürümüdür. Burada üç yöntem geçersiz kılınır — get_img_files(), cache_labels() ve get_labels() — GroundingDataset ise kendi önbellek karması ve örnek sayısı denetimleri dahil olmak üzere daha fazla öğeyi geçersiz kılar.
COCO JSON Veri Kümesi Sınıfını Oluşturma#
COCODataset sınıfı YOLODataset sınıfından türetilir ve etiket yükleme mantığını geçersiz kılar. Etiket dizinindeki .txt dosyalarını okumak yerine COCO JSON dosyasını açar, görüntüye göre gruplanmış ek açıklamalar üzerinde yineler ve her sınırlayıcı kutuyu COCO piksel biçimi olan [x_min, y_min, width, height] biçiminden YOLO'nun normalize edilmiş merkez biçimi olan [x_center, y_center, width, height] biçimine dönüştürür. Kalabalık ek açıklamaları (iscrowd: 1) ve sıfır alanlı kutular otomatik olarak atlanır.
get_img_files() yöntemi, görüntü yolları cache_labels() içindeki JSON file_name alanından çözümlendiği için boş bir liste döndürür. Kategori kimlikleri sıralanır ve sıfır tabanlı sınıf dizinlerine yeniden eşlenir; böylece hem 1 tabanlı (standart COCO) hem de ardışık olmayan kimlik şemaları doğru şekilde ç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):
"""Initialize the dataset with a COCO JSON annotation file."""
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."""
self.fraction = 1.0 # fraction is applied while scanning a directory, which this dataset skips
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)
# 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",
}
)
if not x["labels"]:
raise RuntimeError(f"No images listed in {self.json_file} were found in {self.img_path}")
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ılan etiketler, JSON'un yanında bulunan bir .cache dosyasına (ör. instances_train.cache) kaydedilir. Sonraki eğitim çalıştırmalarında önbellek doğrudan yüklenir ve JSON ayrıştırma adımı atlanır.
get_hash(), dosya içerikleri yerine dosya boyutlarını ve yollarını karmalar; bu nedenle yeniden çalıştırma, JSON'u yalnızca JSON'un bayt sayısı değiştiğinde yeniden ayrıştırır. Görüntü eklemek veya kaldırmak, görüntü dizininin kendi boyutunu da değiştirerek yeniden oluşturmayı tetikleyebilir, ancak buna güvenme — karma tek tek görüntü dosyalarını incelemez; dolayısıyla bir görüntüyü başka bir görüntüyle değiştirmek boyutu değiştirmeden kalabilir. Bayt sayısını koruyan bir düzenleme — bir koordinatı küçük ölçüde değiştirmek, iscrowd değerini tersine çevirmek veya aynı uzunluktaki iki sınıf adını değiştirmek — eski önbelleği yerinde bırakır ve hiçbir uyarı vermeden eski ek açıklamalarla eğitim yapılmasına neden olur; bir görüntünün yerinde değiştirilmesi de aynı nedenle görünmezdir. Ek açıklamaları veya görüntüleri yerinde düzenledikten sonra .cache dosyasını sil.
Veri Kümesini Eğitim İşlem Hattına Bağlama#
Eğiticide gereken tek değişiklik build_dataset() öğesini geçersiz kılmaktır. Varsayılan DetectionTrainer, .txt etiket dosyalarını tarayan bir YOLODataset oluşturur. Bunun yerine COCODataset kullanıldığında eğitici, COCO JSON'dan okuma yapar.
JSON dosyasının yolu, veri yapılandırmasındaki özel bir train_json / val_json alanından alınır (bkz. dataset.yaml'ı yapılandırma). Eğitim sırasında mode="train", train_json olarak çözümlenir; doğrulama sırasında mode="val", val_json olarak çözümlenir. Her iki anahtar da gereklidir — iki bölme farklı görüntü dizinlerini okur; bu nedenle eğitim JSON'u eksik bir val_json öğesinin yerine kullanılamaz.
Veri kümesi ayrıca fraction değerini 1.0 olarak sıfırlar. BaseDataset, bir görüntü dizinini tararken bu bağımsız değişkeni uygular; COCODataset ise bu adımı atlar, dolayısıyla kısmi veri kümesi isteğini yerine getiremez. Bu değeri sıfırlamak, veri kümesinin yok saydığı bir değeri kabul ediyor gibi görünmesini önler. Yerleşik GroundingDataset da aynı nedenle aynı uzlaşmayı yapar.
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):
"""Build a COCODataset for the given split using the JSON file from the data config."""
json_file = self.data["train_json"] if mode == "train" else self.data["val_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, görüntü dizinlerini bulmak için standart path, train ve val alanlarını kullanır. Burada path görüntü kökünü gösterir; bu nedenle train ve val, dönüştürme kılavuzundaki durumun aksine, yalın bölme adlarıdır; kılavuzda path veri kümesi köküdür ve bölmeler images/ ön ekini taşır. İki ek alan olan train_json ve val_json, COCOTrainer tarafından okunan COCO ek açıklama dosyalarını belirtir. names alanı, JSON'daki categories öğesinin sıralanmış düzenindeki sınıf adlarını listeler ve sınıf sayısı bundan türetilir; bu nedenle nc değerini ayarlamaya gerek yoktur.
path: /path/to/my_dataset/images # root with train/ and val/ image subfolders
train: train
val: val
# COCO JSON annotation files (use absolute paths; these custom keys are not resolved against `path`)
train_json: /path/to/my_dataset/annotations/instances_train.json
val_json: /path/to/my_dataset/annotations/instances_val.json
names:
0: person
1: bicycle
# ... remaining class namesBeklenen dizin yapısı:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yamlCOCO JSON ile Eğitimi Çalıştırma#
Veri kümesi sınıfı, eğitici sınıfı ve YAML yapılandırması hazır olduğunda eğitim, standart model.train() çağrısı üzerinden çalışır. Normal bir eğitim çalıştırmasından tek fark, Ultralytics'e varsayılan yerine özel veri kümesi yükleyicisini kullanmasını söyleyen trainer=COCOTrainer bağımsız değişkenidir.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)Eğitim işlem hattının tamamı, eğitim sırasındaki doğrulama, kontrol noktası kaydetme ve metrik günlüğe kaydetme dahil olmak üzere beklendiği gibi çalışır.
Yalnızca eğitim zamanı doğrulaması COCOTrainer.build_dataset üzerinden gerçekleşir. Ayrı bir model.val() çağrısı, görüntülerin yanındaki .txt etiketlerini tarayan yerleşik YOLODataset öğesini oluşturur ve hiçbir etiket bulamaz. Hata vermez: görüntüler arka plan olarak sayılır; bu nedenle doğrulama tamamlanır ve her metrik 0 olarak raporlanır, No labels found in ... ve no labels found in detect set, cannot compute metrics without labels uyarıları verilir. Bir eğitim çalıştırması dışında doğrulama yapmak için doğrulayıcı sınıfını aynı build_dataset geçersiz kılmasıyla türet ve bunu model.val(validator=...) öğesine ilet.
Tam Uygulama#
Kolaylık olması için tam uygulama, aşağıda tek bir kopyala-yapıştır betiği olarak verilmiştir. Özel veri kümesini, özel eğiticiyi ve eğitim çağrısını içerir. Bunu dataset.yaml dosyanın yanına kaydet ve doğrudan çalıştır.
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):
"""Initialize the dataset with a COCO JSON annotation file."""
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."""
self.fraction = 1.0 # fraction is applied while scanning a directory, which this dataset skips
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)
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",
}
)
if not x["labels"]:
raise RuntimeError(f"No images listed in {self.json_file} were found in {self.img_path}")
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"]
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
"""Build a COCODataset for the given split using the JSON file from the data config."""
json_file = self.data["train_json"] if mode == "train" else self.data["val_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)Artık ek açıklamaları tek doğruluk kaynağı olarak koruyan ve ara .txt dosyaları oluşturmadan doğrudan COCO JSON üzerinde Ultralytics YOLO eğitimi yapan minimal bir veri kümesine ve eğiticiye sahipsin. Segmentasyon ve pozu desteklemek için cache_labels() yöntemini segments veya keypoints ile genişlet ve hiperparametre ayarlama önerileri için Model Eğitimi İpuçları kılavuzuna bak.
SSS#
convert_coco(), tek seferlik bir dönüştürme olarak.txtetiket dosyalarını diske yazar. Bu yaklaşım, her eğitim çalıştırmasının başlangıcında JSON'u ayrıştırır ve ek açıklamaları bellekte dönüştürür. Kalıcı YOLO biçimli etiketler tercih ediliyorsaconvert_coco()kullan; ek dosyalar oluşturmadan COCO JSON'u tek doğruluk kaynağı olarak korumak istiyorsan bu yaklaşımı kullan.Mevcut Ultralytics işlem hattıyla hayır; işlem hattı varsayılan olarak YOLO
.txtetiketlerini bekler. Bu kılavuz, gereken minimal özel kodu — bir veri kümesi sınıfı ve bir eğitici sınıfı — sağlar. Tanımlandıktan sonra eğitim yalnızca standart birmodel.train()çağrısı gerektirir.Bu kılavuz nesne algılamayı kapsar. Örnek segmentasyonu desteği eklemek için COCO ek açıklamalarındaki
segmentationçokgen verilerini her etiket sözlüğününsegmentsalanına dahil et. Poz tahmini içinkeypointsöğesini dahil et.GroundingDatasetkaynak kodu, segmentleri işleme için bir referans uygulama sunar.Evet.
COCODataset,YOLODatasetöğesini genişletir; bu nedenle tüm yerleşik veri artırma işlemleri — mozaik, mixup, kopyala-yapıştır ve diğerleri — değişiklik yapılmadan çalışır.Kategoriler
idile sıralanır ve 0'dan başlayan ardışık dizinlere eşlenir. Bu işlem 1 tabanlı kimlikleri (standart COCO), 0 tabanlı kimlikleri ve ardışık olmayan kimlikleri destekler.dataset.yamliçindekinamessözlüğü, COCOcategoriesdizisiyle aynı sıralama düzenini izlemelidir.COCO JSON ilk eğitim çalıştırmasında bir kez ayrıştırılır. Ayrıştırılan etiketler bir
.cachedosyasına kaydedilir; böylece sonraki çalıştırmalar yeniden ayrıştırma yapmadan anında yüklenir. Ek açıklamalar bellekte tutulduğundan eğitim hızı standart YOLO eğitimiyle aynıdır. Önbellek, JSON dosyasının boyutunu temel alır; bu nedenle dosya uzunluğunu değiştirmeyen herhangi bir düzenlemeden sonra.cachedosyasını sil.