将图像从正常图像转换为棕褐色的 Cs50 棕褐色问题

Cs50 sepia problem with converting image from normal to sepia

我正在开发一个名为 filter 的 cs50 程序(不太舒服,第 4 周),它必须将图像从普通图像转换为棕褐色图像。除非必须将颜色转移为白色,否则它工作正常。当尝试转移白色时,它只是将其转换为蓝色和绿色。像这样:

原创

SEPIA

如您所见,它转换得很好,除了白色或接近白色的颜色。 这是我的代码(仅限棕褐色部分):


void sepia(int height, int width, RGBTRIPLE image[height][width])
{
    for(int j = 0; j < width; j++)
    {
       for (int i = 0; i < height; i++)
       {
           int sepiared =  image[i][j].rgbtRed *.393  +   image[i][j].rgbtGreen *.769 +  image[i][j].rgbtBlue *.189;
           int sepiagreen =  image[i][j].rgbtRed *.349  +  image[i][j].rgbtGreen *.686 +  image[i][j].rgbtBlue *.168;
            int sepiablue =  image[i][j].rgbtRed *.272  +   image[i][j].rgbtGreen *.534 +  image[i][j].rgbtBlue *.131;
           image[i][j].rgbtRed = sepiared;
           image[i][j].rgbtGreen = sepiagreen;
           image[i][j].rgbtBlue = sepiablue;
       }
    }

    return;
}

请帮我理解为什么会这样。 Clang 不打印任何错误消息。

真诚的, 迷失在代码中:)

您需要使用“饱和数学”。

对于接近白色的颜色,您的中间值(例如 sepiared)可以超过 255。

255 (0xFF) 是 unsigned char

中可以容纳的最大值

例如sepiared是256(0x100),当它被放入rgbtRed时,只保留最右边的8位,值将被截断 到 0。因此,您将得到一个非常暗的值 [接近黑色],而不是非常亮的值 [接近白色]。

要解决此问题,请添加:

if (sepiared > 255)
    sepiared = 255;

此外,请注意 for 循环的顺序,缓存效率非常低。

而且,到处使用 image[i][j].whatever 很浪费 [而且可能很慢]。最好使用指向当前像素的指针。

无论如何,这是包含以下更改的代码的更新版本:

void
sepia(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE *pix;

    for (int i = 0; i < height; i++) {
        pix = &image[i][0];
        for (int j = 0; j < width; j++, pix++) {
            int sepiared = pix->rgbtRed * .393 +
                pix->rgbtGreen * .769 +
                pix->rgbtBlue * .189;
            int sepiagreen = pix->rgbtRed * .349 +
                pix->rgbtGreen * .686 +
                pix->rgbtBlue * .168;
            int sepiablue = pix->rgbtRed * .272 +
                pix->rgbtGreen * .534 +
                pix->rgbtBlue * .131;

            if (sepiared > 255)
                sepiared = 255;
            if (sepiagreen > 255)
                sepiagreen = 255;
            if (sepiablue > 255)
                sepiablue = 255;

            pix->rgbtRed = sepiared;
            pix->rgbtGreen = sepiagreen;
            pix->rgbtBlue = sepiablue;
        }
    }
}

另请注意,在像素图像上使用浮点数学运算可能会有点慢。在这种情况下,faster/better 使用缩放整数数学。

这是执行此操作的版本:

void
sepia(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE *pix;

    for (int i = 0; i < height; i++) {
        pix = &image[i][0];
        for (int j = 0; j < width; j++, pix++) {
            int sepiared = pix->rgbtRed * 393 +
                pix->rgbtGreen * 769 +
                pix->rgbtBlue * 189;
            int sepiagreen = pix->rgbtRed * 349 +
                pix->rgbtGreen * 686 +
                pix->rgbtBlue * 168;
            int sepiablue = pix->rgbtRed * 272 +
                pix->rgbtGreen * 534 +
                pix->rgbtBlue * 131;

            sepiared /= 1000;
            sepiagreen /= 1000;
            sepiablue /= 1000;

            if (sepiared > 255)
                sepiared = 255;
            if (sepiagreen > 255)
                sepiagreen = 255;
            if (sepiablue > 255)
                sepiablue = 255;

            pix->rgbtRed = sepiared;
            pix->rgbtGreen = sepiagreen;
            pix->rgbtBlue = sepiablue;
        }
    }
}

所以你需要慢慢地编辑你的代码

将原始图像存储到临时文件中一段时间​​


            originalBlue = image[i][j].rgbtBlue;
            originalRed = image[i][j].rgbtRed;
            originalGreen = image[i][j].rgbtGreen;

每个公式的结果可能不是整数,因此请使用 float 并将它们四舍五入为最接近的整数

            sepiaRed = round(.393 * originalRed + .769 * originalGreen + .189 * originalBlue);
            sepiaGreen = round(.349 * originalRed + .686 * originalGreen + .168 * originalBlue);
            sepiaBlue = round(.272 * originalRed + .534 * originalGreen + .131 * originalBlue);

            if (sepiaRed > 255)
            {
                sepiaRed = 255;
            }

            if (sepiaGreen > 255)
            {
                sepiaGreen = 255;
            }

            if (sepiaBlue > 255)
            {
                sepiaBlue = 255;
            }

现在将值存储为原始值

            image[i][j].rgbtBlue = sepiaBlue;
            image[i][j].rgbtRed = sepiaRed;
            image[i][j].rgbtGreen = sepiaGreen;

在循环外声明所有变量

    float sepiaRed;
    float sepiaBlue;
    float sepiaGreen;
    int originalRed;
    int originalBlue;
    int originalGreen;

希望对您有所帮助

首先,顺便说一句,当你有CS50问题时,请在提问前先在Whosebug中搜索关键字。答案可能就在那里。如果您搜索 sepia RGBTRIPLE cs50,您会得到很多结果。

进行大量像素处理后,您将磨练一些有用的调试直觉。其中:

  • 如果您看到对角线偏移,则图像的 bytes-per-row 可能大于宽度乘以像素大小。 (特别是在 YCbCr 图像或图像缓冲区与 128 位矢量大小对齐的平台上。)
  • 2 倍或 0.5 倍图像显示可能意味着您没有注意到 Retina 显示屏上的比例值。
  • 某些颜色空间错误会立即指出 BGR 与 RGB 字节顺序问题。根本没有蓝色通道或全是蓝色?可能是混合了 ARGB 和 BGRA。

但更重要的是:

  • 如果您在明亮或 color-saturated 区域看到古怪,您的像素分量值可能过饱和(超过最大值,并降低了高位)。

每次您 (1) 将颜色分量乘以大于 1 的数或 (2) 将多个颜色分量加在一起时,您需要考虑如果超过最大值会发生什么。如果您的中间数学将两个值相加然后除以 2,请确保您的编译操作将使用足够大的变量大小来容纳额外的位。

所以在你的内部循环中,当对白色像素进行操作时,几乎每个颜色分量都会超过 255(即红色和绿色会超过,但不会超过蓝色,因为棕褐色在蓝色中含量较低):

int sepiared =  image[i][j].rgbtRed *.393  +   image[i][j].rgbtGreen *.769 +  image[i][j].rgbtBlue *.189;
int sepiagreen =  image[i][j].rgbtRed *.349  +  image[i][j].rgbtGreen *.686 +  image[i][j].rgbtBlue *.168;
int sepiablue =  image[i][j].rgbtRed *.272  +   image[i][j].rgbtGreen *.534 +  image[i][j].rgbtBlue *.131;

结果值为 {255, 255, 255} x {.393+.769+.189, .349+.686+.168, .272+.534+.131} 或 {344.5 , 306.8, 238.9}.

但是因为您没有足够的位来存储 RGBTRIPLE 结构的 BYTE 组件中的那些值,所以您的值将不正确。因此,您可以这样做:

int sepiared =  (int) image[i][j].rgbtRed *.393  +   image[i][j].rgbtGreen *.769 +  image[i][j].rgbtBlue *.189;
int sepiagreen =  (int) image[i][j].rgbtRed *.349  +  image[i][j].rgbtGreen *.686 +  image[i][j].rgbtBlue *.168;
int sepiablue =  (int) image[i][j].rgbtRed *.272  +   image[i][j].rgbtGreen *.534 +  image[i][j].rgbtBlue *.131;
sepiared = min(sepiared, 255);
sepiagreen = min(sepiagreen, 255);
sepiablue = min(sepiablue, 255);

请注意,我做了两处更改:

  1. 将每个表达式中的第一个值转换为 (int);否则计算将按字节进行,您将丢失高位。
  2. 对每个像素分量强制执行最大值 255。

在考虑其他答案时,请添加我的第一个修复。如果您已经降低了最高位,检查最大值 255 将无济于事!