3

How can I convert an RGB histogram of an image to create a histogram showing the combined colors along with correct color wavelength range?

Example code:

pkg load image
f=imread('/tmp/marbles.jpg');
f=uint8(f); %need to convert back to uint8 to show picture

%Split into RGB Channels
f_red = f(:,:,1);
f_green = f(:,:,2);
f_blue = f(:,:,3);

%Get histValues for each channel
[y_f_red, x] = imhist(f_red);
[y_f_green, x] = imhist(f_green);
[y_f_blue, x] = imhist(f_blue);
subplot (2,1,1); imshow(f);
subplot (2,1,2); plot(x, y_f_red, 'r', x, y_f_green, 'g', x, y_f_blue, 'b');

Example image along with separate RGB histogram the code produces:

Current Histogram

I'm trying to get the histogram to look like the image below but have the colors go from red to blue:

enter image description here

Another image example:

Histogram I want

PS: I'm using Octave 4.0 which is very similar to MATLAB.

gnovice
  • 125,304
  • 15
  • 256
  • 359
Rick T
  • 3,349
  • 10
  • 54
  • 119
  • 2
    Are you trying to get a histogram of color values, like the second image in [this answer](http://stackoverflow.com/a/4064205/52738)? If so, you want to convert the RGB image to HSV first as described there. – gnovice Apr 19 '17 at 18:17
  • 2
    Also, conversion of RGB values to wavelengths/frequencies is problematic: see posts [here](http://stackoverflow.com/search?q=rgb+wavelength) and [here](http://stackoverflow.com/search?q=rgb+frequency). – gnovice Apr 19 '17 at 18:45
  • It's similar but if you notice red still shows up on both sides – Rick T Apr 19 '17 at 18:45

3 Answers3

7

There's a huge hurdle to converting between standard color representations (like RGB or HSV) and spectral wavelength: many colors can't be represented by a single wavelength of light. Colors such as magenta, pink, brown, or any grayscale color represent mixtures of different wavelengths. Generating an equivalent spectral wavelength is therefore a much more complicated endeavor (you may find some useful ideas and links here and here).

Creating histograms of the colors themselves may be a better way to go (illustrated in one of my other answers), but if you really want to relate color to wavelength in a simple fashion you can try the following...

A first step will be to convert RGB values to HSV values, then create a histogram of the hue channel. I'll adapt part of my answer from here to do that. The next step will be to map hues to wavelengths of light, using some rather gross approximations adapted from this answer:

rgbImage = imread('test_image.png');  % Load image
hsvImage = rgb2hsv(rgbImage);         % Convert the image to HSV space
hPlane = 360.*hsvImage(:, :, 1);      % Get the hue plane scaled from 0 to 360

binEdges = 0:270;                     % Edges of histogram bins
N = histc(hPlane(:), binEdges);       % Bin the pixel hues from above
wavelength = 620-(170/270).*(0:269);  % Approximate wavelength

hBar = bar(wavelength, N(1:end-1), 'histc');  % Plot the histogram

set(hBar, 'CData', 270:-1:1, ...    % Change the color of the bars using
    'CDataMapping', 'direct', ...   %   indexed color mapping (360 colors)
    'EdgeColor', 'none');           %   and remove edge coloring
colormap(hsv(360));                 % Change to an HSV color map with 360 points
axis([450 620 0 max(N)]);           % Change the axes limits
set(gca, 'Color', 'k');             % Change the axes background color
set(gcf, 'Pos', [50 400 560 200]);  % Change the figure size
xlabel('Wavelength (nm)');          % Add an x label
ylabel('Bin counts');               % Add a y label

NOTE: For the above to work properly in Octave, it may be necessary to change the set(hBar, ... line to the following:

set(hBar, 'FaceColor', 'flat', 'EdgeColor', 'none');
set(get(hBar, 'Children'), 'CData', 270:-1:1, 'CDataMapping', 'direct');

And here's the histogram:

enter image description here

There is, however, one issue with this. If we instead use the code exactly as it is in my other answer to plot the histogram of all the hue values, we would get this:

enter image description here

Note that there is a big cluster of magenta, pink, and reddish pixels that gets excluded when we toss out part of the hue range to convert to wavelengths (they don't correspond to a single wavelength in the light spectrum). Incorporating these into the results would require a more complicated conversion from hue to wavelength.

Community
  • 1
  • 1
gnovice
  • 125,304
  • 15
  • 256
  • 359
4

you can not convert RGB to wavelength unless some physical properties of the image and light is met. Anyway you can fake this by inversing:

if you do not know how look at:

But the result will not be the same as physical wavelengths histogram ... For that you would need multi-band image acquisition either by rotating prism optics or by set of bandpass filters ...

PS. HSV is far from accurate ...

Btw. the easiest way to do this is create palette from the spectral colors and convert your input image to it (indexed colors) and then just create histogram sorted by wavelength (and or color index)...

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • thanks I'll do some research into this and post the code if I find it useful. – Rick T Apr 29 '17 at 10:59
  • @RickT the problem is that not all the colors are in the spectra so you need to handle that. To simply chose closest color is not physically correct. Better would be to use dithering with the spectral colors palette. As the RGB are cumulative at the same manner as the individual spectral wavelengths ... You also need to handle the intensity of color so dark yellow is acounted the same as bright yellow. So I would create ordered spectral color palette with few shade grades of each wavelength , then use [dithering](http://stackoverflow.com/a/36820654/2521214) do histogram and group wavelengths. – Spektre Apr 29 '17 at 11:20
  • I'm looking at http://www.fourmilab.ch/documents/specrend/specrend.c and http://www.mathworks.com/matlabcentral/fileexchange/7021-spectral-and-xyz-color-functions also here's a video that explains the issue also https://youtu.be/OmPPnGSlf2Q – Rick T Apr 29 '17 at 14:16
  • This is also something I'm looking into http://stackoverflow.com/questions/2374959/algorithm-to-convert-any-positive-integer-to-an-rgb-value – Rick T Apr 29 '17 at 14:54
  • Most of the spectral color codes out there are wrong (not matching real spectral data) And as I needed it as close to the real thing as possible I create my own ... check the first link in my answer? Also that conversion RGB/XYZ based on just matrix multiplication is cheap fake (like CIE). For more precise values you need to integrate through X,Y,Z sensitivity curves like in [How do I draw a rainbow in Freeglut?](http://stackoverflow.com/a/22149027/2521214) if you really need it ... – Spektre Apr 29 '17 at 16:37
  • Thanks I'm reading that now I'm also looking into the math aspect from http://www.fourmilab.ch/documents/specrend/ – Rick T Apr 29 '17 at 17:26
1

Based on gnovices answer but with an image instead of bar (take 0.12s on my system):

rgbImage = imread ("17S9PUK.jpg");
hsvImage = rgb2hsv(rgbImage);

hPlane = 360 .* hsvImage(:, :, 1); 
binEdges = 1:360;
N = histc (hPlane(:), binEdges);

cm = permute (hsv (360), [3 1 2]);
img = repmat (cm, max(N), 1);

row_index = max(N) - N';
sp = sparse (row_index(row_index>0), (1:360)(row_index>0), true);
mask = flipud (cumsum (sp));

img(repmat (logical(1 - full(mask)), [1 1 3])) = 0;
image (img)
set (gca, "ydir", "normal");
xlabel('hue');
ylabel('Bin counts');

output of script

Andy
  • 7,931
  • 4
  • 25
  • 45