将图像从正常图像转换为棕褐色的 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);
请注意,我做了两处更改:
- 将每个表达式中的第一个值转换为 (int);否则计算将按字节进行,您将丢失高位。
- 对每个像素分量强制执行最大值 255。
在考虑其他答案时,请添加我的第一个修复。如果您已经降低了最高位,检查最大值 255 将无济于事!
我正在开发一个名为 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);
请注意,我做了两处更改:
- 将每个表达式中的第一个值转换为 (int);否则计算将按字节进行,您将丢失高位。
- 对每个像素分量强制执行最大值 255。
在考虑其他答案时,请添加我的第一个修复。如果您已经降低了最高位,检查最大值 255 将无济于事!