5.20. Регрессия и сходство

Ещё два измерения в классе Image обобщают изображение как нечто иное, чем распределение значений пикселей. Линейная регрессия порогированных пикселей даёт приложению линию, на которую оно может реагировать – классический вход для робота, следующего по линии. Измерение сходства даёт приложению одно число, описывающее, насколько похожи два изображения – естественный вход для эталонного регрессионного теста или детектора грубых изменений.

5.20.1. Линейная регрессия

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

get_regression() выполняет это приближение. Он принимает ту же форму кортежа порогов, что используют binary() и find_blobs(), определяет каждый пиксель, совпадающий с порогом, и возвращает единый результат line, описывающий линию наилучшего приближения через эти пиксели:

line = img.get_regression([(0, 60)])
if line:
    img.draw_line((line.x1(), line.y1(),
                   line.x2(), line.y2()),
                  color=(255, 0, 0))

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

Результат line несёт конечные точки, обрезанные до прямоугольника изображения (x1, y1, x2, y2), длину и величину линии (length, magnitude) и геометрическое описание линии в полярной форме (theta, rho) – угол линии от горизонтали и её перпендикулярное расстояние от начала координат. Полярная форма – это то, что обычно нужно контуру управления: theta сообщает роботу, в какую сторону наклонена линия, rho сообщает, где линия пересекает изображение, а контур обратной связи по этим двум удерживает робота по центру линии.

Несколько именованных аргументов настраивают устойчивость и стоимость. x_stride и y_stride пропускают пиксели во время приближения – большие шаги удешевляют регрессию ценой приближения по меньшему числу пикселей. area_threshold и pixels_threshold отклоняют линии, за которыми стоит недостаточно совпадающих пикселей. target_size масштабирует вход до меньшего размера перед приближением – регрессия работает быстрее на суррогате изображения 80 на 60 без значительной потери точности направления линии.

Если приемлемую линию приблизить не удалось – если порог не совпал ни с одним пикселем или совпал с узором, который не похож на линию – метод возвращает None. Настоящий код следования по линии защищает каждый вызов get_regression() проверкой на None, прежде чем обращаться к атрибутам линии.

5.20.2. Сходство изображений

Другой вид измерения: вместо вопроса «что содержит изображение?» задаётся вопрос «насколько похожи эти два изображения?». Операция, к которой следует обратиться – get_similarity(), которая вычисляет индекс структурного сходства (SSIM) между исходным изображением и опорным изображением.

s = img.get_similarity(reference)
print(s.mean, s.stdev)

SSIM – это стандартная метрика сходства изображений, используемая повсеместно в обработке изображений, потому что она ведёт себя так, как ведёт себя человеческая интуиция о сходстве – небольшой сдвиг или небольшое изменение яркости слегка снижают оценку, тогда как крупное структурное изменение (отсутствующий объект, другая сцена) снижает её резко. Оценка варьируется от -1 до +1: +1 означает, что два изображения идентичны, 0 означает, что они не связаны, а -1 означает, что они структурно противоположны. Возвращаемый объект similarity предоставляет среднее SSIM по изображению, плюс стандартное отклонение, min и max оценок по плиткам.

Для сравнения, где малое число лучше, чем большое – регрессионного теста, который должен сообщать ноль при «ничего не изменилось» и расти по мере накопления изменений – флаг dssim=True возвращает структурную несхожесть: среднее SSIM, вычтенное из 1, так что возвращаемое значение равно 0.0 для идентичных изображений и растёт по мере их различия.

5.20.3. Сценарии использования SSIM

Два распространённых применения:

Эталонное регрессионное тестирование. Тестовый фреймворк захватывает опорный кадр при заведомо хороших условиях и сохраняет его как эталонное изображение. Последующие тестовые прогоны выполняют захват при тех же условиях и сравнивают с эталонным изображением с помощью SSIM. Оценка выше некоторого порога (0.95 или 0.98 в зависимости от допуска) – это успех; ниже – провал. Тестовому фреймворку не нужно знать, что изменилось – оценка SSIM является сигналом.

Обнаружение грубых изменений. Приложение, которому нужна более грубая версия разности кадров – та, что игнорирует малые изменения яркости, но реагирует на крупные структурные изменения – может использовать SSIM относительно опорного кадра вместо попиксельной difference(), за которой следует порог. SSIM менее чувствителен к дрейфу освещения, чем попиксельная разность, что делает его лучшим выбором, когда цель – обнаружить, что «сцена выглядит существенно иначе», а не «изменился какой-либо отдельный пиксель».

Оба применения используют один и тот же вызов – img.get_similarity(reference) – и срабатывают по порогу возвращаемой оценки. Разница лишь в том, высок ли порог (регрессионный тест, ищущий почти идентичное совпадение) или низок (обнаружение изменений, ищущее любое крупное структурное изменение).

5.20.4. Форма преобразования и сравнения

Полезная тонкость: get_similarity() принимает те же параметры x, y, x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint и transform, что и draw_image(). Опорное изображение позиционируется, масштабируется и преобразуется этими параметрами перед выполнением сравнения SSIM.

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