Histogram EqualizationΒΆ

This examples enhances an image with low contrast, using a method called histogram equalization, which “spreads out the most frequent intensity values” in an image [1]. The equalized image has a roughly linear cumulative distribution function.

While histogram equalization has the advantage that it requires no parameters, it sometimes yields unnatural looking images. An alternative method is contrast stretching, where the image is rescaled to include all intensities that fall within the 2nd and 98th percentiles [2].

[1]http://en.wikipedia.org/wiki/Histogram_equalization
[2]http://homepages.inf.ed.ac.uk/rbf/HIPR2/stretch.htm
../_images/plot_equalize_1.png

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

from skimage import data, img_as_float
from skimage import exposure


matplotlib.rcParams['font.size'] = 8


def plot_img_and_hist(img, axes, bins=256):
    """Plot an image along with its histogram and cumulative histogram.

    """
    img = img_as_float(img)
    ax_img, ax_hist = axes
    ax_cdf = ax_hist.twinx()

    # Display image
    ax_img.imshow(img, cmap=plt.cm.gray)
    ax_img.set_axis_off()

    # Display histogram
    ax_hist.hist(img.ravel(), bins=bins, histtype='step', color='black')
    ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
    ax_hist.set_xlabel('Pixel intensity')
    ax_hist.set_xlim(0, 1)
    ax_hist.set_yticks([])

    # Display cumulative distribution
    img_cdf, bins = exposure.cumulative_distribution(img, bins)
    ax_cdf.plot(bins, img_cdf, 'r')
    ax_cdf.set_yticks([])

    return ax_img, ax_hist, ax_cdf


# Load an example image
img = data.moon()

# Contrast stretching
p2, p98 = np.percentile(img, (2, 98))
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))

# Equalization
img_eq = exposure.equalize_hist(img)

# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)

# Display results
fig, axes = plt.subplots(nrows=2, ncols=4, figsize=(8, 5))

ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')

y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))

ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')

ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')

ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')

ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))

# prevent overlap of y-axis labels
fig.subplots_adjust(wspace=0.4)
plt.show()

STDOUT


        

STDERR


        

Python source code: download (generated using skimage 0.11dev)

IPython Notebook: download (generated using skimage 0.11dev)

