Распознавание образов - применение на практике


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

В приведенный алгоритм встроен ряд простых базовых алгоритмов работы с данными: волновой алгоритм поиска пути, вычисление максимума величины, а также встроенные в Delphi и Pascal функции: инициализация генератора случайных чисел Randomize, получение квадратного корня sqrt, выделение компонент цвета GetRValue, GetGValue, GetBValue.

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

Описание переменных и констант:

const
  min_i = 1;
  max_i = 2;
  r_index = 1; // индексы для работы с массивом - маской
  g_index = 2;
  b_index = 3;
  max_arr_x = 2001; // максимальные размеры обрабатываемого изображения
  max_arr_y = 1701;
 
var
  aPic : array[-1..max_arr_x, -1..max_arr_y] of byte; массив волны
  BitmapBig : TBitMap; - объект для хранения изображения
  rgb_value: array[1..3, 1..2] of byte; - маска критерия отбора пикселей
                                          по цвету
 
  i, j, rnd, x : integer; - вспомогательные переменные, счетчики циклов
  myColor : TColor; - храним текущий пиксель
  r_color, g_color, b_color : byte; - составляющие цвета текущего пикселя
  co1, co2 : integer; - нужны для следующей переменной
  my_percent : integer; - отношение всего изображения к выбранной части
  max_wave : integer; - максимальное значение волны
  img_x, img_y : integer; - размеры картинки
  r_sqrt : integer; - для определения квадратного корня от большей стороны
 

Формальное описание алгоритма:

Шаг номер 1 - формируем критерии выбора точек

Шаг номер 2 - по маске зажигаем точки матрицы

Шаг номер 3 - пускаем волну

Шаг номер 4 - анализируем собранные данные

 

 

Шаг номер 1 - формируем критерии выбора точек

  Randomize;  // включаем генератор случайных чисел.
  // вводим диапазоны на отбор цвета тела. 
  // используя другие значения, можно настроиться на поиск,
  // например, синего неба или зеленого леса
  rgb_value[r_index, min_i] := 180;
  rgb_value[r_index, max_i] := 250;
 
  rgb_value[g_index, min_i] := 140;
  rgb_value[g_index, max_i] := 220;
 
  rgb_value[b_index, min_i] := 120;
  rgb_value[b_index, max_i] := 210;
 
  // инициализируем вспомогательные переменные
  img_x := BitmapBig.Width;
  img_y := BitmapBig.Height;
  if img_x < img_y
  then r_sqrt := round(abs(sqrt(img_y)))
  else r_sqrt := round(abs(sqrt(img_x)));
 
  co1 := img_x*img_y;
  co2 := 0;
  max_wave := 0;
 

Шаг номер 2 - по маске зажигаем точки матрицы

  for i := 0 to img_x-1 do
    begin
      for j := 0 to img_y-1 do
        begin
          // берем цвет текущего пикселя
          myColor := BitmapBig.Canvas.pixels[i,j];
          r_color := GetRValue(MyColor);
          g_color := GetGValue(MyColor);
          b_color := GetBValue(MyColor);
          x:=(r_color+g_color+b_color)div 3; //средний серый цвет пикселя
 
          // сопоставляем значение цвета пикселя с некоторым условием
          // случайная величина нужна для простого усреднения результатов
          rnd := random(10)+1;
          if
             (r_color > (rgb_value[r_index, min_i]-rnd)) and
             (r_color < (rgb_value[r_index, max_i]+rnd)) and
             (g_color > (rgb_value[g_index, min_i]-rnd)) and
             (g_color < (rgb_value[g_index, max_i]+rnd)) and
             (b_color > (rgb_value[b_index, min_i]-rnd)) and
             (b_color < (rgb_value[b_index, max_i]+rnd)) and
 
             (
              ((r_color < x-2) or (r_color > x+2)) and
              ((g_color < x-2) or (g_color > x+2)) and
              ((b_color < x-2) or (b_color > x+2))
             )
             and
             (
              ((b_color < g_color-4) or (b_color > g_color+4))
             )
 
            then
              begin
                // этот пиксель попадает под критерий отбора
                Inc(co2);
                aPic[i,j] := 1;
              end
            else
              begin
                // этот пиксель не попадает под критерий отбора
                aPic[i,j] := 0;
              end;
        end;
      my_percent := Round((co2/co1)*100);
    end;

В результате массив aPic содержит "рисунок" искомого объекта.

 

Шаг номер 3 - пускаем волну

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

  for i := 0 to img_x-1 do
    begin
      for j := 0 to img_y-1 do
        begin
          x := aPic[i,j];
          if x > 0
            then
              begin
                if x >250 then x := 250;
                if aPic[i-1,j-1] = 1 then
                  begin
                    aPic[i-1,j-1] := x +1;
                    if x > max_wave then max_wave := x+1;
                  end;
                if aPic[i+1,j-1] = 1 then
                  begin
                    aPic[i+1,j-1] := x +1;
                    if x > max_wave then max_wave := x+1;
                  end;
              end;
        end;
    end;

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

     7**
     *5*
     1**

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

 

Шаг номер 4 - анализируем собранные данные

  // my_percent - это некоторая величина отношения общего размера
  // картинки к выбранному изображению в ходе шага номер 2
 
  if ((max_wave > my_percent) and
      (my_percent>7) and
      (max_wave>r_sqrt)
     )
      or
     (my_percent>25)
      or
     (
      (max_wave>r_sqrt) and
      (my_percent>6)
     )
     then
      // ShowMessage ('Нашли искомое изображение');
    else
      // ShowMessage ('Изображение не удовлетворяет критериям поиска');
 

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

Этот алгоритм можно оформить как функцию и встроить ее вызов в процедуру сканирования каталогов. Таким образом, перед нами простой и быстрый инструмент поиска картинок на компьютере. Работающий пример алгоритма реализован в SP-Мониторе (ссылка ведет на страницу программы).

Возможное развитие данного алгоритма - использование различных направлений волны:

справа - налево, сверху - вниз

справа - налево, снизу - вверх

слева - направо, снизу - вверх

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