Когда реальность бьет по голове: хакатон, где все не так
Мы пришли на промышленный хакатон с блеском в глазах и готовым пайплайном YOLOv8. Задача казалась простой: бинарная сегментация загрязнений на защитных стеклах камер. Два класса - чистый стекло и грязь. Что может быть проще?
Пока не открыли датасет.
Первая ошибка 90% команд: они начинают обучать модель до того, как посмотрят на данные. Мы тоже чуть не наступили на эти грабли.
Проблема, которая ломает стандартные подходы
В производственных условиях камеры смотрят на конвейер 24/7. Защитные стекла покрываются пылью, масляными брызгами, мелкими частицами. Когда загрязнение достигает критического уровня - система должна подать сигнал для чистки.
Звучит логично? Вот только в реальности:
- Грязь не имеет четких границ
- Освещение меняется каждые 15 минут (смена дня и ночи, включение прожекторов)
- Некоторые виды загрязнений полупрозрачные
- Аннотации сделаны людьми с разной степенью внимательности
Первое открытие: метрики врут
Мы стартовали с mAP@0.5 - стандартной метрики для YOLO. Первая же модель показала 0.89. Отличный результат! Но когда мы запустили инференс на реальных данных...
Система то не видела критических загрязнений, то кричала о чистом стекле. Почему? Потому что mAP@0.5 измеряет пересечение bounding box с ground truth на уровне 50%. А в нашей задаче даже 10% загрязнения - уже проблема.
Наш план атаки: от простого к сложному
1 Анализ данных до первой строчки кода
Мы потратили 3 часа на визуализацию датасета. Не на обучение - на изучение. Открыли каждое изображение, посмотрели распределение классов, вариации освещения, качество аннотаций.
Что обнаружили:
- Классовый дисбаланс 95:5 (чистые:загрязненные)
- В 30% случаев аннотации не соответствовали реальным границам загрязнений
- Ночные снимки имели другой цветовой профиль
2 Кастомные метрики под бизнес-задачу
Вместо mAP@0.5 мы создали две метрики:
- False Negative Rate (FNR) - процент пропущенных критических загрязнений
- False Positive Rate (FPR) - процент ложных срабатываний на чистых стеклах
В производственных условиях FNR дороже в 10 раз. Пропустить грязь - остановить линию. Ложное срабатывание - отправить техника на проверку.
3 Балансировка данных без oversampling
Oversampling загрязненных образцов - очевидное, но плохое решение. Модель начинает находить загрязнения там, где их нет.
Вместо этого мы:
# Создаем синтетические загрязнения на чистых стеклах
import albumentations as A
def add_contamination(image):
transform = A.Compose([
A.RandomFog(p=0.3), # Туман/дым
A.RandomRain(p=0.2), # Капли
A.RandomShadow(p=0.25), # Тени
A.GaussNoise(p=0.25), # Шум
])
return transform(image=image)['image']
4 Трансферное обучение с умом
Брать предобученную на COCO модель YOLO - все равно что использовать молоток для микрохирургии. COCO знает про людей, машины, животных. Наша задача - найти едва заметные пятна на стекле.
Мы взяли модель, предобученную на датасете с похожими текстурами, и дообучили только последние слои.
Технические лайфхаки, которые сработали
Аугментации, которые имеют смысл
Стандартные аугментации (поворот, масштабирование) бесполезны для камер, закрепленных в одном положении. Вместо них:
| Аугментация | Эффект | Когда использовать |
|---|---|---|
| ColorJitter (только brightness) | Имитация изменения освещения | Всегда |
| GaussianBlur | Дефокусировка | При плохой фокусировке камер |
| CutOut на границах | Учет частичного загрязнения | Когда загрязнения локальные |
Постобработка предсказаний
YOLO выдает bounding boxes. Нам нужны маски загрязнений. Решение:
def boxes_to_contamination_mask(boxes, image_shape):
"""Преобразуем bounding boxes в карту загрязнений"""
mask = np.zeros(image_shape[:2], dtype=np.uint8)
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
confidence = box.conf[0].cpu().numpy()
# Учитываем уверенность модели
intensity = int(confidence * 255)
cv2.rectangle(mask,
(int(x1), int(y1)),
(int(x2), int(y2)),
intensity,
thickness=cv2.FILLED)
# Морфологические операции для сглаживания
kernel = np.ones((5,5), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
return mask
Валидация, которая не врет
Мы разделили данные на три части:
- Тренировочные - 60%
- Валидационные - 20% - для подбора гиперпараметров
- Тестовые - 20% - финальная проверка, которую смотрели только один раз
Самая частая ошибка в хакатонах - использовать тестовый сет для валидации во время обучения. Так модель "подстраивается" под тест, и результаты становятся бессмысленными.
Зафиксируйте тестовый сет до начала экспериментов. Не подглядывайте в него до финальной оценки.
Развертывание на грани: оптимизация для Raspberry Pi
Наш победный трюк: мы не просто обучили точную модель. Мы сделали ее достаточно легкой для работы на Raspberry Pi 4 на производстве.
Как:
# Квантование модели для ускорения инференса
model.export(format='onnx',
dynamic=False,
simplify=True,
opset=12)
# Затем в отдельном скрипте
import onnxruntime as ort
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
# Динамическое квантование
quantized_model = quantize_dynamic(
'model.onnx',
'model_quantized.onnx',
weight_type=QuantType.QUInt8
)
Результат: скорость инференса выросла в 3 раза при падении точности всего на 2%.
Ошибки, которые совершили другие команды
- Слепая гонка за mAP - лучшая по метрике модель оказалась бесполезной на реальных данных
- Игнорирование бизнес-логики - команды не спрашивали, какая ошибка дороже: пропустить загрязнение или вызвать ложную тревогу
- Переобучение на артефактах - модели учились распознавать не загрязнения, а артефакты съемки
- Отсутствие постобработки - raw predictions от YOLO давали слишком много шума
Что в итоге победило
Наша система:
- Обнаруживала 98% критических загрязнений (FNR = 2%)
- Ложные срабатывания - менее 5% (FPR = 5%)
- Работала на Raspberry Pi 4 в реальном времени
- Имела механизм калибровки чувствительности под каждый цех
Но главное - мы создали не просто модель, а систему, которая понимала контекст задачи. Именно это принесло нам победу.
Чему научились
Промышленное компьютерное зрение - это не про точность на валидационном сете. Это про:
- Понимание бизнес-контекста - каждая ошибка имеет свою цену
- Адаптацию под железо - самая точная модель бесполезна, если не влезает в память
- Верификацию на реальных данных - до того, как отдавать заказчику
- Системность мышления - от сбора данных до постобработки
Если вы думаете о переходе от прототипа к продакшену, обязательно прочитайте про технический долг в AI-разработке. Наш опыт показал: ошибки на этапе проектирования обходятся в 10 раз дороже.
И помните: в промышленном CV нет "просто обучить модель". Есть "создать систему, которая работает в условиях цеха". Где пыль, вибрация, перепады температуры и сменный персонал, который может случайно задеть камеру.
Следующий шаг для нас - внедрение active learning. Система будет сама выбирать, какие кадры нужно переразметить для улучшения модели. Но это уже другая история...