обработка изображений — SFML C ++ Обнаружение двойных краев Canny

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

Я пытался следовать типичному пути Canny:
1. Оттенки серого изображения
2. Гауссов фильтр для сглаживания шума
3. Обнаружение края — я использую и Собель, и Шарр
4. Истончение края — я использовал не максимальное подавление в направлении, зависящем от направления градиента — вертикальное, горизонтальное, 45 диагоналей или 135 диагоналей
5. Гистерезис

Мне каким-то образом удалось заставить его работать с обнаружением Шарра, но у меня повторяющаяся проблема с двойными или множественными ребрами, особенно с Собел. Я действительно не могу найти набор параметров, которые заставят его работать.

Мой алгоритм для Собеля:

void sobel(sf::Image &image, pixldata **garray, float division)
{
int t1 = 0, t2 = 0, t3 = 0, t4 = 0;
sf::Color color;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);

for (int i = 1;i < image.getSize().y - 1;i++)
{
for (int j = 1;j < image.getSize().x - 1;j++)
{

t1 = (- image.getPixel(j - 1, i - 1).r - 2 * image.getPixel(j - 1, i).r - image.getPixel(j - 1, i + 1).r + image.getPixel(j + 1, i - 1).r + 2 * image.getPixel(j + 1, i).r + image.getPixel(j + 1, i + 1).r) / division;
t2 = (- image.getPixel(j - 1, i).r - 2 * image.getPixel(j - 1, i + 1).r - image.getPixel(j, i + 1).r + image.getPixel(j + 1, i).r + 2 * image.getPixel(j + 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
t3 = (- image.getPixel(j - 1, i + 1).r - 2 * image.getPixel(j, i + 1).r - image.getPixel(j + 1, i + 1).r + image.getPixel(j - 1, i - 1).r + 2 * image.getPixel(j, i - 1).r + image.getPixel(j + 1, i - 1).r) / division;
t4 = (- image.getPixel(j, i + 1).r - 2 * image.getPixel(j + 1, i + 1).r - image.getPixel(j + 1, i).r + image.getPixel(j - 1, i).r + 2 * image.getPixel(j - 1, i - 1).r + image.getPixel(j, i - 1).r) / division;

color.r = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.g = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.b = (abs(t1) + abs(t2) + abs(t3) + abs(t4));

garray[j][i].gx = t1;
garray[j][i].gy = t3;
garray[j][i].gtrue = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
garray[j][i].gsimpl = sqrt(t1*t1 + t2*t2);

t1 = abs(t1);
t2 = abs(t2);
t3 = abs(t3);
t4 = abs(t4);

if (t1 > t4 && t1 > t3 && t1 > t2)
garray[j][i].fi = 0;
else if (t2 > t4 && t2 > t3 && t2 > t1)
garray[j][i].fi = 45;
else if (t3 > t4 && t3 > t2 && t3 > t1)
garray[j][i].fi = 90;
else if (t4 > t3 && t4 > t2 && t4 > t1)
garray[j][i].fi = 135;
else
garray[j][i].fi = 0;

if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) < 0)
{
color.r = 0;
color.g = 0;
color.b = 0;
}
else if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) > 255)
{
color.r = 255;
color.g = 255;
color.b = 255;
}
else
{
color.r = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.g = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.b = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
}
bufor.setPixel(j, i, color);
}
}
image.copy(bufor, 0, 0);
}

Код для Scharr отличается только умножением значений пикселей.

        t1 = (-3 * image.getPixel(j - 1, i - 1).r - 10 * image.getPixel(j - 1, i).r - 3 * image.getPixel(j - 1, i + 1).r + 3 * image.getPixel(j + 1, i - 1).r + 10 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j + 1, i + 1).r) / division;
t2 = (-3 * image.getPixel(j - 1, i).r - 10 * image.getPixel(j - 1, i + 1).r - 3 * image.getPixel(j, i + 1).r + 3 * image.getPixel(j + 1, i).r + 10 * image.getPixel(j + 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
t3 = (-3 * image.getPixel(j - 1, i + 1).r - 10 * image.getPixel(j, i + 1).r - 3 * image.getPixel(j + 1, i + 1).r + 3 * image.getPixel(j - 1, i - 1).r + 10 * image.getPixel(j, i - 1).r + 3 * image.getPixel(j + 1, i - 1).r) / division;
t4 = (-3 * image.getPixel(j, i + 1).r - 10 * image.getPixel(j + 1, i + 1).r - 3 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j - 1, i).r + 10 * image.getPixel(j - 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;

Разбавляющий код:

void intelligentThin(sf::Image &image, int radius, pixldata **garray)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = true;

for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;

if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}

for (int j = 0;j < ymax;j++)
{
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}

if (garray[i][j].fi == 0)
{
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 135)
{
for (int l = leftBound, t = ceilBound; (l <= rightBound && t <= bottomBound); l++, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 90)
{
for (int t = ceilBound; t <= bottomBound; t++)
{
if ((image.getPixel(i, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 45)
{
for (int l = rightBound, t = ceilBound; (l >= leftBound && t <= bottomBound); l--, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}

if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}

judgeandjury = true;

}
leftBound = rightBound = 0;
}
}

Код гистерезиса:

void hysteresis(sf::Image &image, int radius, int uplevel, int lowlevel)
{

int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = false;

sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);

for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;

if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}

for (int j = 0;j < ymax;j++)
{
int currentPoint = image.getPixel(i, j).r;

if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}

if (currentPoint > uplevel)
{
judgeandjury = true;
}
else if (currentPoint > lowlevel)
{
for (int t = leftBound; t <= rightBound; t++)
{
for (int l = ceilBound; l <= bottomBound; l++)
{
if (image.getPixel(t, l).r > uplevel)
{
judgeandjury = true;
}

}
}
}
else judgeandjury = false;

if (judgeandjury == true)
{
bufor.setPixel(i, j, sf::Color::White);
}
else
{
bufor.setPixel(i, j, sf::Color::Black);
}

judgeandjury = false;
currentPoint = 0;

}
leftBound = rightBound = 0;
}
image.copy(bufor, 0, 0);
}

