// Catalano Imaging Library
// The Catalano Framework
//
// Copyright © Diego Catalano, 2012-2016
// diego.catalano at live.com
//
// Copyright © Stephan Saafeld, 2013
// saalfeld at mpi-cbg.de
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

package Catalano.Imaging.Filters;

import Catalano.Imaging.FastBitmap;
import Catalano.Imaging.IApplyInPlace;

/**
 * Contrast Limited Adaptive Histogram Equalization.
 * 
 * 

This feature can also be applied to global histogram equalization, giving rise to * contrast limited histogram equalization (CLHE) which is rarely used in practice.

* *
References: http://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE * *

  • Supported types: Grayscale, RGB. *
  • Coordinate System: Matrix. * * @author Diego Catalano */ public class Clahe implements IApplyInPlace{ private int blockRadius = 63; private int bins = 255; private float slope = 3f; private Grayscale.Algorithm algorithm = Grayscale.Algorithm.Average; /** * Get Block radius. * The size of the local region around a pixel for which the histogram is equalized. * This size should be larger than the size of features to be preserved. * @return Size of block radius. */ public int getBlockRadius() { return blockRadius; } /** * Set Block radius. * The size of the local region around a pixel for which the histogram is equalized. * This size should be larger than the size of features to be preserved. * @param blockRadius Size of block radius. */ public void setBlockRadius(int blockRadius) { this.blockRadius = blockRadius; } /** * Get the number of histogram bins used for histogram equalization. * @return Number of histogram bins. */ public int getBins() { return bins; } /** * Set the number of histogram bins used for histogram equalization. * @param bins Number of histogram bins. */ public void setBins(int bins) { this.bins = bins; } /** * Get Slope. * Limits the contrast stretch in the intensity transfer function. Very large values will let the * histogram equalization do whatever it wants to do, that is result in maximal local contrast. * The value 1 will result in the original image. * @return Slope. */ public float getSlope() { return slope; } /** * Set Slope. * Limits the contrast stretch in the intensity transfer function. Very large values will let the * histogram equalization do whatever it wants to do, that is result in maximal local contrast. * The value 1 will result in the original image. * @param slope Slope. */ public void setSlope(float slope) { this.slope = slope; } /** * Get Grayscale algorithm. * @return Grayscale algorithm. */ public Grayscale.Algorithm getAlgorithm() { return algorithm; } /** * Set Grayscale algorithm. * @param algorithm Grayscale algorithm. */ public void setAlgorithm(Grayscale.Algorithm algorithm) { this.algorithm = algorithm; } /** * Initialize a new instance of the CLAHE class. *

    Block radius: 63. *
    Bins: 255. *
    Slope: 3. */ public Clahe() {} /** * Initialize a new instance of the CLAHE class. * @param blockRadius Block radius. * @param bins Bins. */ public Clahe(int blockRadius, int bins) { this.blockRadius = blockRadius; this.bins = bins; } /** * Initialize a new instance of the CLAHE class. * @param blockRadius Block radius. * @param bins Bins. * @param slope Slope. */ public Clahe(int blockRadius, int bins, float slope) { this.blockRadius = blockRadius; this.bins = bins; this.slope = slope; } /** * Initialize a new instance of the CLAHE class. * @param blockRadius Block radius. * @param bins Bins. * @param slope Slope. * @param algorithm Grayscale algorithm. */ public Clahe(int blockRadius, int bins, float slope, Grayscale.Algorithm algorithm) { this.blockRadius = blockRadius; this.bins = bins; this.slope = slope; this.algorithm = algorithm; } @Override public void applyInPlace(FastBitmap fastBitmap) { int width = fastBitmap.getWidth(); int height = fastBitmap.getHeight(); if (fastBitmap.isGrayscale()){ for (int i = 0; i < height; i++) { int[][] result = new int[height][width]; int iMin = Math.max( 0, i - blockRadius ); int iMax = Math.min( height, i + blockRadius + 1 ); int h = iMax - iMin; int jMin = Math.max( 0, - blockRadius ); int jMax = Math.min( width - 1, blockRadius ); int[] hist = new int[ bins + 1 ]; int[] clippedHist = new int[ bins + 1 ]; for (int k = iMin; k < iMax; k++) for (int l = jMin; l < jMax; l++) ++hist[ roundPositive(fastBitmap.getGray(k, l) / 255.0f * bins) ]; for (int j = 0; j < width; j++) { int v = roundPositive(fastBitmap.getGray(i, j) / 255.0f * bins); int xMin = Math.max( 0, j - blockRadius ); int xMax = j + blockRadius + 1; int w = Math.min( width, xMax ) - xMin; int n = h * w; int limit = ( int )( slope * n / bins + 0.5f ); /* remove left behind values from histogram */ if ( xMin > 0 ){ int xMin1 = xMin - 1; for ( int yi = iMin; yi < iMax; ++yi ) --hist[ roundPositive(fastBitmap.getGray(yi, xMin1) / 255.0f * bins) ]; } /* add newly included values to histogram */ if ( xMax <= width ){ int xMax1 = xMax - 1; for ( int yi = iMin; yi < iMax; ++yi ) ++hist[ roundPositive(fastBitmap.getGray(yi, xMax1) / 255.0f * bins) ]; } System.arraycopy( hist, 0, clippedHist, 0, hist.length ); int clippedEntries = 0, clippedEntriesBefore; do{ clippedEntriesBefore = clippedEntries; clippedEntries = 0; for ( int z = 0; z <= bins; ++z ){ int d = clippedHist[ z ] - limit; if ( d > 0 ){ clippedEntries += d; clippedHist[ z ] = limit; } } int d = clippedEntries / ( bins + 1 ); int m = clippedEntries % ( bins + 1 ); for ( int z = 0; z <= bins; ++z) clippedHist[ z ] += d; if ( m != 0 ){ int s = bins / m; for ( int z = 0; z <= bins; z += s ) ++clippedHist[ z ]; } } while ( clippedEntries != clippedEntriesBefore ); /* build cdf of clipped histogram */ int hMin = bins; for ( int z = 0; z < hMin; ++z ) if ( clippedHist[ z ] != 0 ) hMin = z; int cdf = 0; for ( int z = hMin; z <= v; ++z ) cdf += clippedHist[ z ]; int cdfMax = cdf; for ( int z = v + 1; z <= bins; ++z ) cdfMax += clippedHist[ z ]; int cdfMin = clippedHist[ hMin ]; result[i][j] = roundPositive(( cdf - cdfMin ) / ( float )( cdfMax - cdfMin ) * 255.0f); } for (int a = 0; a < width; a++) { fastBitmap.setGray(i, a, result[i][a]); } } } else { FastBitmap gray = new FastBitmap(fastBitmap); Grayscale gs = new Grayscale(algorithm); gs.applyInPlace(gray); int[][] result = new int[height][width]; for (int i = 0; i < height; i++) { int iMin = Math.max( 0, i - blockRadius ); int iMax = Math.min( height, i + blockRadius + 1 ); int h = iMax - iMin; int jMin = Math.max( 0, - blockRadius ); int jMax = Math.min( width - 1, blockRadius ); int[] hist = new int[ bins + 1 ]; int[] clippedHist = new int[ bins + 1 ]; for (int k = iMin; k < iMax; k++) for (int l = jMin; l < jMax; l++) ++hist[ roundPositive(gray.getGray(k, l) / 255.0f * bins) ]; for (int j = 0; j < width; j++) { int v = roundPositive(gray.getGray(i, j) / 255.0f * bins); int xMin = Math.max( 0, j - blockRadius ); int xMax = j + blockRadius + 1; int w = Math.min( width, xMax ) - xMin; int n = h * w; int limit = ( int )( slope * n / bins + 0.5f ); /* remove left behind values from histogram */ if ( xMin > 0 ){ int xMin1 = xMin - 1; for ( int yi = iMin; yi < iMax; ++yi ) --hist[ roundPositive(gray.getGray(yi, xMin1) / 255.0f * bins) ]; } /* add newly included values to histogram */ if ( xMax <= width ){ int xMax1 = xMax - 1; for ( int yi = iMin; yi < iMax; ++yi ) ++hist[ roundPositive(gray.getGray(yi, xMax1) / 255.0f * bins) ]; } System.arraycopy( hist, 0, clippedHist, 0, hist.length ); int clippedEntries = 0, clippedEntriesBefore; do{ clippedEntriesBefore = clippedEntries; clippedEntries = 0; for ( int z = 0; z <= bins; ++z ){ int d = clippedHist[ z ] - limit; if ( d > 0 ){ clippedEntries += d; clippedHist[ z ] = limit; } } int d = clippedEntries / ( bins + 1 ); int m = clippedEntries % ( bins + 1 ); for ( int z = 0; z <= bins; ++z) clippedHist[ z ] += d; if ( m != 0 ){ int s = bins / m; for ( int z = 0; z <= bins; z += s ) ++clippedHist[ z ]; } } while ( clippedEntries != clippedEntriesBefore ); /* build cdf of clipped histogram */ int hMin = bins; for ( int z = 0; z < hMin; ++z ) if ( clippedHist[ z ] != 0 ) hMin = z; int cdf = 0; for ( int z = hMin; z <= v; ++z ) cdf += clippedHist[ z ]; int cdfMax = cdf; for ( int z = v + 1; z <= bins; ++z ) cdfMax += clippedHist[ z ]; int cdfMin = clippedHist[ hMin ]; result[i][j] = roundPositive(( cdf - cdfMin ) / ( float )( cdfMax - cdfMin ) * 255.0f); } for (int a = 0; a < width; a++) { float s = ( float )result[i][a] / (float)gray.getGray(i, a); float r = Math.max( 0, Math.min( 255, roundPositive( s * fastBitmap.getRed(i, a) ) ) ); float g = Math.max( 0, Math.min( 255, roundPositive( s * fastBitmap.getGreen(i, a) ) ) ); float b = Math.max( 0, Math.min( 255, roundPositive( s * fastBitmap.getBlue(i, a) ) ) ); fastBitmap.setRGB(i, a, (int)r, (int)g, (int)b); } } } } private int roundPositive( float a ){ return ( int )( a + 0.5f ); } }
  • Ads help maintain this website.