mirror of
https://github.com/Laex/Delphi-OpenCV.git
synced 2024-11-16 16:25:53 +01:00
a016c463c5
Signed-off-by: Laentir Valetov <laex@bk.ru>
647 lines
27 KiB
ObjectPascal
647 lines
27 KiB
ObjectPascal
// *****************************************************************************
|
|
// Contrast Limited Adaptive Histogram Equalization (CLAHE) for OpenCV
|
|
// -----------------------------------------------------------------------------
|
|
// Original CLAHE implementation by Karel Zuiderveld, karel@cv.ruu.nl
|
|
// in 'Graphics Gems IV', Academic Press, 1994.
|
|
// -----------------------------------------------------------------------------
|
|
// Converted to OpenCV format by Toby Breckon, toby.breckon@cranfield.ac.uk
|
|
// Copyright (c) 2009 School of Engineering, Cranfield University
|
|
// License : LGPL - http://www.gnu.org/licenses/lgpl.html
|
|
// -----------------------------------------------------------------------------
|
|
// Improved by Shervin Emami on 17th Nov 2010, shervin.emami@gmail.com
|
|
// http://www.shervinemami.co.cc/
|
|
// ************************************************************************************************************************************************ *)*******************************
|
|
// Project Delphi-OpenCV
|
|
// **************************************************************************************************
|
|
// Contributor:
|
|
// Laentir Valetov
|
|
// email:laex@bk.ru
|
|
// **************************************************************************************************
|
|
// You may retrieve the latest version of this file at the GitHub,
|
|
// located at git://github.com/Laex/Delphi-OpenCV.git
|
|
// **************************************************************************************************
|
|
// License:
|
|
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the 'License');
|
|
// you may not use this file except in compliance with the License. You may obtain a copy of the
|
|
// License at http://www.mozilla.org/MPL/
|
|
//
|
|
// Software distributed under the License is distributed on an 'AS IS' basis, WITHOUT WARRANTY OF
|
|
// ANY KIND, either express or implied. See the License for the specific language governing rights
|
|
// and limitations under the License.
|
|
//
|
|
// Alternatively, the contents of this file may be used under the terms of the
|
|
// GNU Lesser General Public License (the 'LGPL License'), in which case the
|
|
// provisions of the LGPL License are applicable instead of those above.
|
|
// If you wish to allow use of your version of this file only under the terms
|
|
// of the LGPL License and not to allow others to use your version of this file
|
|
// under the MPL, indicate your decision by deleting the provisions above and
|
|
// replace them with the notice and other provisions required by the LGPL
|
|
// License. If you do not delete the provisions above, a recipient may use
|
|
// your version of this file under either the MPL or the LGPL License.
|
|
//
|
|
// For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
|
|
// **************************************************************************************************
|
|
// The Initial Developer of the Original Code:
|
|
// OpenCV: open source computer vision library
|
|
// Homepage: http://ocv.org
|
|
// Online docs: http://docs.ocv.org
|
|
// Q&A forum: http://answers.ocv.org
|
|
// Dev zone: http://code.ocv.org
|
|
// **************************************************************************************************
|
|
// Original: https://github.com/joshdoe/opencv-clahe/blob/master/clahe.cpp
|
|
// **************************************************************************************************
|
|
|
|
{$POINTERMATH ON}
|
|
|
|
unit uclahe;
|
|
|
|
interface
|
|
|
|
Uses
|
|
core.types_c;
|
|
|
|
const
|
|
// CLAHE input/output range flag definitions
|
|
|
|
CV_CLAHE_RANGE_FULL =0;
|
|
CV_CLAHE_RANGE_INPUT= 1;
|
|
|
|
procedure cvCLAdaptEqualize(src:pIplImage; dst:pIplImage;
|
|
xdivs:Cardinal; ydivs:Cardinal; bins:Cardinal;
|
|
limit:float; range:Integer);
|
|
|
|
implementation
|
|
|
|
Uses
|
|
core_c,
|
|
imgproc_c;
|
|
|
|
type
|
|
// type defs. for Graphic Gemms Code - see later
|
|
kz_pixel_t = byte; (* for 8 bit-per-pixel images *)
|
|
pkz_pixel_t=^kz_pixel_t;
|
|
const
|
|
uiNR_OF_GREY = 256;
|
|
|
|
(************* Prototype of Graphic Gemms CLAHE function. *********************)
|
|
|
|
|
|
function CLAHE(pImage:pkz_pixel_t; uiXRes:Cardinal;
|
|
uiYRes:Cardinal; Min:kz_pixel_t;
|
|
Max:kz_pixel_t; uiNrX:Cardinal; uiNrY:Cardinal;
|
|
uiNrBins:Cardinal; fCliplimit:float):Integer; forward;
|
|
|
|
// *****************************************************************************
|
|
|
|
// General Notes:
|
|
//
|
|
// The number of 'effective' greylevels in the output image is set by bins; selecting
|
|
// a small value (eg. 128) speeds up processing and still produce an output image of
|
|
// good quality. The output image will have the same minimum and maximum value as the input
|
|
// image. A clip limit smaller than 1 (?? is this correct ) results in
|
|
// standard (non-contrast limited) AHE.
|
|
|
|
|
|
// cvAdaptEqualize(src, dst, xdivs, ydivs, bins)
|
|
//
|
|
// perform adaptive histogram equalization (AHE)
|
|
//
|
|
// src - pointer to source image (must be single channel 8-bit)
|
|
// dst - pointer to destination image (must be single channel 8-bit)
|
|
// xdivs - number of cell divisions to use in vertical (x) direction (MIN=2 MAX := 16)
|
|
// ydivs - number of cell divisions to use in vertical (y) direction (MIN=2 MAX := 16)
|
|
// bins - number of histogram bins to use per division
|
|
// range - either of CV_CLAHE_RANGE_INPUT or CV_CLAHE_RANGE_FULL to limit the output
|
|
// pixel range to the min/max range of the input image or the full range of the
|
|
// pixel depth (8-bit in this case)
|
|
|
|
procedure cvAdaptEqualize(src:pIplImage; dst:pIplImage;
|
|
xdivs:Cardinal; ydivs:Cardinal; bins:Cardinal; range:Integer);
|
|
begin
|
|
// call CLAHE with negative limit (as flag value) to perform AHE (no limits)
|
|
cvCLAdaptEqualize(src, dst, xdivs, ydivs, bins, -1.0, range);
|
|
end;
|
|
|
|
// cvCLAdaptEqualize(src, dst, xdivs, ydivs, bins, limit)
|
|
//
|
|
// perform contrast limited adaptive histogram equalization (CLAHE)
|
|
//
|
|
// src - pointer to source image (must be single channel 8-bit)
|
|
// dst - pointer to destination image (must be single channel 8-bit)
|
|
// xdivs - number of cell divisions to use in vertical (x) direction (MIN=2 MAX := 16)
|
|
// ydivs - number of cell divisions to use in vertical (y) direction (MIN=2 MAX := 16)
|
|
// bins - number of histogram bins to use per division (MIN=2 MAX := 256)
|
|
// limit - contrast limit for localised changes in contrast
|
|
// (limit >= 0 gives standard AHE without contrast limiting)
|
|
// range - either of CV_CLAHE_RANGE_INPUT or CV_CLAHE_RANGE_FULL to limit the output
|
|
// pixel range to the min/max range of the input image or the full range of the
|
|
// pixel depth (8-bit in this case)
|
|
|
|
|
|
procedure cvCLAdaptEqualize(src:pIplImage; dst:pIplImage;
|
|
xdivs:Cardinal; ydivs:Cardinal; bins:Cardinal;
|
|
limit:float; range:Integer);
|
|
Var
|
|
min_val, max_val:Double;
|
|
min, max:Byte;
|
|
_type:Integer;
|
|
enlarged :Integer;
|
|
origW : integer;
|
|
origH :integer;
|
|
tmpDst :pIplImage;
|
|
newW:Integer;
|
|
newH:Integer;
|
|
enlargedDst:pIplImage;
|
|
begin
|
|
// check the inputs to the function
|
|
|
|
if (src = nil) or (dst = nil) then
|
|
cvError( CV_StsUnsupportedFormat, 'cvCLAdaptEqualize','nil value passed as image to function',nil,0 );
|
|
|
|
_type := cvGetElemType( src );
|
|
if( _type <> CV_8UC1 ) then
|
|
cvError( CV_StsUnsupportedFormat, 'cvCLAdaptEqualize', 'Only 8uC1 images are supported' );
|
|
_type := cvGetElemType( src );
|
|
if( _type <> CV_8UC1 ) then
|
|
cvError( CV_StsUnsupportedFormat, 'cvCLAdaptEqualize', 'Only 8uC1 images are supported' );
|
|
|
|
//if( !CV_ARE_SIZES_EQ( src, dst )) // Modified by Shervin Emami, 17Nov2010.
|
|
if (src^.width <> dst^.width )or( src^.height <> dst^.height) then
|
|
cvError( CV_StsUnmatchedSizes, 'cvCLAdaptEqualize', 'src and dst images must be equal sizes' );
|
|
|
|
if (((xdivs < 2) )or( (xdivs > 16)) )or( ((ydivs < 2) )or( (ydivs > 16))) then
|
|
cvError( CV_StsBadFlag, 'cvCLAdaptEqualize', 'xdivs and ydivs must in range (MIN=2 MAX := 16)' );
|
|
|
|
if ((bins < 2) )or( (bins > 256)) then
|
|
cvError( CV_StsBadFlag, 'cvCLAdaptEqualize', 'bins must in range (MIN=2 MAX := 256)' );
|
|
|
|
// copy src to dst for in-place CLAHE.
|
|
cvCopy(src, dst);
|
|
|
|
|
|
// If the dimensions of the image are not a multiple of the xdivs and ydivs, then enlarge the image to be a correct size and then shrink the image.
|
|
// Also make sure the image is aligned to 8 pixels width, so that OpenCV won't add extra padding to the image.
|
|
// Added by Shervin Emami, 17Nov2010.
|
|
enlarged := 0;
|
|
origW := dst^.width;
|
|
origH := dst^.height;
|
|
tmpDst := nil;
|
|
if ((dst^.width and (8-1))<>0 )or( (dst^.height and (8-1))<>0 )or( (dst^.width mod xdivs)<>0 )or( (dst^.height mod ydivs)<>0) then begin
|
|
newW := ((dst^.width + 8-1) and -8); // Align to 8 pixels, so that widthStep hopefully equals width.
|
|
newW := ((newW + xdivs-1) and -xdivs); // Also align for CLAHE.
|
|
newH := ((dst^.height + ydivs-1) and -ydivs);
|
|
//std::cout << 'w=' << dst^.width << ', h=' << dst^.height << '. new w := ' << newW << ', h := ' << newH << std::endl;
|
|
enlargedDst := cvCreateImage(cvSize(newW, newH), dst^.depth, dst^.nChannels);
|
|
cvResize(dst, enlargedDst, CV_INTER_CUBIC);
|
|
//cvReleaseImage(&dst);
|
|
tmpDst := dst;
|
|
dst := enlargedDst; // Use the enlarged image instead of the original dst image.
|
|
enlarged := 1; // signal that we need to shrink later!
|
|
end;
|
|
// Check if OpenCV adds padding bytes on each row. Added by Shervin Emami, 17Nov2010.
|
|
if (dst^.width <> dst^.widthStep) then
|
|
cvError( CV_StsBadFlag, 'cvCLAdaptEqualize', 'dst^.widthStep should be the same as dst^.width. The IplImage has padding, so use a larger image.' );
|
|
|
|
|
|
// check number of xdivs and ydivs is a multiple of image dimensions
|
|
if (dst^.width mod xdivs)<>0 then
|
|
cvError( CV_StsBadFlag, 'cvCLAdaptEqualize', 'xdiv must be an integer multiple of image width ' );
|
|
if (dst^.height mod ydivs)<>0 then
|
|
cvError( CV_StsBadFlag, 'cvCLAdaptEqualize', 'ydiv must be an integer multiple of image height ' );
|
|
|
|
// get the min and max values of the image
|
|
|
|
if (range = CV_CLAHE_RANGE_INPUT) then begin
|
|
cvMinMaxLoc(dst, @min_val, @max_val);
|
|
min := Trunc(min_val);
|
|
max := Trunc(max_val);
|
|
end else begin
|
|
min := 0;
|
|
max := 255;
|
|
end;
|
|
// call CLHAHE for in-place CLAHE
|
|
|
|
//int rcode =
|
|
CLAHE(pkz_pixel_t(dst^.imageData), dst^.width,
|
|
dst^.height, min, max, xdivs, ydivs,
|
|
bins, limit);
|
|
|
|
//printf('RCODE %i\n', rcode);
|
|
|
|
// If the dst image was enlarged to fit the alignment, then shrink it back now.
|
|
// Added by Shervin Emami, 17Nov2010.
|
|
if (enlarged<>0) then begin
|
|
//std::cout << 'w=' << dst^.width << ', h=' << dst^.height << '. orig w=' << origW << ', h=' << origH << std::endl;
|
|
cvResize(dst, tmpDst, CV_INTER_CUBIC); // Shrink the enlarged image back into the original dst image.
|
|
cvReleaseImage(dst); // Free the enlarged image.
|
|
end;
|
|
end;
|
|
|
|
// *****************************************************************************
|
|
(*
|
|
* ANSI C code from the article
|
|
* 'Contrast Limited Adaptive Histogram Equalization'
|
|
* by Karel Zuiderveld, karel@cv.ruu.nl
|
|
* in 'Graphics Gems IV', Academic Press, 1994
|
|
*
|
|
*
|
|
* These functions implement Contrast Limited Adaptive Histogram Equalization.
|
|
* The main routine (CLAHE) expects an input image that is stored contiguously in
|
|
* memory; the CLAHE output image overwrites the original input image and has the
|
|
* same minimum and maximum values (which must be provided by the user).
|
|
* This implementation assumes that the X- and Y image resolutions are an integer
|
|
* multiple of the X- and Y sizes of the contextual regions. A check on various other
|
|
* error conditions is performed.
|
|
*
|
|
* #define the symbol BYTE_IMAGE to make this implementation suitable for
|
|
* 8-bit images. The maximum number of contextual regions can be redefined
|
|
* by changing uiMAX_REG_X and/or uiMAX_REG_Y; the use of more than 256
|
|
* contextual regions is not recommended.
|
|
*
|
|
* The code is ANSI-C and is also C++ compliant.
|
|
*
|
|
* Author: Karel Zuiderveld, Computer Vision Research Group,
|
|
* Utrecht, The Netherlands (karel@cv.ruu.nl)
|
|
*)
|
|
|
|
(*
|
|
|
|
EULA: The Graphics Gems code is copyright-protected. In other words, you cannot
|
|
claim the text of the code as your own and resell it. Using the code is permitted
|
|
in any program, product, or library, non-commercial or commercial. Giving credit
|
|
is not required, though is a nice gesture. The code comes as-is, and if there are
|
|
any flaws or problems with any Gems code, nobody involved with Gems - authors,
|
|
editors, publishers, or webmasters - are to be held responsible. Basically,
|
|
don't be a jerk, and remember that anything free comes with no guarantee.
|
|
|
|
- http://tog.acm.org/resources/GraphicsGems/ (August 2009)
|
|
|
|
*)
|
|
|
|
(*********************** Local prototypes ************************)
|
|
procedure ClipHistogram (pulHistogram:PCardinal;
|
|
uiNrGreylevels:Cardinal; ulClipLimit:Cardinal);forward;
|
|
procedure MakeHistogram (pImage:pkz_pixel_t; uiXRes:Cardinal;
|
|
uiSizeX:Cardinal; uiSizeY:Cardinal;
|
|
pulHistogram:pCardinal;
|
|
uiNrGreylevels:Cardinal; pLookupTable:pkz_pixel_t);forward;
|
|
procedure MapHistogram (pulHistogram:pCardinal; Min:kz_pixel_t; Max:kz_pixel_t;
|
|
uiNrGreylevels:Cardinal; ulNrOfPixels:Cardinal);forward;
|
|
procedure MakeLut (pLUT:pkz_pixel_t; Min:kz_pixel_t; Max:kz_pixel_t; uiNrBins:cardinal); forward;
|
|
procedure Interpolate (pImage:pkz_pixel_t; uiXRes:Integer; pulMapLU:pCardinal;
|
|
pulMapRU:PCardinal; pulMapLB:pCardinal; pulMapRB:pCardinal;
|
|
uiXSize:Cardinal; uiYSize:Cardinal; pLUT:pkz_pixel_t);forward;
|
|
|
|
// *****************************************************************************
|
|
|
|
(************** Start of actual code **************)
|
|
|
|
const
|
|
uiMAX_REG_X :Cardinal= 16; (* max. # contextual regions in x-direction *)
|
|
uiMAX_REG_Y :Cardinal= 16; (* max. # contextual regions in y-direction *)
|
|
|
|
(************************** main function CLAHE ******************)
|
|
function CLAHE(pImage:pkz_pixel_t; uiXRes:Cardinal;
|
|
uiYRes:Cardinal; Min:kz_pixel_t;
|
|
Max:kz_pixel_t; uiNrX:Cardinal; uiNrY:Cardinal;
|
|
uiNrBins:Cardinal; fCliplimit:float):Integer;
|
|
(* pImage - Pointer to the input/output image
|
|
* uiXRes - Image resolution in the X direction
|
|
* uiYRes - Image resolution in the Y direction
|
|
* Min - Minimum greyvalue of input image (also becomes minimum of output image)
|
|
* Max - Maximum greyvalue of input image (also becomes maximum of output image)
|
|
* uiNrX - Number of contextial regions in the X direction (min 2, max uiMAX_REG_X)
|
|
* uiNrY - Number of contextial regions in the Y direction (min 2, max uiMAX_REG_Y)
|
|
* uiNrBins - Number of greybins for histogram ('dynamic range')
|
|
* float fCliplimit - Normalized cliplimit (higher values give more contrast)
|
|
* The number of 'effective' greylevels in the output image is set by uiNrBins; selecting
|
|
* a small value (eg. 128) speeds up processing and still produce an output image of
|
|
* good quality. The output image will have the same minimum and maximum value as the input
|
|
* image. A clip limit smaller than 1 results in standard (non-contrast limited) AHE.
|
|
*)
|
|
Var
|
|
uiX, uiY:Cardinal; (* counters *)
|
|
uiXSize, uiYSize, uiSubX, uiSubY:Cardinal; (* size of context. reg. and subimages *)
|
|
uiXL, uiXR, uiYU, uiYB:Cardinal; (* auxiliary variables interpolation routine *)
|
|
ulClipLimit, ulNrPixels:Cardinal;(* clip limit and region pixel count *)
|
|
pImPointer:pkz_pixel_t; (* pointer to image *)
|
|
aLUT:array[0..uiNR_OF_GREY] of kz_pixel_t; (* lookup table used for scaling of input image *)
|
|
pulHist, pulMapArray:pCardinal; (* pointer to histogram and mappings*)
|
|
pulLU, pulLB, pulRU, pulRB:pCardinal; (* auxiliary pointers interpolation *)
|
|
|
|
|
|
begin
|
|
if (uiNrX > uiMAX_REG_X) then Exit(-1); (* # of regions x-direction too large *)
|
|
if (uiNrY > uiMAX_REG_Y) then Exit(-2); (* # of regions y-direction too large *)
|
|
if (uiXRes mod uiNrX)<>0 then Exit(-3); (* x-resolution no multiple of uiNrX *)
|
|
if (uiYRes mod uiNrY)<>0 then Exit(-4); (* y-resolution no multiple of uiNrY #TPB FIX *)
|
|
// #ifndef BYTE_IMAGE (* #TPB FIX *)
|
|
//if (Max >= uiNR_OF_GREY) return -5; (* maximum too large *)
|
|
//#endif
|
|
if (Min >= Max) then Exit(-6); (* minimum equal or larger than maximum *)
|
|
if (uiNrX < 2 )or( uiNrY < 2) then Exit(-7);(* at least 4 contextual regions required *)
|
|
if (fCliplimit = 1.0) then Exit(0); (* is OK, immediately returns original image. *)
|
|
if (uiNrBins = 0) then uiNrBins := 128; (* default value when not specified *)
|
|
|
|
GetMem(pulMapArray,sizeof(Cardinal)*uiNrX*uiNrY*uiNrBins);
|
|
if (pulMapArray = nil) then Exit(-8); (* Not enough memory! (try reducing uiNrBins) *)
|
|
|
|
uiXSize := uiXRes div uiNrX;
|
|
uiYSize := uiYRes div uiNrY; (* Actual size of contextual regions *)
|
|
ulNrPixels := uiXSize * uiYSize;
|
|
|
|
if(fCliplimit > 0.0) then begin (* Calculate actual cliplimit *)
|
|
ulClipLimit := Trunc(fCliplimit * (uiXSize * uiYSize) / uiNrBins);
|
|
if ulClipLimit < 1 then
|
|
ulClipLimit := 1;
|
|
end
|
|
else ulClipLimit := 1 shl 14; (* Large value, do not clip (AHE) *)
|
|
MakeLut(@aLUT, Min, Max, uiNrBins); (* Make lookup table for mapping of greyvalues *)
|
|
(* Calculate greylevel mappings for each contextual region *)
|
|
pImPointer := pImage;
|
|
for uiY := 0 to uiNrY-1 do
|
|
begin
|
|
for uiX := 0 to uiNrX-1 do
|
|
begin
|
|
pulHist := @pulMapArray[uiNrBins * (uiY * uiNrX + uiX)];
|
|
MakeHistogram(pImPointer,uiXRes,uiXSize,uiYSize,pulHist,uiNrBins,@aLUT);
|
|
ClipHistogram(pulHist, uiNrBins, ulClipLimit);
|
|
MapHistogram(pulHist, Min, Max, uiNrBins, ulNrPixels);
|
|
pImPointer :=pImPointer+ uiXSize;
|
|
end;
|
|
pImPointer :=pImPointer+ (uiYSize - 1) * uiXRes; (* skip lines, set pointer *)
|
|
end;
|
|
|
|
(* Interpolate greylevel mappings to get CLAHE image *)
|
|
pImPointer := pImage;
|
|
for uiY := 0 to uiNrY do begin
|
|
if (uiY = 0) then begin (* special case: top row *)
|
|
uiSubY := uiYSize shr 1;
|
|
uiYU := 0;
|
|
uiYB := 0;
|
|
end
|
|
else begin
|
|
if (uiY = uiNrY) then begin (* special case: bottom row *)
|
|
uiSubY := uiYSize shr 1;
|
|
uiYU := uiNrY-1;
|
|
uiYB := uiYU;
|
|
end
|
|
else begin (* default values *)
|
|
uiSubY := uiYSize;
|
|
uiYU := uiY - 1;
|
|
uiYB := uiYU + 1;
|
|
end;
|
|
end;
|
|
for uiX := 0 to uiNrX do begin
|
|
if (uiX = 0) then begin (* special case: left column *)
|
|
uiSubX := uiXSize shr 1; uiXL := 0; uiXR := 0;
|
|
end
|
|
else begin
|
|
if (uiX = uiNrX) then begin (* special case: right column *)
|
|
uiSubX := uiXSize shr 1; uiXL := uiNrX - 1; uiXR := uiXL;
|
|
end
|
|
else begin (* default values *)
|
|
uiSubX := uiXSize; uiXL := uiX - 1; uiXR := uiXL + 1;
|
|
end;
|
|
end;
|
|
|
|
pulLU := @pulMapArray[uiNrBins * (uiYU * uiNrX + uiXL)];
|
|
pulRU := @pulMapArray[uiNrBins * (uiYU * uiNrX + uiXR)];
|
|
pulLB := @pulMapArray[uiNrBins * (uiYB * uiNrX + uiXL)];
|
|
pulRB := @pulMapArray[uiNrBins * (uiYB * uiNrX + uiXR)];
|
|
Interpolate(pImPointer,uiXRes,pulLU,pulRU,pulLB,pulRB,uiSubX,uiSubY,@aLUT);
|
|
pImPointer :=pImPointer+ uiSubX; (* set pointer on next matrix *)
|
|
end;
|
|
pImPointer :=pImPointer+ (uiSubY - 1) * uiXRes;
|
|
end;
|
|
freemem(pulMapArray); (* free space for histograms *)
|
|
Result:=0; (* return status OK *)
|
|
end;
|
|
|
|
|
|
procedure ClipHistogram (pulHistogram:PCardinal;
|
|
uiNrGreylevels:Cardinal; ulClipLimit:Cardinal);
|
|
(* This function performs clipping of the histogram and redistribution of bins.
|
|
* The histogram is clipped and the number of excess pixels is counted. Afterwards
|
|
* the excess pixels are equally redistributed across the whole histogram (providing
|
|
* the bin count is smaller than the cliplimit).
|
|
*)
|
|
Var
|
|
pulBinPointer, pulEndPointer, pulHisto:pCardinal;
|
|
ulNrExcess, ulUpper, ulBinIncr, ulStepSize, i:Cardinal;
|
|
ulOldNrExcess:CArdinal; // #IAC Modification
|
|
|
|
lBinExcess:Integer;
|
|
begin
|
|
|
|
ulNrExcess := 0; pulBinPointer := pulHistogram;
|
|
for i := 0 to uiNrGreylevels-1 do
|
|
begin (* calculate total number of excess pixels *)
|
|
lBinExcess := pulBinPointer[i] - ulClipLimit;
|
|
if (lBinExcess > 0) then ulNrExcess :=ulNrExcess+lBinExcess; (* excess in current bin *)
|
|
end;;
|
|
|
|
(* Second part: clip histogram and redistribute excess pixels in each bin *)
|
|
ulBinIncr := ulNrExcess div uiNrGreylevels; (* average binincrement *)
|
|
ulUpper := ulClipLimit - ulBinIncr; (* Bins larger than ulUpper set to cliplimit *)
|
|
|
|
for i := 0 to uiNrGreylevels-1 do begin
|
|
if (pulHistogram[i] > ulClipLimit) then pulHistogram[i] := ulClipLimit (* clip bin *)
|
|
else begin
|
|
if (pulHistogram[i] > ulUpper) then begin (* high bin count *)
|
|
ulNrExcess :=ulNrExcess- pulHistogram[i] - ulUpper; pulHistogram[i]:=ulClipLimit;
|
|
end
|
|
else begin (* low bin count *)
|
|
ulNrExcess :=ulNrExcess- ulBinIncr; pulHistogram[i] :=pulHistogram[i]+ ulBinIncr;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// while (ulNrExcess) begin (* Redistribute remaining excess *)
|
|
// pulEndPointer := &pulHistogram[uiNrGreylevels]; pulHisto := pulHistogram;
|
|
//
|
|
// while (ulNrExcess )and( pulHisto < pulEndPointer) begin
|
|
// ulStepSize := uiNrGreylevels / ulNrExcess;
|
|
// if (ulStepSize < 1) ulStepSize := 1; (* stepsize at least 1 *)
|
|
// for (pulBinPointer=pulHisto; pulBinPointer < pulEndPointer )and( ulNrExcess;
|
|
// pulBinPointer += ulStepSize) begin
|
|
// if (*pulBinPointer < ulClipLimit) begin
|
|
// (*pulBinPointer)++; ulNrExcess--; (* reduce excess *)
|
|
// end;
|
|
// end;
|
|
// pulHisto++; (* restart redistributing on other bin location *)
|
|
// end;
|
|
//end;
|
|
|
|
(* ####
|
|
IAC Modification:
|
|
In the original version of the loop below (commented out above) it was possible for an infinite loop to get
|
|
created. If there was more pixels to be redistributed than available space then the
|
|
while loop would never end. This problem has been fixed by stopping the loop when all
|
|
pixels have been redistributed OR when no pixels where redistributed in the previous iteration.
|
|
This change allows very low clipping levels to be used.
|
|
*)
|
|
|
|
repeat (* Redistribute remaining excess *)
|
|
pulEndPointer := @pulHistogram[uiNrGreylevels]; pulHisto := pulHistogram;
|
|
|
|
ulOldNrExcess := ulNrExcess; (* Store number of excess pixels for test later. *)
|
|
|
|
while (ulNrExcess<>0)and( pulHisto < pulEndPointer) do
|
|
begin
|
|
ulStepSize := uiNrGreylevels div ulNrExcess;
|
|
if (ulStepSize < 1) then
|
|
ulStepSize := 1; (* stepsize at least 1 *)
|
|
pulBinPointer:=pulHisto;
|
|
while (pulBinPointer < pulEndPointer )and( ulNrExcess<>0) do
|
|
begin
|
|
if (pulBinPointer^ < ulClipLimit) then
|
|
begin
|
|
Inc(pulBinPointer^); Dec(ulNrExcess); (* reduce excess *)
|
|
end;
|
|
pulBinPointer :=pulBinPointer+ ulStepSize;
|
|
end;
|
|
Inc(pulHisto); (* restart redistributing on other bin location *)
|
|
end;
|
|
until (ulNrExcess=0) or (ulNrExcess >= ulOldNrExcess);
|
|
(* Finish loop when we have no more pixels or we can't redistribute any more pixels *)
|
|
|
|
|
|
end;
|
|
|
|
procedure MakeHistogram (pImage:pkz_pixel_t; uiXRes:Cardinal;
|
|
uiSizeX:Cardinal; uiSizeY:Cardinal;
|
|
pulHistogram:pCardinal;
|
|
uiNrGreylevels:Cardinal; pLookupTable:pkz_pixel_t);
|
|
(* This function classifies the greylevels present in the array image into
|
|
* a greylevel histogram. The pLookupTable specifies the relationship
|
|
* between the greyvalue of the pixel (typically between 0 and 4095) and
|
|
* the corresponding bin in the histogram (usually containing only 128 bins).
|
|
*)
|
|
Var
|
|
pImagePointer:pkz_pixel_t;
|
|
i:Cardinal;
|
|
begin
|
|
for i := 0 to uiNrGreylevels-1 do pulHistogram[i] := 0; (* clear histogram *)
|
|
|
|
for i := 0 to uiSizeY-1 do begin
|
|
pImagePointer := @pImage[uiSizeX];
|
|
while (pImage < pImagePointer) do
|
|
begin
|
|
Inc(pulHistogram[pLookupTable[pImage^]]);
|
|
Inc(pImage);
|
|
end;
|
|
pImagePointer := pImagePointer+uiXRes;
|
|
pImage := @pImagePointer[-uiSizeX];
|
|
end;
|
|
end;
|
|
|
|
procedure MapHistogram (pulHistogram:pCardinal; Min:kz_pixel_t; Max:kz_pixel_t;
|
|
uiNrGreylevels:Cardinal; ulNrOfPixels:Cardinal);
|
|
(* This function calculates the equalized lookup table (mapping) by
|
|
* cumulating the input histogram. Note: lookup table is rescaled in range [Min..Max].
|
|
*)
|
|
Var
|
|
i, ulSum :Cardinal;
|
|
fScale:float;
|
|
ulMin:CArdinal;
|
|
begin
|
|
ulSum := 0;
|
|
fScale := (Max - Min) / ulNrOfPixels;
|
|
ulMin := Min;
|
|
|
|
for i := 0 to uiNrGreylevels-1 do
|
|
begin
|
|
ulSum :=ulSum+ pulHistogram[i]; pulHistogram[i]:=Trunc(ulMin+ulSum*fScale);
|
|
if (pulHistogram[i] > Max) then pulHistogram[i] := Max;
|
|
end;
|
|
end;
|
|
|
|
procedure MakeLut (pLUT:pkz_pixel_t; Min:kz_pixel_t; Max:kz_pixel_t; uiNrBins:cardinal);
|
|
(* To speed up histogram clipping, the input image [Min,Max] is scaled down to
|
|
* [0,uiNrBins-1]. This function calculates the LUT.
|
|
*)
|
|
Var
|
|
i:Integer;
|
|
BinSize:kz_pixel_t;
|
|
begin
|
|
BinSize := Trunc((1 + (Max - Min) / uiNrBins));
|
|
|
|
for i := Min to Max do pLUT[i] := (i - Min) div BinSize;
|
|
end;
|
|
|
|
procedure Interpolate (pImage:pkz_pixel_t; uiXRes:Integer; pulMapLU:pCardinal;
|
|
pulMapRU:PCardinal; pulMapLB:pCardinal; pulMapRB:pCardinal;
|
|
uiXSize:Cardinal; uiYSize:Cardinal; pLUT:pkz_pixel_t);
|
|
(* pImage - pointer to input/output image
|
|
* uiXRes - resolution of image in x-direction
|
|
* pulMap* - mappings of greylevels from histograms
|
|
* uiXSize - uiXSize of image submatrix
|
|
* uiYSize - uiYSize of image submatrix
|
|
* pLUT - lookup table containing mapping greyvalues to bins
|
|
* This function calculates the new greylevel assignments of pixels within a submatrix
|
|
* of the image with size uiXSize and uiYSize. This is done by a bilinear interpolation
|
|
* between four different mappings in order to eliminate boundary artifacts.
|
|
* It uses a division; since division is often an expensive operation, I added code to
|
|
* perform a logical shift instead when feasible.
|
|
*)
|
|
VAr
|
|
uiIncr:Cardinal;
|
|
GreyValue:kz_pixel_t;
|
|
uiNum:Cardinal;
|
|
|
|
uiXCoef, uiYCoef, uiXInvCoef, uiYInvCoef, uiShift:Cardinal;
|
|
begin
|
|
uiIncr := uiXRes-uiXSize; (* Pointer increment after processing row *)
|
|
uiNum := uiXSize*uiYSize; (* Normalization factor *)
|
|
|
|
uiShift := 0;
|
|
|
|
if (uiNum and (uiNum - 1))<>0 then (* If uiNum is not a power of two, use division *)
|
|
begin
|
|
uiYInvCoef := uiYSize;
|
|
for uiYCoef := 0 to uiYSize-1 do
|
|
begin
|
|
uiXInvCoef := uiXSize;
|
|
for uiXCoef := 0 to uiXSize-1 do
|
|
begin
|
|
GreyValue := pLUT[pImage^]; (* get histogram bin value *)
|
|
pImage^ := ((uiYInvCoef * (uiXInvCoef*pulMapLU[GreyValue]
|
|
+ uiXCoef * pulMapRU[GreyValue])
|
|
+ uiYCoef * (uiXInvCoef * pulMapLB[GreyValue]
|
|
+ uiXCoef * pulMapRB[GreyValue])) div uiNum);
|
|
Inc(pImage);
|
|
Dec(uiXInvCoef);
|
|
end;
|
|
Dec(uiYInvCoef);
|
|
pImage :=pImage+uiIncr;
|
|
end;
|
|
end
|
|
else begin (* avoid the division and use a right shift instead *)
|
|
while uiNum <>0 do (* Calculate 2log of uiNum *)
|
|
begin
|
|
Inc(uiShift);
|
|
uiNum:=uiNum shr 1;
|
|
end;
|
|
uiYInvCoef := uiYSize;
|
|
for uiYCoef := 0 to uiYSize-1 do
|
|
begin
|
|
uiXInvCoef := uiXSize;
|
|
for uiXCoef := 0 to uiXSize-1 do
|
|
begin
|
|
GreyValue := pLUT[pImage^]; (* get histogram bin value *)
|
|
pImage^ := ((uiYInvCoef* (uiXInvCoef * pulMapLU[GreyValue]
|
|
+ uiXCoef * pulMapRU[GreyValue])
|
|
+ uiYCoef * (uiXInvCoef * pulMapLB[GreyValue]
|
|
+ uiXCoef * pulMapRB[GreyValue])) shr uiShift);
|
|
Inc(pImage);
|
|
Dec(uiXInvCoef);
|
|
end;
|
|
Dec(uiYInvCoef);
|
|
pImage:=pImage+uiIncr;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|