تخطي إلى المحتوى

كيفية تدريب YOLO COCO دون الحاجة إلى تحويلها

لماذا التدريب مباشرة على COCO

التعليقات التوضيحية في COCO يمكن استخدام هذا التنسيق مباشرةً لـ Ultralytics YOLO التدريب دون التحويل إلى .txt الملفات أولاً. ويتم ذلك عن طريق إنشاء فئة فرعية YOLODataset لتحليل COCO في الوقت الفعلي ودمجها في مسار التدريب من خلال أداة تدريب مخصصة.

يحافظ هذا النهج على ملف COCO باعتباره المصدر الوحيد للمعلومات — لا convert_coco() استدعاء، دون إعادة تنظيم الدليل، ودون ملفات تسمية وسيطة. YOLO26 كما يتم دعم جميع نماذجYOLO الأخرى Ultralytics . تتطلب نماذج التجزئة وتحديد الوضع حقول تصنيف إضافية (انظر الأسئلة الشائعة).

هل تبحث عن تحويل لمرة واحدة بدلاً من ذلك؟

انظر إلى دليل YOLO COCO YOLO بالنسبة للمعيار convert_coco() سير العمل.

نظرة عامة على البنية

هناك حاجة إلى فصلين دراسيين:

  1. COCOJSONDataset — يقرأ ملفات COCO ويحولها المربعات المحيطة إلى YOLO في الذاكرة أثناء التدريب
  2. COCOJSONTrainer — التجاوزات build_dataset() لاستخدام COCOJSONDataset بدلاً من الإعداد الافتراضي YOLODataset

يتبع التنفيذ نفس نمط الوظيفة المدمجة GroundingDataset، والتي تقرأ أيضًا تعليقات JSON مباشرةً. تم تجاوز ثلاث طرق: get_img_files(), cache_labels()، و get_labels().

إنشاء فئة مجموعة بيانات COCO

في COCOJSONDataset تُورث الفئة من 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 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"]

يتم حفظ العلامات التي تمت معالجتها في ملف .cache ملف بجوار ملف JSON (على سبيل المثال instances_train.cache). وفي عمليات التدريب اللاحقة، يتم تحميل ذاكرة التخزين المؤقت مباشرةً، متخطيةً عملية تحليل JSON. وإذا تغير ملف JSON، يفشل فحص التجزئة ويتم إعادة إنشاء ذاكرة التخزين المؤقت تلقائيًّا.

ربط مجموعة البيانات بمسار التدريب

التغيير الوحيد المطلوب في المدرب هو التجاوز build_dataset(). الإعداد الافتراضي DetectionTrainer يبني YOLODataset الذي يبحث عن .txt ملفات التسمية. عن طريق استبدالها بـ COCOJSONDataset، يقرأ المدرب البيانات من ملف COCO بدلاً من ذلك.

يتم استرداد مسار ملف JSON من ملف مخصص train_json / val_json field in the data config (see Step 3). During training, 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 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,
        )

تكوين ملف dataset.yaml COCO yaml COCO

في dataset.yaml يستخدم المعيار path, train، و val حقول لتحديد مواقع مجلدات الصور. حقلان إضافيان، train_json و val_json، حدد ملفات COCO التوضيحية COCO التي COCOJSONTrainer يقول. 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=COCOJSONTrainer المعلمة، التي توجه Ultralytics أداة تحميل مجموعات البيانات المخصصة بدلاً من الأداة الافتراضية.

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOJSONTrainer)

تعمل سلسلة التدريب بالكامل كما هو متوقع، بما في ذلك التحقق من الصحة وحفظ نقاط التحقق وتسجيل المقاييس.

التنفيذ الكامل

للتسهيل، يرد أدناه التنفيذ الكامل في شكل نص برمجي واحد يمكن نسخه ولصقه. ويشمل هذا النص مجموعة البيانات المخصصة، والمدرب المخصص، ووظيفة التدريب. احفظ هذا النص بجانب ملفك 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 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)

للاطلاع على توصيات بشأن المعلمات الفائقة ، راجع دليل «نصائح تدريب النموذج ».

الأسئلة الشائعة

ما الفرق بين هذه الدالة ودالة convert_coco()؟

convert_coco() يكتب .txt نسخ ملفات التسميات إلى القرص كعملية تحويل تتم مرة واحدة. تعمل هذه الطريقة على تحليل ملف JSON في بداية كل دورة تدريب وتحويل التسميات في الذاكرة. استخدم convert_coco() عندما يُفضل استخدام التسميات الدائمة YOLO استخدم هذه الطريقة للحفاظ على ملف COCO باعتباره المصدر الوحيد الموثوق دون إنشاء ملفات إضافية.

هل يمكن لـ YOLO على بيانات COCO دون الحاجة إلى كتابة كود مخصص؟

ليس مع Ultralytics الحالي Ultralytics ، الذي يتوقع استخدام YOLO .txt العلامات بشكل افتراضي. يقدم هذا الدليل الحد الأدنى من التعليمات البرمجية المخصصة اللازمة — فئة واحدة لمجموعة البيانات وفئة واحدة للمدرب. وبمجرد تعريفها، لا يتطلب التدريب سوى model.train() اتصل.

هل يدعم هذا التطبيق تقسيم الشاشة وتقدير وضع الجسم؟

يغطي هذا الدليل اكتشاف الكائنات. لإضافة تجزئة المثيل الدعم، بما في ذلك segmentation بيانات المضلعات المستمدة من COCO في segments حقل كل قاموس تسميات. بالنسبة لـ تقدير الوضعية، تشمل keypoints. إن GroundingDataset كود المصدر يوفر نموذجًا مرجعيًا لتنفيذ معالجة المقاطع.

هل تعمل عمليات التوسيع مع مجموعة البيانات المخصصة هذه؟

نعم. COCOJSONDataset يمتد YOLODataset، لذا فإن جميع الميزات المدمجة عمليات توسيع البياناتفسيفساء, mixup, نسخ-لصقوغيرها — تعمل دون تعديل.

كيف يتم ربط معرّفات الفئات بفهارس الفئات؟

يتم فرز الفئات حسب id ويتم تعيينها إلى مؤشرات متسلسلة تبدأ من 0. وهذا يعالج المعرفات التي تبدأ من 1 (معيار COCO)، والمعرفات التي تبدأ من 0، والمعرفات غير المتجاورة. names قاموس في dataset.yaml يجب أن يتبع نفس الترتيب الذي يتبعه COCO categories مصفوفة.

هل هناك انخفاض في الأداء مقارنةً بالملصقات التي تم تحويلها مسبقًا؟

يتم تحليل ملف COCO مرة واحدة خلال الجولة الأولى من التدريب. ويتم حفظ التسميات التي تم تحليلها في ملف .cache ملف، وبالتالي يتم تحميل العمليات اللاحقة على الفور دون الحاجة إلى إعادة التحليل. وتكون سرعة التدريب مماثلة YOLO القياسية YOLO نظرًا لأن التعليقات التوضيحية يتم الاحتفاظ بها في الذاكرة. ويتم إعادة إنشاء ذاكرة التخزين المؤقت تلقائيًا في حالة تغيير ملف JSON.



📅 تم الإنشاء قبل 0 أيام ✏️ تم التحديث قبل 0 أيام
glenn-jocherraimbekovm

تعليقات