因此,我正在设计一些程序,以使用python
在PIL
中编辑照片,其中一个程序是将图像转换为灰度(我避免使用{{1 }}。
我采用的算法很简单:对于每个像素(色深为24),我计算了PIL
,R
和G
值的平均值,将RGB值设置为此平均值。
我的程序正在生成看起来准确的灰度图像,但是我想知道我是否使用了正确的算法,并且遇到this answer的问题,看来“正确”的算法是计算B
。
我决定将程序与此算法进行比较。我使用我的程序生成了一个灰度图像,并使用了a website online(0.299 R + 0.587 G + 0.114 B
的Google最高搜索结果)中的另一个图像(使用相同的输入)。
以我的肉眼看来,它们是完全一样的,并且如果有任何变化,我看不到。但是,我决定使用this website('image to grayscale'
的Google最高搜索结果)来比较我的灰度图像。事实证明,在像素深处,它们有细微的变化,但是乍看之下没有人眼可以察觉的变化(可以发现差异,但通常仅当图像相互放置或在几毫秒内切换时才可见)
我的问题(第一个是主要问题):
我的关键代码段(如果需要):
'compare two images online'
“正确”算法(似乎在偏重绿色):
def greyScale(pixelTuple):
return tuple([round(sum(pixelTuple) / 3)] * 3)
尽管上面突出显示了像素的变化,但上面的灰度图像看起来几乎完全相同(至少对我而言)。
另外,关于我的第一个问题,如果有人感兴趣,this site已经对用于转换为灰度的不同算法进行了一些分析,并且还提供了一些自定义算法。
编辑:
为响应@Szulat的回答,我的算法实际上生成了该图像(忽略不良裁剪,原始图像有3个圆圈,但我只需要第一个圆圈):
如果人们想知道转换为灰度的原因是什么(似乎算法取决于目的),我只是在def greyScale(pixelTuple):
return tuple([round(0.299 * pixelTuple[0] + 0.587 * pixelTuple[1] + 0.114 * pixelTuple[2])] * 3)
中制作一些简单的照片编辑工具,这样我就可以迷你Photoshop,无需依靠互联网即可应用滤镜和效果。
赏金理由:此处的不同答案涵盖了不同的内容,这些都是相关且有用的。这使得很难选择要接受的答案。我之所以开始赏金,是因为我喜欢这里列出的一些答案,而且还因为有一个单一的答案能涵盖我对此问题的所有需求,这很高兴。
答案 0 :(得分:46)
图像看起来很相似,但是您的眼睛可以分辨出它们的区别,特别是如果您将其中一个代替另一个:
例如,您可以注意到平均转换中背景中的花朵看起来更亮。
对这三个通道求平均值并不意味着存在任何本质上的“坏”之处。使用该公式的原因是我们对红色,绿色和蓝色的感知不同,因此它们对灰度图像中强度的贡献不应相同。由于我们对绿色的感知更加强烈,因此绿色像素在灰度上应该看起来更亮。但是,as commented by Mark并没有独特的完美转换为灰度,因为我们可以看到颜色,并且在任何情况下,每个人的视线都略有不同,因此任何公式都将尝试近似,以使像素强度感觉“适合”大多数人。
答案 1 :(得分:42)
最明显的例子:
原始
在Gimp中饱和(亮度模式-这就是您的算法所做的事情)
在Gimp中去饱和(光度模式-这就是我们的眼睛)
因此,请勿平均RGB。平均RGB是完全错误的!
(好吧,是的,平均在某些晦涩的应用中可能是有效的,即使当将RGB值视为颜色时,平均值没有物理或生理意义。顺便说一下,“常规”由于存在伽玛,因此进行加权平均在某种程度上也是不正确的。应该先将sRGB线性化,然后将最终结果转换回sRGB(这等同于检索Lab颜色空间中的L分量)
答案 2 :(得分:19)
您可以使用任何转换公式,比例,线性。您找到的一个:
I = 0.299 R + 0.587 G + 0.114 B
基于人眼的平均“平均”原色(R,G,B)感知灵敏度(至少是针对其创建的时间段和人口/硬件);请记住,这些标准是在LED,TFT之前创建的等等)。
您要解决的几个问题:
我们的眼睛不一样
所有人类对颜色的感知方式都不相同。性别之间存在重大差异,地区之间差异也较小;甚至年龄和年龄都起着作用。因此,即使是平均值也应视为“平均值”。
我们对可见光谱中的光强度有不同的敏感性。最敏感的颜色是绿色(因此权重最高)。但是XYZ curve峰对于不同的人可能具有不同的波长(像我一样,我将它们移动了一点,导致对某些波长的识别有所不同,例如某些深浅不一的Aqua-有些人将它们视为绿色,有些像蓝色,即使没有他们有色盲或其他障碍)。
显示器既不使用相同的波长,也不使用光谱色散
因此,如果您使用2个不同的监视器,则它们可能对R,G,B使用略微不同的波长,甚至对光谱滤镜(just use a spectroscope and see)使用不同的宽度。是的,应该通过硬件对其进行“归一化”,但这与使用归一化波长不同。这类似于使用RGB与白噪声光谱光源的问题。
显示器线性度
人类不会以线性尺度看待:我们通常是对数/指数的(取决于您如何看待它),是的,我们可以使用硬件(甚至软件)对它进行归一化,但是问题是如果我们对一个人进行线性化,那么表示我们将其损坏。
如果将所有这些放在一起,则可以使用平均值...或专用(昂贵的)设备根据某些标准或经过校准的人员(取决于行业)进行测量/归一化。
但是,在家庭环境中处理起来实在太多了,因此将所有这些留给行业使用,并像世界上大多数国家一样将权重用于“平均” ...幸运的是,除非您开始,否则我们的大脑可以处理它,因为您看不到差异并排比较两个图像或在动画中比较两个图像:)。所以我(愿意)这样做:
I = 0.299 R + 0.587 G + 0.114 B
R = I
G = I
B = I
答案 3 :(得分:8)
有许多种不同的转换为灰度的方法,它们的确给出了不同的结果,尽管使用不同的输入彩色图像可能更容易看到差异。
正如我们并不是真正在灰度中看到的那样,“最佳” 方法在某种程度上取决于应用程序,并且在旁观者的眼中。
您所指的替代公式是基于人眼对绿色色调的变化更加敏感,因此赋予它们更大的权重-类似于相机中的拜耳阵列,其中红色和蓝色每个都有2个绿色像素一。 Wiki - Bayer array
答案 4 :(得分: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的信息,这可能会在接下来的处理步骤中给您带来麻烦。根据问题,有时使用其他公式更好,例如V = MAX(R,G,B)(来自HSV颜色空间)。
我怎么知道?我是Poynton博士的追随者和朋友。
答案 5 :(得分: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')
此方法真正保留了原始颜色的“明亮”。但是,无需考虑人眼如何处理数据。
答案 6 :(得分: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
}