а я сегодня дымоход проектировал. короче слушай. труба только нержавейка 1 мм. меньше - прогорит и ненадежно. уличная часть только сендвич. из печки выход 100 мм. в магазине в наличии только сендвич 115/190 (не очень стандартный размер). печка высотой 620. выходной патрубок на высоте 730. от него вертикально пойдет 100 мм труба 0.5 метр. сборка (внимание) по конденсату. папа внизу мама сверху. итак. труба 0.5 м - шибер (0.1 м) - труба 1м - отвод 45 градусов - труба 1 м (уходит под углом 45 градусов в стену и выходит на улицу). поскольку труба идет под углом, меняя длину трубы мы сможем подобрать высоту установки монтажной площадки под сендвич и расстояние от стены чтоб не попасть в свес крыши. итого. длина наклонного участка 1 м. и высота дымохода увеличится на косинус (cos) 45 шрадусов. т.е. нп 0.707 м. просуммировав все длины получаем высоту установки монтажной площадки 2.97 метра и расстояние от стены ~ 0.4 м. идем на озон и закупаем соответствующие компоненты. так же удачно получилось, что основной кронштейн для монтажной площадки (на которой будет стоять 3х метровый сендвич) будет крепиться не в обычный газоблок, а в армопояс. итого дымоход будет высотой ~ 6 метров и тяга прогнозируется неплохая. используемые инструменты freecad для эскиза, озон для заказа. докер и питон не понадобились, но под рукой держу на всякий.
Всем привет! Меня зовут Алексей, я руковожу компанией, которая занимается разработкой с применением ИИ-технологий. Сам я тоже погружен в разработку, но больше доверяю это своей команде – нам удалось собрать команду классных профи. Истории из нашей совместной работы я и планирую рассказывать в своем блоге.
Cегодня делюсь историей одного из наших разработчиков – о внедрении компьютерного зрения на реальном производстве.
Первый день на линии выглядел почти комично. Стоим втроём в касках, над конвейером — новая промышленная камера с глобальным затвором (5 Мп, FOV ~0,6 м), а под ней со скоростью 2 м/с уезжают листы с зеркальным блеском. Оператор рядом фыркает:
— «Ну и где твои умные нейросети? Вон же царапина!»
На экране вместо царапины — пересвеченный до насыщения белый блик: без перекрёстной поляризации и со слишком длинной выдержкой он превращается в «идеальный» объект для детектора. Алгоритм радостно машет флажком: «дефект!». Таких ложных срабатываний за смену набегало сотни; настоящие микротрещины и сколы тонули в засветках и смазе.
С этого момента стало ясно: впереди не «проект на пару недель», а марафон, где придётся бороться не только с кодом, но и с реальностью цеха — вибрациями, пылью, бликами и древним MES, говорящим по OPC/Modbus.
Зачем всё это
Один крупный производственный заказчик (линия резки листового материала: зеркальная поверхность, скорость ~2 м/с) решил автоматизировать контроль дефектов.
Раньше инспекция была ручной — оператор просматривал каждый лист.
На старте задача выглядела так:
Ставим камеру над линией: Basler ace2, 5 Мп, глобальный затвор; C‑mount 12 мм; FOV ≈ 600 мм; аппаратный триггер от энкодера; экспозиция 30–50 мкс; импульсный LED 630 нм с перекрёстной поляризацией.
Режем изображение на тайлы: 1024×1024 px с overlap 20%; deskew по кромке листа; маска бликов.
Возвращаем результаты в MES/SCADA: JSON → XML/FTP (атомарный rename) с переходом на OPC UA; координаты дефектов в мм, sheet_id, статус OK/NOK.
Спойлер: из этого получился трёхмесячный марафон — свет, крепёж, синхронизация и интеграции заставили сервера дрожать, а нас — прокачать инженерный дзен.
Сюрприз №1: Светит, но не туда
Проблема: освещение и блики
На раннем этапе мы выяснили, что камера стабильно видит… блики, а не дефекты. Промышленное освещение оказалось агрессивным: холодные направленные прожекторы, паразитные отражения от оборудования и зеркальная поверхность листов. Сенсор клиппился по яркости, и каждый пересвеченный блик принимался за дефект. На старте ловили до 32% ложноположительных срабатываний.
Что сделали
Свет и поляризация: заменили направленные прожекторы на диффузный купольный/низкоугловой свет под ~45°; поставили линейные поляризаторы на источник и анализатор на объектив под 90° (подавление бликов до ~15–20 dB). Перешли на узкополосные LED 625–660 нм.
Экспозиция и синхронизация: включили строб с импульсом 30–50 мкс под триггер энкодера; глобальный затвор, постоянный токовый драйвер без 50/60 Гц мерцания. Отключили автоэкспозицию/автогейн/автобаланс; гамма = 1.0, фиксированный gain.
Экраны и окклюзии: установили матовые чёрные экраны по бортам и над камерой, убрали паразитные отражения от рам и кромок.
Онлайн-мониторинг яркости: ввели контроль доли насыщенных пикселей (P255) и динамического диапазона; кадры с P255 > 0.5% автоматически помечаются и не идут в инференс; гистограммы логируются.
Вывод: оптика и свет — не «поправим в коде». Схему освещения, поляризацию, строб и крепёж нужно закладывать до начала разработки модели — это даёт порядок выигрыша по качеству сразу.
Сюрприз №2: Камера не дружит с резкостью
Проблема: микровибрации и автофокус
Камера стояла над станком, всё казалось стабильным. Но каждые 30–40 секунд появлялась дрожь, тонкие дефекты «смазывались». Причина — микровибрации от привода/роликов (≈25–35 Гц) и отсутствие автофокуса при фиксированном рабочем расстоянии.
Что сделали
Механика/крепёж: вынесли камеру на жёсткий кронштейн с виброизоляторами (fₙ ≈ 9–12 Гц, демпферы Shore A 30–40), отвязали от корпуса станка, добавили массу и развязку кабелей. Резонансные пики выше 60 Гц подавлены, передача вибраций < 0.3.
Фокус: настроили фикс‑фокус на рабочее расстояние по slanted‑edge (MTF50 вырос с ~0.28 до ~0.36 cyc/px), зафиксировали кольцо (lock‑screw) и метки положения; регламент пересмотра при смене температуры/света.
Экспозиция: привязали экспозицию к стробу 30–50 мкс — при 2 м/с смаз ≤ 0.1 мм.
IMU‑контроль: поставили 6DoF‑датчик (1 кГц); логируем события при |ω|RMS > 0.8°/с или Δугла > 0.05° за 200 мс; при срабатывании помечаем кадры и уведомляем оператора.
Онлайн‑метрика резкости: Tenengrad/Laplacian в ROI; кадры ниже τ исключаются из инференса или запрашивается повтор.
Вывод: стабильная механика и фикс‑фокус столь же критичны, как и модель. Развязка вибраций, короткая экспозиция и онлайн‑контроль резкости делают детектор предсказуемым
Сюрприз №3: один тайл — два дефекта и половина чужого листа
Ожидали: лист идёт ровно и по центру. Реальность: заезд под углом, частичное перекрытие соседним листом, дрейф по диагонали. В итоге один тайл нередко содержит две кромки разных листов — модель «плывёт» в предсказаниях.
Что сделали
Маркеры и привязка к движению: наклеили ретрорефлективные метки по краям ленты; считывание в NIR (850 нм) с аппаратным триггером от энкодера (5k PPR). Детектируем leading edge и боковые кромки; дрожание меток < 0.5 мм.
Лидар + фотодатчики: ToF-лидар 1 кГц для контроля lateral offset, три фотошторки по ширине для детекта перекрытия/наличия листа. События уходят в PLC и в CV-пайплайн; допуск смещения ±3 мм, время реакции < 5 мс.
вычисляем гомографию и выполняем deskew; сетка тайлов якорится к кромке, а не к «сырому» кадру;
при перекрытии строим маску «серой зоны» вдоль шва (15–25 px): увеличиваем overlap, нежёстко понижаем вес предсказаний в NMS, логику объединения делаем по sheet_id;
Слежение за листом: sheet_id по импульсам энкодера; пересборка карты дефектов в координатах линии, чтобы MES получал стабильные ROI.
Вывод: физическое позиционирование нужно дублировать алгоритмами CV. У «железа» иногда свои планы, поэтому привязка к энкодеру, маркеры, лидар и дескью — обязательны для стабильного тайлинга и корректной агрегации предсказаний.
Сюрприз №4: Модель любит однотипные дефекты, а у нас каждый раз сюрприз
Проблема: разнообразие артефактов
Обучались на 15 классах дефектов. В проде всплыли неожиданные: пятна клея, отпечатки перчаток, стружка, насекомые, следы чистки и др. За первый месяц зафиксировали 37 новых типов, отсутствовавших в трейне.
Что сделали
Активное выявление «неизвестных» (OOD): отправляем в разметку тайлы при низкой уверенности (conf < τ), высокой энтропии, а также при расхождении ансамбля и классического CV; дополнили energy-score по логитам и расстоянием Махаланобиса в признаковом пространстве.
Еженедельная разметка и инкрементальное обучение: выгружаем 8–12k «неопознанных» тайлов/нед. в LabelStudio (κ ≥ 0.82), дообучаем с rehearsal (30% старых данных), частично замораживаем backbone, применяем дистилляцию знаний и калибровку температурой.
Управление таксономией: «прочее/ nuisance» как буферный класс; промоутим в полноценный класс при ≥200 размеченных примеров и precision ≥ 0.7 на валидации. Зоны с низкой уверенностью помечаем в UI для ручной проверки.
Вывод: CV — это не «обучил и забыл», а непрерывный цикл. Активный OOD-поток, регулярная разметка и инкрементальное обучение с гибридными эвристиками дают управляемость при появлении новых артефактов без деградации по базовым дефектам.
Сюрприз №5: API-интеграция с MES — это уже не про CV, но боль
Проблема: древняя MES-система
Документации нет. REST/GraphQL нет. Только XML-файлы по FTP, которые MES опрашивает раз в несколько секунд. Частые проблемы: частичные чтения, дубликаты, «зависшие» файлы, непредсказуемые задержки.
Что сделали
Прокси-адаптер JSON→XML + надёжная файловая шина
CV выдаёт JSON с sheet_id, ts, speed, списком дефектов (bbox в мм, класс, уверенность).
Адаптер формирует XML по согласованной схеме, пишет атомарно: сначала в outbox/*.xml.part, затем rename в *.xml; после чтения MES кладёт *.ack.
Каталоги: outbox/ → processing/ → done/, ошибки в error/ с ретраем (экспоненциальная задержка, максимум 5 попыток), дедуп по sheet_id.
Кодировка UTF‑8 (без BOM), CRLF по требованию MES. Имя файла: INS_L1_20250912T101532Z_123456.xml.
Мониторинг и SLA для FTP-интеграции
Тайм-аут чтения: если через 30 с нет *.ack — алерт (Prometheus/Alertmanager → Teams/Slack), автоперекладка файла в processing/ не повторяется до разборки инцидента.
Метрики: end-to-end задержка (CV→MES), число «подвисших» файлов, ретраи, доля дубликатов, размер очереди. Трассировка по sheet_id.
Безопасность: при возможности SFTP; иначе FTPS (TLS), учётка с минимальными правами и chroot.
Узлы: ns=2;s=estralin/Sheet/{sheet_id}/Result, .../Defects (массив структур с bbox в мм и классом). Семплирование 100–200 мс, очередь 128, подтверждение обработкой статуса.
Фолбэк: при недоступности OPC UA — автоматический возврат на файловую шину, чтобы не терять данные
Вывод: интеграции — это отдельный проект. Согласуйте протоколы/схемы и SLA с IT-заказчика до старта, закладывайте атомарность, дедуп, мониторинг и фолбэк-канал; при возможности переходите на OPC UA с шифрованием.
Технологии и стек
Камера: Basler ace2 (GigE, mono, global shutter, 5 MP @ 60 fps), C‑mount фикс-объектив 12 мм (F/4), FOV ≈ 600 мм, масштаб ≈ 0.20 мм/пкс. Аппаратный триггер от энкодера 5k PPR, экспозиция 30–50 мкс, строб узкополосного LED 630 нм с перекрёстной поляризацией; гамма 1.0, фиксированный gain.
Модели: YOLOv5m (тайлы 1024×1024, overlap 20%), препроцессинг OpenCV (deskew по Sobel/Hough, CLAHE, маска бликов). Инференс ONNX Runtime CUDA/FP16; p50 7.8 мс/тайл, p95 11.2 мс/тайл (RTX A2000). Итог по листу p95 < 90 мс при 2 м/с. Качество: mAP50 0.96, F1 0.91, FPR 3.1% на холдауте.
Разметка: LabelStudio, экспорт COCO/YOLO; двойная валидация, κ=0.84; гайдлайны по классам/границам; класс «nuisance/прочее» для OOD до промоушена.
Интеграция: Python FastAPI (/infer, /healthz) → JSON; адаптер JSON→XML с атомарным rename и *.ack для legacy-FTP; основная шина — OPC UA (SecurityMode=SignAndEncrypt, Basic256Sha256), узлы ns=2;s=estralin/Sheet/{id}/Defects. Фолбэк на файловый канал.
Мониторинг: Prometheus + Grafana. Метрики: p95/p99 инференса, очередь, GPU util/mem, доля клиппинга (P255), FPR/recall по сменам, OOD rate, задержка CV→MES/OPC. Алерты по таймаутам ACK (>30 с), росту FPR, падению FPS/энкодера.
Деплой: On‑prem, Docker (Compose), NVIDIA Container Toolkit, закрепление версий драйверов/библиотек, healthchecks, автоперезапуск, локальный inference (latency‑critical), офлайн‑буферизация результатов и ретраи.
Финальные цифры
Хотелось показать таблицей, поэтому просто скриншот
Что мы поняли
Оглядываясь назад, понимаем: часть проблем мы могли предсказать. Вот несколько инсайтов, которые пригодятся тем, кто только собирается внедрять CV на производстве.
Не верить в «идеальные данные»
Пыль/блики/вибрации — норма. Введите авто‑контроль качества кадров: P255 ≤ 0.5%, средняя яркость в окне [90;160], резкость (Tenengrad) > τ, доля «горячих» пикселей ≤ 0.05%.
Мониторьте дрейф окружения: деградация света (−5…−8%/мес), смещение экспозиции, рост шума; алерты и напоминания на очистку оптики каждые 8 ч или при падении SNR < 24 dB.
Реплицируйте «грязную реальность» в данных: еженедельно добавляйте 5–10% свежих OOD‑тайлов, аугментации под бликами/смазом/пылью; калибровка порогов раз в смену.
Закладывать бюджет на «внезапности»
Резерв времени: +20–30% к срокам на интеграции/железо; бюджет: +10–15% на запасные части (камеры, БП, драйверы света, кабели).
Операционные SLO: p95 инференса < 100 мс/тайл, p95 CV→MES < 1.2 с, потери данных 0, дедуп по sheet_id, MTTR инцидента интеграции ≤ 15 мин.
Дежурство и плейбуки: on‑call 24/7 для линии, сценарии на «камера/энкодер/свет/OPC/FTP упал», фолбэк на файловую шину, офлайн‑буфер ≥ 24 ч.
Общаться с технарями на месте
Совместно с цехом: выбор света/крепежа/экранирования, допустимые окна простоя, точки триггера от энкодера, маршруты кабелей, HSE‑требования.
Формализовать SAT/UAT: чек‑листы по классам дефектов, выборки на 1–2 смены, критерии приёмки (precision/recall/FPR), график регламентных чисток и перекалибровок.
Напоследок
Компьютерное зрение в промышленности — меньше про «красоту модели» и больше про свет, крепёж, синхронизацию и протоколы. Если идёте в прод, готовьтесь к реальному миру — тому, где рядом со Stack Overflow открыта вкладка «как дернуть автофокус через Modbus» и список запасных кабелей на складе.