Результаты весьма неудовлетворительны для Собеля:

Истончение Собеля

Собел после гистерезиса

С Scharr результаты намного лучше:

Разреженный шарр

Шарр после гистерезиса

Набор параметров:

#define thinsize 1
#define scharrDivision 1
#define sobelDivision 1
#define hysteresisRadius 1
#define level 40
#define hysteresisUpperLevelSobel 80
#define hysteresisLowerLevelSobel 60
#define hysteresisUpperLevelScharr 200
#define hysteresisLowerLevelScharr 100

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

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

РЕДАКТИРОВАТЬ:
отправка main ()

sf::Image imydz;
imydz.loadFromFile("lena.jpg");
int x = imydz.getSize().x;
int y = imydz.getSize().y;pixldata **garray = new pixldata *[x];
for (int i = 0;i < x;i++)
{
garray[i] = new pixldata[y];
}monochrome(imydz);
gauss(imydz, radius, sigma);

//sobel(imydz, garray, sobelDivision);

scharr(imydz, garray, scharrDivision);

intelligentThin(imydz, thinsize, garray);
hysteresis(imydz, hysteresisRadius, hysteresisUpperLevel, hysteresisLowerLevel);

Второе редактирование — исправлено подавление:

sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Black);
for (int i = 1;i < xmax - 1;i++)
{
for (int j = 1;j < ymax - 1;j++)
{
if (garray[i][j].fi == 0)
{
if (((image.getPixel(i, j).r >= image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r > image.getPixel(i - 1, j).r)) ||
((image.getPixel(i, j).r > image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r >= image.getPixel(i - 1, j).r)))
{
judgeandjury = true;
}
else judgeandjury = false;
}
...
if (judgeandjury == false)
{
bufor.setPixel(i, j, sf::Color::Black);
}
else bufor.setPixel(i, j, image.getPixel(i, j));
judgeandjury = false;
}
}
image.copy(bufor, 0, 0);

Отремонтировал шарр на лену
Кажется странным
Еще одно тестовое изображение — странные результаты

До бинаризации

Готовые передачи

1

Решение

Я не прочитал весь ваш код подробно, там слишком много кода. Но, очевидно, ваш не максимальный код подавления неверен. Давайте посмотрим, что он делает для одного пикселя в середине изображения, где градиент близок к 0 градусам:

leftBound = i - radius;
rightBound = i + radius;
// ...
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false; // it's not a maximum: suppress
}
}
// ...
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}

Вот, radius устанавливается на 1 с помощью вызывающего кода. Любое другое значение будет плохим, так что все в порядке. Я бы вообще убрал это как параметр. Теперь ваш цикл:

for (int t = i-1; t <= t+1; t++)
if (t != i)

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

Вот что он делает сейчас:

if (   (image.getPixel(i-1, j).r >= image.getPixel(i, j).r)
|| (image.getPixel(i+1, j).r >= image.getPixel(i, j).r)) {
judgeandjury = false; // it's not a maximum: suppress
}

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

Далее вы подавляете максимум … во входном изображении! Это означает, что когда вы достигнете следующего пикселя в этой строке, вы сравните его значение с этим значением, которое было только что подавлено. Конечно, он будет больше, даже если он был меньше, чем первоначальное значение в этом месте. То есть не максимумы будут выглядеть как максимумы, потому что вы устанавливаете соседний пиксель в 0.

Итак: запишите результат алгоритма в выходное изображение:

if (judgeandjury == true)
{
output.setPixel(i, j, image.getPixel(i, j));
}

…что, конечно, вам нужно выделить, но вы уже это знаете.


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

Чтобы решить это, либо:

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

  2. Разделите величину на какое-то значение так, чтобы оно никогда не превышало 255. Теперь вы измеряете величину, а не обрезаете ее. Квантование должен будет хорошо в этом случае.

Я настоятельно рекомендую вам следовать (1). Я обычно использую значения с плавающей запятой для всех значений и преобразую только 8-битные числа для отображения. Это упростило много вещей!

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]