ESPN DeportesSantos madruga a Pumas con polémico penalESPNReese keeps Dream rolling in Game 3 to take series lead in WNBA FinalsInquirerIn Bohol, cloud seeding begins to save farmsDaily MaverickGovernment websites go dark after AI-discovered flaw sparks security scrambleThe Jerusalem PostAbdul El-Sayed Michigan US Senate campaign hires Jewish outreach director한겨레주말 기온 오르지만 남부지방 비… 태풍 ‘참피’ 주의ZDF heuteAktuelle Pressemitteilungen des ZDFPremium TimesEbola: One imported case does not mean Nigeria is experiencing outbreak – WHO경향신문가을철 미시령 힐링가도 달리면 숙박비 2만 원 할인···22일~11월 30일ABC News (Australia)Breaking: Man shot by police in Sydney's south-westSözcüYapay zekâ İstanbul'un en iyi ilçesini seçtiCNN بالعربيةوزارة الدفاع اليمنية تحذر من المشاركة بـ"النكف القبلي" وغيرها من تجمعات بمناطق سيطرة الحوثي
The Daily Newsstand · Free, Always
Thursday, October 22, 2026

Обучать нельзя фильтровать: убираем тени с дороги без нейросетей

Translate

Где поставить запятую в заголовке? В этой задаче я поставил её после «нельзя»: «Обучать нельзя, фильтровать».

Продолжаю разбирать задачи, которые возникают при анализе дорог. На этот раз - тени на снимках линейной камеры.

Нужно искать трещины и заплатки. Но половина асфальта на изображении тёмная, граница тени добавляет собственный контур, а разметка одновременно попадает и на солнце, и в тень. Всё это потом достаётся алгоритму поиска дефектов.

Хочется просто поднять яркость и пойти дальше. Вместе с яркостью, правда, поднимаются шум и зернистость. Если добавить локальный контраст, можно ещё сильнее подчеркнуть ту самую границу, от которой собирались избавиться. Разметка тоже участвует: она белая и довольно активно портит статистику асфальта.

В итоге получился пайплайн из коррекции камеры, оценки освещения, ограничений на контраст и отдельной обработки разметки. Работает на CPU, без нейросетевого инференса. Гауссовы фильтры, локальная статистика, морфология и немного терпения к uint8 :)

Сначала покажу результат, а дальше разберу, откуда он берётся.

Исходный кадр и результат обработки

Исходный кадр и результат обработки

Почему здесь без нейросети

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

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

Наконец, рабочее решение требовалось получить достаточно быстро - без отдельного этапа разметки и обучения. Сбор обучающих данных, их подготовка, дообучение и проверка модели в эти условия не укладывались.

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

Что у нас на входе и чего я хочу на выходе

Я работаю с последовательностью монохромных TIFF: 8 бит, 2048 × 1024 или 1024 × 2048.

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

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

Дальше - один реальный кадр, от начала до конца

Возьмём снимок с широкой поперечной тенью, полосой разметки и выраженной фактурой асфальта. На нём и будем смотреть шесть крупных преобразований. Без смены дороги посреди объяснения :)

Один реальный кадр и постоянный фрагмент для всех этапов

Один реальный кадр и постоянный фрагмент для всех этапов

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

Под каждым этапом можно раскрыть формулы и промежуточные расчёты.

Кнопками «Назад» и «Вперёд» можно пройти все шесть преобразований: слева показано изображение до выбранного шага, справа - после. В пятом шаге соединяются две ветки; к этой развилке ещё вернёмся.

Открыть просмотрщик отдельно

Начнём чуть издалека. В упрощённом виде пиксель можно представить так:

I(x, y) ≈ R(x, y) · L(x, y) + n(x, y),

где R - свойства поверхности, L - освещение, n - шум и всё, что мы не учли. Уже здесь хватает упрощений: влажность, отражения, оптика и сенсор тоже влияют на картинку.

Имея только I, однозначно разделить поверхность и освещение не получится. Тёмный пиксель может быть тенью, заплаткой или трещиной. Поэтому я использую несколько опор: масштаб изменения яркости, соседние области, статистику светлых кадров и маски разметки.

Весь пайплайн выглядит так:

Схема зависимостей двух веток обработки

Схема зависимостей двух веток обработки

На схеме стоит задержаться. Ветка выравнивания освещения использует яркостную основу и результат обработки разметки. Весь результат коррекции контраста через неё как через очередной фильтр не проходит. Если цели двух веток совпадают, общие вычисления переиспользуются.

