Post #1: Histogram Adjustments in MATLAB

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Post #1: Histogram Adjustments in MATLAB

Nick Loomis, loomsci.wordpress.com

Obtaining an image with the right contrast for your application is never easy. Whether it is for art,
detection, recognition, or improving your own photographs, contrast adjustment is a common task.
Industrial setups can adjust the lighting and camera parameters before image capture—but what
about after the image has been captured? Histogram processing is one answer for computationally
adjusting the contrast, and can also be applied to a number of problems involving color balance or
tone transfer.

This three-part post focuses on different methods for post-processing an image to modify its
underlying histogram, affecting contrast and color balance in some amazing ways. The first part
introduces basic pixel operations and how they affect histograms, and then shows several schemes
for how to stretch a histogram to cover the full range of available pixel values. The second part
discusses how to spread out the pixel values evenly over some range, a process known as histogram
equalization. The third part extends the idea of controllably modifying the pixel values so that the
resulting histogram matches ​ any​arbitrary distribution.

To begin, we’ll look at the basics of working with image histograms using MATLAB and how to
perform histogram stretching to modify contrast and color.

Histogram math

A histogram is the probability distribution of pixel values in an image. (For RGB images, the histogram
is usually broken into three histograms of the three component channels.) Like any other distribution,
histograms have simple mathematical rules. Two operations that affect the pixel values, and thus the
histograms, will be used extensively through these posts:

shifts​
1) Adding a value to all the pixels adds that amount to the histogram; visually, this ​ the histogram

2) Multiplying all the pixel values by a certain amount scales where the histogram data appears;
stretches​
visually, this ​ the histogram
To plot the histogram of an image, there are several options. If you have the Image Processing
Toolbox, the ​imhist​function is the easiest to use, but only works on single color channels. For
example, to plot the green channel of an image,

img = imread(​ ); 
'Flowers.jpg'​
imhist(img(:,:,2)) 
 

If you don’t have the Toolbox or want to have greater control over the histogram data, you can
compute the values directly using the ​
hist​function and plot using your desired methods:

img = imread(​ ); 
'Flowers.jpg'​
g = img(:,:,2); 
bins = 0:1:255; 
H = hist(g(:), bins); 
plot(bins, H,​ ); 
 'linewidth',2, 'color', 'g'​
This second example assumes that the image uses eight bits per color channel, or a maximum value of
2^8-1 = 255. The remainder of the examples in these posts will use the assumption that pixel values
range from 0 to 255.

Histogram stretching

<a Wikipedia: Contrast (vision)>Contrast</a> is a measure of how much the pixel brightness changes
relative to the average brightness. A technique known as ​ histogram stretching​
can be used to shift the
pixel values to fill the entire brightness range, resulting in high contrast.

The first step is to find the pixel values that should get mapped to 0% and 100% brightness. Any
real-world image has noise, however. To keep the noise from unduly influencing the stretching, an
assumption is made: a small percentage of the brightest and darkest pixels are ignored, writing them
off to sensor noise. If you have the Image Processing Toolbox (we’ll cover what you can do if you don’t
have the toolbox in a moment), you can use

limits = stretchlim(img, tol);

to find the lower and upper ​ of the pixels in image ​


limits​ img​ so that a fraction, ​
tol​
, will be ignored.
(See the ​stretchlim​ documentation for other ways to call the function.)

Next, the ​
imadjust​
function can apply the scaling:

img_adjusted = imadjust(img, limits, []);

The function actually shifts and scales the pixel values so that the ​
limits​
are scaled between 0 and
100% (the default when ​ []​ is used as the third input argument).

img = imread(​ ); 
'treeArt.png'​
limits = stretchlim(img, 0.01); 
img_adjusted = imadjust(img, limits, []); 
imagesc(img_adjusted, [0,255]);  
axis ​
image​
; 
 

