C - rgb values ​​- Расчет среднего значений rgb для фильтра размытия - PullRequest
0 голосов
/ 06 мая 2020

Первые два были не такими сложными, но третий меня бесит. Фильтр размытия должен вычислять среднее значение 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-файл размером 55 x 55 пикселей:

> ~/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

Ответы [ 2 ]

1 голос
/ 06 мая 2020

Для корректности вам необходимо сохранить исходные значения.

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

Точнее, игнорируя верхний / нижний / левый / правый края (которые требуют особой осторожности) и притворяется моно chrome (для 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;)

Чтобы обработать левый / правый края, вы хотите "снять" первую итерацию "for цикл "для каждого пикселя в строке" и последняя итерация цикла "для каждого пикселя в строке"; затем измените их по своему усмотрению. Например, для первого пикселя, который вы хотите сделать, 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++) {, вы копируете середину l oop и пропускаете его, чтобы он дублировался до и после l oop и выполните for(i = 1; i < something-1; i++) {.

Чтобы обработать верхний / нижний края, вы хотите «снять» первую итерацию «для каждой строки» l oop и последнюю итерация цикла «для каждой строки»; затем измените их по своему усмотрению. Например, для самой первой строки пикселей вы хотите сгенерировать горизонтальные суммы для 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 и т. Д. c. В этом случае вы будет делиться на сумму весов, которая оказывается равной 16 (и означает, что деление может быть выполнено с помощью более быстрого «сдвига вправо»).

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

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

1 голос
/ 06 мая 2020

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


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

Это потому, что для 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).

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

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

Их нужно сбрасывать после завершения каждого ядра свертки 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;
    }
}

UPDATE # 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);
        }
    }
}
...