Last active
April 2, 2025 08:14
-
-
Save sadimanna/52c320ce5c49e200ce398f800d39a2c1 to your computer and use it in GitHub Desktop.
Contrast Limited Adaptive Histogram Equalization in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
"Contrast Limited Adaptive Histogram Equalization" | |
by Karel Zuiderveld, [email protected] | |
in "Graphics Gems IV", Academic Press, 1994 | |
_Author_ -- Siladittya Manna | |
The below implementation does not assume that the | |
X- and Y image resolutions are an integer multiple | |
of the X- and Y sizes of the contextual regions. | |
Instead it pads the image with the number of excess pixels | |
required to make the X and Y resolutions an integral multiple | |
of X and Y sizes of the contextual region(if required) | |
Minimum and Maximum values are assued to be 0 and 255 | |
respectively even if they are not present in the image | |
''' | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from skimage import io | |
#INTERPOLATION FUNCTION | |
def interpolate(subBin,LU,RU,LB,RB,subX,subY): | |
subImage = np.zeros(subBin.shape) | |
num = subX*subY | |
for i in range(subX): | |
inverseI = subX-i | |
for j in range(subY): | |
inverseJ = subY-j | |
val = subBin[i,j].astype(int) | |
subImage[i,j] = np.floor((inverseI*(inverseJ*LU[val] + j*RU[val])+ i*(inverseJ*LB[val] + j*RB[val]))/float(num)) | |
return subImage | |
#CLAHE FUNCTION | |
#ALL UTILITY FUNCTIONS COMBINED INTO ONE FUNCTION | |
def clahe(img,clipLimit,nrBins=128,nrX=0,nrY=0): | |
'''img - Input image | |
clipLimit - Normalized clipLimit. Higher value gives more contrast | |
nrBins - Number of graylevel bins for histogram("dynamic range") | |
nrX - Number of contextial regions in X direction | |
nrY - Number of Contextial regions in Y direction''' | |
h,w = img.shape | |
if clipLimit==1: | |
return | |
nrBins = max(nrBins,128) | |
if nrX==0: | |
#Taking dimensions of each contextial region to be a square of 32X32 | |
xsz = 32 | |
ysz = 32 | |
nrX = np.ceil(h/xsz).astype(int)#240 | |
#Excess number of pixels to get an integer value of nrX and nrY | |
excX= int(xsz*(nrX-h/xsz)) | |
nrY = np.ceil(w/ysz).astype(int)#320 | |
excY= int(ysz*(nrY-w/ysz)) | |
#Pad that number of pixels to the image | |
if excX!=0: | |
img = np.append(img,np.zeros((excX,img.shape[1])).astype(int),axis=0) | |
if excY!=0: | |
img = np.append(img,np.zeros((img.shape[0],excY)).astype(int),axis=1) | |
else: | |
xsz = round(h/nrX) | |
ysz = round(w/nrY) | |
nrPixels = xsz*ysz | |
xsz2 = round(xsz/2) | |
ysz2 = round(ysz/2) | |
claheimg = np.zeros(img.shape) | |
if clipLimit > 0: | |
clipLimit = max(1,clipLimit*xsz*ysz/nrBins) | |
else: | |
clipLimit = 50 | |
#makeLUT | |
print("...Make the LUT...") | |
minVal = 0 #np.min(img) | |
maxVal = 255 #np.max(img) | |
#maxVal1 = maxVal + np.maximum(np.array([0]),minVal) - minVal | |
#minVal1 = np.maximum(np.array([0]),minVal) | |
binSz = np.floor(1+(maxVal-minVal)/float(nrBins)) | |
LUT = np.floor((np.arange(minVal,maxVal+1)-minVal)/float(binSz)) | |
#BACK TO CLAHE | |
bins = LUT[img] | |
print(bins.shape) | |
#makeHistogram | |
print("...Making the Histogram...") | |
hist = np.zeros((nrX,nrY,nrBins)) | |
print(nrX,nrY,hist.shape) | |
for i in range(nrX): | |
for j in range(nrY): | |
bin_ = bins[i*xsz:(i+1)*xsz,j*ysz:(j+1)*ysz].astype(int) | |
for i1 in range(xsz): | |
for j1 in range(ysz): | |
hist[i,j,bin_[i1,j1]]+=1 | |
#clipHistogram | |
print("...Clipping the Histogram...") | |
if clipLimit>0: | |
for i in range(nrX): | |
for j in range(nrY): | |
nrExcess = 0 | |
for nr in range(nrBins): | |
excess = hist[i,j,nr] - clipLimit | |
if excess>0: | |
nrExcess += excess | |
binIncr = nrExcess/nrBins | |
upper = clipLimit - binIncr | |
for nr in range(nrBins): | |
if hist[i,j,nr] > clipLimit: | |
hist[i,j,nr] = clipLimit | |
else: | |
if hist[i,j,nr]>upper: | |
nrExcess += upper - hist[i,j,nr] | |
hist[i,j,nr] = clipLimit | |
else: | |
nrExcess -= binIncr | |
hist[i,j,nr] += binIncr | |
if nrExcess > 0: | |
stepSz = max(1,np.floor(1+nrExcess/nrBins)) | |
for nr in range(nrBins): | |
nrExcess -= stepSz | |
hist[i,j,nr] += stepSz | |
if nrExcess < 1: | |
break | |
#mapHistogram | |
print("...Mapping the Histogram...") | |
map_ = np.zeros((nrX,nrY,nrBins)) | |
#print(map_.shape) | |
scale = (maxVal - minVal)/float(nrPixels) | |
for i in range(nrX): | |
for j in range(nrY): | |
sum_ = 0 | |
for nr in range(nrBins): | |
sum_ += hist[i,j,nr] | |
map_[i,j,nr] = np.floor(min(minVal+sum_*scale,maxVal)) | |
#BACK TO CLAHE | |
#INTERPOLATION | |
print("...interpolation...") | |
xI = 0 | |
for i in range(nrX+1): | |
if i==0: | |
subX = int(xsz/2) | |
xU = 0 | |
xB = 0 | |
elif i==nrX: | |
subX = int(xsz/2) | |
xU = nrX-1 | |
xB = nrX-1 | |
else: | |
subX = xsz | |
xU = i-1 | |
xB = i | |
yI = 0 | |
for j in range(nrY+1): | |
if j==0: | |
subY = int(ysz/2) | |
yL = 0 | |
yR = 0 | |
elif j==nrY: | |
subY = int(ysz/2) | |
yL = nrY-1 | |
yR = nrY-1 | |
else: | |
subY = ysz | |
yL = j-1 | |
yR = j | |
UL = map_[xU,yL,:] | |
UR = map_[xU,yR,:] | |
BL = map_[xB,yL,:] | |
BR = map_[xB,yR,:] | |
#print("CLAHE vals...") | |
subBin = bins[xI:xI+subX,yI:yI+subY] | |
#print("clahe subBin shape: ",subBin.shape) | |
subImage = interpolate(subBin,UL,UR,BL,BR,subX,subY) | |
claheimg[xI:xI+subX,yI:yI+subY] = subImage | |
yI += subY | |
xI += subX | |
if excX==0 and excY!=0: | |
return claheimg[:,:-excY] | |
elif excX!=0 and excY==0: | |
return claheimg[:-excX,:] | |
elif excX!=0 and excY!=0: | |
return claheimg[:-excX,:-excY] | |
else: | |
return claheimg | |
image = io.imread('example.jpg') | |
clahe_img = clahe(image[:,:,0],8,0,0) | |
#clipLimit = 8 gave decent results on eyePACs Dataset | |
#and setting xsz and ysz = 32 | |
#and calculating nrX and nrY | |
fig,axs = plt.subplots(1,2,figsize=(200,100)) | |
axs[0].imshow(image[:,:,0],cmap='gray') | |
axs[1].imshow(clahe_img,cmap='gray') | |
plt.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment