C - значения rgb - вычисление среднего значения rgb для фильтра размытия

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

На данный момент я сделал следующее:

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
    int n;
    int m;
    int averageRed;
    int averageBlue;
    int averageGreen;

    //For each row..
    for (int i = 0; i < height; i++)
    {
        //..and then for each pixel in that row...
        for (int j = 0; j < width; j++)
        {

            //...if i and j equal 0...         
            if (i == 0 && j == 0)
            {
                for (m = i; m <= 1; m++)
                {
                    for (n = j; n <= 1; n++)
                    {
                        averageRed = averageRed + image[m][n].rgbtRed;
                        averageBlue = averageBlue + image[m][n].rgbtBlue;
                        averageGreen = averageGreen + image[m][n].rgbtGreen;

                        printf("%i\n", averageRed);
                        printf("%i\n", averageBlue);
                        printf("%i\n", averageGreen); 
                    }
                }

                image[i][j].rgbtRed = round((float)averageRed / 4);
                image[i][j].rgbtBlue = round((float)averageBlue / 4);
                image[i][j].rgbtGreen = round((float)averageGreen / 4);

                printf("%i\n", image[i][j].rgbtRed);
                printf("%i\n", image[i][j].rgbtBlue);
                printf("%i\n", image[i][j].rgbtGreen);
            }


            //If i equals 0 and j is greater than 0...
            else if (i == 0 && j > 0)
            {
                //..take the line that equals i..
                for (m = i; m <= 1; m++)
                {
                    //..and take from each pixel ot that line...
                    for (n = j - 1; n <= 1; n++)
                    {
                        //..the color values and add them to the average-variables
                        averageRed = averageRed + image[m][n].rgbtRed;
                        averageBlue = averageBlue + image[m][n].rgbtBlue;
                        averageGreen = averageGreen + image[m][n].rgbtGreen;
                    }
                }

                //Set the current pixel values to the averages
                image[i][j].rgbtRed = round((float)averageRed / 6);
                image[i][j].rgbtBlue = round((float)averageBlue / 6);
                image[i][j].rgbtGreen = round((float)averageGreen / 6);

                printf("%i\n", image[i][j].rgbtRed);
                printf("%i\n", image[i][j].rgbtBlue);
                printf("%i\n", image[i][j].rgbtGreen);
            }


            else if (i > 0 && j == 0)
            {
                for (m = i - 1; m <= 1; m++)
                {
                    for (n = j; n <= 1; n++)
                    {
                        averageRed = averageRed + image[m][n].rgbtRed;
                        averageBlue = averageBlue + image[m][n].rgbtBlue;
                        averageGreen = averageGreen + image[m][n].rgbtGreen;
                    }
                }

                image[i][j].rgbtRed = round((float)averageRed / 6);
                image[i][j].rgbtBlue = round((float)averageBlue / 6);
                image[i][j].rgbtGreen = round((float)averageGreen / 6);
            }


            else if (i > 0 && j > 0 )
            {

                // ..take every line from i - 1 to i + 1...
                for (m = i - 1; m <= 1; m++)
                {

                    //...and in each line take every pixel from j - 1 to j + 1...
                    for (n = j - 1; n <= 1; n++)
                    {

                        //...and add the RGB value to average-variables
                        averageRed = averageRed + image[m][n].rgbtRed;
                        averageBlue = averageBlue + image[m][n].rgbtBlue;
                        averageGreen = averageGreen + image[m][n].rgbtGreen;
                    }
                }

                //Set current value to the rounded average
                image[i][j].rgbtRed = ((float)averageRed / 9);
                image[i][j].rgbtBlue = ((float)averageBlue / 9);
                image[i][j].rgbtGreen = ((float)averageGreen / 9);
            }  


        }

    }
    return;

}

Компиляция работает без каких-либо нареканий, но результаты немного странные (особенно первые четыре блока) - Test.bmp - это всего лишь черно-белый bmp-файл размером 55px x 55px:

