Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОСИ / АСУ_Меркулова_Обработкаизобр_ Методичка к курсовому.docx
Скачиваний:
37
Добавлен:
03.03.2016
Размер:
216.12 Кб
Скачать

Фильтрация

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

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

Методы выделения границ

Ниже будут рассмотрены методы (фильтры) выделения границ (контуров) изображения. При этом может быть использовано ранее введённое понятие апертуры фильтра. Если в описании используется некая матрица, индексы элементов в которой расставлены отлично от естественного порядка индексов в массиве точек, то при реализации удобно пользоваться рабочим массивом, который предварительно заполняется значениями точек текущего рабочего окна обрабатываемого изображения таким образом, чтобы индексы элементов в рабочем массиве соответствовали описанию метода.

Все методы выделения границ работают с яркостью точки, то есть со значением, полученным из значений трёх цветовых каналов по известной формуле. Однако для этого вовсе необязательно предварительно преобразовывать всё изображение к оттенкам серого, достаточно лишь получать значение яркости в тот момент и для той точки, с которой идёт работа, а полученное в результате преобразований значение повторять по трём каналам. И ещё: нельзя забывать о необходимости коррекции результата (0..255).

Метод Кирша работает с двумерной апертурой 3х3 следующего вида:

Сначала в цикле находятся все значения переменных Si и Ti, где i изменяется от 0 до 7, по приведённым выше формулам, в которых «(+)» означает сложение по модулю 8, для которого может быть использована следующая функция:

int SumMod8(int x, int y)

{

int summ = 0;

summ = x + y;

while (summ > 7)

{

summ = summ - 8;

}

return summ;

}

После находятся значения модуля разности для каждого i от 0 до 7 и значение максимума среди этих модулей:

Возможно, для обеспечения наблюдаемости потребуется повышение порога яркости сложением эдак с числом 100. Окончательное значение F' заносится в элемент F, после чего рабочее окно сдвигается на один элемент влево (далее – слева направо и сверху вниз).

Реализация фильтра будет иметь следующий вид:

object Kirsh(Bitmap bmp)

{

Bitmap rezultImage = new Bitmap(bmp);

int i, j, g;

double[] S, T, A;

S = new double[8];

T = new double[8];

A = new double[8];

double max, mod;

for (i = 1; i < (bmp.Width - 1); i++)

{

for (j = 1; j < (bmp.Height - 1); j++)

{

//Получаем уровни яркости каждого пикселя в окне 3х3

// A0 A1 A2

// A7 F' A3

// A6 A5 A4

A[0] = 0.3 * bmp.GetPixel(i - 1, j - 1).R + 0.59 * bmp.GetPixel(i - 1, j - 1).G + 0.11 * bmp.GetPixel(i - 1, j - 1).B;

A[1] = 0.3 * bmp.GetPixel(i, j - 1).R + 0.59 * bmp.GetPixel(i, j - 1).G + 0.11 * bmp.GetPixel(i, j - 1).B;

A[2] = 0.3 * bmp.GetPixel(i + 1, j - 1).R + 0.59 * bmp.GetPixel(i + 1, j - 1).G + 0.11 * bmp.GetPixel(i + 1, j - 1).B;

A[3] = 0.3 * bmp.GetPixel(i + 1, j).R + 0.59 * bmp.GetPixel(i + 1, j).G + 0.11 * bmp.GetPixel(i + 1, j).B;

A[4] = 0.3 * bmp.GetPixel(i + 1, j + 1).R + 0.59 * bmp.GetPixel(i + 1, j + 1).G + 0.11 * bmp.GetPixel(i + 1, j + 1).B;

A[5] = 0.3 * bmp.GetPixel(i, j + 1).R + 0.59 * bmp.GetPixel(i, j + 1).G + 0.11 * bmp.GetPixel(i, j + 1).B;

A[6] = 0.3 * bmp.GetPixel(i - 1, j + 1).R + 0.59 * bmp.GetPixel(i - 1, j + 1).G + 0.11 * bmp.GetPixel(i - 1, j + 1).B;

A[7] = 0.3 * bmp.GetPixel(i - 1, j).R + 0.59 * bmp.GetPixel(i - 1, j).G + 0.11 * bmp.GetPixel(i - 1, j).B;

for (g = 0; g < 8; g++)

{

//Находим сумму Si

S[g] = A[g] + A[SumMod8(g, 1)] + A[SumMod8(g, 2)];

//Находим сумму Ti

T[g] = A[SumMod8(g, 3)] + A[SumMod8(g, 4)] + A[SumMod8(g, 5)] + A[SumMod8(g, 6)] + A[SumMod8(g, 7)];

}

max = 0;

for (g = 0; g < 8; g++)

{

//Расчитываем значение |5*Si-3*Ti|

mod = Math.Abs((5 * S[g]) - (3 * T[g]));

//Находим максимум из полученных решений

if (mod > max)

{

max = mod;

}

}

//Для обеспечения более хорошей наблюдаемости повышаем полученное значение на 100

max = max + 100;

//Выполняем коррекцию в пределах 0...255

if (max > 255)

{

max = 255;

}

else if (max < 0)

{

max = 0;

}

//Помещаем результирующее значение в каждый из каналов R, G, B на место F'

rezultImage.SetPixel(i, j, Color.FromArgb((int)max, (int)max, (int)max));

}

}

return rezultImage;

}

Метод Лапласа осуществляет домножение каждого элемента двумерной апертуры 3х3 на соответствующий элемент так называемой матрицы Лапласа:

Здесь речь идёт именно о простом умножении каждого элемента исходной матрицы на соответствующий элемент матрицы коэффициентов, и это не надо путать с перемножением матриц.

После перемножения все полученные значения элементов суммируются и при необходимости повышается порог яркости сложением с числом около 100. Результат помещается в центр, то есть в точку E. Затем рабочее окно сдвигается на один элемент влево (далее – слева направо и сверху вниз).

Существуют и другие матрицы Лапласа:

Реализация фильтра будет иметь следующий вид:

object Laplace(Bitmap bmp)

{

Bitmap rezultImage = new Bitmap(bmp);

int i, j, k, m, count;

double[] A = new double[9];

double S;

for (i = 1; i < (bmp.Width - 1); i++)

{

for (j = 1; j < (bmp.Height - 1); j++)

{

//Получаем уровни яркости каждого пикселя в окне 3х3

// A0 A1 A2

// A3 A4 A5

// A6 A7 A8

count = 0;

for (k = -1; k <= 1; k++)

{

for (m = -1; m <= 1; m++)

{

A[count] = 0.3 * bmp.GetPixel(i + k, j + m).R + 0.59 * bmp.GetPixel(i + k, j + m).G + 0.11 * bmp.GetPixel(i + k, j + m).B;

count++;

}

}

//Находим сумму по матрице Лапласа вида:

// -1 -2 -1

// -2 12 -2

// -1 -2 -1

S = (A[0] * (-1)) + (A[1] * (-2)) + (A[2] * (-1)) + (A[3] * (-2)) + (A[4] * (12)) + (A[5] * (-2)) + (A[6] * (-1)) + (A[7] * (-2)) + (A[8] * (-1));

//Для обеспечения более хорошей наблюдаемости повышаем полученное значение на 100

S = S + 100;

//Выполняем коррекцию в пределах 0...255

if (S > 255)

{

S = 255;

}

else if (S < 0)

{

S = 0;

}

//Помещаем результирующее значение в каждый из каналов R, G, B на место A8

rezultImage.SetPixel(i, j, Color.FromArgb((int)S, (int)S, (int)S));

}

}

return rezultImage;

}

