RGB to Grayscale using CImg

RGB to Grayscale using CImg

I have been looking for a CImg function that would translate a RGB image to grayscale directly, but couldn’t find one. This is what I did, it could probably help you with this task.

It separates all 3 RGB channels and then calculates two different gray image versions –one standard arithmetic and the other using different weights for each channel.

#include "CImg.h"
#include  <iostream>

// Use the library namespace to ease the declarations afterward.
using namespace cimg_library;
using namespace std;

int main() {

  /*
  * VARIABLES ---------------------
  * Creation of two instances of images of unsigned char pixels.
  * The first image _image_ is initialized by reading an image file from the disk.
  * Here, lena.jpg must be in the same directory than the current program.
  * Note that you must also have installed the ImageMagick package
  * in order to be able to read JPG images.
  * var(size_x, size_y, size_z, dv, default_fill)
  */
  CImg<unsigned char> image("images/lena.jpg"),
          gray(image.width(), image.height(), 1, 1, 0),
          grayWeight(image.width(), image.height(), 1, 1, 0),
          imgR(image.width(), image.height(), 1, 3, 0),
          imgG(image.width(), image.height(), 1, 3, 0),
          imgB(image.width(), image.height(), 1, 3, 0);

  // for all pixels x,y in image
  cimg_forXY(image,x,y) {
    imgR(x,y,0,0) = image(x,y,0,0),    // Red component of image sent to imgR
    imgG(x,y,0,1) = image(x,y,0,1),    // Green component of image sent to imgG
    imgB(x,y,0,2) = image(x,y,0,2);    // Blue component of image sent to imgB

    // want to print out the RGB value of each pixel? Uncomment the following:
    /*
    * cout << image.width() << "x" << image.height() << endl;
    * cout << "(" << x << "," << y << ") ="
    *             << " R:" << (int)image(x,y,0,0)
    *             << " G:" << (int)image(x,y,0,1)
    *             << " B:" << (int)image(x,y,0,2) << endl;
    */

    // Separation of channels
    int R = (int)image(x,y,0,0);
    int G = (int)image(x,y,0,1);
    int B = (int)image(x,y,0,2);
    // Arithmetic addition of channels for gray
    int grayValue = (int)(0.33*R + 0.33*G + 0.33*B);
    // Real weighted addition of channels for gray
    int grayValueWeight = (int)(0.299*R + 0.587*G + 0.114*B);
    // saving píxel values into image information
    gray(x,y,0,0) = grayValue;
    grayWeight(x,y,0,0) = grayValueWeight;
}

  // 4 display windows, one for each image
  CImgDisplay main_disp(image,"Original"),
      draw_dispR(imgR,"Red"),
      draw_dispG(imgG,"Green"),
      draw_dispB(imgB,"Blue"),
      draw_dispGr(gray,"Gray"),
      draw_dispGrWeight(grayPond,"Gray (Weighted)");

  // wait until main window is closed
  while (!main_disp.is_closed()){
      main_disp.wait();
  }

  return 0;
}

Results

lena in channels

lena in gray

But why use weighted gray images?

Because our eyes don’t perceive all frequencies of visible light, or color, in the same way. We have more M cone opsines than L or S, so we are more sensible to light from the green frequencies. The following graph shows the relative brightness sensitivity of the human visual system as a function of wavelength.

Eye Sensitivity

Comparison

To see the difference between a weighted image and one that it not, have a close look at the example above. Can you see how we managed to save the luminance of the original image?

VFX for The Wolf of Wall Street

Next Article

VFX for The Wolf of Wall Street

10 Comments

Cancel

  1. There is a simpler way to do this. Basically, you want to compute the so-called ‘luminance’ map of the RGB colors, and you can do this with one line in CImg :

    img.RGBtoYCbCr().channel(0);

    • But in that case you can’t have a weighted mix of the different color channels, right?

  2. This is actually a weighted mix of the channels, with more weights to the green channel.
    If you need to specify your own weights, you can also do a simple thing like this :

    CImgList channels = img<'c';
    ((channels[0]*=0.2) += (channels[1]*=0.6) += (channels[2]*=0.4)).move_to(img);

    with weights 0.1, 0.6 and 0.3 here :)

    • Is it by default? I didn’t know. But don’t you think that having to convert the whole image to another color format is consuming? After all, your total solution is 3 lines and mine’s 4… Strip away the fuzz and printing the other three channels separately and you’re left with the essentials:

      >> int R = (int)image(x,y,0,0);
      >> int G = (int)image(x,y,0,1);
      >> int B = (int)image(x,y,0,2);
      >> int grayValueWeight = (int)(0.299*R + 0.587*G + 0.114*B);

      So in your opinion why should I convert the whole image first?

  3. I tried to test it but it would not compile and run; it said “image.cimg.library::CImg::width’ cannot be used as a function…

  4. Thank you very much for your post! by the way :) I find it hard to employ image processes with CImg because we haven’t been taught much in school…

  5. Thank you..this worked perfectly for the project I was working on!