Link to this sectionComo treinar YOLO em COCO JSON sem converter#
Link to this sectionPor que treinar diretamente em COCO JSON#
Anotações no formato COCO JSON podem ser usadas diretamente para o treinamento do Ultralytics YOLO sem converter para arquivos .txt primeiro. Isso é feito criando uma subclasse de YOLODataset para processar o COCO JSON em tempo real e integrá-lo ao pipeline de treinamento através de um treinador personalizado.
Essa abordagem mantém o COCO JSON como a única fonte da verdade — sem chamadas convert_coco(), sem reorganização de diretórios e sem arquivos de rótulos intermediários. O YOLO26 e todos os outros modelos de detecção Ultralytics YOLO são suportados. Modelos de segmentação e pose exigem campos de rótulos adicionais (consulte o FAQ).
Veja o Guia de Conversão de COCO para YOLO para o fluxo de trabalho padrão convert_coco().
Link to this sectionVisão geral da arquitetura#
Duas classes são necessárias:
COCODataset— lê o COCO JSON e converte caixas delimitadoras para o formato YOLO na memória durante o treinamentoCOCOTrainer— sobrescrevebuild_dataset()para usarCOCODatasetem vez do padrãoYOLODataset
A implementação segue o mesmo padrão que o GroundingDataset integrado, que também lê anotações JSON diretamente. Três métodos são sobrescritos: get_img_files(), cache_labels() e get_labels().
Link to this sectionConstruindo a classe de conjunto de dados COCO JSON#
A classe COCODataset herda de YOLODataset e sobrescreve a lógica de carregamento de rótulos. Em vez de ler arquivos .txt de um diretório de rótulos, ela abre o arquivo COCO JSON, itera sobre as anotações agrupadas por imagem e converte cada caixa delimitadora do formato de pixel do COCO [x_min, y_min, width, height] para o formato central normalizado do YOLO [x_center, y_center, width, height]. Anotações de multidão (iscrowd: 1) e caixas de área zero são ignoradas automaticamente.
O método get_img_files() retorna uma lista vazia porque os caminhos das imagens são resolvidos a partir do campo file_name do JSON dentro de cache_labels(). Os IDs das categorias são classificados e remapeados para índices de classe de base zero, portanto, esquemas de ID baseados em 1 (padrão COCO) e não contíguos funcionam corretamente.
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."""
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"]Os rótulos processados são salvos em um arquivo .cache próximo ao JSON (por exemplo, instances_train.cache). Em execuções de treinamento subsequentes, o cache é carregado diretamente, ignorando o processamento do JSON. Se o arquivo JSON mudar, a verificação de hash falha e o cache é reconstruído automaticamente.
Link to this sectionConectando o conjunto de dados ao pipeline de treinamento#
A única alteração necessária no treinador é sobrescrever build_dataset(). O DetectionTrainer padrão constrói um YOLODataset que procura arquivos de rótulos .txt. Ao substituí-lo por COCODataset, o treinador lê o COCO JSON.
O caminho do arquivo JSON é obtido a partir de um campo personalizado train_json / val_json na configuração de dados (veja o Passo 3). Durante o treinamento, mode="train" é resolvido para train_json; durante a validação, mode="val" é resolvido para val_json. Se val_json não estiver definido, ele volta para 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):
"""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.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,
)Link to this sectionConfigurando o dataset.yaml para COCO JSON#
O dataset.yaml usa os campos padrão path, train e val para localizar diretórios de imagens. Dois campos adicionais, train_json e val_json, especificam os arquivos de anotação COCO que o COCOTrainer lê. Os campos nc e names definem o número de classes e seus nomes, correspondendo à ordem classificada das categories no JSON.
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 namesEstrutura de diretório esperada:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yamlLink to this sectionExecutando o treinamento em COCO JSON#
Com a classe de conjunto de dados, classe de treinador e configuração YAML no lugar, o treinamento funciona através da chamada padrão model.train(). A única diferença de uma execução de treinamento normal é o argumento trainer=COCOTrainer, que diz ao Ultralytics para usar o carregador de conjunto de dados personalizado em vez do padrão.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)O pipeline completo de treinamento é executado conforme o esperado, incluindo validação, salvamento de checkpoint e registro de métricas.
Link to this sectionImplementação completa#
Para conveniência, a implementação completa é fornecida abaixo como um script único pronto para copiar e colar. Ele inclui o conjunto de dados personalizado, o treinador personalizado e a chamada de treinamento. Salve isso junto com seu dataset.yaml e execute-o diretamente.
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."""
return []
def cache_labels(self, path=Path("./labels.cache")):
"""Parse COCO JSON and convert annotations to YOLO format, saving results 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"]}
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):
"""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.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)Para recomendações de hiperparâmetros, veja o guia de Dicas de Treinamento de Modelo.
Link to this sectionFAQ#
Link to this sectionQual a diferença entre isso e convert_coco()?#
convert_coco() grava arquivos de rótulos .txt em disco como uma conversão única. Esta abordagem processa o JSON no início de cada execução de treinamento e converte as anotações na memória. Use convert_coco() quando rótulos em formato YOLO permanente forem preferidos; use esta abordagem para manter o COCO JSON como a única fonte da verdade sem gerar arquivos adicionais.
Link to this sectionO YOLO pode treinar em COCO JSON sem código personalizado?#
Não com o pipeline atual do Ultralytics, que espera rótulos YOLO .txt por padrão. Este guia fornece o código personalizado mínimo necessário — uma classe de conjunto de dados e uma classe de treinador. Uma vez definido, o treinamento requer apenas uma chamada padrão model.train().
Link to this sectionIsso suporta segmentação e estimativa de pose?#
This guide covers object detection. To add instance segmentation support, include the segmentation polygon data from COCO annotations in the segments field of each label dictionary. For pose estimation, include keypoints. The GroundingDataset source code provides a reference implementation for handling segments.
Link to this sectionAs aumentações funcionam com este conjunto de dados personalizado?#
Sim. COCODataset estende YOLODataset, então todas as aumentações de dados integradas — mosaico, mixup, copiar-colar e outras — funcionam sem modificação.
Link to this sectionComo os IDs de categoria são mapeados para índices de classe?#
As categorias são classificadas por id e mapeadas para índices sequenciais começando em 0. Isso lida com IDs baseados em 1 (padrão COCO), IDs baseados em 0 e IDs não contíguos. O dicionário names no dataset.yaml deve seguir a mesma ordem classificada que a matriz de categories do COCO.
Link to this sectionExiste uma sobrecarga de desempenho em comparação com rótulos pré-convertidos?#
O COCO JSON é processado uma vez na primeira execução de treinamento. Os rótulos processados são salvos em um arquivo .cache, então execuções subsequentes carregam instantaneamente sem reprocessar. A velocidade de treinamento é idêntica ao treinamento YOLO padrão, pois as anotações são mantidas na memória. O cache é reconstruído automaticamente se o arquivo JSON for alterado.