Color curve/Tone curve for an image
Show older comments
If you open up an image editor such as Gimp or photoshop, in addition to the image histogram you can also view the colour curve. I want to know how this tone curve is created and updated. I understand it matches the input pixel along the x axis to the output pixel along the y.
For the original image the curve is a straight diagonal line from bottom left to top right. When a value such as brightness or contrast is changed then this curve will change shape.
My initial thought was that the change in y would be the difference between the original histogram values 0-255 and the current values. So the curve would be displaced +ve or -ve from its original position depending on how much the edited image was changed.
However I then read that it is a mapping function between the values along the x and y so i am confused as to what the correct approach is and how you would go about creating this programatically.
Below is an example of the original tone curve for an image in Gimp.

Accepted Answer
More Answers (1)
This is an example of how one might back-calculate the curve applied to an image. For the purposes of this example, I'm going to use MIMT imcurves(), since MATLAB/IPT do not have a similar image adjustment tool (though interp1() should suffice). A couple other MIMT tools are used as well. Throughout, I'm assuming that the images are uint8.
% say we have a grayscale image
inpict = imread('peppers.png');
if size(inpict,3)==3
inpict = rgb2gray(inpict);
end
imshow(inpict)

% and we have a modified version of the image
% wherein the intensity transformation is global
x = [0 0.25 0.5 0.75 1];
y = [0 0.10 0.5 0.90 1];
modifiedpict = imcurves(inpict,x,y); % adjust the image
modifiedpict = jpegger(modifiedpict,20); % severely degrade the image
imshow(modifiedpict)

% find unique values of the input image
% and the mean of corresponding pixels in the modified image
[g uin] = findgroups(inpict(:));
umod = splitapply(@mean,modifiedpict(:),g);
% use a basic curve fit to back-calculate the transformation
estx = linspace(0,255,9); % assume relatively few points were used
[pp s mu] = polyfit(double(uin),double(umod),5); % adjust order to suit
esty = imclamp(polyval(pp,estx,s,mu),[0 255]);
% plot the original and estimated curves
plot(x*255,y*255,'ko'); hold on; grid on
plot(uin,umod,'r')
plot(estx,esty,'b*')
legend('original','modified','estimate','location','northwest')
xlim([0 255])
ylim([0 255])

% use the estimated curve to recreate the value transformation
recreatedpict = imcurves(inpict,estx/255,esty/255);
imshow(recreatedpict)

By default, imcurves() will use cubic spline interpolation, so the use of a relatively short point list is typically sufficient to produce smooth curves.
This approach works well for this image, but bear in mind that the modified image only describes the curve over the range of the input image. For an input which spans only a small part of the nominal data range, you have less information about the shape of the curve.
Of particular interest is the behavior at the endpoints. Consider what happens if the above code is run on this image:

This is the curve estimate:

While this estimated curve would accurately reproduce the modification to the original image, the inversion near black would become apparent if it were applied to an image with a wider value range. If some endpoint values can be assumed, they can always be asserted prior to doing the fit.
% ... finding points, etc
% if the input image does not span the data range
% then it's hard to tell how the curve should be extrapolated
% it may suffice to assume endpoint values (e.g. [0 0] and [1 1])
% otherwise, you may try your luck with other fitting methods
if min(uin)>0
uin = [0; uin];
umod = [0; umod];
end
if max(uin)<255
uin = [uin; 255];
umod = [umod; 255];
end
% start doing curve fit ...
Now the fit looks fine and should work on other images.

MIMT is not necessary to do any of this, but it's a convenience that I don't feel like avoiding at the moment. imcurves() can be replaced with interp1(...,'pchip') after casting/scaling the image with im2double(). Afterwards, the image needs to be cast/scaled back with im2uint8(). imclamp() can be replaced with min() and max(). Although it's only used for creating the test image, jpegger() could be replaced by simply saving the image to disk as JPG with a specified 'quality' parameter and then reading it back. FWIW, it would also suffice to use imnoise() instead. I just prefer to use jpegger(), since compression artifacts are the most typical sort of image damage that I run into.
Categories
Find more on Contrast Adjustment in Help Center and File Exchange
Products
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!