In order to understand and program some of the more advanced histogram-processing techniques, it
will help to take a moment to better understand what is happening behind the scenes of these two
functions. (Similarly: if you don’t have the Image Processing Toolbox, this is how you could perform
the same operations.) The first step, finding the input pixel values which correspond to some
percentile of pixel values, is an inverse look-up. We can start by calculating the histogram, then
computing its <a Wikipedia: CDF>cumulative distribution function (CDF)</a>:

bins = linspace(0,255,256); 
H = hist(img(:), bins); 
H(H==0) = eps(sum(H)); 
cdf = [0,cumsum(H)/sum(H)]; 
 

 
 
There are several assumptions here. The first is that the image, ​ img​
, is assumed to have pixels which
could range in value from ​ 0​to ​
255​. The second is that the histogram, ​ H​, could have bins with ​
0​
counts.
The problem is that, in the next step, we’ll be doing an inverse look-up, which means that we need an
​​
invertible function. If Hhas zeros, then c​umsum(H)​ will have at least two identical values and will not
​​
be invertible. Setting the zero-count values in H to e​ps(sum(H))​ ensures that H ​​does not have
non-zero values but only has a trivial effect on the CDF. Using the CDF for the inverse look-up can be
as simple as this:

h_low = interp1(cdf, [0,bins], pct); 
h_high = interp1(cdf, [0,bins], 1­pct); 
 
where ​
pct​is the percent of pixels to ignore. This will give similar limits as ​
stretchlim​
.
Interpolating the CDF at ​ to find the corresponding ​
pct = 0.05​ and ​
h_low​ h_high​
pixel values.

The next step is to adjust the image values so that the ​ and ​
h_low​ h_high​
values are remapped to the
0% and 100% brightness levels. Using simple histogram math,

img_adjusted2 = uint8( (double(img) – h_low)/(h_high­h_low) * 255);

Note that this includes both a shift, ​ , and a stretch, ​


double(img) – h_low​ 1/(h_high­h_low)​
of
the original histogram.

In general, histogram stretching tends to work well on images that have a poor image contrast to start
with and which should have full contrast. You can limit the amount of stretching that occurs by not
mapping h​_low​ ​_high​
and h to the 0% and 100% brightness levels, but instead by only scaling them a
certain amount.

Multi-channel Histogram Stretching

The histogram stretching that we’ve looked at so far has focused on single-channel images. What’s
useful is that the same functions can be applied to multi-channel images, like RGB images, by applying
the codes to each channel independently. MATLAB’s stretchlim and imadjust functions are both
written to be able to take in RGB images in addition to single-channel images using the exact same
code as we used earlier.

Again, if you don’t have the Image Processing Toolbox or want to use multi-channel images that don’t
have three channels (for example, some microscopy or satellite imagery), you’ll need to take a few
​mg​
extra steps. For example, say you have a multi-channel image, i , and you plan to adjust each
channel. Then you could consider using something like this:

img_adjusted = zeros(size(img), 'uint8');

for ch = 1:size(img,3)
img_channel = img(:,:,ch);

limits = stretchlim(img_channel, 0.01);

img_adjusted(:,:,ch) = imadjust(img_channel, limits, []);

end

Variations on Histogram Stretching

The RGB color space is the de-facto standard for images. It doesn’t always result in the best
processing, however, especially when the image is intended for human viewing. A color space that
better represents the human visual system, like <a Wikipedia: CIE LAB>L*a*b*</a> or <a Wikipedia:
CIE LUV>L’u’v’<a>, can provide more natural stretching in some cases. In both of these color spaces,
the L channel represents the brightness, while the (a*, b*) or (u’, v’) channels represent the color.
(This is conceptually similar to <a Wikipedia: YCbCr>YCbCr</a>, used in JPEG encoding.) The stretching
is performed on the L channel after converting the colors. Using the functions in the Image Processing
Toolbox,

img = imread(​ ); 
'lion.png'​
 
c_rgb2lab = makecform(​ ); 
'srgb2lab'​
labimg = applycform(img, c_rgb2lab); 
 
labimg(:,:,1) = imadjust(labimg(:,:,1), ... 
stretchlim(labimg(:,:,1), 0.01) ); 
 
