【问题标题】:C - rgb values - Calculating the average of rgb values for a blur filterC - rgb 值 - 计算模糊滤镜的 rgb 值的平均值
【发布时间】:2020-05-05 21:22:34
【问题描述】:

前两个没那么难,但第三个让我很生气。 模糊过滤器必须计算某些像素组的 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 只是一个 55px x 55px 黑白 bmp 文件:

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

非常感谢您的任何建议!

问候

【问题讨论】:

  • 您将模糊图像数据存储在适当的位置。所以当你到达下一个像素时,你已经改变了前一个像素的值。您需要做的是创建一个完全独立的数组来存储模糊图像,然后在完成后将数据复制回image[][]
  • @r3mainer:谢谢提示!
  • @Eugene:现在好点了吗?
  • @ALL 谢谢。

标签: c loops average rgb blur


【解决方案1】:

请注意,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 个白色像素:255 255 255 -> 10 个白色像素:2550 2550 2550 / 10 --> .....

是的,根据我上面的“编辑:”,它可能不会。我最近回答了一个类似的问题,其中值可能超过 255。

但是,您的运行时错误显示值确实超过了一个字节的容量(即unsigned char)。

这可能是由于未初始化的 sum 变量。

但是,它也是,因为总和/平均变量没有在循环开始时被重置。您从不重置它们,因此它们只会继续增长和增长。

在完成每个 3x3 卷积核后(即在存储每个输出像素后),它们需要被重置。

而且,我认为您的 for (n = j; n &lt;= 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);
        }
    }
}

【讨论】:

  • 这个问题听起来很愚蠢,但这个值怎么会达到 256?即使每个像素都是白色的,没有一个值可以达到 256 或者我的错误在哪里? (1 个白色像素:255 255 255 -> 10 个白色像素:2550 2550 2550 / 10 --> .....
  • 不需要 FP 数学。建议pixel-&gt;rgbtRed = roundf((float) avgRed / tot); --> pixel-&gt;rgbtRed = (avgRed + tot/2) / tot;pixel-&gt;rgbtRed = (2*avgRed + tot) / (tot *2);
【解决方案2】:

为了正确起见,您需要保留原始值。

为了速度,您只需要保留原始值直到不再需要它们;并且水平总和可以循环使用以最小化添加。

更具体地说,对于每一行像素,忽略上/下/左/右边缘(需要格外小心)并假装它是单色的(对于 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&gt;= 3) previous_buffer_row = 0;current_buffer_row++; if(current_buffer_row&gt;= 3) current_buffer_row = 0;next_buffer_row++; if(next_buffer_row&gt;= 3) next_buffer_row = 0;

要处理左/右边缘,您需要“剥离”“for each pixel in row”循环的第一次迭代,以及“for each pixel in row”循环的最后一次迭代;然后修改它们以适应。例如。对于你想要做的第一个像素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 &lt; something; i++) {,而是复制并越过循环的中间,以便在循环前后复制它并执行for(i = 1; i &lt; something-1; i++) { .

要处理顶部/底部边缘,您需要“剥离”“for each row”循环的第一次迭代和“for each row”循环的最后一次迭代;然后修改它们以适应。例如。对于第一行像素,您要生成 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 21 2 1 的 2 倍)。如果不是这种情况,那么我描述的方法需要一个额外的中间行缓冲区(可以是 2 个像素,而不是一整行像素);并且您将无法在中间行重复使用“水平总和(加权值)”。

最后;要获得极其准确的结果,您需要意识到 RGB 值通常是经过伽马编码的(请参阅 https://en.wikipedia.org/wiki/Gamma_correction )。这意味着进行“伽玛解码”,然后进行模糊处理,然后进行“伽玛重新编码”。然而,伽马编码/解码是昂贵的(即使你使用查找表来避免pow());如果您关心这种完美程度,那么最好为原始值(没有伽马编码)设计整个管道(包括存储和/或生成将被模糊的图像),然后在结束。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-22
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多