Автоматизация подготовки датасета для сегментации на CPU за дни | AiManual
AiManual Logo Ai / Manual.
01 Янв 2026 Гайд

Собираем датасет для сегментации на CPU за выходные: пайплайн, который не требует GPU и тысяч долларов

Пошаговая инструкция по созданию пайплайна для автоматической разметки данных для сегментации. Работает на обычном процессоре, не требует дорогого железа и меся

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

Вспомни последний проект по компьютерному зрению. Собрал тысячу изображений? Отлично. Теперь их надо разметить. Ты открываешь CVAT или Label Studio, берешь мышку и начинаешь обводить. Первые десять картинок - нормально. Сотня - уже скучно. Тысяча - хочется выбросить монитор в окно.

Особенно в робототехнике, где нужны специфичные объекты: детали на конвейере, инструменты в цеху, упаковки на складе. Готовых датасетов нет. Собирать руками - ад. Нанимать аутсорс - дорого и долго. А если проект экспериментальный, и нужно быстро проверить гипотезу?

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

В моей предыдущей статье "Автоматизация разметки датасетов: от недель работы к нескольким минутам без GPU" я рассказывал про базовые подходы. Сегодня - конкретный пайплайн для сегментации. Работает на обычном CPU, не требует дорогой видеокарты.

Секрет в трех слоях автоматизации

Весь фокус в том, чтобы не размечать с нуля, а использовать то, что уже умеют современные модели. Даже маленькие. Даже медленные. Главное - правильно выстроить процесс.

Наш пайплайн состоит из трех ключевых этапов:

  1. Сбор и предварительная фильтрация сырых данных
  2. Автоматическая предварительная разметка lightweight-моделями
  3. Постобработка и валидация разметки

Звучит просто? Так и есть. Сложность в деталях, которые определяют, будет ли твой датасет мусором или золотом.

1 Где брать данные, если их нет

Первая ошибка - пытаться собрать "идеальный" датасет с первого раза. Не надо. Собери всё, что можешь:

  • Скриншоты с камер видеонаблюдения (если есть доступ)
  • Фотографии со смартфона в разных условиях освещения
  • Публичные датасеты со схожими объектами
  • Сгенерированные изображения (Blender, Unity)

Важное правило: собирай в 3-5 раз больше, чем нужно. Потому что 80% отсеется на следующих этапах.

# Структура папок для сбора
raw_data/
├── source_1/          # Данные с первой камеры
│   ├── images/       # Исходные изображения
│   └── metadata.json # Инфо о времени, условиях
├── source_2/         # Данные со смартфона
├── synthetic/        # Сгенерированные данные
└── public/          # Скачанные публичные датасеты
💡
Не пытайся сразу чистить данные. Собери всё в кучу. Автоматическая фильтрация на следующем этапе отсеет мусор. Если проект похож на кейс с мансийским языком, где данных изначально мало, этот этап критически важен.

2 Автоматический отсев мусора

Ты собрал 20 000 изображений. Половина - брак: слишком темные, размытые, без целевых объектов. Ручная проверка займет вечность.

Вот что делаем автоматически:

Фильтр Что делает Порог отсева
Контрастность Отбрасывает слишком темные/светлые Нижние 10%
Размытие Детектирует смазанные кадры Выше 0.3 (Laplacian variance)
Дубликаты Находит почти идентичные Хэш-расстояние < 5
Размер объекта Проверяет, что объект достаточно крупный > 5% площади изображения
import cv2
import numpy as np
from PIL import Image
import imagehash

def filter_blurry(image_path, threshold=0.3):
    """Отбрасываем размытые изображения"""
    image = cv2.imread(image_path)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    fm = cv2.Laplacian(gray, cv2.CV_64F).var()
    return fm > threshold  # True если не размыто

def filter_duplicate(image_path, seen_hashes, threshold=5):
    """Находим дубликаты через perceptual hash"""
    img = Image.open(image_path)
    img_hash = imagehash.phash(img)
    
    for seen_hash in seen_hashes:
        if img_hash - seen_hash < threshold:
            return False  # Дубликат
    
    seen_hashes.append(img_hash)
    return True  # Уникальное

После этой фильтрации из 20 000 останется 8-10 тысяч качественных изображений. Это нормально. Лучше меньше, но лучше.

Магия автоматической разметки на CPU

Вот где начинается самое интересное. Ты мог слышать, что для разметки нужны мощные GPU и огромные модели типа Segment Anything. Неправда.

YOLO-NAS nano весит 4 МБ. Запускается на CPU за 200-300 мс на изображение. Да, она менее точная, чем большие модели. Но нам не нужна идеальная разметка - нужна хорошая основа для ручной доработки.

3 Настраиваем YOLO-NAS для предразметки

Первое: не используй предобученные веса из коробки. Они обучены на COCO - там другие объекты. Нужен transfer learning, даже минимальный.

from super_gradients.training import models
from super_gradients.training import training_hyperparams

# Загружаем tiny модель (самую легкую)
model = models.get('yolo_nas_s', 
                   num_classes=1,  # Только наш класс
                   pretrained_weights='coco')

