Сегодня на рынке программных продуктов можно найти огромный выбор средств для обработки изображений. Есть и такие гиганты как Adobe Photoshop и просто любительские программы, написанные студентами для курсовых проектов. Поэтому выбор пользователя в этом плане велик. В этой статье я не буду обсуждать программные средства для обработки изображений, аналитических статей на этот счет предостаточно. Тут я хочу рассмотреть очень подробно лишь только один маленький пункт из огромного количества предложенных путей обработки изображений.

Обработка изображений сейчас модное направление в области разработки программного обеспечения. Начать обработку изображения можно уже на стадии фотографирования. По поводу качества такой обработки сказать ничего не могу, но такие возможности фотоаппаратов, как обнаружение лица и других объектов вполне себе работают и причем неплохо. Другое дело – это распознавание лиц, тут пока технологии отстают. Мой ноутбук никак не хочет идентифицировать меня по лицу!

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

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

— По какому критерию производить сравнение?
— Как определить, что пиксели относятся к одной области?

Начнем с первого пункта. Тут я постараюсь описать все довольно просто.

Все изображение состоит из маленьких частиц-пикселей. На сегодняшний день пиксель может быть представлен двумя способами – это с помощью RGB или YCrCb. Это так называемое представление цветного пространства пикселя. Между двумя этими представлениями существуют формулы перехода от одного пространства к другому. На мой взгляд, цветное пространство YCbCr используется только в формате кодирования JPEG, так как позволяет значительно уменьшить хранимую информацию о пикселях. При работе с растровыми изображениями для анализа чаще всего рассматривают формат представления RGB, хотя в последнее время к нему добавили еще одну компоненту – это так называемая "яркость". Таким образом, для рассмотрения алгоритмов в этой статье я выбираю представление пикселей в RGB - пространстве, при необходимости может добавится еще одна, четвертая компонента.

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

Для присоединения пикселя к области существует несколько алгоритмов. Это и алгоритм "k-средних", и "watershed" алгоритм. Смысл алгоритмов определить критерий, границу, по которой будет происходить отбор пикселей.

Для выбора этой границы произведем анализ изображения. Будем оценивать изображение по распределению пикселей и их значению. Наиболее популярный инструмент для разработки и анализа алгоритмов, на мой взгляд, это Matlab. В последних версиях Matlab появился ToolBox для обработки и анализа изображения. Тут реализованы почти все функции, которые необходимы для этого.

Одна из самых популярных функций в Matlab для анализа – это построение гистограмм. Тут надо заметить, что стандартная функция для считывания изображения в память – это imread(). Это функция на выходе формирует трехмерный массив пикселей, где число строк и столбцов равно ширине и высоте изображения соответственно. Итак, после применения этой функции получаем массивы пикселей в пространстве RGB.

Для построения гистограммы требуется одномерный массив, поэтому построим три гистограммы.

Наше изображение

Для построения гистограмм изображений предусмотрен специальный инструмент imhist, который строит распределения пикселей по цветам (256 цветов).

Гистограммы по трем матрицам.

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

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

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

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

1
2
3
4
5
6
7
8
for i=1:1:x
  for j=1:1:y
    if(abs( fgrey(i,j)-position ) < 2)
      fgrey(i,j)=t;
      count=count+1;
    end
  end
end

Здесь position - это значение пикселей, по которым происходит выборка областей. В этом выражении принимается поправка, что отклонение от выбранного значения может составлять 2 пункта.

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

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

Рассмотрим тут его упрощенный вариант.

Будем рассматривать массив пикселей из трех подряд идущих значений. В нашем случае возьмем три пикселя. Найдем их среднее значение.

1
среднее=(f(i-1,j,z)+f(i,j,z)+f(i+1,j,z))/3;

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

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

Функция для нахождения среднего по трем пикселям.

1
2
3
4
5
6
7
8
9
10
for z=1:1:3
  for i=2:3:x-1
    for j=1:1:y
      dev=(f(i-1,j,z)+f(i,j,z)+f(i+1,j,z))/3;
      if(dev~=0)
	break;
      end
    end
  end
end

Функция для выделения областей.

1
2
3
4
5
6
7
8
9
10
11
12
13
for z=1:1:3
  for i=2:3:x-1
    for j=1:1:y
      devnew=(f(i-1,j,z)+f(i,j,z)+f(i+1,j,z))/3;
      if((dev-devnew)<0.1)
	dev=devnew;
	f(i-1,j,z)=0;
	f(i,j,z)=0;
	f(i+1,j,z)=0;
      end
    end
  end
end

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

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

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

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

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

Например, выбирается точка из заданной области point(i,j), далее ищется точка со схожими значения в окружающем пространстве.

1
2
3
4
5
6
7
8
9
10
11
for j=point_j:1:y
  if(F(point_i,j)~=0)
    [point_i,j]=Point(F,point_i-1,j);
  else
    for z=1:1:3
      f(i-2,j,z)=0;
      f(i-1,j,z)=0;
      f(i,j,z)=0;
    end
  end
end

Здесь рассмотрение стоящих рядом точек идет только в одном направлении, сдвигаясь вправо по столбам.

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

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

Теперь рассмотрим реализацию этого примера на языках программирования высокого уровня.

В .NET существует класс Bitmap. Данный класс предоставляет большие возможности для обработки изображений.

Данный класс, также как и Matlab, представляет изображение в виде массива пикселей. Есть лишь одно отличие - вместо размерности матрицы три, в классе Bitmap представляется изображение в виде матрицы размерностью четыре.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public void FindMean(BitmapIm, intlength, charcanal)
{
  int middlepixel = 0;
  middlepixel=Im.GetPixel(460,256).G;
  int count = 0;
  int countofcolor=0;
  countofcolor=Math.Abs(256/length);
  mas_color=new int[countofcolor];
 
  for (int i = 0; i<countofcolor; i++)
    mas_color[i] = 0;
 
  if (canal=='B')
  {
    for (int i = 0; i<Im.Width; i++)
      for (int j = 0; j <Im.Height; j++)
	if (Im.GetPixel(i,j).B!=Color.Black.B)
        {
	  mas_color[Math.Abs(Im.GetPixel(i, j).B / length)]++;
	  count++;
	}
  }
 
  if (canal == 'G')
  {
    for (int i = 0; i<Im.Width; i++)
      for (int j = 0; j <Im.Height; j++)
	if (Im.GetPixel(i, j).G != Color.Black.G)
	{
	  mas_color[Math.Abs(Im.GetPixel(i, j).G / length)]++;
	  count++;
	}
  }
 
  if (canal == 'R')
  {
    for (int i = 0; i<Im.Width; i++)
      for (int j = 0; j <Im.Height; j++)
	if (Im.GetPixel(i, j).R != Color.Black.R)
	{
	  mas_color[Math.Abs(Im.GetPixel(i, j).R / length)]++;
	  count++;
	}
  }
 
  if (canal == 'A')
  {
    for (int i = 0; i<Im.Width; i++)
      for (int j = 0; j <Im.Height; j++)
	if (Im.GetPixel(i, j).A != Color.Black.A)
	{
	  mas_color[Math.Abs(Im.GetPixel(i, j).A / length)]++;
	  count++;
	}
  }
}

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

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

Как видно, выделилась область голубого неба.

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

Пример кода, для обработки голубого канала.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void MarkBlue(BitmapIm)
{
  int metka = -1, count = 0, count_opposite = 0;
  // Color pixel = MarkIm.GetPixel(0, 0);
  for (int i = 0; i<Im.Width; i++)
    for (int j = 0; j <Im.Height; j++)
      if (Im.GetPixel(i, j) == Color.Black)
	//    count++;
 
  count = 0;
  FindMean(Im, 4,'B');
  int maxColor = mas_color[0];
  int maxIndex = 0;
  for (int i = 1; i< 256 / 4; i++)
    if (maxColor<mas_color[i])
    {
      maxColor = mas_color[i];
      maxIndex = i;
    }
 
  //  MessageBox.Show(Convert.ToString(mas_color[20]));
  for (int i = 0; i<Im.Width; i++)
    for (int j = 0; j <Im.Height; j++)
      if (maxIndex> (Im.GetPixel(i, j).B) / 4)
      {
	//  count++;
	Im.SetPixel(i, j, Color.Black);
      }
 
  for (int i = 0; i<Im.Width; i++)
    for (int j = 0; j <Im.Height; j++)
      if (Im.GetPixel(i, j) == Color.Black)
	count++;
}

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

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

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

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