Meet YOLO26: next-gen vision AI.

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 a necessidade de 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 por meio de um treinador personalizado.

Esta abordagem mantém o COCO JSON como a única fonte de verdade — sem chamadas convert_coco(), sem reorganização de diretórios e sem arquivos de rótulos intermediários. YOLO26 e todos os outros modelos de detecção do Ultralytics YOLO são suportados. Modelos de segmentação e pose exigem campos de rótulos adicionais (veja o FAQ).

Procurando uma conversão única em vez disso?

Consulte 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:

  1. COCODataset — lê COCO JSON e converte caixas delimitadoras para o formato YOLO na memória durante o treinamento
  2. COCOTrainer — substitui build_dataset() para usar COCODataset em vez do YOLODataset padrão

A implementação segue o mesmo padrão da GroundingDataset integrada, que também lê anotações JSON diretamente. Três métodos são substituídos: get_img_files(), cache_labels() e get_labels().

Link to this sectionCriando a classe de conjunto de dados COCO JSON#

A classe COCODataset herda de YOLODataset e substitui 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, percorre as anotações agrupadas por imagem e converte cada caixa delimitadora do formato de pixel COCO [x_min, y_min, width, height] para o formato de centro 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 de base 1 (COCO padrão) 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 for alterado, a verificação de hash falhará e o cache será reconstruído automaticamente.

Link to this sectionConectando o conjunto de dados ao pipeline de treinamento#

A única alteração necessária no treinador é substituir build_dataset(). O DetectionTrainer padrão cria um YOLODataset que procura arquivos de rótulo .txt. Ao substituí-lo por COCODataset, o treinador passa a ler do COCO JSON.

O caminho do arquivo JSON é obtido de um campo personalizado train_json / val_json na configuração de dados (veja o Passo 3). Durante o treinamento, mode="train" resolve para train_json; durante a validação, mode="val" resolve para val_json. Se val_json não estiver definido, ele recorre ao 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 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 names

Estrutura de diretório esperada:

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

Link to this sectionExecutando o treinamento no COCO JSON#

Com a classe de conjunto de dados, a classe de treinador e a configuração YAML definidas, 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 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 de 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, consulte o guia 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ótulo .txt no disco como uma conversão única. Essa 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 preferir rótulos permanentes no formato YOLO; use esta abordagem para manter o COCO JSON como a única fonte de 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 .txt do YOLO 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 model.train() padrão.

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 — mosaic, mixup, copy-paste 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 de base 1 (COCO padrão), IDs de base 0 e IDs não contíguos. O dicionário names em dataset.yaml deve seguir a mesma ordem classificada do array 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, para que execuções subsequentes carreguem instantaneamente sem reprocessar. A velocidade de treinamento é idêntica ao treinamento padrão do YOLO, pois as anotações são mantidas na memória. O cache é reconstruído automaticamente se o arquivo JSON for alterado.

Comentários