Пример сегментации изображений средствами PHP

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

Итак, рассмотрим исходные данные

032d2e6ade361288f4a5cc2343461910

Прямоугольные картинки разного размера и пропорций, с белым фоном, на которых изображены фигурки киндер сюрприза и water mark, который иногда касается фигурок. Задача — получить картинки фигурок без water mark’a

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

Интерация 1. Выбор не белых областей

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

99cfe3aa54861f2884b8de03cdfc1c8c

PHP код

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

Закрасим теперь полученные области на исходном изображении.

b59a7789c536e46878f64f88ade5bf5a

Как видно сейчас наш алгоритм цепляет кусочки мусора (поэтому в вышеприведенном коде и есть фильтрация по цвету)

Ошибочный подход, попытка выделения граничных областей сегментов

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

3a9a6d7f81f4aafe5232f8994c87708a

Шлифуем

a29d0ac913d0d7d953dc378ac35d76aa

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

f56def7fe58870cd1c6810032ec9fa99

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

70cadcfd47e1838ee17861e4bf61cac8

Со стороны человеческого взгляда эти сегменты уже можно однозначно разделить, но со стороны нашего алгоритма это все еще массив единичек и ноликов, а так же массив ноликов и единиц – только границ сегментов (но сейчас это все еще 1 массив и основной вопрос в том, как разделить его в соответствии с сегментами).
На исходном изображении можно видеть явные белые пробелы между сегментами, попробуем разделить сегменты по принципу: «Если у нас есть большие разрывы (больше чем N) между закрашенными сегментами, то делим пиксели в основном массиве по левую и правую сторону от разрыва».

cca225aa867801b670f8be7055fdb6ad

1828f7e6a5aa7fe8099891458920ea391

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

Метод разрастания областей

Краткое описание дано выше (в начале статьи), поэтому приступим сразу к делу.

4c3bc9e9bf47e9f922166faed2a796933

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

e9d8f1943ee73759cd50c18bfc25fc04

PHP код

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

88da4bc577efbf5a0960811cd125f4a52

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

e316c711d69a839b22be87f4dc58d20b3

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