Cómo entrenar YOLO COCO sin necesidad de convertirlos
¿Por qué entrenar directamente con COCO ?
Anotaciones en COCO el formato se puede utilizar directamente para Ultralytics YOLO entrenar sin pasar a .txt los archivos primero. Para ello, se crea una subclase de YOLODataset para analizar COCO sobre la marcha e integrarlo en el proceso de entrenamiento mediante un entrenador personalizado.
Este enfoque mantiene el COCO como la única fuente de información fiable — no convert_coco() llamada, sin reorganización del directorio, sin archivos de etiquetas intermedios. YOLO26 y son compatibles todos los demás modelosYOLO Ultralytics . Los modelos de segmentación y de pose requieren campos de etiquetado adicionales (véase Preguntas frecuentes).
¿Prefieres una conversión única?
Consulte la Guía YOLO COCO YOLO para el estándar convert_coco() flujo de trabajo.
Descripción General de la Arquitectura
Se necesitan dos clases:
COCOJSONDataset— lee COCO y lo convierte cajas delimitadoras YOLO en memoria durante el entrenamientoCOCOJSONTrainer— anulabuild_dataset()para usarCOCOJSONDataseten lugar del valor predeterminadoYOLODataset
La implementación sigue el mismo patrón que la función integrada GroundingDataset, que también lee directamente las anotaciones JSON. Se sobrescriben tres métodos: get_img_files(), cache_labels(), y get_labels().
Creación de la clase del conjunto de datos COCO
El COCOJSONDataset la clase hereda de YOLODataset y anula la lógica de carga de etiquetas. En lugar de leer .txt archivos del directorio «labels», abre el archivo COCO , recorre las anotaciones agrupadas por imagen y convierte cada cuadro delimitador del formato COCO [x_min, y_min, width, height] al formato de centro YOLO [x_center, y_center, width, height]. Anotaciones colaborativas (iscrowd: 1) y las casillas de área cero se omiten automáticamente.
El get_img_files() El método devuelve una lista vacía porque las rutas de las imágenes se resuelven a partir del JSON file_name campo interior cache_labels(). Los identificadores de categoría se ordenan y se reasignan a índices de clase con índice cero, por lo que tanto los esquemas de identificadores basados en 1 ( COCO estándar) como los no contiguos funcionan correctamente.
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 COCOJSONDataset(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"]
Las etiquetas analizadas se guardan en un .cache archivo junto al JSON (por ejemplo, instances_train.cache). En las ejecuciones de entrenamiento posteriores, la caché se carga directamente, sin necesidad de analizar el JSON. Si el archivo JSON cambia, la comprobación del hash falla y la caché se reconstruye automáticamente.
Conectar el conjunto de datos al proceso de entrenamiento
El único cambio que hay que hacer en el entrenador es anular build_dataset(). El valor predeterminado DetectionTrainer crea un YOLODataset que busca .txt archivos de etiquetas. Al sustituirlo por COCOJSONDataset, el entrenador lee el archivo COCO de COCO .
La ruta del archivo JSON se obtiene de un archivo personalizado train_json / val_json field in the data config (see Step 3). During training, mode="train" decide train_json; durante la validación, mode="val" decide val_json. Si val_json si no está configurado, se recurre a train_json.
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import colorstr
class COCOJSONTrainer(DetectionTrainer):
"""Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
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,
)
Configuración del archivo dataset.yaml COCO
El dataset.yaml utiliza el estándar path, train, y val campos para localizar los directorios de imágenes. Dos campos adicionales, train_json y val_json, especifique los archivos COCO que COCOJSONTrainer dice. El nc y names Los campos definen el número de clases y sus nombres, siguiendo el orden en que aparecen en categories en el 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
Estructura de directorios prevista:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yaml
Entrenamiento de carrera en COCO
Una vez que se dispone del conjunto de datos, la clase de entrenamiento y la configuración YAML, el entrenamiento se lleva a cabo siguiendo el procedimiento estándar model.train() llamada. La única diferencia con respecto a un entrenamiento normal es la trainer=COCOJSONTrainer argumento, que indica Ultralytics utilice el cargador de conjuntos de datos personalizado en lugar del predeterminado.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOJSONTrainer)
El proceso completo de entrenamiento se ejecuta según lo previsto, incluyendo la validación, el guardado de puntos de control y el registro de métricas.
Aplicación íntegra
Para mayor comodidad, a continuación se incluye la implementación completa en forma de un único script que se puede copiar y pegar. Incluye el conjunto de datos personalizado, el entrenador personalizado y la llamada de entrenamiento. Guárdalo junto con tu dataset.yaml y ejecútalo directamente.
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 COCOJSONDataset(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 COCOJSONTrainer(DetectionTrainer):
"""Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
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=COCOJSONTrainer)
Para obtener recomendaciones sobre hiperparámetros, consulta la guía «Consejos para el entrenamiento de modelos ».
Preguntas frecuentes
¿Cuál es la diferencia entre esto y convert_coco()?
convert_coco() escribe .txt Guardar los archivos de etiquetas en el disco como una conversión única. Este método analiza el JSON al inicio de cada sesión de entrenamiento y convierte las anotaciones en memoria. Utiliza convert_coco() cuando se prefieran etiquetas permanentes YOLO; utiliza este método para mantener el COCO como única fuente de información fiable sin generar archivos adicionales.
¿Se puede YOLO con los datos COCO sin necesidad de código personalizado?
No con el Ultralytics actual Ultralytics , que prevé YOLO .txt etiquetas de forma predeterminada. Esta guía proporciona el código personalizado mínimo necesario: una clase de conjunto de datos y una clase de entrenador. Una vez definidas, el entrenamiento solo requiere un model.train() llamada.
¿Admite la segmentación y la estimación de poses?
Esta guía trata sobre detección de objetos. Para añadir segmentación de instancias apoyo, incluye el segmentation datos poligonales procedentes de COCO en el segments campo de cada diccionario de etiquetas. Para estimación de pose, entre otros keypoints. El GroundingDataset código fuente ofrece una implementación de referencia para el manejo de segmentos.
¿Funcionan las ampliaciones con este conjunto de datos personalizado?
Sí. COCOJSONDataset se extiende YOLODataset, por lo que todas las funciones integradas métodos de aumento de datos — mosaico, mixup, copiar-pegar, y otros, funcionan sin necesidad de modificaciones.
¿Cómo se asignan los identificadores de categoría a los índices de clase?
Las categorías están ordenadas por id y se asignan a índices secuenciales que comienzan en 0. Esto permite gestionar identificadores que comienzan en 1 (estándar COCO), identificadores que comienzan en 0 e identificadores no contiguos. El names diccionario en dataset.yaml debería seguir el mismo orden de clasificación que el COCO categories matriz.
¿Supone esto una merma en el rendimiento en comparación con las etiquetas preconvertidas?
El archivo COCO se analiza una vez durante la primera ronda de entrenamiento. Las etiquetas analizadas se guardan en un .cache archivo, por lo que las ejecuciones posteriores se cargan al instante sin necesidad de volver a analizarlo. La velocidad de entrenamiento es idéntica a YOLO estándar YOLO , ya que las anotaciones se mantienen en memoria. La caché se reconstruye automáticamente si el archivo JSON cambia.