Метод Робертса, как показывает практика, является самым простым, самым быстрым и эффективным (поистине, всё гениальное – просто). Работает он с двумерной апертурой 2х2 следующего вида:

Здесь вторая форма записи (с квадратным корнем) работает медленнее, но точнее. Возможно, для обеспечения наблюдаемости потребуется повышение порога яркости сложением с числом около 100. Окончательное значение A' заносится в элемент A, после чего рабочее окно сдвигается на один элемент влево (далее – слева направо и сверху вниз).

Реализация фильтра будет иметь следующий вид:

object Roberts(Bitmap bmp)

{

Bitmap rezultImage = new Bitmap(bmp);

int i, j;

double A, B, C, D;

double F;

for (i = 0; i < (bmp.Width - 1); i++)

{

for (j = 1; j < (bmp.Height - 1); j++)

{

//Находим яркость каждого пикселя в окне 2х2

// A C

// B D

A = 0.3 * bmp.GetPixel(i, j - 1).R + 0.59 * bmp.GetPixel(i, j - 1).G + 0.11 * bmp.GetPixel(i, j - 1).B;

B = 0.3 * bmp.GetPixel(i, j).R + 0.59 * bmp.GetPixel(i, j).G + 0.11 * bmp.GetPixel(i, j).B;

C = 0.3 * bmp.GetPixel(i + 1, j - 1).R + 0.59 * bmp.GetPixel(i + 1, j - 1).G + 0.11 * bmp.GetPixel(i + 1, j - 1).B;

D = 0.3 * bmp.GetPixel(i + 1, j).R + 0.59 * bmp.GetPixel(i + 1, j).G + 0.11 * bmp.GetPixel(i + 1, j).B;

//Находим результирующее значение по формуле:

F = Math.Sqrt(Math.Pow(A - D, 2) + Math.Pow(B - C, 2));

//Для обеспечения более хорошей наблюдаемости повышаем полученное значение на 50

F = F + 50;

//Выполняем коррекцию в пределах 0...255

if (F > 255)

{

F = 255;

}

else if (F < 0)

{

F = 0;

}

//Помещаем результат на место пикселя А:

rezultImage.SetPixel(i, j, Color.FromArgb((int)F, (int)F, (int)F));

}

}

return rezultImage;

}

Метод Собела работает с двумерной апертурой 3х3 следующего вида:

Сначала находятся значения переменных X и Y по приведённым выше формулам. После находится новое значение центрального элемента:

Возможно, для обеспечения наблюдаемости потребуется повышение порога яркости сложением с числом около 100. Окончательное значение F' помещается вместо элемента F, после чего рабочее окно сдвигается на один элемент влево (далее – слева направо и сверху вниз).

Реализация фильтра будет иметь следующий вид:

object Sobel(Bitmap bmp)

{

Bitmap rezultImage = new Bitmap(bmp);

int i, j, k, m, count;

double[] A = new double[8];

double F, X, Y;

for (i = 1; i < (bmp.Width - 1); i++)

{

for (j = 1; j < (bmp.Height - 1); j++)

{

//Получаем уровни яркости каждого пикселя в окне 3х3

// A0 A1 A2

// A3 F A4

// A5 A6 A7

count=0;

for (k = -1; k <= 1; k++)

{

for (m = -1; m <= 1; m++)

{

if (m == 0 && k == 0)

{

continue;

}

A[count] = 0.3 * bmp.GetPixel(i + k, j + m).R + 0.59 * bmp.GetPixel(i + k, j + m).G + 0.11 * bmp.GetPixel(i + k, j + m).B;

count++;

}

}

//Находим значения X, Y по формулам

X = (A[2] + 2 * A[4] + A[6]) - (A[0] + 2 * A[3] + A[5]);

Y = (A[0] + 2 * A[1] + A[2]) - (A[5] + 2 * A[6] + A[7]);

//Расчитываем значение центрального пикселя F

F = Math.Sqrt(X * X + Y * Y);

//Для обеспечения более хорошей наблюдаемости повышаем полученное значение на 50

F = F + 50;

//Выполняем коррекцию в пределах 0...255

if (F > 255)

{

F = 255;

}

else if (F < 0)

{

F = 0;

}

//Помещаем результат на место пикселя F:

rezultImage.SetPixel(i, j, Color.FromArgb((int)F, (int)F, (int)F));

}

}

return rezultImage;

}

