简体   繁体   English

C - rgb 值 - 计算模糊滤镜的 rgb 值的平均值

[英]C - rgb values - Calculating the average of rgb values for a blur filter

The first two ones were not so difficult but the third one makes me mad.前两个并不难,但第三个让我很生气。 The blur filter has to calculate an average of the rgb values of certain pixel groups in order to replace the values of the centered pixel.模糊过滤器必须计算某些像素组的 rgb 值的平均值,以替换居中像素的值。 Imagine a 3x3 grid where the pixel in the center has to be manipulated with the rgb values of the average of the eight surrounding pixels and the center-pixel itself.想象一个 3x3 网格,其中中心的像素必须使用 8 个周围像素的平均值和中心像素本身的 rgb 值进行操作。

What I have done so far is the following:到目前为止,我所做的如下:

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

}

The compiling works without any complaint, but the results are slightly strange (especially the first four blocks) - the Test.bmp ist just a 55px x 55px black/white bmp-file:编译工作没有任何抱怨,但结果有点奇怪(尤其是前四个块) - 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/ $

Thanks very much in advance for any advise!非常感谢您的任何建议!

Greetz格雷茨

Note that average* variables are uninitialized , so when you sum to them, you have UB.请注意, average*变量是uninitialized ,所以当你对它们求和时,你就有了 UB。 They need to be preset to 0, certainly at the outset, but possibly before each major loop.它们需要预设为 0,当然是在开始时,但可能在每个主要循环之前。


Also, aside from your other issues that others have noted, you may need to do saturation math.此外,除了其他人注意到的其他问题外,您可能需要进行饱和度数学运算。

That's because for rgbt* (eg rgbtRed ) is a byte , so the value can be clipped incorrectly.那是因为rgbt* (例如rgbtRed )是一个byte ,所以该值可能被错误地裁剪。

You are doing:你正在做:

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

This can be rewritten as:这可以重写为:

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