Ещё договоримся о терминах. Изображение - то, что в конце получит детектор. Поле коррекции - карта коэффициентов, которыми меняется яркость. Маска - ограничение, где и что разрешено делать. Дальше эта разница пригодится: сгладить поле коррекции и размыть сам асфальт - две очень разные операции.

1. Убираем особенности камеры

Сначала убираю то, что добавила камера: полосы, неодинаковую чувствительность и пространственную неравномерность. Большая тень здесь остаётся - до работы с освещением мы ещё не дошли.

Убираем особенности камеры: реальный кадр и неизменный фрагмент

Убираем особенности камеры: реальный кадр и неизменный фрагмент

Как это считается: профили камеры, выбор силы коррекции и отбор опорных кадров

У камеры есть свои особенности: полосы, разная чувствительность столбцов, пространственная неравномерность. Если сразу начать оценивать освещение, всё это тоже попадёт в оценку. Алгоритму ведь никто не объяснил, где тень, а где особенность сенсора.

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

I₁(x, y) = [I₀(x, y) − s · a(x)] / max(1 + s · k(x), 0.5),

I₀ - исходное изображение, I₁ - изображение после полосовой коррекции; a - аддитивная поправка в единицах яркости, k - относительное отклонение чувствительности столбца от единицы, s - сила коррекции от 0 до 1. Ограничение знаменателя снизу не даёт чрезмерно усилить сигнал. Затем возвращается медианный уровень допустимых пикселей, значения ограничиваются диапазоном и округляются. Следом компенсируется пространственная неравномерность.

Внутренний расчёт 1. Убираем особенности камеры

Внутренний расчёт 1. Убираем особенности камеры

Силу s выбираю из нескольких фиксированных значений на достаточно однородных кадрах. Проверка идёт на разных поднаборах: если улучшение не подтверждается, полосовая поправка отключается. Коррекция должна помогать конкретной серии; применять её на максимуме просто потому, что она есть, смысла мало.

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

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

2. Выравниваем освещение

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

Выравниваем освещение: реальный кадр и неизменный фрагмент

Выравниваем освещение: реальный кадр и неизменный фрагмент

Как это считается: целевая яркость, две оценки освещения, опора и локальная коррекция яркости

Захардкодить какое-нибудь приятное серое значение было бы удобно. Только у следующей серии условия съёмки могут оказаться другими.

Поэтому целевую яркость я получаю из светлых опорных кадров этой же серии. Исключаю разметку и непригодные пиксели, считаю статистики по ячейкам 8 × 8, объединяю кадры, сглаживаю поле в логарифмическом представлении и использую опорные области после коррекции камеры. Для ветки выравнивания освещения из этого получается одна пространственно постоянная цель серии. Для оценки по предварительно сглаженным кадрам целевая яркость рассчитывается отдельно.

В этой серии из 128 кадров выбрано 102 опорных. Полученные целевые уровни - примерно 75,05 и 75,30 DN. DN - единица интенсивности: у 8-битного изображения значения лежат от 0 до 255.

Постоянная цель не означает одинаковую поправку везде. Тёмный участок и светлый участок получают разные коэффициенты, чтобы приблизиться к одному уровню.

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

И ещё: от соседних кадров здесь нужна только статистика. Куски «хорошего асфальта» между изображениями не копируются.

Общий ориентир T ≈ 75,05 DN. Средняя яркость тёмной ячейки А равна 16,84 DN, светлой Б - 89,61 DN. Поэтому T/M даёт разные коэффициенты: 4,46 для А и 0,84 для Б. Дальше поле коэффициентов интерполируется между ячейками.

Общий ориентир T ≈ 75,05 DN. Средняя яркость тёмной ячейки А равна 16,84 DN, светлой Б - 89,61 DN. Поэтому T/M даёт разные коэффициенты: 4,46 для А и 0,84 для Б. Дальше поле коэффициентов интерполируется между ячейками.

Строим поле освещения

Пусть M - текущая средняя интенсивность на сетке ячеек, а T - целевое поле. Тогда базовая коррекция:

g = ln[max(T, 2) / max(M, 2)]
I′ ≈ I · exp(ĝ).

Здесь I - изображение до преобразования, I′ - после него, ĝ - поле g, интерполированное до полного разрешения. Первое такое поле на рисунке обозначено g₀. ln обозначает натуральный логарифм, exp - экспоненту. Логика простая: сравниваем текущую яркость с желаемой, интерполируем поле до полного разрешения и меняем интенсивности. Двойка в знаменателе и числителе нужна, чтобы почти чёрные значения не превращали расчёт в деление на ноль или огромный коэффициент.

После применения поля g₀ перепад освещения стал слабее. Изменился и вид разметки: локальное среднее смешивает влияние освещения и разных материалов.

