This is the third and final installment about histogram processing methods. The first part focused on basic histogram methods and histogram stretching for contrast and color adjustments. The second partexamined histogram equalization and its advanced relative, contrast-limited adaptive histogram equalization, for the sake of modifying the contrast in particular ways. In this final post, we will discuss a method to adjust an image’s pixel values so that the final histogram matches an arbitrary histogram.
Histogram Matching
The topic for this final post is a technique called histogram matching. The general idea is to adjust an image’s histogram so that its CDF matches that of a target CDF. Histogram equalization is actually one example, where the target CDF is a straight line. Histogram matching can be used to copy some of the tonal features in an example image. It can also be used to match the brightness between two images of the same scene captured by sensors with different sensitivity curves.
Histogram matching can be done through a look-up. Given that the CDFs are meant to be the same, the look-up maps between the pixel value of the image and the pixel value of the target image that give the equivalent CDF values (see the plot, below). This results in a single input-output mapping curve (or transfer curve), the same as in many image editing programs like Photoshop.
Below is simple code for doing histogram matching on images that are defined over the 0-255 range. The input t is the image that will be modified so that its histogram matches that of the target image (or a target CDF). The output is S, the histogram-matched version of t.
function S = histMatch(t, target)if size(target,2)>1%an image, replace the image with its cdftarget = getImageCDF(target);end%get the CDF for the input imagetcdf = getImageCDF(t);%compute the look-up curveLU = interp1(target,-1:255,tcdf);%do the look-up for the pixels in TS = interp1(-1:255, LU, double(t(:)));S = uint8(reshape(S, size(t)));%subfunction: compute the CDF for an image that started as uint8function cdf = getImageCDF(img)bins = 0:255;H = hist(img(:), bins);Hmod = H + eps(sum(H));cdf = [0, cumsum(Hmod)/sum(Hmod)];
You’ll notice that this snippet uses the interp1 command to both create the look-up curve (an inverse CDF look-up, essentially) and to do the look-up itself. This makes it easier to modify the concept for other color spaces, multi-channel images, to use sparse histograms, or even use histogram matching between an image that started with uint8 and another that’s uint16. If you’re a purist, MATLAB does have an integer-to-integer look-up table function, intlut. Using the example code is straight-forward:
adams = imread('tetons, ansel adams 1948.png');park = imread('citypark.png');img_adjusted = histMatch(park, rgb2gray(adams));
One of the assumptions in tonal copying is that both the target image and the example image started with similar histograms. Otherwise, there can be significant distortion in the tones which does not look natural, or the tones may not copy well. There have been a number of papers that try to solve that problem; one of my favorites is from Bae, Paris, and Durand, “Two-scale Tone Management for Photographic Look” (http://people.csail.mit.edu/soonmin/photolook/). You’ll note that they use bilateral filtering (which was covered in a Bilateral Filtering recent post on this blog) to separate out different components of their image.
Like many of the histogram concepts discussed, histogram matching can be done on color images as well—and again, either by matching the individual channels or by converting to another color space and matching specific channels. Given what you know, replicating the below samples should be good practice of your skills.