كيفية تدريب YOLO COCO دون الحاجة إلى تحويلها
لماذا التدريب مباشرة على COCO
التعليقات التوضيحية في COCO JSON يمكن استخدام هذا التنسيق مباشرةً لـ Ultralytics YOLO التدريب دون التحويل إلى .txt الملفات أولاً. ويتم ذلك عن طريق إنشاء فئة فرعية YOLODataset لتحليل COCO في الوقت الفعلي ودمجها في مسار التدريب من خلال أداة تدريب مخصصة.
يحافظ هذا النهج على ملف COCO باعتباره المصدر الوحيد للمعلومات — لا convert_coco() استدعاء، لا إعادة تنظيم للدليل، لا ملفات تسمية وسيطة. YOLO26 وجميع نماذج الكشف الأخرى من Ultralytics YOLO مدعومة. تتطلب نماذج التجزئة والوضع حقول تسمية إضافية (انظر الأسئلة الشائعة).
هل تبحث عن تحويل لمرة واحدة بدلاً من ذلك؟
انظر إلى دليل تحويل COCO إلى YOLO بالنسبة للمعيار convert_coco() سير العمل.
نظرة عامة على البنية
هناك حاجة إلى فصلين دراسيين:
COCODataset— يقرأ ملفات COCO ويحولها المربعات المحيطة إلى YOLO في الذاكرة أثناء التدريبCOCOTrainer— التجاوزاتbuild_dataset()لاستخدامCOCODatasetبدلاً من الإعداد الافتراضيYOLODataset
يتبع التنفيذ نفس نمط الوظيفة المدمجة GroundingDataset، والتي تقرأ أيضًا تعليقات JSON التوضيحية مباشرةً. يتم تجاوز ثلاث طرق: get_img_files(), cache_labels()، و get_labels().
بناء فئة مجموعة بيانات COCO JSON
في COCODataset الفئة ترث من YOLODataset ويتجاوز منطق تحميل التسميات. فبدلاً من قراءة .txt الملفات من دليل "labels"، فتفتح ملف COCO وتقوم بتكرار معالجة التعليقات التوضيحية المجمعة حسب الصورة، وتحوّل كل مربع تحديد من تنسيق COCO [x_min, y_min, width, height] إلى تنسيق المركز YOLO [x_center, y_center, width, height]. تعليقات الحشود التوضيحية (iscrowd: 1) ويتم تخطي المربعات ذات المساحة الصفرية تلقائيًا.
في get_img_files() تُرجع هذه الطريقة قائمة فارغة لأن مسارات الصور يتم تحديدها من ملف JSON file_name المساحة الداخلية cache_labels(). يتم فرز معرفات الفئات وإعادة تعيينها إلى فهارس الفئات ذات الفهرسة الصفرية، بحيث تعمل كل من مخططات المعرفات المستندة إلى 1 (COCO القياسي) وغير المتجاورة بشكل صحيح.
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"]
يتم حفظ العلامات التي تمت معالجتها في ملف .cache ملف بجوار ملف JSON (على سبيل المثال instances_train.cache). في عمليات التدريب اللاحقة، يتم تحميل ذاكرة التخزين المؤقت مباشرةً، متجاوزًا تحليل JSON. إذا تغير ملف JSON، يفشل فحص التجزئة ويتم إعادة بناء ذاكرة التخزين المؤقت تلقائيًا.
ربط مجموعة البيانات بمسار التدريب
التغيير الوحيد المطلوب في المدرب هو التجاوز build_dataset()الافتراضي. DetectionTrainer يبني YOLODataset الذي يبحث عن .txt ملفات التسمية. عن طريق استبدالها بـ COCODataset، يقرأ المدرب من ملف COCO JSON بدلاً من ذلك.
يتم استرداد مسار ملف JSON من ملف مخصص train_json / val_json الحقل الموجود في إعدادات البيانات (انظر الخطوة 3). أثناء التدريب، mode="train" يقرر train_json؛ أثناء التحقق من الصحة، mode="val" يقرر val_jsonإذا. val_json إذا لم يتم تعيينه، يتم الرجوع إلى 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,
)
تكوين ملف dataset.yaml لـ COCO JSON
في dataset.yaml يستخدم المعيار path, train، و val حقول لتحديد مواقع مجلدات الصور. حقلان إضافيان، train_json و val_json، حدد ملفات تعليقات COCO التوضيحية التي COCOTrainer يقول. nc و names تحدد الحقول عدد الفئات وأسمائها، بما يتوافق مع الترتيب المصنف لـ categories في ملف 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
هيكل المجلدات المتوقع:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yaml
تدريب على تشغيل COCO
بعد تجهيز فئة مجموعة البيانات وفئة المدرب وملف التكوين YAML، يتم التدريب وفقًا للمعيار القياسي model.train() استدعاء. الفرق الوحيد عن تشغيل تدريب عادي هو trainer=COCOTrainer الوسيط، الذي يوجه Ultralytics لاستخدام مُحمّل مجموعة البيانات المخصص بدلاً من الافتراضي.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)
تعمل سلسلة التدريب بالكامل كما هو متوقع، بما في ذلك التحقق من الصحة وحفظ نقاط التحقق وتسجيل المقاييس.
التنفيذ الكامل
للتسهيل، يرد أدناه التنفيذ الكامل في شكل نص برمجي واحد يمكن نسخه ولصقه. ويشمل هذا النص مجموعة البيانات المخصصة، والمدرب المخصص، ووظيفة التدريب. احفظ هذا النص بجانب ملفك dataset.yaml وتشغيلها مباشرة.
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)
للاطلاع على توصيات بشأن المعلمات الفائقة ، راجع دليل «نصائح تدريب النموذج ».
الأسئلة الشائعة
ما الفرق بين هذه الدالة ودالة convert_coco()؟
convert_coco() يكتب .txt نسخ ملفات التسميات إلى القرص كعملية تحويل تتم مرة واحدة. تعمل هذه الطريقة على تحليل ملف JSON في بداية كل دورة تدريب وتحويل التسميات في الذاكرة. استخدم convert_coco() عندما يُفضل استخدام التسميات الدائمة YOLO استخدم هذه الطريقة للحفاظ على ملف COCO باعتباره المصدر الوحيد الموثوق دون إنشاء ملفات إضافية.
هل يمكن لـ YOLO التدريب على COCO JSON بدون تعليمات برمجية مخصصة؟
ليس مع Ultralytics الحالي Ultralytics ، الذي يتوقع استخدام YOLO .txt العلامات بشكل افتراضي. يقدم هذا الدليل الحد الأدنى من التعليمات البرمجية المخصصة اللازمة — فئة واحدة لمجموعة البيانات وفئة واحدة للمدرب. وبمجرد تعريفها، لا يتطلب التدريب سوى model.train() استدعاء.
هل يدعم هذا التطبيق تقسيم الشاشة وتقدير وضع الجسم؟
يغطي هذا الدليل اكتشاف الكائناتلإضافة. تجزئة المثيل الدعم، بما في ذلك segmentation بيانات المضلعات المستمدة من COCO في segments حقل كل قاموس تسميات. بالنسبة لـ تقدير الوضعية، تتضمن keypoints. إن GroundingDataset كود المصدر يوفر نموذجًا مرجعيًا لتنفيذ معالجة المقاطع.
هل تعمل عمليات التوسيع مع مجموعة البيانات المخصصة هذه؟
نعم. COCODataset يمتد YOLODataset، لذا جميع المدمجة زيادات البيانات — فسيفساء, mixup, نسخ-لصق، وغيرها — تعمل دون تعديل.
كيف يتم ربط معرّفات الفئات بفهارس الفئات؟
يتم فرز الفئات حسب id ويتم تعيينها إلى فهارس متسلسلة تبدأ من 0. يتعامل هذا مع المعرفات القائمة على 1 (COCO القياسي)، والمعرفات القائمة على 0، والمعرفات غير المتجاورة. الـ names قاموس في dataset.yaml يجب أن يتبع نفس الترتيب الذي يتبعه COCO categories مصفوفة.
هل هناك انخفاض في الأداء مقارنةً بالملصقات التي تم تحويلها مسبقًا؟
يتم تحليل ملف COCO مرة واحدة خلال الجولة الأولى من التدريب. ويتم حفظ التسميات التي تم تحليلها في ملف .cache ملف، وبالتالي يتم تحميل العمليات اللاحقة على الفور دون الحاجة إلى إعادة التحليل. وتكون سرعة التدريب مماثلة YOLO القياسية YOLO نظرًا لأن التعليقات التوضيحية يتم الاحتفاظ بها في الذاكرة. ويتم إعادة إنشاء ذاكرة التخزين المؤقت تلقائيًا في حالة تغيير ملف JSON.