После применения поля g₀ перепад освещения стал слабее. Изменился и вид разметки: локальное среднее смешивает влияние освещения и разных материалов.

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

Но локальное среднее смешивает сразу всё: освещение, фактуру, границу материалов. Особенно неприятно это на резкой границе тени. В одно окно попадают две области, которым нужны разные поправки.

Локальная коррекция яркости: смотрим на кадр двумя способами

Здесь я строю два варианта:

  • A: поле оценивается по исходному кадру и применяется к нему же;

  • B: поле оценивается по предварительно сглаженному кадру, но применяется снова к исходному.

То есть в B сохраняется исходная фактура. Сглаживание используется при оценке освещения.

Теперь можно посмотреть на A − B. Разность показывает места, где результат сильно зависит от масштаба оценки. Положительную часть беру как кандидата на локальную поправку, а опору ищу в окружении, где сглаженный модуль разности A − B невелик. Разметку и непригодные пиксели из этой опоры исключаю.

Разность A и B показывает, где результат чувствителен к масштабу оценки освещения. Справа пока остаётся A: полученную разность ещё нужно ограничить по опорному окружению.

Разность A и B показывает, где результат чувствителен к масштабу оценки освещения. Справа пока остаётся A: полученную разность ещё нужно ограничить по опорному окружению.

Средний уровень такого окружения считается нормированной свёрткой:

R = Gσ(m · A) / max(Gσ(m), ε).

R - опорная яркость, Gσ - гауссово сглаживание с масштабом σ, m - маска допустимых пикселей: 1 внутри опоры, 0 за её пределами. Малое положительное ε защищает от деления на ноль. Сначала сглаживаем изображение с маской, затем делим на сглаженную маску. Без деления выкинутые пиксели фактически превратились бы в чёрные и потянули среднее вниз.

Внутренний расчёт 5. Ищем опорную яркость вокруг кандидатов

Внутренний расчёт 5. Ищем опорную яркость вокруг кандидатов

Но даже ненулевая маска ещё мало что говорит. Если в огромное окно попало несколько пикселей с краю, доверять им как полноценному окружению странно. Поэтому проверяю эффективное число наблюдений:

N = (Σwᵢ)² / Σwᵢ².

N - эффективное число наблюдений, wᵢ - вес допустимого пикселя в окне. Это характеристика распределения весов. Независимыми соседние пиксели от такой формулы, конечно, не становятся. Для быстрой проверки используется нижняя оценка: сумма весов допустимых пикселей, делённая на максимальный вес полного двумерного гауссова ядра. Если уже её достаточно, точную свёртку квадратов весов можно пропустить.

Внутренний расчёт 6. Проверяем, хватает ли опоры

Внутренний расчёт 6. Проверяем, хватает ли опоры

Если опоры достаточно, поправка определяется разностью опорного и локального низкочастотного уровней A. Если недостаточно - используется ограниченная резервная оценка по компонентам.

Сверху поправку ограничивает запас до 248,49 DN. Отдельно сохраняю карту того, насколько осветлили каждый пиксель, в логарифмическом виде. После коррекции контраста эта карта понадобится, чтобы сгладить переходы самой поправки.

Внутренний расчёт 7. Добавляем локальную поправку

Внутренний расчёт 7. Добавляем локальную поправку

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

3. Настраиваем фактуру и тона

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

Настраиваем фактуру и тона: реальный кадр и неизменный фрагмент

Настраиваем фактуру и тона: реальный кадр и неизменный фрагмент

Как это считается: ограничения контраста, проекция средних, сглаживание поля, тональное преобразование и ограничение светлых деталей

После нормализации детали могут выглядеть слабо. Напрашивается идея подтянуть локальное стандартное отклонение к опорному. Но на почти однородном участке это удобный способ сделать из слабого шума очень убедительную фактуру. Для поиска трещин подарок сомнительный.

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

Дальше этот запрос ослабляется:

ρ = C₀,₁(1 − U / Δ²),  если Δ > 0
ρ = 0,                  если Δ ≤ 0
h = max(Δ, 0) · ρ · (1 − p),

ρ - доля разрешённого усиления, h - логарифм коэффициента усиления, p - доля защищённых пикселей. Здесь и далее Cₐ,ᵦ(x) = min(max(x, a), b) ограничивает значение диапазоном от a до b. U объединяет неопределённость текущей и опорной статистики; при нулевом запросе Δ усиление не добавляется. Если статистики для оценки недостаточно, усиление тоже отключается. Если само различие невелико на фоне неопределённости, усиление сокращается, вплоть до нуля. Защищённые области тоже уменьшают его силу.

Коэффициент ρ ослабляет ненадёжный запрос на усиление контраста. Он рассчитан на сетке 32 × 64, поэтому карта выглядит крупными ячейками.

Коэффициент ρ ослабляет ненадёжный запрос на усиление контраста. Он рассчитан на сетке 32 × 64, поэтому карта выглядит крупными ячейками.

Дополнительно ставлю предел по фактуре, которая уже есть во входе:

S = min(V₈(Iк), V₈(Iя))
S′ = max(S − 0.25, 0)
kₘ = 1 + √(S′ / 0.25).

V₈ - дисперсия внутри ячейки 8 × 8, Iк - изображение после коррекции камеры, Iя - яркостная основа, S′ - дисперсия после вычитания порога, kₘ - верхняя граница усиления. Беру минимум дисперсий изображения после коррекции камеры и яркостной основы, вычитаю небольшой порог и получаю верхнюю границу усиления. На защищённых пикселях коэффициент равен единице.

Число 0,25 DN² здесь связано с квадратом максимальной ошибки округления ±0,5 DN. Это инженерный порог; измеренной дисперсией шума камеры я его не называю.

Внутренний расчёт 9. Ограничиваем усиление фактуры

Внутренний расчёт 9. Ограничиваем усиление фактуры

Есть ещё одна тонкость. Меняя контраст, легко заодно незаметно сдвинуть среднюю яркость. Поэтому из добавленной поправки вычитаю её локальную среднюю составляющую. Затем решаю задачу с ограничениями: сохранить средний уровень ячейки, не менять защищённые пиксели и оставить остальные в диапазоне 0-255. Сохранение среднего относится к расчёту до финального целочисленного округления.

После усиления деталей получается кандидат на новое изображение. Его локальная средняя яркость может сдвинуться, а отдельные значения - выйти за диапазон 0-255. Следующий шаг накладывает эти ограничения.

После усиления деталей получается кандидат на новое изображение. Его локальная средняя яркость может сдвинуться, а отдельные значения - выйти за диапазон 0-255. Следующий шаг накладывает эти ограничения.

Запишем ограничение на среднее внутри ячейки. Для свободных пикселей нужно найти общий сдвиг s:

Σᵢ∈F C₀,₂₅₅(cᵢ − s) + Σᵢ∈P bᵢ = Q.

F - свободные пиксели ячейки, P - защищённые; cᵢ - яркость после усиления деталей, bᵢ - исходная яркость защищённого пикселя, Q - требуемая сумма яркостей в ячейке.

Проекция возвращает среднюю яркость ячеек и ограничивает диапазон пикселей. При последующем округлении в uint8 средние могут немного измениться.

Проекция возвращает среднюю яркость ячеек и ограничивает диапазон пикселей. При последующем округлении в uint8 средние могут немного измениться.

Теперь возвращаюсь к сохранённой карте осветления. Обозначу её H, а сглаженную версию - H̃. Изображение после коррекции контраста умножаю на exp(H̃ − H): так меняется плавность поправки, а сама фактура не размывается. Порядок здесь важен: это действие выполняется после проекции, перед тональным преобразованием.

В центре - множитель exp(H̃ − H). Сглаживается поле поправки; фактура асфальта напрямую гауссовым фильтром не размывается.

В центре - множитель exp(H̃ − H). Сглаживается поле поправки; фактура асфальта напрямую гауссовым фильтром не размывается.

Получается чуть сложнее, чем просто умножить детали на коэффициент. Зато понятно, что именно этой операции разрешено менять.

Тональное преобразование и ограничение светлых деталей

Сначала делю яркости на 13 диапазонов по квантильным порогам. Для каждого диапазона задаётся свой множитель: так можно по-разному преобразовать тёмные, средние и светлые участки. Для 8-битного изображения это удобно представить таблицей соответствия на 256 значений.

У исходного и защищённого вариантов согласованные пороги. Иначе небольшая локальная поправка контраста могла бы перестроить тональное разбиение всего кадра.

После тонального преобразования ограничиваю светлые детали - локальные отклонения яркости выше фона. Алгоритм сравнивает их относительную амплитуду с опорной статистикой и при необходимости уменьшает её. Этот шаг может ослабить светлую деталь, но не усиливает её.

Так тональное преобразование задаёт общий вид изображения, а ограничение светлых деталей сдерживает локальные выбросы яркости.

Обе контрастные ветки используют одну таблицу тонального преобразования. Локальное ограничение усиления поэтому не перестраивает тональное разбиение всего кадра.