> ~/pset4/filter/ $ ./filter -b images/test.bmp blur.bmp0 38118032 0 0
> 38118032 0 0 38118032 0 0 38118032 0 helpers.c:93:40: runtime error:
> 9.52951e+06 is outside the range of representable values of type 'unsigned char' 0 164 0 helpers.c:120:40: runtime error: 6.35303e+06
> is outside the range of representable values of type 'unsigned char' 0
> 137 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0
> 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160
> 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0
> 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0
> 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160
> 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0 160 0 0
> 160 0 0 160 0 helpers.c:142:40: runtime error: 6.35311e+06 is outside
> the range of representable values of type 'unsigned char'
> helpers.c:167:40: runtime error: 4.23546e+06 is outside the range of
> representable values of type 'unsigned char' ~/pset4/filter/ $

Заранее большое спасибо за любой совет!

Greetz


person ALL    schedule 05.05.2020    source источник
comment
Вы сохраняете данные размытого изображения на месте. Итак, когда вы переходите к следующему пикселю, вы уже изменили значение предыдущего. Что вам нужно сделать, так это создать полностью отдельный массив для хранения размытого изображения, а затем скопировать данные обратно в image[][], когда вы закончите.   -  person r3mainer    schedule 06.05.2020
comment
@ r3mainer: Спасибо за подсказку!   -  person ALL    schedule 06.05.2020
comment
@ Евгений: Лучше сейчас?   -  person ALL    schedule 06.05.2020
comment
@ALL Спасибо.   -  person Eugene Sh.    schedule 06.05.2020


Ответы (2)


Обратите внимание, что переменные average* неинициализированы, поэтому, когда вы суммируете их, у вас есть UB. Они должны быть предварительно установлены на 0, конечно, в начале, но, возможно, перед каждым основным циклом.


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

Это потому, что для rgbt* (например, rgbtRed) - это байт, поэтому значение может быть неправильно обрезано.

Ты делаешь:

image[i][j].rgbtRed = round((float)averageRed / 6);

Это можно переписать как:

averageRed = round((float)averageRed / 6);
image[i][j].rgbtRed = averageRed;

Но, если (например) averageRed было 256, тогда rgbtRed будет равно 1 [потому что присвоение image [эффективно]:

image[i][j].rgbtRed = averageRed & 0xFF;

Таким образом, вместо ярко-красного вы храните почти черный цвет. Конечным значением должно быть 255, максимальное значение «насыщенного» цвета.

Итак, чтобы исправить это [или просто предотвратить это], выполните:

averageRed = round((float)averageRed / 6);
if (averageRed > 255)
    averageRed = 255;
image[i][j].rgbtRed = averageRed;

Изменить: После дальнейшего размышления вам нужно сделать это только в том случае, если правая сторона может превышать 255, но я [сейчас] не уверен, что это возможно. Чтобы проверить это, вы можете добавить (например):

if (averageRed > 255) {
    fprintf(stderr,"value overflow\n");
    exit(1);
}

Вы можете заключить это в #ifdef, провести тесты, и, если он не сработает, вы можете удалить его позже.


ОБНОВЛЕНИЕ:

Как бы глупо этот вопрос ни звучал, но как может это значение достигнуть 256? Даже если каждый пиксель белый, ни одно из значений не может достигать 256 или в чем моя ошибка? (1 белый Px: 255 255 255 -> 10 белый Px: 2550 2550 2550/10 -> .....

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

Но ваша ошибка времени выполнения показывает, что значение действительно превышает емкость байта (т.е. unsigned char).

Вероятно, это из-за неинициализированных переменных суммы.

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

Их необходимо сбрасывать после завершения каждого ядра свертки 3x3 (то есть после сохранения каждого выходного пикселя).

И я не думаю, что ваши for (n = j; n <= 1; n++) петли правильные. Вы смешиваете абсолютные значения координат (от j) и смещения координат.

Вероятно, вам нужно что-то вроде:

for (m = -1; m <= 1; m++) {
    for (n = -1; n <= 1; n++) {
        averageRed += image[i + m][j + n].rgbtRed;
    }
}

ОБНОВЛЕНИЕ №2:

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

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

Кроме того, использование более описательных имен вместо i, j, m, n может помочь облегчить понимание и поддержку кода.

В любом случае, вот несколько реорганизованная версия вашей функции, которая немного проще:

#include <math.h>

#if 1
typedef struct {
    unsigned char rgbtRed;
    unsigned char rgbtGreen;
    unsigned char rgbtBlue;
} __attribute__((__packed__)) RGBTRIPLE;
#endif

// Blur image
void
blur(int height, int width,
    RGBTRIPLE image[height][width],
    RGBTRIPLE imgout[height][width])
{
    int wid = width - 1;
    int hgt = height - 1;
    RGBTRIPLE *pixel;

    // For each row..
    for (int ycur = 0;  ycur <= hgt;  ++ycur) {
        int ylo = (ycur == 0) ? 0 : -1;
        int yhi = (ycur == hgt) ? 0 : 1;

        // ..and then for each pixel in that row...
        for (int xcur = 0;  xcur <= wid;  ++xcur) {
            int xlo = (xcur == 0) ? 0 : -1;
            int xhi = (xcur == wid) ? 0 : 1;

            int avgRed = 0;
            int avgGreen = 0;
            int avgBlue = 0;

            for (int yoff = ylo;  yoff <= yhi;  ++yoff) {
                for (int xoff = xlo;  xoff <= xhi;  ++xoff) {
                    pixel = &image[ycur + yoff][xcur + xoff];
                    avgRed += pixel->rgbtRed;
                    avgGreen += pixel->rgbtGreen;
                    avgBlue += pixel->rgbtBlue;
                }
            }

            int tot = ((yhi - ylo) + 1) * ((xhi - xlo) + 1);

            pixel = &imgout[ycur][xcur];
            pixel->rgbtRed = roundf((float) avgRed / tot);
            pixel->rgbtGreen = roundf((float) avgGreen / tot);
            pixel->rgbtBlue = roundf((float) avgBlue / tot);
        }
    }
}
person Craig Estey    schedule 05.05.2020
comment
Как бы глупо этот вопрос ни звучал, но как может это значение достигнуть 256? Даже если каждый пиксель белый, ни одно из значений не может достигать 256 или в чем моя ошибка? (1 белый Px: 255 255 255 - ›10 белых Px: 2550 2550 2550/10 -› ..... - person ALL; 06.05.2020
comment
Нет необходимости в математике FP. Предложите pixel->rgbtRed = roundf((float) avgRed / tot); - ›pixel->rgbtRed = (avgRed + tot/2) / tot; или pixel->rgbtRed = (2*avgRed + tot) / (tot *2); - person chux - Reinstate Monica; 06.05.2020

Для корректности нужно сохранить исходные значения.

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

В частности, игнорируя верхний / нижний / левый / правый края (которые требуют особой осторожности) и притворяется монохромным (для RGB вы просто делаете это все 3 раза) для каждой строки пикселей:

  • для каждого пикселя в строке выполните buffer[next_buffer_row][x] = image[y+2][x-1] + image[y+2][x] + image[y+2][x+1], чтобы сохранить горизонтальные суммы в буфере.

  • для каждого пикселя в строке вычислить размытые значения, например image[y][x] = (buffer[previous_buffer_row][x] + buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 9

  • перейти к следующей строке изображения (y++); и поверните буфер (previous_buffer_row++; if(previous_buffer_row>= 3) previous_buffer_row = 0;, current_buffer_row++; if(current_buffer_row>= 3) current_buffer_row = 0; и next_buffer_row++; if(next_buffer_row>= 3) next_buffer_row = 0;)

Чтобы обработать левый / правый края, вы хотите «отделить» первую итерацию циклов «для каждого пикселя в строке» и последнюю итерацию циклов «для каждого пикселя в строке»; затем измените их по своему усмотрению. Например. для первого пикселя, который вы хотите сделать buffer[next_buffer_row][x] = image[y+2][x] + image[y+2][x+1] (потому что пиксель в image[y+2][x-1] не существует) и image[y][x] = (buffer[previous_buffer_row][x] + buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 6 (потому что было усреднено только 6 пикселей, потому что 3 были за левым краем изображения).

Примечание. Когда я говорю «отклеить», я имею в виду, что вместо выполнения (например) for(i = 0; i < something; i++) { вы копируете и пропускаете середину цикла, чтобы он дублировался до и после цикла и выполнял for(i = 1; i < something-1; i++) {.

Чтобы обработать верхний / нижний края, вы хотите «отделить» первую итерацию цикла «для каждой строки» и последнюю итерацию цикла «для каждой строки»; затем измените их по своему усмотрению. Например. для самой первой строки пикселей вы хотите сгенерировать горизонтальные суммы для 2 строк (а не для одной), а затем сделать image[y][x] = (buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 6, потому что одна строка (3 пикселя) не существует (потому что она находится за верхним краем). Обратите внимание, что в итоге вы получите 9 вариантов («левый / средний / правый для горизонтальных * верхний / средний / нижний для вертикальных»).

При усреднении с целочисленными делениями результат будет немного темнее (из-за округления / усечения), чем должен быть. Чтобы избежать этого (если вам интересно), используйте result = (max * (sums + max/2)) / (9 * max) (например, если максимальное значение 255, то result = 255 * (sums + 127) / 2295. Однако это добавляет накладные расходы и сложность, и большинство людей не заметят, что изображение немного темнее, так что хорошо ли это или плохо, зависит от вашего варианта использования.

Для лучшего качества размытия вы можете использовать веса, чтобы пиксели, расположенные дальше от центрального пикселя, меньше влияли на конечное значение пикселя. Проблема здесь в том, что заусенцы должны выполняться по кругу, а вы используете квадрат; что сделает диагональные края более размытыми, чем горизонтальные / вертикальные края. Обычно выбранные веса описываются в виде матрицы. Например:

| 1 2 1 |
| 2 4 2 |
| 1 2 1 |

... будет означать, что вес центрального пикселя равен 4 (поэтому вы умножаете значения для среднего пикселя на 4), вес пикселя над ним равен 2 и т. д. В этом случае вы должны разделить на сумму весов , что оказывается равным 16 (и означает, что деление может быть выполнено с помощью более быстрого «сдвига вправо»).

Подход, который я описал (наличие буфера «горизонтальных сумм» только для 3 строк), может быть легко применен к некоторым весам (например, весам, которые я показал выше), потому что средний ряд весов кратен верхнему / нижнему веса (2 4 2 это 2 раза 1 2 1). Если это не так, то описанный мной подход требует дополнительного отдельного буфера для средней строки (которая может составлять 2 пикселя, а не целую строку пикселей); и вы не сможете повторно использовать «горизонтальную сумму (взвешенных значений)» для средней строки.

Наконец-то; для получения очень точных результатов вам необходимо понимать, что значения RGB обычно кодируются гамма-кодом (см. https://en.wikipedia.org/wiki/Gamma_correction). Это означает выполнение «гамма-декодирования», затем размытия, а затем «гамма-перекодирования». Однако гамма-кодирование / декодирование обходится дорого (даже если вы используете таблицы поиска, чтобы избежать pow()); и если вы заботитесь об этом уровне совершенства, то лучше всего спроектировать весь конвейер (включая хранение и / или генерацию изображений, которые будут размыты) для необработанных значений (без гамма-кодирования), а затем выполнить гамма-кодирование один раз в конец.

person Brendan    schedule 06.05.2020