# Мини-дообучение на 100 ручных примерах
# Это займет 30 минут на CPU
hyperparams = training_hyperparams.get('training_hyperparams')
hyperparams['max_epochs'] = 10
hyperparams['batch_size'] = 8
hyperparams['warmup_initial_lr'] = 0.001

# Тренируем
model.train(...)

После 10 эпох дообучения на 100 ручных примерах модель уже понимает, что мы от нее хотим. Точность будет 60-70% mAP. Для предразметки - достаточно.

Не пытайся добиться 90% точности на этом этапе. Это предразметка, а не финальная модель. Цель - сократить ручную работу в 10 раз, а не заменить её полностью.

4 Пайплайн разметки: от изображения к маске

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

import os
from tqdm import tqdm
import cv2
import json

class AutoSegmentationPipeline:
    def __init__(self, model_path):
        self.model = self.load_model(model_path)
        self.results_dir = "auto_annotations"
        os.makedirs(self.results_dir, exist_ok=True)
    
    def process_folder(self, images_folder):
        """Обрабатываем всю папку с изображениями"""
        image_files = [f for f in os.listdir(images_folder) 
                      if f.endswith(('.jpg', '.png', '.jpeg'))]
        
        for filename in tqdm(image_files, desc="Processing"):
            image_path = os.path.join(images_folder, filename)
            
            # Пропускаем если уже обработано
            json_path = os.path.join(self.results_dir, 
                                    filename.replace('.jpg', '.json'))
            if os.path.exists(json_path):
                continue
            
            # Детекция
            predictions = self.model.predict(image_path)
            
            # Конвертируем в COCO формат
            annotations = self.predictions_to_coco(predictions, filename)
            
            # Сохраняем
            with open(json_path, 'w') as f:
                json.dump(annotations, f, indent=2)
            
            # Генерируем предпросмотр с bounding boxes
            self.save_preview(image_path, predictions, filename)
    
    def predictions_to_coco(self, predictions, image_id):
        """Конвертируем предсказания в COCO формат"""
        annotations = []
        for i, pred in enumerate(predictions):
            if pred.confidence > 0.3:  # Порог уверенности
                annotation = {
                    "id": i,
                    "image_id": image_id,
                    "category_id": 1,
                    "bbox": pred.bbox.tolist(),
                    "segmentation": self.bbox_to_polygon(pred.bbox),
                    "area": (pred.bbox[2] * pred.bbox[3]),
                    "iscrowd": 0
                }
                annotations.append(annotation)
        return annotations
    
    def bbox_to_polygon(self, bbox):
        """Преобразуем bounding box в полигон (пока упрощенно)"""
        x, y, w, h = bbox
        # Простой прямоугольный полигон
        return [[x, y, x+w, y, x+w, y+h, x, y+h]]

Этот скрипт за ночь на 8-ядерном процессоре обработает 5-8 тысяч изображений. На выходе - JSON файлы с разметкой в COCO формате.

Постобработка: превращаем сырую разметку в чистый датасет

Автоматическая разметка сделала 70% работы. Остальные 30% - самая важная часть. Потому что если их пропустить, получится мусор.

5 Фильтрация плохих предсказаний

Модель иногда глючит: рисует bounding boxes на пустом месте, путает объекты, делает слишком маленькие или слишком большие боксы.

def filter_bad_predictions(annotations, image_width, image_height):
    """Фильтруем явно ошибочные предсказания"""
    good_annotations = []
    
    for ann in annotations:
        bbox = ann["bbox"]  # [x, y, width, height]
        
        # 1. Слишком маленькие объекты (< 0.5% изображения)
        area_ratio = (bbox[2] * bbox[3]) / (image_width * image_height)
        if area_ratio < 0.005:
            continue
            
        # 2. Слишком большие (> 80% изображения)
        if area_ratio > 0.8:
            continue
            
        # 3. Выходят за границы изображения
        if (bbox[0] < -10 or bbox[1] < -10 or 
            bbox[0] + bbox[2] > image_width + 10 or
            bbox[1] + bbox[3] > image_height + 10):
            continue
            
        # 4. Неверное соотношение сторон (вероятно ошибка)
        aspect_ratio = bbox[2] / bbox[3]
        if aspect_ratio > 10 or aspect_ratio < 0.1:
            continue
            
        good_annotations.append(ann)
    
    return good_annotations

Эта фильтрация убирает 20-30% мусорных предсказаний. Автоматически.

💡
Помнишь статью про семантический пайплайн для LLM? Тот же принцип: итеративная обработка с улучшением качества на каждом шаге. Не пытайся сделать всё идеально за один проход.

6 Интеллектуальная ручная проверка

Теперь нужно проверить то, что осталось. Но не всё подряд. Используем приоритизацию:

Приоритет Критерий Что проверять
Высокий Низкая уверенность модели Confidence 0.3-0.6
Высокий Много объектов на изображении > 5 bounding boxes
Средний Сложный фон Высокая энтропия текстуры
Низкий Высокая уверенность, один объект Confidence > 0.8, 1-2 boxes