CmltcG9ydCBtYXRwbG90bGliCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKaW1wb3J0IG51bXB5IGFzIG5wCgpmcm9tIHNraW1hZ2UgaW1wb3J0IGRhdGEsIGltZ19hc19mbG9hdApmcm9tIHNraW1hZ2UgaW1wb3J0IGV4cG9zdXJlCgoKbWF0cGxvdGxpYi5yY1BhcmFtc1snZm9udC5zaXplJ10gPSA4CgoKZGVmIHBsb3RfaW1nX2FuZF9oaXN0KGltZywgYXhlcywgYmlucz0yNTYpOgogICAgIiIiUGxvdCBhbiBpbWFnZSBhbG9uZyB3aXRoIGl0cyBoaXN0b2dyYW0gYW5kIGN1bXVsYXRpdmUgaGlzdG9ncmFtLgoKICAgICIiIgogICAgaW1nID0gaW1nX2FzX2Zsb2F0KGltZykKICAgIGF4X2ltZywgYXhfaGlzdCA9IGF4ZXMKICAgIGF4X2NkZiA9IGF4X2hpc3QudHdpbngoKQoKICAgICMgRGlzcGxheSBpbWFnZQogICAgYXhfaW1nLmltc2hvdyhpbWcsIGNtYXA9cGx0LmNtLmdyYXkpCiAgICBheF9pbWcuc2V0X2F4aXNfb2ZmKCkKCiAgICAjIERpc3BsYXkgaGlzdG9ncmFtCiAgICBheF9oaXN0Lmhpc3QoaW1nLnJhdmVsKCksIGJpbnM9YmlucywgaGlzdHR5cGU9J3N0ZXAnLCBjb2xvcj0nYmxhY2snKQogICAgYXhfaGlzdC50aWNrbGFiZWxfZm9ybWF0KGF4aXM9J3knLCBzdHlsZT0nc2NpZW50aWZpYycsIHNjaWxpbWl0cz0oMCwgMCkpCiAgICBheF9oaXN0LnNldF94bGFiZWwoJ1BpeGVsIGludGVuc2l0eScpCiAgICBheF9oaXN0LnNldF94bGltKDAsIDEpCiAgICBheF9oaXN0LnNldF95dGlja3MoW10pCgogICAgIyBEaXNwbGF5IGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uCiAgICBpbWdfY2RmLCBiaW5zID0gZXhwb3N1cmUuY3VtdWxhdGl2ZV9kaXN0cmlidXRpb24oaW1nLCBiaW5zKQogICAgYXhfY2RmLnBsb3QoYmlucywgaW1nX2NkZiwgJ3InKQogICAgYXhfY2RmLnNldF95dGlja3MoW10pCgogICAgcmV0dXJuIGF4X2ltZywgYXhfaGlzdCwgYXhfY2RmCgoKIyBMb2FkIGFuIGV4YW1wbGUgaW1hZ2UKaW1nID0gZGF0YS5tb29uKCkKCiMgQ29udHJhc3Qgc3RyZXRjaGluZwpwMiwgcDk4ID0gbnAucGVyY2VudGlsZShpbWcsICgyLCA5OCkpCmltZ19yZXNjYWxlID0gZXhwb3N1cmUucmVzY2FsZV9pbnRlbnNpdHkoaW1nLCBpbl9yYW5nZT0ocDIsIHA5OCkpCgojIEVxdWFsaXphdGlvbgppbWdfZXEgPSBleHBvc3VyZS5lcXVhbGl6ZV9oaXN0KGltZykKCiMgQWRhcHRpdmUgRXF1YWxpemF0aW9uCmltZ19hZGFwdGVxID0gZXhwb3N1cmUuZXF1YWxpemVfYWRhcHRoaXN0KGltZywgY2xpcF9saW1pdD0wLjAzKQoKIyBEaXNwbGF5IHJlc3VsdHMKZmlnLCBheGVzID0gcGx0LnN1YnBsb3RzKG5yb3dzPTIsIG5jb2xzPTQsIGZpZ3NpemU9KDgsIDUpKQoKYXhfaW1nLCBheF9oaXN0LCBheF9jZGYgPSBwbG90X2ltZ19hbmRfaGlzdChpbWcsIGF4ZXNbOiwgMF0pCmF4X2ltZy5zZXRfdGl0bGUoJ0xvdyBjb250cmFzdCBpbWFnZScpCgp5X21pbiwgeV9tYXggPSBheF9oaXN0LmdldF95bGltKCkKYXhfaGlzdC5zZXRfeWxhYmVsKCdOdW1iZXIgb2YgcGl4ZWxzJykKYXhfaGlzdC5zZXRfeXRpY2tzKG5wLmxpbnNwYWNlKDAsIHlfbWF4LCA1KSkKCmF4X2ltZywgYXhfaGlzdCwgYXhfY2RmID0gcGxvdF9pbWdfYW5kX2hpc3QoaW1nX3Jlc2NhbGUsIGF4ZXNbOiwgMV0pCmF4X2ltZy5zZXRfdGl0bGUoJ0NvbnRyYXN0IHN0cmV0Y2hpbmcnKQoKYXhfaW1nLCBheF9oaXN0LCBheF9jZGYgPSBwbG90X2ltZ19hbmRfaGlzdChpbWdfZXEsIGF4ZXNbOiwgMl0pCmF4X2ltZy5zZXRfdGl0bGUoJ0hpc3RvZ3JhbSBlcXVhbGl6YXRpb24nKQoKYXhfaW1nLCBheF9oaXN0LCBheF9jZGYgPSBwbG90X2ltZ19hbmRfaGlzdChpbWdfYWRhcHRlcSwgYXhlc1s6LCAzXSkKYXhfaW1nLnNldF90aXRsZSgnQWRhcHRpdmUgZXF1YWxpemF0aW9uJykKCmF4X2NkZi5zZXRfeWxhYmVsKCdGcmFjdGlvbiBvZiB0b3RhbCBpbnRlbnNpdHknKQpheF9jZGYuc2V0X3l0aWNrcyhucC5saW5zcGFjZSgwLCAxLCA1KSkKCiMgcHJldmVudCBvdmVybGFwIG9mIHktYXhpcyBsYWJlbHMKZmlnLnN1YnBsb3RzX2FkanVzdCh3c3BhY2U9MC40KQpwbHQuc2hvdygp