But, if (eg) averageRed was 256, then rgbtRed would end up as 1 [because the assignment to image is [effectively]:但是,如果(例如) averageRed是 256,那么rgbtRed最终会是 1 [因为分配给image是 [有效地]:

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

So, instead of storing bright red, you're storing nearly black.因此,您存储的不是鲜红色,而是几乎是黑色。 The final would need to be 255, the "saturated" maximum color value.最终需要为 255,即“饱和”的最大颜色值。

So, to fix that [or merely to guard against it], do:因此,要解决这个问题[或仅仅为了防止它],请执行以下操作:

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

Edit: Upon further reflection, you only need to do this if the right hand side can exceed 255, but I'm [now] not sure that it can.编辑:经过进一步思考,您只需要在右侧可以超过 255 时执行此操作,但我 [现在] 不确定是否可以。 To check against this, you could add (eg):要检查这一点,您可以添加(例如):

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

You could wrap this in an #ifdef , do tests, and if it doesn't trigger, you can remove it later.您可以将其包装在#ifdef中,进行测试,如果它没有触发,您可以稍后将其删除。


UPDATE:更新:

As stupid as the question might sound, but how can the value ever reach 256?这个问题听起来很愚蠢,但这个值怎么会达到 256? Even if every pixel is white, none of the values can reach 256 or where is my mistake?即使每个像素都是白色的,也没有一个值可以达到 256,或者我的错误在哪里? (1 white Px: 255 255 255 -> 10 white Px: 2550 2550 2550 / 10 -->..... (1 个白色像素:255 255 255 -> 10 个白色像素:2550 2550 2550 / 10 -->.....

Yes, per my "Edit:" above, it may not.是的,根据我上面的“编辑:”,它可能不会。 I recently answered a similar question where the value could exceed 255.我最近回答了一个类似的问题,其中值可能超过 255。

But, your runtime error shows that the value does exceed the capacity of a byte (ie unsigned char ).但是,您的运行时错误表明该值确实超过了一个字节的容量(即unsigned char )。

That is probably due to the uninitialized sum variables.这可能是由于未初始化的总和变量。

But, also it is because the sum/average variables are not being reset at the start of a loop.但是,这也是因为总和/平均变量在循环开始时没有被重置。 You never reset them, so they just continue to grow and grow.永远不会重置它们,因此它们只会继续增长和增长。

They need to be reset after you complete each 3x3 convolution kernel (ie after you store each output pixel).在您完成每个 3x3 卷积 kernel 后(即在您存储每个 output 像素后),它们需要被重置。

And, I don't think your for (n = j; n <= 1; n++) loops are correct.而且,我不认为你的for (n = j; n <= 1; n++)循环是正确的。 You're mixing up absolute coordinate values (from j ) and coordinate offsets.您正在混合绝对坐标值(来自j )和坐标偏移量。

You probably want something like:你可能想要这样的东西:

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

UPDATE #2:更新#2:

It may be easier to have a single set of loops, using some extra limit variables.使用一些额外的限制变量可能更容易拥有一组循环。

Also, on a per pixel basis, using floating point (ie round ) can be slow .此外,在每个像素的基础上,使用浮点(即round )可能会很 Although, I didn't do it, it can be replaced with integer math easily enough.虽然,我没有这样做,但它可以很容易地用 integer 数学替换。

Further, using more descriptive names instead of i, j, m, n can help make the code a bit easier to understand and maintain.此外,使用更具描述性的名称而不是i, j, m, n可以帮助使代码更易于理解和维护。

Anyway, here's a somewhat refactored version of your function that is a bit simpler:无论如何,这里有一个稍微重构的 function 版本,它更简单一些:

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

For correctness you need to preserve the original values.为了正确起见,您需要保留原始值。

For speed, you only need to preserve the original values until they're no longer needed;为了速度,您只需要保留原始值,直到不再需要它们; and the horizontal sums can be recycled to minimize additions.并且可以循环使用水平总和以最小化添加。

More specifically, ignoring top/bottom/left/right edges (which need extra care) and pretending it's monochrome (for RGB you just do it all 3 times), for each row of pixels:更具体地说,对于每一行像素,忽略上/下/左/右边缘(需要格外小心)并假装它是单色的(对于 RGB,您只需执行 3 次):

  • for each pixel in the row, do buffer[next_buffer_row][x] = image[y+2][x-1] + image[y+2][x] + image[y+2][x+1] to store the horizontal sums in a buffer.对于行中的每个像素,执行buffer[next_buffer_row][x] = image[y+2][x-1] + image[y+2][x] + image[y+2][x+1]到将水平总和存储在缓冲区中。

  • for each pixel in the row calculate the blurred values, like image[y][x] = (buffer[previous_buffer_row][x] + buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 9为行中的每个像素计算模糊值,例如image[y][x] = (buffer[previous_buffer_row][x] + buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 9

  • advance to the next line in the image ( y++ );前进到图像中的下一行( y++ ); and rotate the buffer ( previous_buffer_row++; if(previous_buffer_row>= 3) previous_buffer_row = 0; and current_buffer_row++; if(current_buffer_row>= 3) current_buffer_row = 0; and next_buffer_row++; if(next_buffer_row>= 3) next_buffer_row = 0; )并旋转缓冲区 ( previous_buffer_row++; if(previous_buffer_row>= 3) previous_buffer_row = 0; and current_buffer_row++; if(current_buffer_row>= 3) current_buffer_row = 0; and next_buffer_row++; if(next_buffer_row>= 3) next_buffer_row = 0; )

To handle left/right edges, you want to "peel off" the first iteration of the "for each pixel in row" loops, and the last iteration of the "for each pixel in row" loops;要处理左/右边缘,您需要“剥离”“for each pixel in row”循环的第一次迭代,以及“for each pixel in row”循环的最后一次迭代; then modify them to suit.然后修改它们以适应。 Eg for the first pixel you want to do buffer[next_buffer_row][x] = image[y+2][x] + image[y+2][x+1] (because the pixel at image[y+2][x-1] doesn't exist) and image[y][x] = (buffer[previous_buffer_row][x] + buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 6 (because there were only 6 pixels being averaged because 3 were past the left edge of the image).例如,对于您要执行的第一个像素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 个像素超出了图像的左边缘)。

Note: When I say "peel off", I mean that instead of doing (eg) for(i = 0; i < something; i++) { you copy and past the middle of the loop so its duplicated before and after the loop and do for(i = 1; i < something-1; i++) { .注意:当我说“剥离”时,我的意思是不要做(例如) for(i = 0; i < something; i++) {你复制并越过循环的中间,所以它在循环之前和之后重复并且做for(i = 1; i < something-1; i++) { .

To handle top/bottom edges, you want to "peel off" the first iteration of the "for each row" loop, and last iteration of the "for each row" loop;要处理顶部/底部边缘,您需要“剥离”“for each row”循环的第一次迭代和“for each row”循环的最后一次迭代; then modify them to suit.然后修改它们以适应。 Eg for the very first row of pixels you want generate the horizontal sums for 2 lines (not one) and then do image[y][x] = (buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 6 because one row (3 pixels) doesn't exist (because it's past the top edge).例如,对于您想要生成 2 行(不是一个)的水平总和的第一行像素,然后执行image[y][x] = (buffer[current_buffer_row][x] + buffer[next_buffer_row][x]) / 6因为一行(3 个像素)不存在(因为它超过了顶部边缘)。 Note that this will actually end up giving you 9 cases ("left/middle/right for horizontals * top/middle/bottom for verticals").请注意,这实际上最终会为您提供 9 个案例(“水平方向的左/中/右 * 垂直方向的上/中/下”)。

For averaging, with integer divisions the result will be slightly darker (due to rounding/truncation) than it should be.对于平均,使用 integer 除法,结果将比应有的稍暗(由于舍入/截断)。 To avoid that (if you care), use result = (max * (sums + max/2)) / (9 * max) (eg if the maximum value is 255, then result = 255 * (sums + 127) / 2295 . However, this adds overhead and complexity, and most people won't notice that the image is slightly darker, so whether this is good or bad depends on your use case.为避免这种情况(如果您在意),请使用result = (max * (sums + max/2)) / (9 * max) (例如,如果最大值为 255,则result = 255 * (sums + 127) / 2295 . 但是,这会增加开销和复杂性,并且大多数人不会注意到图像稍暗,因此这是好还是坏取决于您的用例。

For better quality blurring, you can use weights so that pixels further from the center pixel have less effect on the final value of a pixel.为了获得更好的模糊质量,您可以使用权重,以便远离中心像素的像素对像素的最终值的影响较小。 The problem here is that burring should be done with a circle but you're using a square;这里的问题是毛刺应该用圆形进行,但您使用的是方形; which will make diagonal edges look "more blurred" than horizontal/vertical edges.这将使对角线边缘看起来比水平/垂直边缘“更模糊”。 Usually the selected weights are described as a matrix.通常选择的权重被描述为一个矩阵。 For an example:例如:

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

... would mean that the center pixel's weight is 4 (so you multiply the values for the middle pixel by 4), the weight for the pixel above it is 2, etc. In this case you would divide by the sum of the weights, which happens to be 16 (and means that the division can be done with a faster "shift right"). ...意味着中心像素的权重为 4(因此您将中间像素的值乘以 4),其上方像素的权重为 2,等等。在这种情况下,您将除以权重的总和,恰好是 16(这意味着可以通过更快的“右移”来完成除法)。

The approach I've described (of having a buffer of "horizontal sums" for 3 lines only) can be applied to some weights easily (eg the weights I showed above) because the middle row of weights are a multiple of the top/bottom weights ( 2 4 2 is 2 times 1 2 1 ).我描述的方法(只有 3 行的“水平总和”缓冲区)可以很容易地应用于一些权重(例如我上面显示的权重),因为中间的权重行是顶部/底部的倍数权重( 2 4 21 2 1的 2 倍)。 If this isn't the case then the approach I've described needs an extra separate buffer for the middle row (which can be 2 pixels and not a whole row of pixels);如果不是这种情况,那么我描述的方法需要一个额外的中间行单独缓冲区(可以是 2 个像素,而不是一整行像素); and you won't be able to re-use the "horizontal sum (of weighted values)" for the middle row.并且您将无法在中间行重复使用“水平总和(加权值)”。

Finally;最后; for extremely accurate results you need to realize that RGB values are usually gamma encoded (see https://en.wikipedia.org/wiki/Gamma_correction ).要获得极其准确的结果,您需要意识到 RGB 值通常是伽马编码的(请参阅https://en.wikipedia.org/wiki/Gamma_correction )。 This means doing "gamma decode", then blur, then "gamma re-encode".这意味着进行“伽玛解码”,然后进行模糊处理,然后进行“伽玛重新编码”。 However, gamma encoding/decoding is expensive (even when you use lookup tables to avoid pow() );但是,伽玛编码/解码很昂贵(即使您使用查找表来避免pow() ); and if you care about this level of perfection then it's best to design the entire pipeline (including storage and/or generation of the image/s that will be blurred) for raw values (without gamma encoding) and then do gamma encoding once at the end.如果您关心这种完美程度,那么最好为原始值(没有伽马编码)设计整个管道(包括存储和/或生成将被模糊的图像),然后在结尾。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM