使用粗糙的灰度算法存在问题吗?

59
所以我正在使用Python中的PIL设计几个编辑照片的程序,其中之一是将图像转换为灰度(我避免使用任何来自PIL的函数)。 我采用的算法很简单:对于每个像素(颜色深度为24),我计算了R、G和B值的平均值,并将RGB值设置为此平均值。 我的程序生成的黑白图像似乎很准确,但我想知道是否采用了正确的算法,然后我发现了这个问题的答案,在那里看起来“正确”的算法是计算0.299R+0.587G+0.114B。 我决定将我的程序与该算法进行比较。我使用我的程序生成了一个灰度图像,另一个灰度图像(使用相同的输入)来自在线网站(Google搜索结果中的顶部),通过肉眼观察,它们似乎完全相同,如果有任何变化,我看不到它。但是,我决定使用此网站(Google搜索结果中的顶部)比较我的灰度图像。结果证明,在像素的深处,它们有轻微的变化,但从肉眼观察上看,第一眼无法感知任何变化(只有当图像重叠或在毫秒内切换时才能发现差异)。 我的问题(第一个是主要问题):
1.使用我的“粗略”灰度算法有什么缺点吗? 2.是否有任何输入图像,其中我的灰度算法将产生可见不同于“正确”的图像? 3.对于哪些颜色/RBG组合,我的算法效果不佳?
我的关键代码(如果需要):
def greyScale(pixelTuple):
    return tuple([round(sum(pixelTuple) / 3)] * 3)

“正确”的算法(似乎严重偏向绿色):

def greyScale(pixelTuple):
    return tuple([round(0.299 * pixelTuple[0] + 0.587 * pixelTuple[1] + 0.114 * pixelTuple[2])] * 3)

我的输入图像: My input image

我的算法生成的灰度图像: The greyscale image my algorithm produces

'正确'的灰度图像: The greyscale image which is 'correct'

当在线比较这些灰度图像(使用10%的模糊标记红色差异)时: When the greyscale images are compared online (highlighted red are the differences, using a fuzz of 10%)

尽管上面突出显示的像素变化,对我来说,上面的灰度图像看起来几乎完全相同。

此外,关于我的第一个问题,如果有人感兴趣,此网站已经对不同的灰度转换算法进行了分析,并且还有一些自定义算法。

编辑

作为对@Szulat答案的回应,我的算法实际上生成了这个图像(忽略糟糕的裁剪,原始图像有三个圆,但我只需要第一个):

This is what my algorithm **actually** produces

如果人们想知道将图像转换为灰度的原因(因为似乎算法取决于目的),那么我只是在python中制作一些简单的照片编辑工具,以便我可以拥有迷你版Photoshop,并且不需要依靠互联网来应用滤镜和效果。

博客奖励的原因:这里的不同答案涵盖了不同的内容,这些内容都是相关和有帮助的。这使得选择要接受的答案变得非常困难。我开始了一项悬赏,因为我喜欢在这里列出的一些答案,但也因为拥有一个覆盖此问题所需所有内容的单个答案是很好的。


你应该注意到,差异发生在输入图像非常绿色的地方,因为“正确”的公式是以绿色为重的。 - Mark Setchell
还要注意,灰度处理的方式对美学有很大的影响:https://photo.stackexchange.com/questions/86599/which-color-filter-do-i-use-for-a-black-white-portrait - Framester
2
但是对于人眼来说,没有任何一个是可感知的:我将这两张图片放在两个Firefox标签中,并使用(Shift +)Ctrl + Tab在它们之间切换。在我看来,差异非常大;事实上,不可能看不到它。但我确实同意,没有任何一个选项比另一个“明显”更好,当然,“更好”的形容词高度主观和/或取决于您特定的应用程序。 - Andreas Rejbrand
为什么要近似呢?使用Python和PIL逐像素转换为灰度图的速度很慢,但如果使用Image.convert的matrix参数,转换将在C中完成(而不是在Python中),速度会快得多。 - Mr. Llama
如果你尝试将一个具有大面积绿色区域和大面积蓝色区域相互转换,你会注意到它们之间的差异。 - user253751
显示剩余11条评论
8个回答

47

这些图片看起来非常相似,但是你的眼睛能够分辨它们之间的差异,特别是当你用其中一个替换另一个时:

输入图像描述

例如,你可以注意到背景中的花在平均转换中看起来更亮。

不是说对三个通道求平均有什么固有的“不好”。那个公式的原因是我们没有同等地感知红、绿和蓝色,因此它们对灰度图像中的强度的贡献不应该是相同的;由于我们更强烈地感知绿色,因此灰度上的绿色像素应该看起来更亮。然而,正如 Mark 评论所述,并不存在唯一完美的灰度转换,因为我们能够看到彩色,而且每个人的视力略有不同,因此任何公式都只是试图使像素的强度在大多数人看来感觉“正确”。


我还没有给你颁发赏金,因为我觉得你的回答上投票的数量已经足够了 :) 不过我已经接受了,因为我真的觉得你的回答非常好,并且 .gif 只会产生积极的影响 :) 谢谢! - Adi219
2
@Adi219 没问题,点赞数确实比我预期的要多得多。我想我低估了鸟儿的GIF图吧。感谢您接受我的答案。 - jdehesa
2
实际上,我非常喜欢那只鸟的 .gif(我觉得很多人也是 :))。我对我的问题获得的赞数和浏览量也感到非常惊讶 :) 没问题!我也尊重这个答案,因为你提到了 @MarkSetchell 的答案,我觉得他的答案非常好,特别是因为他的答案是第一个,当我发布这个问题的那天,我最初打算接受他的答案。我很感激! :) - Adi219

42

最明显的例子:

  1. 原始图片

  2. 在Gimp中去饱和度(亮度模式 - 这是你的算法所做的)

  3. 在Gimp中去饱和度(亮度模式 - 这是我们的眼睛所做的)

gimp desaturate: lightness vs luminosity

因此,不要对RGB求平均值。对RGB求平均值是错误的!

(好吧,你是对的,在一些晦涩的应用程序中,平均可能是有效的,即使当RGB值被视为颜色时,它也没有物理或生理意义。顺便说一下,“常规”的加权平均方式也有更微妙的错误,因为存在伽马值。sRGB 应该首先线性化,然后再将最终结果转换回 sRGB(这相当于检索 Lab 颜色空间中的 L 分量))


24
非常感谢这个生动的图片,但是"将RGB取平均值是完全错误的"这句话给予了-1分,因为它完全取决于当前的应用场景。 - Andreas Rejbrand
https://en.wikipedia.org/wiki/File:7bit-each.svg 是另一张展示眼睛对RGB敏感度的好演示图片。 - qwr
3
尽管这个回答有许多赞成票,但我不会点赞,因为这个举例的图片并不是我算法生成的(请看我对问题的编辑),尽管这个回答声称我的算法在亮度模式下产生与Gimp中去饱和度效果相同的效果。 - Adi219
1
尽管这个回答获得了许多赞同,但由于存在技术错误,即使在几天前指出后仍未进行任何修正,我也不会授予此回答任何奖励。 - Adi219
1
是的,“群众的智慧”有时令人失望...当然,图片是不正确的,我还没有时间更新它 :-( (尽管我相信这并不改变结论) - szulat

20
你可以使用任何换算公式、规模、线性。你找到的那个:
I = 0.299 R + 0.587 G + 0.114 B

此基于人类眼睛对"平均值"主要颜色(R,G,B)的感知敏感度(至少在它被创建的时间段和人口/硬件范围内;请注意,这些标准是在 LED,TFT等屏幕问世之前创建的)。

您正在与以下几个问题作斗争:

  1. 我们的眼睛不一样

    所有人的颜色感知都不相同。性别之间有重大差异,地区之间也有较小差异;甚至代际和年龄也会起到作用。因此,即使是“平均值”,也应该作为“平均值”处理。

    我们对可见光谱中的光强度敏感度不同。最敏感的颜色是绿色(因此在其上具有最高权重)。但是,对于不同的人,例如我,这种XYZ曲线峰值可能出现在不同的波长处,导致对某些波长的识别存在差异,例如某些水蓝色调 - 有些人会将其视为绿色,而其他人则视为蓝色,即使他们没有任何色盲或其他视觉障碍。

  2. 监视器不使用相同的波长或光谱分散

    因此,如果您使用两个不同的监视器,则它们可能会使用略有差异的R、G、B波长,甚至可能在光谱滤波器的宽度方面存在不同(只需使用分光镜查看)。是的,它们应该通过硬件进行“标准化”,但这并不等同于使用标准化波长。这类似于使用RGB与白噪声光源光谱的问题。

  3. 监视器线性度

    人类的感知不是线性比例:我们通常是对数/指数(取决于您如何看待它),因此,是的,我们可以通过硬件(甚至软件)来标准化它,但问题是如果我们为一个人线性化,这就意味着我们会损坏另一个人的感知。

如果将所有这些放在一起,您可以使用平均值......或者使用特殊(昂贵)的设备根据某些标准或校准的人来测量/标准化(具体取决于行业)。

但在家庭环境下,这太难处理了,因此请使用“平均值”的权重,就像世界上大多数人一样......幸运的是,我们的大脑可以处理它,因为除非您开始将两个图像并排或以动画方式进行比较,否则您看不到差异: )。所以我建议:

I = 0.299 R + 0.587 G + 0.114 B
R = I
G = I
B = I

1
我已经授予了这个答案赏金,因为我觉得它应该获得更多的赞和点赞,而且答案本身也相当详细,所以很不错 :) - Adi219
1
@Adi219 谢谢... 缺乏投票很可能是因为没有图像,因为我不想添加其他回答已经得到的冗余图像,也不想复制链接的 QAs 中的图像...PS 还有一些应用程序需要 I= R+G+B,比如这个将 RGB 像素转换为波长,但这些通常都是出于特殊原因/任务/HW... - Spektre
我原以为只是因为你回答的那一天,但也许你是对的。不过,我理解你的决定。谢谢! - Adi219

8

根据R、G、B颜色原色,亮度有许多公式:

Rec.601/NTSC: Y = 0.299*R + 0.587*G + 0.114*B , 

Rec.709/EBU:  Y = 0.213*R + 0.715*G + 0.072*B , 

Rec.2020/UHD: Y = 0.263*R + 0.678*G + 0.059*B . 

这是因为我们的眼睛对蓝色的敏感度比对红色和绿色的敏感度要低。

话虽如此,你可能正在计算亮度而非光度,所以所有的公式都是错误的。对于恒定光度,您必须转换为线性光。

R = R' ^ 2.4 , G = G' ^ 2.4 , B = B' ^ 2.4 , 

应用亮度公式,然后转换回伽马领域。
Y' = Y ^ (1/2.4) . 

此外,需要考虑将3D颜色空间转换为1D数量会丢失2/3的信息,这可能会在下一步处理中带来麻烦。根据问题的不同,有时候使用不同的公式更好,比如从HSV颜色空间计算 V = MAX(R,G,B)。
我是Poynton博士的追随者和朋友,对此有所了解。

抱歉,作为一个非色彩理论专家,我觉得这个答案没有太多意义。 - Adi219
Poynton博士是Charles Poynton,最近获得博士学位。他在视频处理领域写了几本权威的书籍。强烈推荐阅读。 - StessenJ
在视频处理中学到的一件事是,必须对线性光信号进行颜色空间转换。例如,由OETF执行的压缩必须首先被撤销,如我的示例所示。如果不这样做,那么红色和蓝色的灰度值将会太低,太暗。这显示了(Y'、Cb、Cr)信号的“恒定亮度误差”,即Cb、Cr也携带了一些红色和蓝色的亮度。 - StessenJ

8

有许多不同的方法可以转换为灰度图像,尽管差异可能更容易通过不同的输入彩色图像来看到,但它们确实会产生不同的结果。

由于我们实际上并不是以灰度方式看待事物,因此“最佳”方法在某种程度上取决于应用程序,并且在观察者眼中也有所不同。

您提到的替代公式基于人眼对绿色调变化更加敏感,因此给予它们更大的权重 - 类似于相机中的Bayer阵列,其中每个红色和蓝色像素对应2个绿色像素。 维基百科 - Bayer阵列


我知道你是第一个回答的,而且我很喜欢你的回答,但简单来说,其他答案更详细。抱歉 :( - Adi219
2
很酷 - 你可以自由选择任何你喜欢的答案 - 我没有任何抱怨!和其他回答者一样,我只是想帮忙。祝你的项目好运! - Mark Setchell
谢谢理解 :) 还有,感谢您的回答!这里几乎所有的答案都对我有很大帮助;如果一切顺利,我将把我的所有照片编辑程序放在Github上供其他人使用。谢谢! :) - Adi219

5

已提供的答案足够了,但我想以不同的方式更多地讨论这个话题。

由于我的兴趣是数字绘画,因此我更经常使用HSV。

在绘画过程中使用HSV更加可控,但简而言之,主要是S:饱和度将颜色概念与光线分离。 然后将S转为0,就已经是图像的“计算机”灰度。

from PIL import Image
import colorsys

def togrey(img):
    if isinstance(img,Image.Image):
        r,g,b = img.split()
        R = []
        G = []
        B = [] 
        for rd,gn,bl in zip(r.getdata(),g.getdata(),b.getdata()) :
            h,s,v = colorsys.rgb_to_hsv(rd/255.,gn/255.,bl/255.)
            s = 0
            _r,_g,_b = colorsys.hsv_to_rgb(h,s,v)
            R.append(int(_r*255.))
            G.append(int(_g*255.))
            B.append(int(_b*255.))
        r.putdata(R)
        g.putdata(G)
        b.putdata(B)
        return Image.merge('RGB',(r,g,b))
    else:
        return None

a = Image.open('../a.jpg')
b = togrey(a)
b.save('../b.jpg')

这种方法确实保留了原始颜色的“明亮”特性。但是,它没有考虑人眼如何处理数据


我认为HSV与考虑人眼无关。如果你看一下colorsys conversions,你会发现rgb->hsvv设置为max(r, g, b),而将hsv->rgb转换时,当s == 0时返回(v, v, v)。所以,并没有什么魔法--只是一个不同的解决方案。这是图片使用grey = max(r, g, b)生成的。 - Alistair Carscadden
更离题的是,我真的很喜欢grey = max(r, g, b)这张图片。对比强烈,鸟儿很亮。 - Alistair Carscadden
@AlistairCarscadden,是的,正如我在最后所述,这种方法没有考虑到人眼如何处理数据。 - MT-FreeHK
我进行了评论,因为我不同意,我认为这种方法确实考虑了我们如何看待光线。明亮的蓝色是明亮的,明亮的绿色是明亮的,明亮的红色也是明亮的。因此,max(r, g, b) 完全考虑了这一点。 - Alistair Carscadden
@AlistairCarscadden,不是很准确。考虑一张长时间曝光拍摄的照片,你可以辨认出颜色但失去了大部分细节。在这种情况下,使用HSV方法处理的图片肯定会更差。因此,实际上这是一张对比度图。 - MT-FreeHK
这说明了在3D彩色空间中,任何一个1D灰度值都会失去2/3的信息。V = MAX(R,G,B)对于某些目的来说是一个很好的值,特别是用于量化显示驱动。如果V=1,则所有颜色的显示都不能更强。亮度值大大低估了红色和蓝色的显示驱动。选择一个好的1D灰度值对于HDR-SDR色调映射至关重要。 - StessenJ

1

回答您的主要问题,使用任何单一的灰度测量方法都存在缺点。这取决于您希望从图像中获得什么。例如,如果您在白色背景上有彩色文本,并且想要使文本突出显示,则可以使用r、g、b值的最小值作为您的测量方法。但是,如果您在彩色背景上使用黑色文本,则可以使用相同结果的值的最大值。在我的软件中,我为用户提供了最大值、最小值或中位数值的选项进行选择。连续色调图像的结果也很有启示性。 针对要求更多细节的评论,下面是一个像素的代码(没有任何防御措施)。

int Ind0[3] = {0, 1, 2};                 //all equal
int Ind1[3] = {2, 1, 0};                 // top, mid ,bot from mask...
int Ind2[3] = {1, 0, 2};
int Ind3[3] = {1, 2, 0};
int Ind4[3] = {0, 2, 1};
int Ind5[3] = {2, 0, 1};
int Ind6[3] = {0, 1, 2};
int Ind7[3] = {-1, -1, -1};              // not possible
int *Inds[8] = {Ind0, Ind1, Ind2, Ind3, Ind4, Ind5, Ind6, Ind7};
void grecolor(unsigned char *rgb, int bri, unsigned char *grey)
{                         //pick out bot, mid or top according to bri flag
    int r = rgb[0];
    int g = rgb[1];
    int b = rgb[2];
    int mask = 0;
    mask |= (r > g);
    mask <<= 1;
    mask |= (g > b);
    mask <<= 1;
    mask |= (b > r);
    grey[0] = rgb[Inds[mask][2 - bri]];  // 2, 1, 0 give bot, mid, top
}

这只是间接自我推销你自己的软件的尝试。 - Adi219
如果我没有确定软件,那怎么可能呢?实际上,我想指出到目前为止讨论的重点是正确性,但通常用户选择才更为重要。 - Steve J
你实际上提到了两种使用不同方法的用例,然后说明你的软件允许你选择想要的方法。这很明显是间接的自我推销,因为任何想要了解更多信息的人基本上都会要求你的软件,因为在你的回答中没有其他重要的要点。 - Adi219
抱歉,我以为我已经提供了足够的信息让人们实现这个过程。我已经编辑了我的帖子,包括代码。 - Steve J

-2

使用粗糙的灰度算法可能会导致几个问题。首先,输出可能缺乏灰色阴影之间的平滑过渡,导致视觉上的锯齿状或像素化外观。这可能使图像看起来不自然并且失去细节。其次,该算法可能无法准确表示原始图像的色调值,导致对比度和亮度的微妙变化。此外,粗糙的灰度算法可能未考虑到颜色感知,从而导致不准确的转换。最后,该算法可能未考虑输入图像的特定特征,从而采用了通用的、大小适合所有情况的方法,无法捕捉各个图像的细微差别。

有限的动态范围:粗糙的灰度算法可能无法正确捕捉图像中完整的色调范围。这可能导致高光和阴影中细节的丢失,从而呈现出平淡或褪色的外观。

纹理和细节的丢失:算法的不精确性可能导致图像中细微纹理和复杂细节的丢失。这可能影响灰度转换的整体质量和视觉保真度。

结果不一致:不同的图像可能需要不同的调整和优化才能获得令人满意的灰度表示。粗糙的算法可能未考虑这些变化,导致在不同图像上产生不一致和次优的结果。

工艺品和噪音:算法的简化和近似可能会导致工艺品和噪音出现在灰度输出中。这些工艺品可能表现为不需要的图案、不规则性或像素失真,从而降低了图像质量。

对图像内容的不敏感性:粗糙的灰度算法可能不考虑图像的具体内容,如主题或构图。结果是,重要元素可能无法充分突出或区分,影响整体视觉效果。我们还可以在工具中观察到这一点,如图像拾色器调色盘

用户控制不足:用户可能对算法的参数和调整有限的控制权。这可能限制了他们根据自己的艺术视觉或特定需求来精细调整灰度转换的能力。
值得注意的是,这些问题的严重程度可能因所使用的具体算法和实现质量而异。然而,这些都是与粗糙灰度算法相关的一些常见挑战。

1
这个回答看起来像是ChatGPT。 - DavidW
1
这个回答看起来像是ChatGPT。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接