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

  • Генеративный ИИ на базе мультимодальных языковых моделей отлично распознаёт и объясняет содержимое изображения, но способен выявлять различия лишь в тех аспектах, на которых он был явно обучен.

  • Эта задача обычно решается с помощью сверточной нейронной сети (CNN), сравнивающей небольшие фрагменты изображений (область 9×9 пикселей) вместо отдельных пикселей.

  • Решения на CNN можно реализовать с использованием инструментов TensorFlow, PyTorch и Keras API.

  • Дисплеи высокого разрешения могут приводить к ложноположительным срабатываниям из-за смещений более чем на несколько пикселей. Чтобы решить проблему, вместо увеличения размера окна до длины смещения обучайте сеть возвращать булевый флаг равенства и значения x и y вектора смещения.

Сравнение двух изображений на предмет структурных изменений — задача, с которой ИИ неожиданно справляется с трудом. Генеративный ИИ на базе мультимодальных языковых моделей блестяще распознаёт и объясняет содержимое изображения, но способен выявлять различия лишь в аспектах, на которых был явно обучен. Между тем библиотеки сравнения изображений требуют строгого выравнивания — часто на пиксельном уровне — и не терпят даже малейших искажений.

Поиск отличий — актуальная задача в визуальной автоматизации тестирования, когда скриншот нужно сравнить с предыдущей версией или референсным дизайном. В идеале важно определить, какие элементы изменились и как именно. Переместились ли они, изменили масштаб или были заменены? Текущие технологии визуального тестирования страдают одной из указанных проблем. Методы на базе моделей больших языков (LLM) пропускают даже серьёзные проблемы компоновки, если объект ими не распознан. Пиксельные алгоритмы сообщают о крупных различиях, когда произошло лишь незначительное смещение на несколько пикселей.

Визуальный аспект в тестировании ПО

Цель тестирования ПО — обнаруживать отклонения от ожидаемого состояния. Финальный и самый сложный аспект программы — её итоговый вид на экране. Наша зрительная система чрезвычайно гибка: мы замечаем мельчайшие детали и одновременно подсознательно компенсируем значительные сдвиги в положении и окраске. Поэтому формализовать границу между допустимой перестановкой и нарушением ожиданий чрезвычайно трудно. Чтобы избежать этих сложностей, большинство тестов проверяют лишь внутренние технические состояния, например DOM-деревья. Однако по мере роста возможностей ИИ проверка того, как интерфейс будет воспринят человеком, становится всё более практичной.

Состояние ИИ

Рассмотрим следующий пример с двумя картами. Многие из нас в детстве играли в игру «найди 10 отличий», поэтому мы за пару секунд замечаем пропавшую улицу, ведущую прямо в центр города. Поскольку эта задача кажется нам примитивной, трудно поверить, что для алгоритмов компьютерного зрения она очень сложна. Помимо исчезнувшей улицы вся карта была также сдвинута на два пикселя вниз и вправо. Этот, казалось бы, несущественный нюанс ломает все пиксельные алгоритмы — такие как Pixelmatch, resemble.js, Python Pillow и OpenCV. Генеративные ИИ-модели обещают глубокое понимание изображений. Посмотрим, как они справляются с этим примером.

Рисунок 1. Две версии карты. Пропавшую улицу человек находит быстро, а ИИ — нет:

С мультимодальными ИИ-моделями мы можем напрямую загрузить обе картинки и попросить описать различия. Анализ выполнялся с использованием Claude 3.7, Claude 4, Gemini 2.5 pro, ChatGPT-o3 и ChatGPT-4o. Claude и Gemmini сразу ошибаются и отвечают, что существенных изменений нет. ChatGPT достаточно «умен», чтобы ошибиться только после генерации кода на Python, который анализирует пару изображений несколькими популярными графическими библиотеками. Из-за небольшого рассинхрона пикселей между двумя картами все пиксельные алгоритмы различий фиксируют изменения цвета на каждом контуре, из-за чего пропавшая улица видна, но теряется среди множества ложных срабатываний — как показано на рисунке 2. Все перечисленные библиотеки сравнения изображений дают схожие результаты и не способны выделить релевантное изменение.

Рисунок 2: Пиксельные алгоритмы сравнения изображений находят множество ложных срабатываний
Рисунок 2: Пиксельные алгоритмы сравнения изображений находят множество ложных срабатываний

Генеративные ИИ-модели рассуждают об изображениях с впечатляющей детализацией: распознают источник карты, проверяют названия и анализируют цветовую схему. Тем не менее ни одна из моделей не отметила пропавшую улицу. Во всех случаях Claude, Gemmini и ChatGPT явно подтвердили эквивалентность всех маршрутов. В одном из прогонов новейшая модель GPT-o3 подвела итог так: «Существенных изменений не обнаружено», как показано на рисунке 3.

Рисунок 3: Спустя 51 секунду «размышлений» ChatGPT-o3 не сказала о пропавшей улице.
Рисунок 3: Спустя 51 секунду «размышлений» ChatGPT-o3 не сказала о пропавшей улице.

Как это делают люди?

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

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

