"Standard" RGB to Grayscale Conversion
Asked Answered
L

6

30

I'm trying to write a converters algorithm that takes a JPEG image and returns its PGM (Portable Gray Map) version. The problem is that I can't understand how the "official" JPG->PGM convertitors work in terms of what value to assign to the final pixel (i guess, 0->255) starting from the classic RGB format.

At the beginning, I used this formula (it's the same used by OpenCV's CV_RGB2GRAY conversion):

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

I wrote a simple code to test my results: it takes a color image and its PGM version (already converted using GIMP). Then it converts the color image using the previous formula. The goal is to have a grayscale image that is pixel-to-pixel equal to the PGM input.

At this point, it does not return the same values. Can you help me?

Larry answered 12/7, 2013 at 13:24 Comment(2)
Have you already had a look at wikipedia: Grayscale?Aeromancy
Are you sure that's what it does? What if it just decodes the Y plane and ignores the colour-coefficients? You'd have different noise, and the factors may be different.Ratel
J
37

The problem is that I can't understand how the "official" JPG->PGM convertitors work in terms of what value to assign to the final pixel (i guess, 0->255) starting from the classic RGB format.

There is likely a gamma adjustment in the conversion those "official" tools are using.
That is, it is not just a linear transform.

See this Wikipedia section for the details: Converting color to grayscale

I believe you want to use the formula for Csrgb.
Try it out and see if it matches the results you're expecting.

Basically, you'll do this:

  1. Take R, G, B color (each in [0,1] range)
    • If they're in the range 0..255 instead, simply divide by 255.0
  2. Compute Clinear = 0.2126 R + 0.7152 G + 0.0722 B
    • This is likely the linear transform you were applying before
  3. Compute Csrgb according to it's formula, based on Clinear
    • This is the nonlinear gamma correction piece you were missing
    • Check out this WolframAlpha plot
    • Csrgb = 12.92 Clinear when Clinear <= 0.0031308
    • Csrgb = 1.055 Clinear1/2.4 - 0.055 when Clinear > 0.0031308
Jochebed answered 12/7, 2013 at 16:22 Comment(3)
Timothy, please correct me if I am wrong, but I think after step (1) you have to transform the values to linear intensity, since when you take RGB values from file they are already gamma-encoded with power 1/2.4. So first you need to remove this encoding by applying transform with power 2.4, and only then do steps (2) and (3) of your answer. Is that right?Katrinka
@JohnSmith You're correct. It's expand, find linear luminance, then compress.Millicent
For CSS color $rgb(91, 91, 102) / 256.0, c_{lin}=0.35857109 > 0.0031308$, thus the 2nd formula, which yields nonsensical $c_{srgb}=1.055 × c_{lin}^{1/2.4} - 0.055 = 0.63311151$, with $gray = \left[ 0.63311151 × 256 \right] = 162$. Since B has little contribution, the expected result should be somewhere about the 92–94 range. Dropping the $1/\gamma$ power yields 83, which is too low. :(Cacology
M
4

To harold's point about the "Y plane": standard color JPEGs are encoded using the YCbCr colorspace, where Y is the luminance component (i.e. the brightness) and Cb and Cr are the blue-difference and red-difference chroma components. So one way of turning a color JPEG into a grayscale one is to simply drop the Cb and Cr components.

There is a utility called jpegtran than can do this losslessly, using the -grayscale option. (The lossless part would really only matter if you wanted to end up with a JPEG and not PGM, to avoid generation loss.) In any case, this would probably be the fastest way to do this transformation, because it doesn't even decode the image into pixels, much less do math on each one.

Myatt answered 15/12, 2017 at 21:16 Comment(0)
P
1

In theory, with a few pixels (3, in this case), you can determine what their algorithm is doing. Juste pick your three pixel (p1, p2, p3), their RGB value and their PGM gray value, and you have:

RedConstant * p1.redValue + GreenConstant * p1.greenValue + BlueConstant * p1.blueValue = p1.grayValue

RedConstant * p2.redValue + GreenConstant * p2.greenValue + BlueConstant * p2.blueValue = p2.grayValue

RedConstant * p3.redValue + GreenConstant * p3.greenValue + BlueConstant * p3.blueValue = p3.grayValue.

Then solve this problem (look up "equation solver" or something) and see what are the constants they use.

Pucker answered 12/7, 2013 at 13:51 Comment(6)
Thanks but, no, it doesn't work. I put 3 pixel values and solved a three-equations system. It results in three constants that are good for those equations and not for a fourth pixel.Larry
1) Are you sure you picked the same pixels for RGB and gray value? 2) from this article: tannerhelland.com/3643/grayscale-image-algorithm-vb6 I saw that they were several different RGB-to-PGM algorithm. Try them all and try to discover which one is used. Good luck!Pucker
What if I tell you that I didn't find any valid method?Larry
Well, GIMP probably use their own weird algorithm, what is the real purpose of trying to recreate exactly their converter?Pucker
The process of PGM convertion is a subpart of a longer algorithm. After some tests, I've seen that the results of such algorithm are better if I use a "GIMP" PGM image as input, and not a simpler version created with all those methods. So I though that the real PGM format describes pixel values in a way that seem to be more... "manageable" by my algorithm. I've also tried to look at GIMP's code, I've found a possible convertion point but it's not so readable...Larry
@Pucker This isn't about GIMP "[using] their own weird algorithm." It's gamma correction. See my answer and the Wikipedia page it links to.Jochebed
D
0

SIMPLE ALGORITHM TO CONVERT RGB IMAGE TO GRAYSCALE IN OPENCV PYTHON!

I used comments so code is self-explanatory.But it works swiftly.

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

SO I used this image file ...enter image description here

My program generated following Grayscale file ..

enter image description here

Dispute answered 20/5, 2017 at 17:5 Comment(2)
Iterating over all pixels in Python code, performing the calculation in Python and appending the result into a list only to convert it back to a numpy array is going to be slow as hell (and uses approximately 4 to 8 times more memory than the optimal solution). Since you already have numpy you can perform the whole calculation on the C side using broadcasting, way faster and avoids wasting memory.Freedwoman
So, after loading the image, the whole thing can be reduced to gr = img1[:,:,0]*0.07 + img1[:,:,1]*0.72 + img1[:,:,2]*0.21 cv2.imwrite("test1.png", gr). On my machine your code with your image take 0.41 seconds, my code 0.06; the difference is more dramatic with bigger images.Freedwoman
T
-1

Converts a single input pixel in the default RGB ColorModel to a single gray pixel.

/* 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);
}
Ting answered 2/1, 2019 at 14:34 Comment(0)
M
-5

Average method is the most simple one. You just have to take the average of three colors. Since its an RGB image, so it means that you have add r with g with b and then divide it by 3 to get your desired grayscale image.

Its done in this way.

Grayscale = (R + G + B / 3)

If you have an color image like the image shown above and you want to convert it into grayscale using average method.

Misapply answered 2/2, 2019 at 17:50 Comment(2)
This doesn't even divide the sum by 3 correctly as it only divides the B component by three. Also, taking the average is not correct.Andalusia
This is wrong, Parenthesis are located wrong and doesn't get the right order of operations for the average ... and while that is one method, that is not for correct grayscale since eyes react differently to each of the three colors .. the right formula is Grayscale = 0.299 * R + 0.587 * G + 0.114 * BSibley

© 2022 - 2024 — McMap. All rights reserved.