Метод Уоллеса работает с двумерной апертурой 3х3 следующего вида:

Сразу находится новое значение центрального элемента по приведённой выше формуле; при этом, если знаменатель (Ai с нечётными значениями i) равен нулю, то к нему и к числителю добавляется единица (проще добавлять эту единицу всегда). Возможно, для обеспечения наблюдаемости потребуется домножение результата на очень большое число (в районе 500) и повышение порога яркости сложением с числом около 100. Окончательное значение F' помещается вместо элемента F, после чего рабочее окно сдвигается на один элемент влево (далее – слева направо и сверху вниз).

Реализация фильтра будет иметь следующий вид:

object Wallace(Bitmap bmp)

{

Bitmap rezultImage = new Bitmap(bmp);

int i, j, k, m, count;

double[] A = new double[8];

double F;

double Fr;

for (i = 1; i < (bmp.Width - 1); i++)

{

for (j = 1; j < (bmp.Height - 1); j++)

{

//Получаем уровни яркости каждого пикселя в окне 3х3

// A0 A1 A2

// A3 F A4

// A5 A6 A7

count = 0;

for (k = -1; k <= 1; k++)

{

for (m = -1; m <= 1; m++)

{

if (m == 0 && k == 0)

{

continue;

}

A[count] = 0.3 * bmp.GetPixel(i + k, j + m).R + 0.59 * bmp.GetPixel(i + k, j + m).G + 0.11 * bmp.GetPixel(i + k, j + m).B;

count++;

}

}

F = 0.3 * bmp.GetPixel(i, j).R + 0.59 * bmp.GetPixel(i, j).G + 0.11 * bmp.GetPixel(i, j).B;

//Расчитываем значение центрального пикселя F

Fr = (1 + F * F) / (1 + A[1] * A[3]);

Fr += (1 + F * F) / (1 + A[4] * A[6]);

Fr = Math.Abs(Math.Log(Fr));

Fr = Fr / 4;

//Для обеспечения более хорошей наблюдаемости умножаем полученное значение на 500

Fr = Fr * 500;

//И прибавляем к нему 100 для увеличения яркости

Fr += 100;

//Выполняем коррекцию в пределах 0...255

if (Fr > 255)

{

Fr = 255;

}

else if (Fr < 0)

{

Fr = 0;

}

//Помещаем результат на место пикселя F:

rezultImage.SetPixel(i, j, Color.FromArgb((int)Fr, (int)Fr, (int)Fr));

}

}

return rezultImage;

}

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

Далее вычисляется значение среднеквадратичного отклонения значений элементов рабочего окна от среднеарифметического значения:

Затем значения всех элементов рабочего окна домножаются на полученное отклонение:

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

Стоит заметить, что статистический метод – единственный из здесь рассмотренных, у которого изменяются значения сразу всех элементов.

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

Существует 2 важных условия для успешной работы алгоритма:

  1. Должен существовать способ отделения объекта от фона (в простейшем случае это пороговая обработка). Для серых изображений иногда объединяют оператор оценки градиента и процедуру прослеживания контура. Оператор оценки градиента отыскивает локальное направление контура, определяя для этой цели направление наиболее-заметного края вблизи задней точки. Предполагается, что небольшой шаг, сделанный в найденном направлении дает новую точку около границы объекта. Что бы обнаружить перепад вблизи этой точки – снова используют оператор градиента и т.д.

  2. Отсутствие ложных разрывов в силуэте объекта.

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

