How can i plot the YCbCr histograms with the correct colour points (0-255)

9 views (last 30 days)
Hi,
So i am trying to replicate one of MATLAB's plots but unsure how to get the varying change in colour in the histograms that depend on the pixel value (0-255). YCbCr file is what im trying to achieve. This is what i have so far.
img = imread('peppers.png');
ycbcr=rgb2ycbcr(img);
Y = ycbcr(:,:,1);
Cb = ycbcr(:,:,2);
Cr = ycbcr(:,:,3);
figure;
subplot(311);
histogram(Y,'BinMethod','integers','EdgeAlpha',0,'FaceAlpha',1);
title('Y');xlim([0 255]);
subplot(312);
histogram(Cb,'BinMethod','integers','EdgeAlpha',0,'FaceAlpha',0.7);
title('Cb');xlim([0 255]);
subplot(313);
histogram(Cr,'BinMethod','integers','EdgeAlpha',0,'FaceAlpha',0.7);
title('Cr');xlim([0 255]);

Accepted Answer

DGM
DGM on 27 Mar 2023
Edited: DGM on 18 May 2023
Personally, I've always thought those histograms were a bad idea. The fact that the bins are colored against a background of similar lightness makes them very difficult to read in any detail. In short, it would be a more complicated task to create something that I argue is less useful.
Method 1
Using a colorstrip under a regular histogram is what imhist() does. This can be extended to represent Cb/Cr in pseudocolor as follows:
% an image in YCbCr
inpict = imread('peppers.png');
yccpict = rgb2ycbcr(inpict);
% some things we'll need first
x0 = [54 133];
CT0cb = [0.986 1 0.367; 0.604 1 0.985];
CT0cr = [0.261 1 0.139; 0.753 0.706 0.758];
% generate the colormaps for each channel
CTycc = cell(3,1);
CTycc{1} = gray(220); % luma
CTycc{2} = imclamp(interp1(x0,CT0cb,linspace(0,255,225),'linear','extrap')); % Cb
CTycc{3} = imclamp(interp1(x0,CT0cr,linspace(0,255,225),'linear','extrap')); % Cr
% generate the histograms
for c = 1:3
subplot(3,1,c)
imhist(yccpict(:,:,c));
% build padded colorstripe
if c == 1
% use gray padding for the luma bar
% that way neither end disappears into the padding
thisCS = [0.7*ones(16,3); CTycc{c}; 0.7*ones(20,3)];
else
thisCS = [zeros(16,3); CTycc{c}; zeros(15,3)];
end
% update the colorstripe
hi = findobj('type','image');
hi(1).CData = permute(thisCS,[3 1 2]);
end
Where did those values come from in CT0cb and CT0cr? Those approximately define the colormap used by the histogram tools used by the Color Thresholder app. It's worth noting that those are all piecewise-linear in RGB, and they aren't actually axis-aligned in YCbCr. It's just a pretty color sweep. It's not technically accurate or anything.
Note that because I used a colorstrip instead of trying to color the histogram bars, the full sweep is visible, regardless of the color distribution in the image. Also note that I added padding bars to indicate the margin regions where image data shouldn't normally exist.
Method 2
Alternatively, if you really really want to to color the histogram, then getting each bin colored is the challenge. Unlike scatter(), stem objects (what imhist() uses) don't support independent coloring of the stems. A bar chart might be one option, but with the number of bins you'd be using, the setup might have some limitations. I don't know what the Color Thresholder App uses, but here's one way.
% an image in YCbCr
inpict = imread('peppers.png');
yccpict = rgb2ycbcr(inpict);
% some things we'll need first
x0 = [54 133];
CT0cb = [0.986 1 0.367; 0.604 1 0.985];
CT0cr = [0.261 1 0.139; 0.753 0.706 0.758];
CTycc = cell(3,1);
CTycc{1} = gray(220); % luma
CTycc{2} = imclamp(interp1(x0,CT0cb,linspace(0,255,225),'linear','extrap')); % Cb
CTycc{3} = imclamp(interp1(x0,CT0cr,linspace(0,255,225),'linear','extrap')); % Cr
% generate the histograms
for c = 1:3
subplot(3,1,c)
% build padded colormaps
if c == 1
thisCS = [zeros(16,3); CTycc{c}; zeros(20,3)];
else
thisCS = [zeros(16,3); CTycc{c}; zeros(15,3)];
end
% construct patch object
ytop = imhist(yccpict(:,:,c)).';
ybot = zeros(size(ytop));
xrange = getrangefromclass(yccpict(:,:,c));
x = linspace(xrange(1),xrange(2),numel(ytop));
xc = linspace(0,1,numel(ytop));
hp = patch([x fliplr(x)],[ybot fliplr(ytop)],[xc fliplr(xc)]);
hp.EdgeColor = 'none';
hold on; grid on
% plot envelope curve to make sure it's even visible
plot(x,ytop,'k','linewidth',1);
% set the colormap
colormap(gca,CTycc{c})
% set y-scaling to mimic imhist()
ylim([0 2.5*sqrt(ytop*ytop.'/length(ytop))])
xlim(xrange)
end
Note that I added a line which traces out the envelope of the histogram. Without it, the top end of the Y histogram would be completely invisible.
  3 Comments
DGM
DGM on 27 Mar 2023
Edited: DGM on 18 May 2023
Notes on the colormaps
As I said, the color sweeps that are used to make the original histograms are just piecewise linear RGB sweeps -- piecewise only because the simple linear trajectory gets truncated as it reaches the faces of the RGB cube. If we were to plot these in YCbCr, they would look like this
Note that they are not aligned even remotely with the Cb,Cr axes, nor are they mutually-orthogonal. I have no idea why these were chosen for this purpose. The fact that there is so little isolation of the color components makes these maps misleading.
It might make more sense to use something grid-aligned in the represented color space. For example:
% an image in YCbCr
inpict = imread('peppers.png');
yccpict = rgb2ycbcr(inpict);
% generate the colormaps for each channel
CTycc = cell(3,1);
CTycc{1} = gray(220); % luma
ramp = (16:240).'; % full chroma swing
mid = 128*ones(size(ramp)); % both Cb,Cr trajectories cross the neutral axis
Y = 0.7*255*ones(size(ramp)); % luma at which Cb,Cr are sampled
CTycc{2} = ycbcr2rgb(uint8([Y ramp mid])); % Cb
CTycc{3} = ycbcr2rgb(uint8([Y mid ramp])); % Cr
% generate the histograms
for c = 1:3
subplot(3,1,c)
imhist(yccpict(:,:,c));
% build padded colorstripe
if c == 1
% use gray padding for the luma bar
% that way neither end disappears into the padding
thisCS = [0.7*ones(16,3); CTycc{c}; 0.7*ones(20,3)];
else
thisCS = [zeros(16,3); CTycc{c}; zeros(15,3)];
end
% update the colorstripe
hi = findobj('type','image');
hi(1).CData = permute(thisCS,[3 1 2]);
end
Those colors aren't as vibrant and pretty, but they actually make some sense in YCbCr.
Again, the trajectory is piecewise-linear due to the fact that there's no way a constant-luma full chroma swing will stay in gamut.
Note that these trajectories are largely grid-aligned and orthogonal.
While I plotted this in YPbPr out of my own convenience, YCbCr and YPbPr differ only in scaling and offsets. These plots would look identical in YCbCr. The only difference would be the axis ticks and labels.
DGM
DGM on 24 May 2024
Edited: DGM on 24 May 2024
Update:
MIMT now has a tool for doing this sort of thing.
% an RGB image
inpict = imread('peppers.png');
% display the distribution of the image content
% as projected into the specified color model
cshist(inpict,'ycbcr'); % using defaults with uint8 YCbCr
% use LAB instead of YCbCr
% use a filled patch instead of the default bar plot
cshist(inpict,'lab','style','patch');
% set non-default bin count
% set xlim to the data extrema
% set ylim to include all peaks
cshist(inpict,'lab','nbins',25,'extents','extrema','yscale','full');
% use a polar model
% get all the relevant histogram data and axes handles
[counts, centers, edges, handles] = cshist(inpict,'lchuv');
% it can also just be used for RGB just the same.
% when selecting 'rgb', the data is processed in its native scale
cshist(inpict,'rgb');
MIMT cshist() supports most models that MIMT supports, though only with their default transformation parameters (e.g. BT601 for YCbCr/YPbPr)
The target axes can be created beforehand and specified in the call to cshist() if it's easier to arrange plots that way.

Sign in to comment.

More Answers (1)

David Szwer
David Szwer on 27 Mar 2023
You would want to change the CData property of the histograms, which would let you set the colour of each individual bar. However, I don't think histograms actually have this property - only bar charts:
You probably need to get the histogram counts, and then manually plot a graph of them. Someone called Wolfie on StackExchange created this example. I'll copy it here for reference (changing the data variable x to a random array, just so it is runnable), but they deserve the credit!
[h,edges] = histcounts(rand([1 100]),10); % calculate the histogram data
b = bar( (edges(1:end-1)+edges(2:end))/2, h ); % plot the bar chart
b.BarWidth = 1; % make the bars full width to look the same as 'histogram'
b.CData = parula( 10 ); % generate colours as a 10x3 array (columns are RGB), can
% do this manually if you want
b.FaceColor = 'flat'; % Make 'bar' use the CData colours

Categories

Find more on Read, Write, and Modify Image in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!