Обе контрастные ветки используют одну таблицу тонального преобразования. Локальное ограничение усиления поэтому не перестраивает тональное разбиение всего кадра.

Внутренний расчёт 14. Ограничиваем положительную фактуру

Внутренний расчёт 14. Ограничиваем положительную фактуру

4. Обрабатываем разметку

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

Обрабатываем разметку: реальный кадр и неизменный фрагмент

Обрабатываем разметку: реальный кадр и неизменный фрагмент

Как это считается: маска, проверка границы, бюджет яркости и надёжность

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

Если считать её обычным асфальтом, оценка освещения начнёт компенсировать различие материалов. Разметка станет темнее, а соседние участки получат поправку, на которую мы не рассчитывали.

Для этого есть отдельный блок обработки разметки. Маска строится классическими методами: используются интенсивность, положительное отклонение от локального фона, модуль лапласиана, GrabCut и связные компоненты. Затем идут проверки по статистике серии, областям и границам.

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

Внутренний расчёт 15. Получаем маску разметки

Внутренний расчёт 15. Получаем маску разметки

Проверяем, есть ли на границе отдельная ступень

Вот этот момент мне кажется интереснее обычного порога яркости.

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

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

Пиксели контура зависимы, поэтому сначала объединяю отклики в блоки 6 × 6 на уменьшенном изображении. Уже по средним блоков сравниваю две модели: дополнительной положительной ступени нет или она есть.

В коде используется критерий с BIC-подобным штрафом:

E₀ = Σrᵢ²
E₁ = Σ(rᵢ − r̄)²
B = n · ln(E₀ / E₁)

поддержка границы:
    n > 1,
    r̄ > 0,
    B > ln(n).

rᵢ - отклик блока контура после вычитания плавного перепада, r̄ - средний отклик, n - число блоков. E₀ и E₁ - суммы квадратов ошибок двух моделей, B - выигрыш от добавления ступени. Знаменатели защищены от нуля. Смысл проверки - понять, достаточно ли улучшилась модель, чтобы оправдать дополнительную ступень. Это эвристика выбора модели; вероятностью принадлежности разметке её считать нельзя. И она помогает принимать дополнительные области, а не заменяет все остальные проверки маски.

Для границы на следующем рисунке 30 блоков контура дают средний избыток 0,541. Выигрыш B = 55,065 превышает штраф ln(30) = 3,401: дополнительная ступень проходит проверку.

Внутренний расчёт 16. Проверяем ступень на границе

Внутренний расчёт 16. Проверяем ступень на границе

Сколько яркости можно добавить

Найденную разметку тоже нельзя бездумно залить белым. Бюджет высветления оцениваю по окружению без самой маски. Область поиска расширяется, пока не наберётся достаточно поддерживающих пикселей. Дополнительно смотрю на надёжность компоненты: слабое отличие от окружения - меньший подъём.

Внутренний расчёт 17. Поднимаем разметку в пределах бюджета

Внутренний расчёт 17. Поднимаем разметку в пределах бюджета

Надёжность компоненты уменьшает разрешённый подъём. Поэтому справа часть пикселей темнее, чем слева: сокращается добавка, а не яркость исходного изображения.

Надёжность компоненты уменьшает разрешённый подъём. Поэтому справа часть пикселей темнее, чем слева: сокращается добавка, а не яркость исходного изображения.

Результат обработки разметки сохраняю отдельно. Ниже он вернётся при сборке веток и при финальной защите разметки.

5. Собираем результат двух веток

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

Собираем результат двух веток: реальный кадр и неизменный фрагмент

Собираем результат двух веток: реальный кадр и неизменный фрагмент

Как это считается: признаки, статистический вес, морфология поля и смешивание веток

Даже после всех ограничений локальная нормализация может переусилить необычный участок поверхности. Для таких мест в ветке выравнивания освещения есть отдельная статистическая проверка.

В ней используется заранее собранный банк признаков с 24 кадров. Это отдельная опора для оценки необычных участков; её не нужно путать со светлыми кадрами текущей серии, по которым выбирается целевая яркость. На текущем изображении считаются локальные квантили в окнах 16 × 16 с шагом 4 пикселя, яркость окружающего кольца и его зернистость.

Два основных отклика:

τ = ln[1 + max(q₅₀ − q₁₀, 0) / (max(q₉₀ − q₅₀, 0) + 1)]
d = arsinh[(μ − q₅₀) / (1.5 · ν + 1)].

q₁₀, q₅₀ и q₉₀ - 10-й, 50-й и 90-й процентили яркости в окне. μ - средняя яркость окружающего кольца, ν - его зернистость: средний модуль разности изображения и его гауссово сглаженной версии с σ = 2. Кольцо получается из окна 65 × 65 с исключённым центром 33 × 33. arsinh - обратный гиперболический синус, который сжимает большие значения.

Первый признак, τ, сравнивает разброс яркости ниже и выше медианы. Чем сильнее разброс в тёмную сторону относительно светлой, тем он выше. Второй, d, показывает, насколько участок темнее окружения, с поправкой на зернистость. Оба признака считаются по изображению после коррекции камеры; корректировать по ним будем яркостную основу.

Внутренний расчёт 19. Оцениваем разброс яркости в тёмную и светлую стороны

Внутренний расчёт 19. Оцениваем разброс яркости в тёмную и светлую стороны

Внутренний расчёт 20. Считаем глубину затемнения

Внутренний расчёт 20. Считаем глубину затемнения

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

На выходе получаю числовую меру аномальности и перевожу её в ограниченный вес:

w = z / (z + 1).

Здесь z ≥ 0 - мера аномальности, w - сила подавления локального переусиления. Значение от 0 до 1 выглядит знакомо, но это всё ещё не вероятность тени. Это сила воздействия на поле усиления.

Внутренний расчёт 21. Переводим статистическую аномальность в вес

Внутренний расчёт 21. Переводим статистическую аномальность в вес

Пусть b - текущий яркостный результат, I - изображение после коррекции камеры. Дальше делаю следующее:

g = ln[max(b, 1) / max(I, 1)]
gₒ = g ∘ E
δ = max(g − gₒ, 0) · w
b′ = b · exp(−δ).

Знак ∘ обозначает морфологическое открытие, E - эллиптический элемент 23 × 23. gₒ - поле после открытия, δ - подавляемая часть, b′ - скорректированная яркостная основа. Морфологическое открытие даёт более спокойную основу поля. Положительные локальные выступы относительно неё сокращаются с весом w. Защищённые области исключаются из поправки.

Внутренний расчёт 22. Подавляем выступы поля усиления

Внутренний расчёт 22. Подавляем выступы поля усиления

Здесь морфология работает с полем усиления. Непосредственно «открывать» дорожную фактуру вместе с мелкими деталями не требуется.

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

Внутренний расчёт 23. Сглаживаем поле поправки во второй ветке

Внутренний расчёт 23. Сглаживаем поле поправки во второй ветке

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

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

Во второй ветке также ограничиваются светлые детали. Дополнительное усиление контраста здесь не применяется.

Во второй ветке также ограничиваются светлые детали. Дополнительное усиление контраста здесь не применяется.

Дальше подмешиваю сохранённый результат обработки разметки. На пикселях, которые блок разметки сделал светлее своего входа, вес смешивания равен единице; около них переход сглажен. Обозначу этот вес p, результат выравнивания освещения C, а результат обработки разметки M:

J = ⌊C · (1 − p) + M · p⌉.

J - собранное изображение; скобки ⌊ · ⌉ здесь означают округление до ближайшего целого.

Внутренний расчёт 26. Возвращаем обработанную разметку

Внутренний расчёт 26. Возвращаем обработанную разметку

Банк хранит опорные статистики дорожной поверхности. Вместе с коррекцией камеры он помогает адаптировать обработку к серии без обучения нейросети.

6. Согласуем остаточную границу

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

Согласуем остаточную границу: реальный кадр и неизменный фрагмент

Согласуем остаточную границу: реальный кадр и неизменный фрагмент

Как это считается: сила коррекции, поддержка окружения, согласование и защита разметки

Основной перепад яркости уже стал слабее, но на границе может остаться заметный след. Для него есть ещё одна локальная поправка.

Вычисляю разность двух яркостных преобразований сглаженного кадра с сохранением знака. Здесь масштаб σ = 16 пикселей; у второй ветки есть дополнительное внутреннее сглаживание.

Сила коррекции растёт плавно на интервале отклика 4-12 DN:

t = C₀,₁[(max(r, 0) − 4) / 8]
α = t² · (3 − 2t).

r - отклик разности двух преобразований, t - его нормированное значение, α - сила коррекции. Кубическая функция t² · (3 − 2t) обеспечивает плавное включение поправки вместо резкого скачка на пороге. На защищённых пикселях α обнуляется.

Карта α задаёт силу согласования остаточной границы. Окончательное воздействие также зависит от поддержки окружения и доступного диапазона яркости.

Карта α задаёт силу согласования остаточной границы. Окончательное воздействие также зависит от поддержки окружения и доступного диапазона яркости.