Повторяя этот процесс по всему изображению, вы начинаете формировать гипотезы — например: «А эта дорога действительно исчезла?» — что побуждает приложить дополнительные усилия для проверки предположения. Человек воспринимает мир гипотезо-ориентированно: у зрительной коры в десять раз больше связей, идущих от коры к таламусу (и, в конечном счёте, обратно к глазу), чем в обратном направлении. Это означает, что мы тратим во много раз больше ресурсов на проектирование и уточнение наших проверок, чем на «чистую» обработку поступающих сигналов.

Описанная процедура — наглядный пример «цепочки рассуждений». Вместо немедленного результата мы осознанно разворачиваем стратегию через итерации, ошибки и уточнения выводов. Именно здесь генеративный ИИ испытывает трудности. Хотя последние достижения позволили моделям рассуждать пошагово в текстовых задачах, перенос той же идеи на двумерный пространственный анализ в нескольких масштабах ещё сложнее. Генеративные модели научились обрабатывать изображения заметно позже, чем чисто текстовые промпты. Решение всех задач, связанных со зрением, вероятно, займёт годы — возможно, дольше, учитывая, что и человек справляется с этим ценой высокой подверженности оптическим иллюзиям.

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

Устойчивость к пиксельным смещениям

При сравнении изображений важно учитывать два разных понятия. Первое — определить, эквивалентна ли пара изображений. Второе — выявить минимальное изменение, необходимое, чтобы они стали эквивалентными. В тестировании ПО оба шага критичны. Первый говорит, провалится ли тест и нужна ли ручная проверка, а второй помогает быстро принять решение. Знание того, что кнопка «просто» переместилась, означает одно; если же она и переместилась, и измен��лась — совсем другое. Без надёжного отчёта о фактическом изменении тестировщикам приходится возвращаться к утомительным ручным сравнениям в стиле «найди 10 отличий».

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

Распространённое решение — использовать свёрточную нейронную сеть (CNN). Обучать сеть на целых парах изображений вычислительно дорого, поскольку реалистичные скриншоты — это миллионы пикселей с высоким разрешением. Задачу можно упростить, сравнивая небольшие сегменты вместо отдельных пикселей. В следующем примере используется область 9×9 пикселей. Этого достаточно, чтобы учитывать небольшие смещения при определении эквивалентности, и одновременно мало для лёгкой нейросети. При 9×9×2 = 162 входных нейронах и одном выходном нейроне требуемая сеть получается минимальной. Поскольку каждый цветовой канал можно обрабатывать отдельно, достаточно обучать на монохромных выборках.

Рисунок 4: Фрагмент 9×9 из пары изображений. Структурную эквивалентность изображений 1 и 3 можно отвергнуть. Для изображений 1 и 2 — нельзя.
Рисунок 4: Фрагмент 9×9 из пары изображений. Структурную эквивалентность изображений 1 и 3 можно отвергнуть. Для изображений 1 и 2 — нельзя.

Обучение такой сети несложно выполнить с помощью TensorFlow (равно как и PyTorch). В следующем примере кода показана эффективная сеть, обученная на 200 тыс. размеченных изображениях в градациях серого. Чтобы использовать все симметрии, объём тренировочных примеров можно увеличить в 16 раз — с учётом трёх осей симметрии и коммутативности двух входных изображений. Процедура обучения по сути задаётся одним вызовом Keras API и завершается за несколько минут на типовом оборудовании. 15 лет назад это было бы передовой научной задачей в области ИИ. Сейчас — это рутинное машинное обучение.

Доступен Colab notebook с инструкциями по обучению и обучающими данными.

Подбор архитектуры, баланс глубины и ширины слоёв, регуляризация и анализ метрик — всё это стандартная инженерная практика в машинном обучении. На курсе Machine Learning. Advanced разбираются эти аспекты на уровне продакшен-пайплайнов, включая оптимизацию нейросетей и работу с фреймворками PyTorch и TensorFlow.

Архитектура нейросети в общих чертах следует оригинальной LeNet образца 1990 года, за исключением того, что применяется к изображению, которое больше окна входа. Поэтому сеть выдаёт не одну метку, а метку для каждой точки. Это решает проблему нашего исходного примера с двумя картами, которые выровнены не идеально. На рисунке 5 показан результат простой свёрточной сети с 162 входными узлами, 8 слоями и в общей сложности 48 211 обучаемыми параметрами. Все обучающие данные и схема сети доступны в Colab notebook.

Рисунок 5: Свёрточная нейронная сеть способна обнаружить отличающийся участок на карте.
Рисунок 5: Свёрточная нейронная сеть способна обнаружить отличающийся участок на карте.

Работа с крупными смещениями

На дисплеях высокого разрешения или при более сложных макетах различия между двумя изображениями могут возникать из-за смещений более чем на несколько пикселей. В задачах автоматизации тестирования такие перестановки обычно приводят к падению тестов. Ручная проверка всех слегка «переформатированных» скриншотов на предмет консистентности — огромный объём работы. Было бы крайне полезно, если бы инструмент на базе ИИ мог корректно определять, произошли ли существенные изменения помимо подобных мелких правок в макете.