Сначала проверяем высокоприоритетные - там больше всего ошибок. Низкоприоритетные часто можно принять как есть.

Интеграция в Label Studio: полуавтоматическая доработка

Label Studio - не просто инструмент для ручной разметки. Это платформа для human-in-the-loop. Мы загружаем туда автоматически сгенерированные разметки, а человек только правит ошибки.

import label_studio_sdk

# Подключаемся к Label Studio
ls = label_studio_sdk.Client('http://localhost:8080', 
                            api_key='your-api-key')

project = ls.start_project(
    title='Auto-segmentation review',
    label_config='''
    
        
        
            
    
    '''
)

# Импортируем задачи с предразметкой
for json_file in auto_annotation_files:
    with open(json_file) as f:
        annotation = json.load(f)
    
    task = {
        "data": {
            "image": f"/data/{annotation['image_id']}"
        },
        "predictions": [{
            "model_version": "yolo_nas_auto",
            "result": annotation["segmentation"]
        }]
    }
    
    project.import_tasks([task])

Теперь разметчики видят уже готовые полигоны. Их задача - поправить границы, удалить ложные срабатывания, добавить пропущенные объекты. Вместо 3 минут на изображение - 30 секунд.

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

Ошибки, которые все совершают (и как их избежать)

Я видел десятки попыток автоматизировать разметку. 90% проваливаются из-за одних и тех же ошибок.

Ошибка 1: Жадность

Пытаются разметить всё и сразу. Собирают 50 000 изображений, запускают пайплайн, через неделю получают гору мусора. Разочаровываются.

Правильно: начать с 1000 изображений. Настроить пайплайн, проверить качество. Потом масштабировать.

Ошибка 2: Перфекционизм

Хотят, чтобы автоматическая разметка была идеальной. Трава не должна пробиваться за контур объекта. Тень должна быть исключена из маски. Тратят месяцы на доработку алгоритмов.

Правильно: приемлемое качество + быстрая ручная правка. 80/20 правило.

Ошибка 3: Игнорирование class imbalance

В робототехнике часто: 95% кадров - конвейер пустой, 5% - с деталями. Если не балансировать, модель научится только детектировать "пустоту".

Правильно: oversampling редких классов на этапе сбора данных.

# Балансировка датасета
def balance_dataset(image_files, target_ratio=0.3):
    """Добиваемся, чтобы целевые объекты были в target_ratio изображений"""
    # Определяем, есть ли объекты на изображении
    has_objects = []
    no_objects = []
    
    for img_file in image_files:
        if has_object(img_file):  # Детектим хотя бы один объект
            has_objects.append(img_file)
        else:
            no_objects.append(img_file)
    
    # Рассчитываем, сколько "пустых" оставить
    current_ratio = len(has_objects) / len(image_files)
    if current_ratio < target_ratio:
        # Нужно добавить больше с объектами
        # (ищем дополнительные данные)
        pass
    else:
        # Отбираем подмножество без объектов
        keep_count = int(len(has_objects) * (1-target_ratio) / target_ratio)
        no_objects = np.random.choice(no_objects, keep_count, replace=False)
    
    return list(has_objects) + list(no_objects)

Сколько времени это реально занимает

Давай посчитаем на примере датасета в 10 000 изображений:

Этап Ручной способ Наш пайплайн Экономия
Сбор данных 3-5 дней 2-3 дня 30%
Фильтрация Не делается (или неделя) 4 часа (авто) 95%
Разметка 300-500 часов 50 часов (80% авто) 85%
Проверка 50-100 часов 20 часов (приоритетная) 60%
ИТОГО 1-2 месяца 3-5 дней 90-95%

Из 2 месяцев в 5 дней. На обычном процессоре. Без GPU. Без тысяч долларов на аутсорс.

Что дальше? Итеративное улучшение

Самый крутой трюк, о котором мало кто говорит: твой первый датасет - это не конец, а начало.

1. Тренируешь модель на автоматически размеченных данных
2. Запускаешь её на новых данных
3. Собираешь случаи, где модель ошибается
4. Добавляешь их в датасет и переразмечаешь
5. Повторяешь

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

💡
Этот подход работает не только для сегментации. Похожие принципы использует AI-агент 3-го уровня для автономного улучшения своих навыков. Система учится на своих же ошибках.

Самый частый вопрос: "А если у меня специфичные объекты, которых нет в COCO?"

Ответ: неважно. Даже на 100 ручных примерах YOLO-NAS nano дообучается за час. Потом используешь её для разметки следующих 1000. Это как замена ETL-конвейера на агентов - начинаешь с малого, потом система масштабируется сама.

В следующий раз, когда услышишь "нам нужен датасет, но размечать некому", покажи эту статью. Пайплайн работает. Проверено на робототехнических проектах, медицинских изображениях, сельском хозяйстве.

CPU - не приговор. Это просто ещё один параметр, который нужно учитывать. Как время, бюджет или качество данных.