"标准" RGB 转灰度转换

30

我正在尝试编写一个转换算法,将JPEG图像转换为其PGM(便携式灰度地图)版本。

问题在于,我无法理解“官方”的JPG->PGM转换器的工作方式,即从经典的RGB格式开始,分配给最终像素的值是什么(我猜是0->255)。

起初,我使用了这个公式(与OpenCV的CV_RGB2GRAY转换相同):

0.30*R + 0.59*G + 0.11*B = val

我编写了一个简单的代码来测试我的结果:它接受一张彩色图像及其PGM版本(已使用GIMP转换)。然后使用先前的公式转换彩色图像。目标是获得一个灰度图像,其像素与PGM输入一一对应。

但是,此时它并没有返回相同的值。能否帮助我?


你已经看过维基百科:灰度了吗? - MrSmith42
你确定它就是这样做的吗?如果它只解码Y平面并忽略色彩系数呢?你会得到不同的噪点,而且因素可能也不同。 - harold
6个回答

36
问题在于我不理解“官方”的JPG->PGM转换器如何在从经典RGB格式开始为最终像素分配值时工作(我猜测是0->255)。
这些“官方”工具中可能有伽马调整。也就是说,这不仅仅是一个线性变换。 请参见维基百科中的此部分详细信息:将彩色转换为灰度 我认为您想使用Csrgb的公式。
尝试一下,看看它是否与您期望的结果相匹配。
基本上,您要这样做:
  1. R,G,B颜色(每个值在[0,1]范围内)
    • 如果它们的范围是0..255,则只需除以255.0
  2. 计算Clinear = 0.2126 R + 0.7152 G + 0.0722 B
    • 这可能是您之前应用的线性变换
  3. 根据其公式计算Csrgb,基于Clinear
    • 这是您缺少的非线性伽马校正部分
    • 查看此WolframAlpha图
    • Clinear <= 0.0031308时,Csrgb = 12.92 Clinear
    • Clinear > 0.0031308时,Csrgb = 1.055 Clinear1/2.4 - 0.055

