Размытие по Гауссу с ошибками / сегментация

Я пытаюсь реализовать Gaussian Blur с нуля (используя C ++). В приведенном ниже коде я жестко запрограммировал используемое гауссовское ядро. Я сохранил только одно измерение, поскольку я пытаюсь использовать оптимизацию, о которой я читал, где вы можете выполнить горизонтальный проход свертки и вертикальный, чтобы сделать размытие более эффективным. К сожалению, у меня возникли некоторые проблемы. Вот мой код:

float gKern[5] = {0.05448868, 0.24420134, 0.40261995, 0.24420134, 0.05448868};
int** gaussianBlur(int** image, int height, int width) {
    int **ret  = new int*[height];
    for(int i = 0; i < height; i++) {
        ret[i] = new int[width];
    }

    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            if (i == 0) {
                ret[i][j] = (gKern[0] * image[2][j]) + (gKern[1] * image[1][j]) + (gKern[2] * image[0][j]) + (gKern[3] * image[1][j]) + (gKern[4] * image[2][j]);
            } else if (i == 1) {
                ret[i][j] = (gKern[0] * image[1][j]) + (gKern[1] * image[0][j]) + (gKern[2] * image[1][j]) + (gKern[3] * image[2][j]) + (gKern[4] * image[3][j]);
            } else if (i == (height - 2)) {
                ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i + 1][j]) + (gKern[4] * image[i][j]);
            } else if (i == (height - 1)) {
                ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i - 1][j]) + (gKern[4] * image[i - 2][j]);
            } else {
                ret[i][j] = (gKern[0] * image[i - 2][j]) + (gKern[1] * image[i - 1][j]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i + 1][j]) + (gKern[4] * image[i + 2][j]);
            }
        }
    }

    int** temp = image;
    image = ret;

    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            if (j == 0) {
                ret[i][j] = (gKern[0] * image[i][2]) + (gKern[1] * image[i][1]) + (gKern[2] * image[i][0]) + (gKern[3] * image[i][1]) + (gKern[4] * image[i][2]);
            } else if (j == 1) {
                ret[i][j] = (gKern[0] * image[i][1]) + (gKern[1] * image[i][0]) + (gKern[2] * image[i][1]) + (gKern[3] * image[i][2]) + (gKern[4] * image[i][3]);
            } else if (j == (width - 2)) {
                ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j + 1]) + (gKern[4] * image[i][j]);
            } else if (j == (width - 1)) {
                ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j - 1]) + (gKern[4] * image[i][j - 2]);
            } else {
                ret[i][j] = (gKern[0] * image[i][j - 2]) + (gKern[1] * image[i][j - 1]) + (gKern[2] * image[i][j]) + (gKern[3] * image[i][j + 1]) + (gKern[4] * image[i][j + 2]);
            }
        }
    }

    image = temp;

    return ret;
}

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


person vsrin1    schedule 31.05.2020    source источник


Ответы (1)


Проблема в используемых вами указателях.

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

Второй шаг должен использовать ret в качестве входных данных и записывать либо в исходный вход (перезапись входного изображения), либо в новое изображение. Вместо этого вы делаете:

int** temp = image;
image = ret;
// read from image and write to ret
image = temp;
return ret;

То есть во втором проходе и image, и ret указывают на одни и те же данные, затем вы читаете и записываете одни и те же данные. Затем вы выполняете присвоение указателя, которое не имеет никакого эффекта (image никогда не используется после этого), и возвращаете промежуточный буфер.

Если вы хотите записать во входное изображение, просто поменяйте местами указатели image и ret перед вторым проходом:

std::swap(image, res);

Если вы этого не хотите, вам нужно new другое изображение, чтобы писать.


Использование массива массивов для хранения изображения - плохая практика. Если вы посмотрите исходный код любой библиотеки обработки изображений, вы увидите, что они выделяют один большой блок памяти для изображения, в котором хранятся все строки изображения, соединенные. Зная ширину изображения, вы знаете, как индексировать: image[x + y*width].

Это не только упрощает код (нет циклов для выделения одного изображения), но также значительно ускоряет код: больше нет поиска по указателям, и все данные расположены близко друг к другу, чтобы лучше использовать кеш.

Весь этот код можно значительно упростить, следуя приведенному выше совету: два прохода могут выполняться с одним и тем же кодом. Напишите функцию, которая фильтрует одну строку изображения. Требуется указатель на первый пиксель, длина строки и шаг (который равен 1 для горизонтальных линий и width для вертикальных линий). Затем эта одномерная функция вызывается в цикле по строкам другой функции. Затем эта вторая функция вызывается один раз для выполнения горизонтального прохода и один раз для выполнения вертикального прохода. (Подробнее см. здесь.)

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

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

person Cris Luengo    schedule 31.05.2020
comment
даже если я удалю эти строки и изменю все ссылки на изображение на ret во втором блоке, я получу те же результаты? Я посмотрю на ссылку, которую вы отправили, и попытаюсь реализовать реализацию 1d, спасибо за помощь! - person vsrin1; 31.05.2020