Наивная мысль — просто увеличить размер окна нейросети. Допустим, у нас есть сеть, которая умеет компенсировать сдвиги длиной «n», и мы хотим увеличить этот предел в 2 раза. Поскольку дополнительное смещение может происходить в двух измерениях, нам нужно расширить поиск до четырёх различных сегментов исходного размера. Так как мы будем принимать совпадение по любому из этих четырёх сегментов, вероятность ложноположительных результатов возрастает вчетверо. Малые области могут выглядеть одинаково (например, содержать один и тот же тип штриха), но не быть следствием согласованного смещения всей области. Чтобы не терять специфичность, пришлось бы сравнивать области экрана в 4 раза большего размера. В сумме это даёт 16-кратный рост затрат, то есть сложность O(n^4) для сети сравнения изображений, устойчивой к смещениям длины n. Вероятно, это нижняя оценка, потому что на больших областях смещение может быть неравномерным, и придётся учитывать более сложные виды искажений.

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

Рисунок 6: Тогда как детям не составляет труда заметить релевантные отличия, ИИ не справляется с этой задачей.
Рисунок 6: Тогда как детям не составляет труда заметить релевантные отличия, ИИ не справляется с этой задачей.

Возможное решение

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

Ниже приведён псевдокод, реализующий решение задачи соответствия. Он строит карту векторов, указывающих на соответствующие позиции в левом и правом изображениях. Алгоритм рекурсивно вызывает сам себя на уменьшенных копиях. Обученной сети остаётся компенсировать только ошибки, допущенные на более грубом уровне, то есть находить совпадающие признаки в очень небольшой области поиска. Алгоритм использует метод «resize» фреймворка OpenCV для масштабирования изображений вверх/вниз и «remap» для применения смещений, предсказанных на грубых уровнях. Также используется метод cnn_predict для оценки относительного смещения нейросетью. Предсказание содержит два выходных канала — для компонент x и y.

# Возвращает тензор dxy, такой что для каждой пары x,y:
#   img1(y + dxy(y,x,1)/2, x + dxy(y,x,0)/2) соответствует 
#   img2(y - dxy(y,x,1)/2, x - dxy(y,x,0)/2)
def get_correspondence_map(img1, img2):
  assert_equal(img1.shape, img2.shape)
  if img1.shape > window_shape:
    dxy_1 = resize(         # Грубая оценка получается из
      get_correspondence_map( # рекурсивного вызова на уменьшенных изображениях.
        resize(img1, 0.5), 
        resize(img2, 0.5)), 2)    
    dxy_2 = cnn_predict([ # Вызов CNN для предсказания остаточных смещений.

        remap(img1,  dxy_1),  # Каждое изображение сдвигается
        remap(img2, -dxy_1)]) # в сторону другого.
    dxy = 2 * dxy_1 + dxy_2  # Возвращаем сумму грубых и тонких (уточняющих) сдвигов
  else:
    dxy = zeros((img1.height, img1.width ,2)) # Слишком мало: считаем, что выравнивание достигнуто

Этот блокнот Colab показывает полный алгоритм со всеми граничными случаями на Python с использованием OpenCV и предобученной сети, способной распознавать смещения до 3 пикселей из окна 15×11 при 64 741 параметре.

После восстановления карты смещений мы применяем половину эффекта к каждому изображению. Теперь оба изображения «сходятся посередине» и их можно сравнивать стандартными методами. Разумеется, искажение дополнительный алиасинг (артефакты дискретизации), поэтому совпадение не будет «пиксель-в-пиксель». Нейросеть из предыдущего раздела решает эту проблему «из коробки». Все релевантные отличия обнаруживаются; в правом верхнем углу — лишь два ложноположительных срабатывания. Нет нужды говорить, что ни одно из этих тестовых изображений не использовалось на этапе обучения.

Рисунок 7: Простые алгоритмы вычитания различий применимы после компенсации искажений.
Рисунок 7: Простые алгоритмы вычитания различий применимы после компенсации искажений.

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

Заключение

Сопоставление двух графических результатов — ключевая проблема визуального регрессионного тестирования. Обнаружить, что два изображения различаются на каком-то пиксельном уровне, легко; гораздо сложнее кратко сформулировать результат в духе «логотип X сместился на n пикселей». К сожалению, текущие генеративные мультимодальные ИИ-модели с этой задачей не справляются. Они хорошо находят различия, которые можно назвать (например, текстовые значения или порядок кнопок), но не справляются там, где объекты не поддаются именованию — например, эстетические выравнивания, соединения на нерегулярной карте или любые сущности, которым модель не была обучена.

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


Автоматизация визуального тестирования опирается на те же принципы, что и классическое QA: надёжные проверки, воспроизводимость и минимизация ручных ошибок. Понимание того, как устроены фреймворки, от PyTest до Selenium, напрямую влияет на качество подобных систем. Курс Python QA Engineer даёт практику построения автотестов UI и API — тот самый фундамент, на котором держится современное тестирование, включая визуальное.

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