Steve on Image Processing and MATLABConcepts, algorithms & MATLAB

This is machine translation

Mouseover text to see original. Click the button below to return to the Original version of the page.

Displaying a color gamut surface8

Posted by Steve Eddins,

Today I'll show you one way to visualize the sRGB color gamut in L*a*b* space with assistance with a couple of new functions introduced last fall in the R2014b release. (I originally planned to post this a few months ago, but I got sidetracked writing about colormaps.)

The first new function is called boundary, and it is in MATLAB. Given a set of 2-D or 3-D points, boundary computes, well, the boundary.

Here's an example to illustrate.

x = gallery('uniformdata',30,1,1);
y = gallery('uniformdata',30,1,10);
plot(x,y,'.')
axis([-0.2 1.2 -0.2 1.2])
axis equal Now compute and plot the boundary around the points.

k = boundary(x,y);
hold on
plot(x(k),y(k))
hold off "But, Steve," some of you are saying, "that's not the only possible boundary around these points, right?"

Right. The function boundary has an optional shrink factor that you can specify. A shrink factor of 0 corresponds to the convex hull. A shrink factor of 1 gives a compact boundary that envelops all the points.

k0 = boundary(x,y,0);
k1 = boundary(x,y,1);
hold on
plot(x(k0),y(k0))
plot(x(k1),y(k1))
hold off
legend('Original points','Shrink factor: 0.5 (default)',...
'Shrink factor: 0','Shrink factor: 1') Here's a 3-D example using boundary. First, the points:

P = gallery('uniformdata',30,3,5);
plot3(P(:,1),P(:,2),P(:,3),'.','MarkerSize',10)
grid on Now the boundary, plotted using trisurf:

k = boundary(P);
hold on
trisurf(k,P(:,1),P(:,2),P(:,3),'Facecolor','red','FaceAlpha',0.1)
hold off The second new function I wanted to mention is rgb2lab. This function is in the Image Processing Toolbox. The toolbox could convert from sRGB to L*a*b* before, but this function makes it a bit easier. (And, if you're interested, it supports not only sRGB but also Adobe RGB 1998).

Just for grins, let's reverse the a* and b* color coordinates for an image.

rgb = imread('peppers.png');
imshow(rgb) lab = rgb2lab(rgb);
labp = lab(:,:,[1 3 2]);
rgbp = lab2rgb(labp);
imshow(rgbp) Now let's get to work on visualizing the sRGB gamut surface. The basic strategy is to make a grid of points in RGB space, transform them to L*a*b* space, and find the boundary. (We'll use the default shrink factor.)

[r,g,b] = meshgrid(linspace(0,1,50));
rgb = [r(:), g(:), b(:)];
lab = rgb2lab(rgb);
a = lab(:,2);
b = lab(:,3);
L = lab(:,1);
k = boundary(a,b,L);
trisurf(k,a,b,L,'FaceColor','interp',...
'FaceVertexCData',rgb,'EdgeColor','none')
xlabel('a*')
ylabel('b*')
zlabel('L*')
axis([-110 110 -110 110 0 100])
view(-10,35)
axis equal
title('sRGB gamut surface in L*a*b* space') Here are another couple of view angles.

view(75,20) view(185,15) That's it for this week. Have fun with color-space surfaces!

Get the MATLAB code

Published with MATLAB® R2015a

Note

Lauren replied on : 1 of 8
"A shrink factor of 1 is the smallest polygonal region containing all the points." Can you elaborate on this? It seems that the polygon's area could be easily reduced by adding more edges. Is there some other constraint on the solution?
Steve Eddins replied on : 2 of 8
Lauren—You are correct; I misstated this. The calculation performed by boundary is based on the concept of alpha shapes. When the shrink factor is 1, boundary, uses the smallest alpha value that avoids introducing holes in the object or splitting it into multiple disconnected regions.
Steve Eddins replied on : 3 of 8
I updated the wording to correct my error.
Imagineer replied on : 4 of 8
Well, I missed the whole parula discussion, and the comments are closed. Mind if I add my two cents here? For one thing, parula is similar to my twilight colormap, which goes from dark blue to bright orange, as in a twilight sky. But I have problems with both parula and jet, so I made my own simple rainbow colormap. Is there a way to set my own default colormap?
Imagineer replied on : 6 of 8
Thank you very much, Steve. If you're not completely colormapped out, here are my two cents. I plot a lot of point clouds on a white background, and have two problems with a sequential dark-to-light colormap like parula: the darker points all look the same, and the yellow points are hard to see on a white background. So I use a rainbow colormap for this, but not jet. You've acknowledged that jet is divergent, dark on both ends, but that's not the case for a simple fully-saturated rainbow that goes from pure blue to pure green to pure red. Actually, it's a little divergent, because pure green appears brighter than pure blue and red, but this works well for the point clouds. The green could be darkened to appear as dark as the blue and red, but this makes the whole rainbow quite dark and the colors hard to distinguish. The problem with the saturated rainbow is that it has yellow and cyan colors that form bright bands and are hard to see on a white background, but this is solved by normalizing the colors by their intensity, which darkens the cyan and yellow without affecting the pure blue, green, and red. For monochrome images such as photos, parula is fine, but grayscale might work best for picking up subtle features, since eyes are more sensitive to changes in brightness than in hue. On the other hand, as your antialiased test pattern demo showed, grayscale and parula hide intermediate values near big jumps, because black next to white looks like gray. A rainbow colormap doesn't hide them, because blue next to red does not look like green.
Steve Eddins replied on : 7 of 8