Автоматическая разметка датасетов на CPU: пайплайн для YOLO и сегментации | AiManual
AiManual Logo Ai / Manual.
31 Дек 2025 Гайд

Автоматизация разметки датасетов: от недель работы к нескольким минутам без GPU

Практический гайд по созданию пайплайна автоматической разметки датасетов на CPU. Экономим недели ручной работы без использования GPU.

Проблема: ручная разметка — бутылочное горлышко ML-проектов

Каждый ML-инженер сталкивался с этой проблемой: вы находите интересную задачу для компьютерного зрения, собираете изображения, но на их разметку уходят недели, а то и месяцы ручной работы. Особенно остро это чувствуется в робототехнике, где нужны специфические объекты в нестандартных условиях.

Традиционный подход с платформами типа LabelImg или CVAT требует:

  • Ручного обведения каждого объекта
  • Назначения классов
  • Проверки качества разметки
  • Часов монотонной работы

Среднее время разметки одного изображения с 5-10 объектами — 2-3 минуты. Для датасета в 10 000 изображений это 300-500 часов чистой работы!

Решение: умная автоматизация на базе существующих моделей

Вместо того чтобы размечать всё с нуля, мы можем использовать предобученные модели для автоматической предварительной разметки. Ключевая идея: не нужно идеального результата с первого раза. Достаточно получить 70-80% точности, а затем быстро проверить и исправить ошибки.

💡
Этот подход особенно эффективен, когда у вас уже есть небольшой размеченный датасет для дообучения или когда объекты похожи на те, что есть в публичных датасетах.

1Архитектура пайплайна

Наш пайплайн состоит из трёх основных этапов:

  1. Предварительная обработка: подготовка изображений, фильтрация по качеству
  2. Автоматическая разметка: использование легковесных моделей на CPU
  3. Постобработка и проверка: фильтрация результатов, визуализация для ручной проверки

Для работы на CPU нам нужны модели, которые достаточно легковесны, но при этом дают приемлемое качество. Отличным выбором станут:

  • YOLOv8n (нано-версия) для детекции
  • MobileSAM для сегментации
  • EfficientNet для классификации

Пошаговая реализация пайплайна

2Шаг 1: Установка зависимостей

Создаём виртуальное окружение и устанавливаем необходимые библиотеки:

python -m venv label_env
source label_env/bin/activate  # для Linux/Mac
# или label_env\Scripts\activate для Windows

pip install ultralytics
pip install opencv-python
pip install pillow
pip install numpy
pip install pandas
pip install tqdm

Ultralytics (YOLOv8) отлично работает на CPU благодаря оптимизированным операциям. На одном ядре он обрабатывает 10-15 FPS даже без GPU.

3Шаг 2: Базовый скрипт автоматической разметки

Создаём основной пайплайн для детекции объектов:

import os
from pathlib import Path
from ultralytics import YOLO
import cv2
import json
from tqdm import tqdm

class AutoLabeler:
    def __init__(self, model_path="yolov8n.pt", conf_threshold=0.25):
        """Инициализация модели YOLO"""
        self.model = YOLO(model_path)
        self.conf_threshold = conf_threshold
        
    def process_folder(self, input_folder, output_folder):
        """Обработка всех изображений в папке"""
        input_path = Path(input_folder)
        output_path = Path(output_folder)
        output_path.mkdir(parents=True, exist_ok=True)
        
        # Поддерживаемые форматы изображений
        image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']
        image_files = []
        for ext in image_extensions:
            image_files.extend(input_path.glob(f"*{ext}"))
            image_files.extend(input_path.glob(f"*{ext.upper()}"))
        
        print(f"Найдено {len(image_files)} изображений")
        
        results = []
        for img_path in tqdm(image_files, desc="Обработка изображений"):
            result = self.process_image(img_path, output_path)
            if result:
                results.append(result)
                
        # Сохраняем метаданные
        self.save_metadata(results, output_path / "metadata.json")
        return results
    
    def process_image(self, img_path, output_path):
        """Обработка одного изображения"""
        try:
            # Запуск предсказания на CPU
            results = self.model(
                str(img_path), 
                device="cpu",  # Явно указываем CPU
                conf=self.conf_threshold,
                verbose=False
            )
            
            # Извлекаем bounding boxes
            boxes = []
            for result in results:
                for box in result.boxes:
                    x1, y1, x2, y2 = box.xyxy[0].tolist()
                    conf = box.conf[0].item()
                    cls = int(box.cls[0].item())
                    cls_name = result.names[cls]
                    
                    boxes.append({
                        'class': cls_name,
                        'class_id': cls,
                        'bbox': [x1, y1, x2, y2],
                        'confidence': conf
                    })
            
            # Сохраняем разметку в формате YOLO
            self.save_yolo_format(img_path, output_path, boxes)
            
            return {
                'image': img_path.name,
                'boxes': boxes,
                'count': len(boxes)
            }
            
        except Exception as e:
            print(f"Ошибка при обработке {img_path}: {e}")
            return None
    
    def save_yolo_format(self, img_path, output_path, boxes):
        """Сохранение в формате YOLO (txt файл)"""
        txt_path = output_path / f"{img_path.stem}.txt"
        
        # Получаем размеры изображения
        img = cv2.imread(str(img_path))
        if img is None:
            return
            
        h, w = img.shape[:2]
        
        with open(txt_path, 'w') as f:
            for box in boxes:
                x1, y1, x2, y2 = box['bbox']
                
                # Конвертируем в YOLO формат (нормализованные координаты центра)
                x_center = ((x1 + x2) / 2) / w
                y_center = ((y1 + y2) / 2) / h
                width = (x2 - x1) / w
                height = (y2 - y1) / h
                
                f.write(f"{box['class_id']} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")