2
Timothy,请纠正我如果我错了,但我认为在步骤(1)之后,您必须将值转换为线性强度,因为当您从文件中获取RGB值时,它们已经使用功率1/2.4进行伽马编码。因此,您首先需要通过应用功率2.4的变换来去除此编码,然后才能执行您答案的步骤(2)和(3)。是这样吗? - John Smith
1
@JohnSmith 你说得对。先扩展,再查找线性亮度,最后压缩。 - James South
对于CSS颜色$rgb(91, 91, 102) / 256.0$,$c_{lin}=0.35857109 > 0.0031308$,因此第二个公式得出荒谬的$c_{srgb}=1.055 × c_{lin}^{1/2.4} - 0.055 = 0.63311151$,$gray = \left[ 0.63311151 × 256 \right] = 162$。由于B的贡献很小,预期结果应该在92-94范围内。去掉$1/\gamma$的幂后得到83,这太低了。 :( - kkm

4
对于Harold提到的“Y平面”的问题:标准色彩JPEG使用 YCbCr颜色空间进行编码,其中Y是亮度分量(即亮度),Cb和Cr是蓝差和红差色度分量。因此,将彩色JPEG转换为灰度JPEG的一种方法是简单地删除Cb和Cr分量。

有一个名为jpegtran的实用程序可以使用-grayscale选项无损地执行此操作。(无损部分只有在您想要得到JPEG而不是PGM以避免生成损失时才真正重要。)无论如何,这可能是最快的方式进行此转换,因为它甚至不会将图像解码为像素,更不用对每个像素进行数学运算。


1
理论上,只需几个像素(在此情况下为3),您就可以确定他们的算法在做什么。 只需选择三个像素(p1、p2、p3),它们的RGB值和它们的PGM灰度值,您就可以得到:
红色常量* p1.redValue + 绿色常量* p1.greenValue + 蓝色常量* p1.blueValue = p1.grayValue 红色常量* p2.redValue + 绿色常量* p2.greenValue + 蓝色常量* p2.blueValue = p2.grayValue 红色常量* p3.redValue + 绿色常量* p3.greenValue + 蓝色常量* p3.blueValue = p3.grayValue。
然后解决这个问题(查找“方程求解器”或类似内容),看看他们使用的常量是什么。

谢谢,但是不行。我输入了3个像素值并解决了一个三元方程组。这导致了三个常数,它们适用于这些方程而不适用于第四个像素。 - TheUnexpected
1
  1. 你确定你选择的RGB和灰度值是相同的像素吗?
  2. 从这篇文章中:http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/ 我看到了几种不同的RGB-to-PGM算法。尝试它们并发现哪一个被使用。祝你好运!
- Fabinout
如果我告诉你我没有找到任何有效的方法,该怎么办? - TheUnexpected
嗯,GIMP可能使用了他们自己奇怪的算法,试图完全重现他们的转换器的真正目的是什么? - Fabinout
PGM转换的过程是更长算法的一个子部分。经过一些测试,我发现如果使用“GIMP” PGM图像作为输入,而不是使用所有这些方法创建的简单版本,则此算法的结果更好。 因此,我认为真正的PGM格式以一种似乎更易于我的算法处理的方式描述像素值。 我还尝试查看了GIMP的代码,找到了可能的转换点,但它并不那么易读... - TheUnexpected
@Fabinout,这不是关于GIMP“使用他们自己的奇怪算法”的问题。这是伽马校正。请查看我的回答和维基百科页面。 - Timothy Shields

0

在OpenCV Python中将RGB图像转换为灰度图像的简单算法!

我使用了注释,所以代码是自解释的。但它可以快速运行。

import cv2
import numpy as np
img1 = cv2.imread('opencvlogo.png')
row,col,ch = img1.shape
g = [ ]  #the list in which we will stuff single grayscale pixel value inplace of 3 RBG values
#this function converts each RGB pixel value into single Grayscale pixel value and appends that value to list 'g'
def rgb2gray(Img):
    global g
    row,col,CHANNEL = Img.shape
    for i in range(row) :
        for j in range(col):
        a =      (   Img[i,j,0]*0.07  +  Img[i,j,1]*0.72 +    Img[i,j,2] *0.21   ) #the algorithm i used id , G =  B*0.07 + G*0.72 + R* 0.21
                                                                                   #I found it online
        g.append(a)
rgb2gray(img1)  #convert the img1 into grayscale
gr = np.array(g)  #convert the list 'g' containing grayscale pixel values into numpy array
cv2.imwrite("test1.png" , gr.reshape(row,col)) #save the image file as test1.jpg

所以我使用了这个图像文件...在此输入图像描述

我的程序生成了以下灰度文件...

enter image description here


在Python代码中迭代所有像素,使用Python执行计算并将结果附加到列表中,然后将其转换回numpy数组会非常慢(并且使用的内存大约是最佳解决方案的4到8倍)。由于您已经有了numpy,因此可以使用广播在C端执行整个计算,速度更快,避免浪费内存。 - Matteo Italia
4
加载图像后,整个过程可以简化为 gr = img1[:,:,0]*0.07 + img1[:,:,1]*0.72 + img1[:,:,2]*0.21 cv2.imwrite("test1.png", gr)。在我的电脑上,您的代码与您的图像需要 0.41 秒,而我的代码则需要 0.06 秒;对于更大的图像,差异更加明显。 - Matteo Italia

-1

将默认的RGB ColorModel中的单个输入像素转换为单个灰色像素。

/* Convertation function 
 * @param x    the horizontal pixel coordinate
 * @param y    the vertical pixel coordinate
 * @param rgb  the integer pixel representation in the default RGB color model
 * @return a gray pixel in the default RGB color model.*/

    public int filterRGB(int x, int y, int rgb) {
    // Find the average of red, green, and blue.
    float avg = (((rgb >> 16) & 0xff) / 255f +
                 ((rgb >>  8) & 0xff) / 255f +
                  (rgb        & 0xff) / 255f) / 3;
    // Pull out the alpha channel.
    float alpha = (((rgb >> 24) & 0xff) / 255f);

    // Calculate the average.
    // Formula: Math.min(1.0f, (1f - avg) / (100.0f / 35.0f) + avg);
    // The following formula uses less operations and hence is faster.
    avg = Math.min(1.0f, 0.35f + 0.65f * avg);
    // Convert back into RGB.
   return (int) (alpha * 255f) << 24 |
          (int) (avg   * 255f) << 16 |
          (int) (avg   * 255f) << 8  |
          (int) (avg   * 255f);
}

-5

平均法是最简单的方法。您只需要取三个颜色的平均值即可。由于它是一张RGB图像,所以意味着您必须将r与g与b相加,然后除以3来获得所需的灰度图像。

这样做。

Grayscale = (R + G + B / 3)

如果您有一张彩色图像,想要使用平均方法将其转换为灰度图像,可以按照上面所示的图像进行操作。

3
这甚至不能正确地将总和除以3,因为它只将B组件除以3。而且,取平均值也是不正确的。 - keith
这是错误的,括号位置不正确,并且无法得到平均值的正确操作顺序...虽然这是一种方法,但对于灰度来说并不正确,因为眼睛对三种颜色的反应不同...正确的公式是Grayscale = 0.299 * R + 0.587 * G + 0.114 * B。 - Dan Ortega

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