c_lab2rgb = makecform(​ ); 
'lab2srgb'​
img_adjusted = applycform(labimg, c_lab2rgb); 

 
Histogram stretching within a L*a*b* color space and in an RGB color space. Note that the RGB stretching changes
the colors slightly, emphasizing reds and yellows in this particular case.
 
Since only the L channel was adjusted, the colors remain the same, but the brightness is re-mapped.
This adjusts the contrast in a way that sometimes can be more visually pleasing without significantly
affecting the color balance.

If you don’t have the Image Processing Toolbox, Mark Ruzon wrote simple functions to convert
between RGB and L*a*b*: Lab2RGB
(​
http://www.mathworks.com/matlabcentral/fileexchange/24010​ ) and
RGB2Lab(​ http://www.mathworks.com/matlabcentral/fileexchange/24009​ ).

Another variation is to apply histogram stretching in the <a Wikipedia: HSV>hue-saturation-value


(HSV) color space</a>. The histogram stretching is only applied to the S and V channels, but not the
hue channel. This tends to result in reasonable contrast and fuller colors without significantly affecting
the color balance.

img = imread(​ ); 
'bopFlower.png'​
hsvimg = rgb2hsv(img); 
 ch=2:3 
for​
  hsvimg(:,:,ch) = imadjust(hsvimg(:,:,ch), ... 
stretchlim(hsvimg(:,:,ch), 0.01)); 
end 
img_adjusted = hsv2rgb(hsvimg); 
 

Stretching on HSV is also sometimes done after stretching in the RGB color space to ensure full color
saturation. One example is this underwater image, which required significant stretching in RGB, but
then benefits from an additional HSV stretch to gain full contrast:
This is a good point to try out histogram stretching on your own images and get familiar with the
different color transforms.

In the next post, we’ll explore histogram equalization, primarily used for modifying the contrast in an
image, and some of its extensions.

Post #2: Histogram Adjustments in MATLAB


Nick Loomis, loomsci.wordpress.com

This is the second part of a three-part post on understanding and using histograms to modify the
appearance of images. The first part covered introductory material on histograms and a method
known as histogram stretching for improving contrast and color. This post will cover histogram
equalization and an advanced technique called contrast-limited adaptive histogram equalization, both
intended for increasing the contrast of an image. The final post will extend the concepts of histogram
equalization to arbitrary distributions of pixel values.

Histogram Equalization
The next stop on our tour of histogram-processing techniques is histogram equalization. If you plotted
the CDF of some of your image histograms, you may have noticed that the CDF does not form a
straight line—meaning that the pixel values are ​not​ equally likely to occur (since the CDF is the
integral of the PDF). The good news is that most natural images do ​ not​have flat CDFs. That said, some
industrial applications can benefit from having a flat CDF. The process of flattening the CDF is called
histogram equalization.

MATLAB’s Image Processing Toolbox includes the ​


histeq​
function, which performs histogram
equalization:

img_histeq = histeq(img);

One example:

img = rgb2gray(imread(​ )); 
'harborSydney.png'​
img_adjusted = histeq(img); 
 

 
 
The resulting image has particularly dark islands and a bright sky, which is not visually appealing, but
the detail within the buildings is improved significantly. The skyline also happens to be much easier to
threshold:

imagesc(histeq(img)>160);
A more advanced version of histogram equalization, adaptive histogram equalization, makes the
assumption that the image varies significantly over its spatial extent. The algorithm divides the image
into smaller tiles, applies histogram equalization to each tile, then interpolates the results. MATLAB’s
implementation, a ​dapthisteq​ , includes limits on how much the contrast is allowed to be changed,
called <a ​
http://en.wikipedia.org/wiki/CLAHE#Contrast_Limited_AHE​ >contrast-limited adaptive
histogram equalization</a>, or CLAHE for short. Again, CLAHE will modify the image in strange ways,
but those may be better for certain tasks.

img = imread('Spores.jpg');

img_adjusted = adjusthisteq(img);

This test image highlights two peculiarities of CLAHE. First, sharp edges, like those around the spores,
look like they are glowing. This occurs because CLAHE computes histograms over areas, and the sharp
change in values from the background to the spore body affects the normalization. (The effect is
related to what you would get by dividing the original image by the low-pass filtered version of the
image.) Fortunately, that additional contrast near the edges can help some edge detection algorithms,
even if the glow is not natural. The second effect from CLAHE is seen in the background areas, where
some out-of-focus spores becomes visible and the overall noise increases. This is exactly what CLAHE
supposed​
is ​ to do: increase the contrast, even in the background areas. It is also limiting the amount
of contrast adjustment; to see this in a dramatic way, try using h ​isteq​ on the spores image and
compare that against the a ​dapthisteq​ result. (Hint: if you wanted to remove the out-of-focus spores
but still increase the overall contrast, look at combining adapthisteq with <a IP blog post>bilateral
filtering</a>.)
MATLAB’s histeq and adapthisteq both assume a single-channel image. Similar to the discussion about
<a IP blog: post #1 on histograms>multi-channel images in the first post</a>, you could apply the
histogram equalization to each channel:

img = imread(​ ); 
'harborSydney.png'​
img_adjusted = zeros(size(img),​ ); 
'uint8'​
 ch=1:3 
for​
    img_adjusted(:,:,ch) = adapthisteq(img(:,:,ch)); 
end 
 
 

 
 
Or, apply the equalization to the L* component of a L*a*b*-transformed image:

img = imread(​ ); 
'harborSydney.png'​
c_rgb2lab = makecform(​ ); 
'srgb2lab'​
c_lab2rgb = makecform(​ ); 
'lab2srgb'​
labimg = applycform(img, c_rgb2lab); 
labimg(:,:,1) = adapthisteq(labimg(:,:,1)); 
img_adjusted = applycform(labimg, c_lab2rgb); 
 
If you don’t have the Image Processing Toolbox, you could try Leslie Smith’s implementation of the
CLAHE algorithm:
(​
http://www.mathworks.com/matlabcentral/fileexchange/22182-contrast-limited-adaptive-histogram
-equalization-clahe​).

Histogram equalization focuses on making the CDF flat so that each pixel value is equally likely to
occur. The final post in this series will extend that concept to matching an image’s CDF (or histogram)
to an arbitrary CDF.

Post #3: Histogram Adjustments in MATLAB


Nick Loomis, loomsci.wordpress.com

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
part examined 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.

 S = histMatch(t, target) 
function​
  
 size(target,2)>1 
if​
%an image, replace the image with its cdf 
   ​
   target = getImageCDF(target);   
end 
  
%get the CDF for the input image 
tcdf = getImageCDF(t); 
  
%compute the look­up curve 
LU = interp1(target,­1:255,tcdf); 
  
%do the look­up for the pixels in T 
S = interp1(­1:255, LU, double(t(:))); 
S = uint8(reshape(S, size(t))); 
  
%subfunction: compute the CDF for an image that started as uint8  
 cdf = getImageCDF(img) 
function​
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 u ​int8​ and another that’s u​int16​. If you’re a
purist, MATLAB does have an integer-to-integer look-up table function, i ​ntlut​ .

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 <a IPblog: Bilateral Filtering>recent post on this blog</a>) 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.
Conclusions

At this point, we have covered many of the basic concepts in histogram processing, especially those
that are easily implemented through MATLAB’s built-in functions. There are a slew of additional topics
waiting for your own projects: modifying shadows and highlights, trying different neighborhoods or
distributions in the CLAHE algorithm, investigating advanced tonality-transfer methods, various color
spaces, and artistic effects, amongst many others.

Nick Loomis writes for loomsci.wordpress.com and uses the methods from this series of posts for
various projects: histogram stretching to <a
http://loomsci.wordpress.com/2013/06/20/automatic-color-correction-underwater-photos/​ >correct
the colors of underwater photos</a> and histogram matching to <a
http://loomsci.wordpress.com/2013/07/11/learning-instagrams-filters/>replicate the effect of color
filters </a>.

You might also like