# Использование
if __name__ == "__main__":
    labeler = AutoLabeler()
    results = labeler.process_folder(
        input_folder="./raw_images",
        output_folder="./labels"
    )
    print(f"Обработано {len(results)} изображений")

4Шаг 3: Оптимизация для CPU

Для максимальной производительности на CPU добавляем оптимизации:

import torch
import torchvision

class OptimizedLabeler(AutoLabeler):
    def __init__(self, model_path="yolov8n.pt", conf_threshold=0.25):
        super().__init__(model_path, conf_threshold)
        
        # Оптимизации для CPU
        torch.set_num_threads(4)  # Ограничиваем количество потоков
        
        # Используем более легковесную модель для CPU
        if "nano" not in model_path.lower():
            print("Рекомендуется использовать YOLOv8n для CPU")
    
    def batch_process(self, image_paths, batch_size=4):
        """Пакетная обработка для увеличения производительности"""
        batches = [image_paths[i:i+batch_size] 
                  for i in range(0, len(image_paths), batch_size)]
        
        all_results = []
        for batch in tqdm(batches, desc="Пакетная обработка"):
            # Загружаем все изображения батча
            images = []
            valid_paths = []
            for img_path in batch:
                img = cv2.imread(str(img_path))
                if img is not None:
                    images.append(img)
                    valid_paths.append(img_path)
            
            if not images:
                continue
                
            # Обрабатываем батч
            batch_results = self.model(
                images,
                device="cpu",
                conf=self.conf_threshold,
                verbose=False
            )
            
            for img_path, result in zip(valid_paths, batch_results):
                # Обработка результатов...
                pass

Продвинутые техники автоматической разметки

Активное обучение с человеческой проверкой

Вместо того чтобы проверять все изображения, мы можем использовать стратегию активного обучения:

def select_images_for_review(results, n=50):
    """Выбор изображений для ручной проверки"""
    # Сортируем по неопределённости (низкая уверенность модели)
    uncertain_images = []
    for result in results:
        if result['boxes']:
            # Средняя уверенность по всем боксам
            avg_conf = sum(b['confidence'] for b in result['boxes']) / len(result['boxes'])
            uncertain_images.append({
                'image': result['image'],
                'avg_confidence': avg_conf,
                'boxes': result['boxes']
            })
    
    # Сортируем по возрастанию уверенности
    uncertain_images.sort(key=lambda x: x['avg_confidence'])
    
    # Берём N самых неопределённых
    return uncertain_images[:n]

def generate_review_interface(images_to_review, output_html="review.html"):
    """Генерация HTML интерфейса для проверки"""
    html_content = """
    
    
    
        
    
    
    

Проверка автоматической разметки

""" for img_data in images_to_review: html_content += f"""

{img_data['image']}

""" for box in img_data['boxes']: x1, y1, x2, y2 = box['bbox'] html_content += f"""
""" html_content += """
""" html_content += """ """ with open(output_html, 'w', encoding='utf-8') as f: f.write(html_content)

Инкрементальное обучение

После проверки части данных можно дообучить модель на исправленных примерах:

def fine_tune_on_corrections(corrected_data, base_model="yolov8n.pt"):
    """Дообучение модели на исправленных данных"""
    # Подготовка датасета
    dataset_yaml = """
    path: ./corrected_dataset
    train: images/train
    val: images/val
    
    names:
      0: person
      1: car
      2: traffic_light
    """
    
    with open("dataset.yaml", "w") as f:
        f.write(dataset_yaml)
    
    # Дообучение
    model = YOLO(base_model)
    
    results = model.train(
        data="dataset.yaml",
        epochs=50,
        imgsz=640,
        device="cpu",  # Можно использовать CPU для обучения
        workers=4,     # Количество потоков для загрузки данных
        batch=16,      # Размер батча для CPU
        patience=10,   # Ранняя остановка
        save=True,
        verbose=True
    )
    
    return results

Практические примеры применения

Кейс 1: Разметка датасета для робототехники

В робототехнике часто нужны специфические объекты: инструменты, детали, компоненты. Вместо ручной разметки 5000 изображений с инструментами:

  1. Используем предобученную модель на COCO (уже знает "ножницы", "чашка", "книга")
  2. Дообучаем на 100 размеченных вручную изображениях с нашими инструментами
  3. Запускаем автоматическую разметку на остальных 4900 изображениях
  4. Проверяем только 10% с наименьшей уверенностью модели

Экономия времени: с 200 часов до 20 часов работы. Точность после проверки: 95%+.

Кейс 2: Сегментация медицинских изображений

Для задач сегментации можно использовать MobileSAM — оптимизированную версию Segment Anything от Meta:

# Упрощённый пример использования MobileSAM
from mobile_sam import SamPredictor, sam_model_registry
import numpy as np

class SAMLabeler:
    def __init__(self):
        model_type = "vit_t"  # Самая лёгкая версия
        sam_checkpoint = "./mobile_sam.pt"
        
        sam = sam_model_registry[model_type](checkpoint=sam_checkpoint)
        self.predictor = SamPredictor(sam)
        
    def segment_objects(self, image_path, points):
        """Сегментация по точкам"""
        image = cv2.imread(image_path)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        self.predictor.set_image(image)
        
        # points: [[x, y]] координаты объектов
        # labels: [1] для объектов, [0] для фона
        input_point = np.array(points)
        input_label = np.array([1] * len(points))
        
        masks, scores, logits = self.predictor.predict(
            point_coords=input_point,
            point_labels=input_label,
            multimask_output=True,
        )
        
        return masks[0]  # Берём лучшую маску

Оптимизация производительности на CPU

ОптимизацияЭффектРеализация
Кэширование модели+40% скоростиЗагрузить модель один раз
Пакетная обработка+60% скоростиbatch_size=4-8
Оптимизация памяти-50% памятиdel промежуточных данных
Многопоточность+200% на 4 ядрахtorch.set_num_threads(4)

Распространённые ошибки и как их избежать

Ошибка 1: Слишком низкий порог уверенности → много ложных срабатываний.
Решение: Начинать с conf=0.5, постепенно снижая до 0.25.

Ошибка 2: Попытка использовать большие модели на CPU.
Решение: Всегда начинать с nano или small версий моделей.

Ошибка 3: Отсутствие проверки качества.
Решение: Обязательно проверять 5-10% случайных изображений + 5% с низкой уверенностью.

Интеграция с существующими ML-пайплайнами

Автоматическую разметку можно интегрировать в более крупные системы. Например, в ETL-конвейеры на основе ИИ-агентов или в системы активного обучения.

Для проектов, где нужно масштабировать обучение на несколько GPU, можно использовать подходы из статьи про стратегии масштабирования локальных LLM, адаптировав их для задач компьютерного зрения.

Заключение

Автоматическая разметка датасетов на CPU — это не фантастика, а практичный инструмент, который экономит сотни часов работы. Ключевые преимущества:

  • Скорость: 1000 изображений за 30-60 минут вместо недели
  • Доступность: Не требует дорогого GPU
  • Гибкость: Легко адаптируется под конкретные задачи
  • Качество: 80-90% точности с первого прохода

Начните с простого пайплайна на YOLOv8n, добавьте активное обучение для проверки, и вы превратите самую рутинную часть ML-проекта в автоматизированный процесс. Как показывает практика из статьи про мультиагентные системы, иногда простые решения работают лучше сложных.

Помните: идеальная автоматическая разметка — это не 100% точность с первого раза, а 80% точность + эффективный процесс проверки и исправления.