Опорные яркость и фактуру беру из допустимого окружения с σ = 48. Если допустимых пикселей мало, воздействие дополнительно ослабляется. Изменение фактуры ограничено коэффициентами 0,65-1,5, сдвиг яркости - диапазоном −80…80 DN. Широкое сглаживание для оценки опоры выполняется на уменьшенной вдвое сетке.

Внутренний расчёт 28. Учитываем поддержку опорного окружения

Внутренний расчёт 28. Учитываем поддержку опорного окружения

В сокращённом виде получается:

J′ = J + β · [Δμ + (k − 1) · (J − μₗ)].

J - изображение до согласования, J′ - после него, β - сила коррекции с учётом поддержки окружения, Δμ - сдвиг яркости, k - коэффициент изменения фактуры, μₗ - локальная оценка фона: медианная фильтрация окном 11 × 11 с последующим гауссовым сглаживанием при σ = 3.

После ограничения диапазона и округления защищённые пиксели и пиксели с нулевой силой коррекции сохраняют прежние значения.

Внутренний расчёт 29. Согласуем яркость и фактуру

Внутренний расчёт 29. Согласуем яркость и фактуру

В самом конце ветки выравнивания освещения действует вот такое условие:

Iитог(x, y) = max(J′(x, y), M(x, y))  на маске разметки,
Iитог(x, y) = J′(x, y)               вне маски.

То есть уже обработанная разметка не должна стать темнее результата коррекции контраста и обработки разметки. Принудительного значения 255 здесь нет.

Условие легко проверить. А вот правильность самой маски - отдельная история: ошибочно выбранный пиксель оно тоже будет защищать.

Внутренний расчёт 30. Накладываем нижний предел разметки

Внутренний расчёт 30. Накладываем нижний предел разметки

Общий результат и второй пример

Теперь посмотрим на весь путь обработки и на другой снимок с широкой тенью и более мягким переходом.

Посмотреть этот кадр целиком: сводный обзор и увеличенный участок

Поперечная тень на фактурном асфальте

Локальная коррекция яркости включена. Четвёртая и пятая панели относятся к связанным веткам, а не к двум последовательным фильтрам.

Локальная коррекция яркости включена. Четвёртая и пятая панели относятся к связанным веткам, а не к двум последовательным фильтрам.

Здесь хорошо видно, ради чего вообще затевалась обработка: широкая поперечная тень с утолщением справа занимает заметную часть кадра. Есть и полоса разметки, и неоднородная фактура асфальта.

На первых двух панелях тень остаётся. Коррекция компенсирует особенности камеры, но сама по себе задачу освещения не решает. На третьей перепад уже сильно слабее; после коррекции контраста и тональной обработки гораздо заметнее мелкие детали. В финале поперечная тень подавлена, хотя её остаточный след местами различим по фактуре.

Теперь тот же участок крупнее:

Один участок кадра на четырёх этапах

Один участок кадра на четырёх этапах

На увеличении хорошо видно, что результат не сводится к прибавлению константы яркости: меняется и контраст фактуры. При этом каждую появившуюся тёмную линию объявлять трещиной рано - семантикой дефектов занимается следующий алгоритм.

Широкая тень с мягким переходом

Шесть состояний снимка с мягкой границей тени

Шесть состояний снимка с мягкой границей тени

Здесь тень тянется вдоль верхнего края, а переход к освещённому асфальту заметно мягче. Разметки нет, поэтому можно отдельно посмотреть, что происходит с освещением и фактурой.

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

Участок мягкой границы тени в исходном снимке и в результате

Участок мягкой границы тени в исходном снимке и в результате

На увеличении тёмная область уже не скрывает фактуру так сильно. При этом по обе стороны бывшей границы текстура выглядит немного по-разному: выравнивание освещения не делает весь асфальт одинаковым.

Маска разметки и карта коррекции - отдельно

Маски разметки и карты силы коррекции для двух кадров

Маски разметки и карты силы коррекции для двух кадров

Белое слева означает: финальный пиксель должен быть не темнее результата ветки обработки разметки. Жёлтое справа означает большую заданную силу локального согласования.

У второго снимка маска разметки пустая: белой краски в кадре нет. А карта согласования выделяет полосу вдоль мягкой границы тени.

Если внутри широкой тени справа почти ничего не подсвечено - это нормально. Основная нормализация освещения уже была раньше. Эта карта реагирует на остаточные перепады, а не закрашивает всю площадь тени.

И маски на двух панелях не обязаны дополнять друг друга. При расчёте α исключаются исходные защищённые области и пиксели, уже осветлённые при обработке разметки. Нижний предел по полной маске разметки накладывается позже.

Для тех, кто хочет рассмотреть детали, оставлю все этапы в полном разрешении:

Состояние изображения

Поперечная тень на фактурном асфальте

Широкая тень с мягким переходом

RAW

Полный размер

Полный размер

После коррекции камеры

Полный размер

Полный размер

Яркостная основа

Полный размер

Полный размер

Коррекция контраста + обработка разметки

Полный размер

Полный размер

Выравнивание освещения до согласования границ

Полный размер

Полный размер

Итог

Полный размер

Полный размер

Третья панель на рисунках показывает яркостную основу до коррекции контраста и тональной обработки.

Как алгоритм выполняет расчёты на CPU

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

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

1. Окно сдвинулось. Зачем сортировать всё заново?

Для оценки локальной яркости нужны квантили - значения, ниже которых лежит заданная доля пикселей окна. Алгоритм находит их по гистограмме окна 16 × 16: 256 счётчиков показывают, сколько раз встретилась каждая яркость от 0 до 255.

При сдвиге на четыре пикселя 192 из 256 пикселей остаются прежними. Меняются только края:

Сдвиг окна: ушедшие, общие и новые пиксели

Сдвиг окна: ушедшие, общие и новые пиксели

Из гистограммы вычитаются пиксели ушедших столбцов и добавляются пиксели пришедших. Затем по накопленным счётчикам находятся квантили. Общая часть окна уже учтена - заново собирать её статистику не нужно.

2. Изменился участок. Зачем пересчитывать весь кадр?

В блоке коррекции контраста есть два близких расчёта: один использует найденное усиление, второй дополнительно ограничивает его по локальной дисперсии. После этого нужно вернуть средние яркости ячеек 8 × 8 - тот самый внутренний расчёт 11.

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

От изменённого усиления к области повторного расчёта

От изменённого усиления к области повторного расчёта

На схеме усиление изменилось в одном блоке, но пересчитать нужно девять: интерполяция поля поправки может затронуть соседей. Поэтому отмеченная область на сетке расширяется окном 3 × 3. Если затронутых блоков много, алгоритм выбирает полный проход по кадру.

3. Пиксель всё равно станет 80. Зачем уточнять дальше?

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

Допустим, после оценки границ известно: значение пикселя лежит между 80,10 и 80,30. Где бы оно ни оказалось внутри этого интервала, после округления получим 80. А вот интервал от 80,45 до 80,55 пересекает границу округления - здесь такой гарантии нет:

Когда интервал гарантирует один результат округления, а когда нужен полный поиск

Когда интервал гарантирует один результат округления, а когда нужен полный поиск

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

Остальные детали реализации: память и Numba

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

Для разметки используются 64-уровневые гистограммы. Они участвуют в расчёте бюджета высветления. Эти циклы и часть целочисленных редукций компилируются Numba.

Затронутые блоки упаковываются перед пересчётом. Они собираются в массив высотой 8 пикселей, проходят модуль проекции, затем результат возвращается на места. Порядок суммирования float32 фиксирован, чтобы результат округления оставался воспроизводимым.

Промежуточная оценка зернистости хранится в uint8. Абсолютная разность пикселя и медианного окружения лежит в диапазоне 0-255. Разность и результат следующей медианной фильтрации сохраняют этот тип и порядковую статистику. Переход во float32 и умножение на 1.4826 происходят после фильтрации. На элемент промежуточного массива нужен один байт.

Ещё пара примеров

Два примера до и после обработки

Два примера до и после обработки

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

После обработки освещение становится ровнее, фактура заметнее, но часть границ остаётся. Насколько хорошо подавится тень, зависит от самой сцены.

Для нашей следующей задачи - обучения моделей поиска трещин и заплаток - такой подготовки уже хватает. Результаты поиска дефектов получаются хорошими: по сравнению с RAW прибавка к качеству существенная, хотя и не в разы.

Полное разрешение: первый пример - RAW и результат; второй пример - RAW и результат.

Где этот подход заканчивается

Пайплайн завязан на конкретную съёмку: монохромные 8-битные кадры, заданные размеры и профили коррекции. Другая камера или 16-битный сигнал потребуют пересмотра этих профилей и масштабов параметров.

Ещё нужна серия со светлыми опорными кадрами. Статистика собирается по папке, поэтому здесь показана пакетная обработка. Состав серии тоже имеет значение: поменяем опорные кадры - может поменяться результат для того же самого снимка.

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

Нейросети здесь достаётся следующая задача - искать трещины и заплатки на подготовленных кадрах. А для работы с освещением хватило локальной статистики, фильтров и ограничений на коррекцию. Так что запятая остаётся на своём месте: «Обучать нельзя, фильтровать».

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.