Бинаризацию можно выполнить по уже известной функции.

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

Дальнейшая работа жука будет выглядеть таким образом:

object BugMethod(Bitmap bmp, int x, int y)

{

Bitmap rezultImage = new Bitmap(bmp);

int px, py;

bool isLeft, isRight, isDown, isUp;

//px, py - положение "жука"

//x, y - место старта "жука"

px=x;

py=y;

//Создаем логическую матрицу для контуров изображения

bool[,] RezultObject = new bool[rezultImage.Width, rezultImage.Height];

//Изначальное направление будет - вверх

isLeft = false;

isRight = false;

isDown = false;

isUp = true;

//Если выбранная точка не содержит объекта - двигаемся вправо, пока не попадем на объект

//При попадании на объект - поворачиваем влево от направления движения

if (DetectObject[x, y] == false)

{

while (DetectObject[px, py] != true)

{

x = px;

RezultObject[px, py] = false;

px++;

}

RezultObject[px, py] = true;

py--;

}

else

{

RezultObject[px, py] = true;

py--;

}

if (py < 0)

{

py = 0;

}

//Пока не вернемся в начальную точку - "жук" будет двигаться по изображению согласно заданному алгоритму

while (px != x || py != y)

{

//Если произошло движение вправо

if (isRight == true)

{

if (DetectObject[px, py] == false)

{

//Если произошло попадание на фон - отмечаем, что это фон

RezultObject[px, py] = false;

//Поворачиваем вправо от направления движения (разворачиваемся вниз)

//И делаем шаг

py++;

}

else

{

//Если произошло попадание на объект - отмечаем, что это объект

RezultObject[px, py] = true;

//Поворачиваем влево от направления движения (разворачиваемся вверх)

//И делаем шаг

py--;

}

continue; //Переход к следующему шагу "жука"

}

//Если произошло движение вверх

else if (isUp == true)

{

if (DetectObject[px, py] == false)

{

//Если произошло попадание на фон - отмечаем, что это фон

RezultObject[px, py] = false;

//Поворачиваем вправо от направления движения (в этом случае направление относительно движения и относительно изображения совпадут)

//И делаем шаг

px++;

}

else

{

//Если произошло попадание на объект - отмечаем, что это объект

RezultObject[px, py] = true;

//Поворачиваем влево от направления движения (в этом случае направление относительно движения и относительно изображения совпадут)

//И делаем шаг

px--;

}

continue;

}

//Если произошло движение влево

else if(isLeft==true)

{

if (DetectObject[px, py] == false)

{

//Если произошло попадание на фон - отмечаем, что это фон

RezultObject[px, py] = false;

//Поворачиваем вправо от направления движения (поворачиваем вверх)

//И делаем шаг

py--;

}

else

{

//Если произошло попадание на объект - отмечаем, что это объект

RezultObject[px, py] = true;

//Поворачиваем влево от направления движения (поворачиваем вниз)

//И делаем шаг

py++;

}

continue;

}

//Если произошло движение вниз

else if(isDown==true)

{

if (DetectObject[px, py] == false)

{

//Если произошло попадание на фон - отмечаем, что это фон

RezultObject[px, py] = false;

//Поворачиваем вправо от направления движения (поворачиваем влево)

//И делаем шаг

px--;

}

else

{

//Если произошло попадание на объект - отмечаем, что это объект

RezultObject[px, py] = true;

//Поворачиваем влево от направления движения (поворачиваем вправо)

//И делаем шаг

px++;

}

continue;

}

}

//После окончания движения жука переводим логическую матрицу в матрицу цветов

for (int i = 0; i < rezultImage.Width; i++)

{

for (int j = 0; j < rezultImage.Height; j++)

{

if (RezultObject[i, j] == true)

{

rezultImage.SetPixel(i, j, Color.Black);

}

else

{

rezultImage.SetPixel(i, j, Color.White);

}

}

}

return rezultImage;

}