I am studying image processing and I am writing a code to do a Histogram equalization, but the output always give me an all black image.
This is my code:
import numpy as np
import scipy.misc, math
from PIL import Image
img = Image.open('/home/thaidy/Desktop/ex.jpg').convert('L')
#converting to ndarray
img1 = np.asarray(img)
#converting to 1D
fl = img1.flatten()
#histogram and the bins are computed
hist, bins = np.histogram(img1,256,[0,255])
#cdf computed
cdf = hist.cumsum()
#places where cdf = 0 is ignored
#rest stored in cdf_m
cdf_m = np.ma.masked_equal(cdf,0)
#histogram eq is performed
num_cdf_m = (cdf_m - cdf_m.min())*255
den_cdf_m = (cdf_m.max()-cdf_m.min())*255
cdf_m = num_cdf_m/den_cdf_m
#the masked places are now 0
cdf = np.ma.filled(cdf_m,0).astype('uint8')
#cdf values assigned in the flattened array
im2 = cdf[fl]
#transformin in 2D
im3 = np.reshape(im2,img1.shape)
im4 = Image.fromarray(im3)
im4.save('/home/thaidy/Desktop/output.jpg')
im4.show()
The cdf needs to be normalized before the equalization.
One way to do that is to set the optional parameter density of np.histogram to True:
hist, bins = np.histogram(img1,256,[0,255],density=True)
Other way is to divide cdf after computation by total pixel count:
cdf = hist.cumsum()
cdf /= cdf[-1]
I would also change the equalization part to simply:
T = (255 * cdf).astype(np.uint8)
im2 = T[fl]
Wikipedia suggests to use this transformation formula instead:
T = (np.ceil(256 * cdf) - 1).astype(np.uint8)
Related
I wrote the code below and what I get is the output below. What I suppose to do is write an histogram equalization function(without built in methods) I get no error, however output is not what it should to be. I could not notice any logic mistakes im my code. Although, while writing the loop for calculating cdf and/or mapping I couldn't follow what happens behind it exactly, maybe the problem is there but I am not sure.
def my_float2int(img):
img = np.round(img * 255, 0)
img = np.minimum(img, 255)
img = np.maximum(img, 0)
img = img.astype('uint8')
return img
def equalizeHistogram(img):
img_height = img.shape[0]
img_width = img.shape[1]
histogram = np.zeros([256], np.int32)
# calculate histogram
for i in range(0, img_height):
for j in range(0, img_width):
histogram[img[i, j]] +=1
# calculate pdf of the image
pdf_img = histogram / histogram.sum()
### calculate cdf
# cdf initialize .
cdf = np.zeros([256], np.int32)
# For loop for cdf
for i in range(0, 256):
for j in range(0, i+1):
cdf[i] += pdf_img[j]
cdf_eq = np.round(cdf * 255, 0) # mapping, transformation function T(x)
imgEqualized = np.zeros((img_height, img_width))
# for mapping input image to s.
for i in range(0, img_height):
for j in range(0, img_width):
r = img[i, j] # feeding intensity levels of pixels into r.
s = cdf_eq[r] # finding value of s by finding r'th position in the cdf_eq list.
imgEqualized[i, j] = s # mapping s thus creating new output image.
# calculate histogram equalized image here
# imgEqualized = s # change this
return imgEqualized
# end of function
# 2.2 obtain the histogram equalized images using the above function
img_eq_low = equalizeHistogram(img_low)
img_eq_high = equalizeHistogram(img_high)
img_eq_low = my_float2int(img_eq_low)
img_eq_high = my_float2int(img_eq_high)
# 2.3 calculate the pdf's of the histogram equalized images
hist_img_eq_low = calcHistogram(img_eq_low)
hist_img_eq_high = calcHistogram(img_eq_high)
pdf_eq_low = hist_img_eq_low / hist_img_eq_low.sum()
pdf_eq_high = hist_img_eq_high / hist_img_eq_high.sum()
# 2.4 display the histogram equalized images and their pdf's
plt.figure(figsize=(14,8))
plt.subplot(121), plt.imshow(img_eq_low, cmap = 'gray', vmin=0, vmax=255)
plt.title('Hist. Equalized Low Exposure Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_eq_high, cmap = 'gray', vmin=0, vmax=255)
plt.title('Hist. Equalized High Exposure Image'), plt.xticks([]), plt.yticks([])
plt.show()
plt.close()
My output:
Expected output: with the built-in methods.
I found two minor bugs, and one efficiency issue:
Replace cdf = np.zeros([256], np.int32) with cdf = np.zeros([256], float)
In the loop, you are putting float elements in cdf, so the type should be float instead of int32.
Replace img = np.round(img * 255, 0) with img = np.round(img, 0) (in my_float2int).
You are scaling img by 255 twice (the first time is in cdf_eq = np.round(cdf * 255, 0)).
You may compute cdf more efficiently.
Your implementation:
for i in range(0, 256):
for j in range(0, i+1):
cdf[i] += pdf_img[j]
Suggested implementation (more efficient way for computing "accumulated sum"):
cdf[0] = pdf_img[0]
for i in range(1, 256):
cdf[i] = cdf[i-1] + pdf_img[i]
It's not a bug, but a kind of academic issue (regarding complexity).
Here is an example for corrected code (uses only img_low):
import numpy as np
import cv2
def my_float2int(img):
# Don't use *255 twice
# img = np.round(img * 255, 0)
img = np.round(img, 0)
img = np.minimum(img, 255)
img = np.maximum(img, 0)
img = img.astype('uint8')
return img
def equalizeHistogram(img):
img_height = img.shape[0]
img_width = img.shape[1]
histogram = np.zeros([256], np.int32)
# calculate histogram
for i in range(0, img_height):
for j in range(0, img_width):
histogram[img[i, j]] +=1
# calculate pdf of the image
pdf_img = histogram / histogram.sum()
### calculate cdf
# cdf initialize .
# Why does the type np.int32?
#cdf = np.zeros([256], np.int32)
cdf = np.zeros([256], float)
# For loop for cdf
for i in range(0, 256):
for j in range(0, i+1):
cdf[i] += pdf_img[j]
# You may implement the "accumulated sum" in a more efficient way:
cdf = np.zeros(256, float)
cdf[0] = pdf_img[0]
for i in range(1, 256):
cdf[i] = cdf[i-1] + pdf_img[i]
cdf_eq = np.round(cdf * 255, 0) # mapping, transformation function T(x)
imgEqualized = np.zeros((img_height, img_width))
# for mapping input image to s.
for i in range(0, img_height):
for j in range(0, img_width):
r = img[i, j] # feeding intensity levels of pixels into r.
s = cdf_eq[r] # finding value of s by finding r'th position in the cdf_eq list.
imgEqualized[i, j] = s # mapping s thus creating new output image.
# calculate histogram equalized image here
# imgEqualized = s # change this
return imgEqualized
# end of function
# Read input image as Grayscale
img_low = cv2.imread('img_low.png', cv2.IMREAD_GRAYSCALE)
# 2.2 obtain the histogram equalized images using the above function
img_eq_low = equalizeHistogram(img_low)
img_eq_low = my_float2int(img_eq_low)
# Use cv2.imshow (instead of plt.imshow) just for testing.
cv2.imshow('img_eq_low', img_eq_low)
cv2.waitKey()
Result:
After reading an image with PIL I usually perform a Gaussian filter using scipy.ndimage as follow
import PIL
from scipy import ndimage
PIL_image = PIL.Image.open(filename)
data = PIL_image.getdata()
array = np.array(list(data)).reshape(data.size[::-1]+(-1,))
img = array.astype(float)
fimg = ndimage.gaussian_filter(img, sigma=sigma, mode='mirror', order=0)
There is Gaussian blur function within PIL as follows (from this answer), but I don't know how it works exactly or what kernel it uses:
from PIL import ImageFilter
fimgPIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=r)
This documentation does not provide details.
Questions about PIL.ImageFilter.GaussianBlur:
What exactly is the radius parameter; is it equivalent to the standard deviation σ?
For a given radius, how far out does it calculate the kernel? 2σ? 3σ? 6σ?
This comment on an answer to Gaussian Blur - standard deviation, radius and kernel size says the following, but I have not found information for PIL yet.
OpenCV uses kernel radius of (sigma * 3) while scipy.ndimage.gaussian_filter uses kernel radius of int(4 * sigma + 0.5)
From the source code, it looks like PIL.ImageFilter.GaussianBlur uses PIL.ImageFilter.BoxBlur. But I wasn't able to figure out how the radius and sigma are related.
I wrote a script to check the difference between scipy.ndimage.gaussian_filter and PIL.ImageFilter.GaussianBlur.
import numpy as np
from scipy import misc
from scipy.ndimage import gaussian_filter
import PIL
from PIL import ImageFilter
import matplotlib.pyplot as plt
# Load test color image
img = misc.face()
# Scipy gaussian filter
sigma = 5
img_scipy = gaussian_filter(img, sigma=(sigma,sigma,0), mode='nearest')
# PIL gaussian filter
radius = 5
PIL_image = PIL.Image.fromarray(img)
img_PIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=radius))
data = img_PIL.getdata()
img_PIL = np.array(data).reshape(data.size[::-1]+(-1,))
img_PIL = img_PIL.astype(np.uint8)
# Image difference
img_diff = np.abs(np.float_(img_scipy) - np.float_(img_PIL))
img_diff = np.uint8(img_diff)
# Stats
mean_diff = np.mean(img_diff)
median_diff = np.median(img_diff)
max_diff = np.max(img_diff)
# Plot results
plt.subplot(221)
plt.imshow(img_scipy)
plt.title('SciPy (sigma = {})'.format(sigma))
plt.axis('off')
plt.subplot(222)
plt.imshow(img_PIL)
plt.title('PIL (radius = {})'.format(radius))
plt.axis('off')
plt.subplot(223)
plt.imshow(img_diff)
plt.title('Image difference \n (Mean = {:.2f}, Median = {:.2f}, Max = {:.2f})'
.format(mean_diff, median_diff, max_diff))
plt.colorbar()
plt.axis('off')
# Plot histogram
d = img_diff.flatten()
bins = list(range(int(max_diff)))
plt.subplot(224)
plt.title('Histogram of Image difference')
h = plt.hist(d, bins=bins)
for i in range(len(h[0])):
plt.text(h[1][i], h[0][i], str(int(h[0][i])))
Output for sigma=5, radius=5:
Output for sigma=30, radius=30:
The outputs of scipy.ndimage.gaussian_filter and PIL.ImageFilter.GaussianBlur are very similar and the difference is negligible. More than 95% of difference values are <= 2.
PIL version: 7.2.0, SciPy version: 1.5.0
This is a supplementary answer to #Nimal's accepted answer.
Basically the radius parameter is like sigma. I won't dig too deep, but I think the Gaussian kernel is slightly different internally in order to preserve normalization after rounding back to integers, since the PIL method returns 0 to 255 integer levels.
The script below generates an image that is 1 on the left and 0 on the right, then does a sigma = 10 pixel blur with both methods, then plots the center horizontal lines through each, plus their differences. I do difference twice since log can only display positive differences.
The first panel is the difference between PIL and the SciPy float results, the second is the truncated integer SciPy results, and the third is rounded SciPy.
import numpy as np
import matplotlib.pyplot as plt
import PIL
from scipy.ndimage import gaussian_filter
from PIL import ImageFilter
import PIL
sigma = 10.0
filename = 'piximg.png'
# Save a PNG with a central pixel = 1
piximg = np.zeros((101, 101), dtype=float)
piximg[:, :50] = 1.0
plt.imsave(filename, piximg, cmap='gray')
# Read with PIL
PIL_image = PIL.Image.open(filename)
# Blur with PIL
img_PIL = PIL_image.filter(ImageFilter.GaussianBlur(radius=sigma))
data = img_PIL.getdata()
img_PIL = np.array(list(data)).reshape(data.size[::-1]+(-1,))
g1 = img_PIL[..., 1]
# Blur with SciPy
data = PIL_image.getdata()
array = np.array(list(data)).reshape(data.size[::-1]+(-1,))
img = array.astype(float)
fimg = gaussian_filter(img[...,:3], sigma=sigma, mode='mirror', order=0)
g2 = fimg[..., 1]
g2u = np.uint8(g2)
g2ur = np.uint8(g2+0.5)
if True:
plt.figure()
plt.subplot(3, 1, 1)
plt.plot(g1[50])
plt.plot(g2[50])
plt.plot(g2[50] - g1[50])
plt.plot(g1[50] - g2[50])
plt.yscale('log')
plt.ylim(0.1, None)
plt.subplot(3, 1, 2)
plt.plot(g1[50])
plt.plot(g2u[50])
plt.plot(g2u[50] - g1[50])
plt.plot(g1[50] - g2u[50])
plt.yscale('log')
plt.ylim(0.1, None)
plt.subplot(3, 1, 3)
plt.plot(g1[50])
plt.plot(g2ur[50])
plt.plot(g2ur[50] - g1[50])
plt.plot(g1[50] - g2ur[50])
plt.yscale('log')
plt.ylim(0.1, None)
plt.show()
I'm trying to blurr an image by mapping each pixel to the average of the N pixels to the right of it (in the same row). My iterative solution produces good output, but my linear-algebra solution is producing bad output.
From testing, I believe my kernel-matrix is correct; and, I know the last N rows don't get blurred, but that's fine for now. I'd appreciate any hints or solutions.
iterative-solution output (good), linear-algebra output (bad)
original image; and here is the failing linear-algebra code:
def blur(orig_img):
# turn image-mat into a vector
flattened_img = orig_img.flatten()
L = flattened_img.shape[0]
N = 3
# kernel
kernel = np.zeros((L, L))
for r, row in enumerate(kernel[0:-N]):
row[r:r+N] = [round(1/N, 3)]*N
print(kernel)
# blurr the img
print('starting blurring')
blurred_img = np.matmul(kernel, flattened_img)
blurred_img = blurred_img.reshape(orig_img.shape)
return blurred_img
The equation I'm modelling is this:
One option might be to just use a kernel and a convolution?
For example if we load a gray scale image like so:
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from scipy import ndimage
# load a hackinsh grayscale image
image = np.asarray(Image.open('cup.jpg')).mean(axis=2)
plt.imshow(image)
plt.title('Gray scale image')
plt.show()
Now one can use a kernel and convolution. For example to create a filter that filters just one rows and compute the value of the center pixel as the difference between the pixels to the right and left one can do the following:
# Create a kernel that takes the difference between neighbors horizontal pixes
k = np.array([[-1,0,1]])
plt.subplot(121)
plt.title('Kernel')
plt.imshow(k)
plt.subplot(122)
plt.title('Output')
plt.imshow(ndimage.convolve(image, k, mode='constant', cval=0.0))
plt.show()
Therefore, one can blurr an image by mapping each pixel to the average of the N pixels to the right of it by creating the appropiate kernel.
# Create a kernel that takes the average of N pixels to the right
n=10
k = np.zeros(n*2);k[n:]=1/n
k = k[np.newaxis,...]
plt.subplot(121)
plt.title('Kernel')
plt.imshow(k)
plt.subplot(122)
plt.title('Output')
plt.imshow(ndimage.convolve(image, k, mode='constant', cval=0.0))
plt.show()
The issue was incorrect usage of cv2.imshow() in displaying the output image. It expects floating-point pixel values to be in [0, 1]; which, is done in the below code (near bottom):
def blur(orig_img):
flattened_img = orig_img.flatten()
L = flattened_img.shape[0]
N = int(round(0.1 * orig_img.shape[0], 0))
# mask (A)
mask = np.zeros((L, L))
for r, row in enumerate(mask[0:-N]):
row[r:r+N] = [round(1/N, 2)]*N
# blurred img = A * flattened_img
print('starting blurring')
blurred_img = np.matmul(mask, flattened_img)
blurred_img = blurred_img.reshape(orig_img.shape)
cv2.imwrite('blurred_img.png', blurred_img)
# normalize img to [0,1]
blurred_img = (
blurred_img - blurred_img.min()) / (blurred_img.max()-blurred_img.min())
return blurred_img
Ammended output
Thank you to #CrisLuengo for identifying the issue.
This question already has answers here:
Python OpenCV skew correction for OCR
(3 answers)
Closed 10 months ago.
I want a way to automatically detect and correct skew of a image of a receipt,
I tried to find variance between the rows for various angles of rotation and choose the angle which has the the maximum variance.
To calculate variance I did the following:
1.For each row I calculated the sum of the pixels values and stored it in a list.
2.Found the the variance of the list using np.var(list)
src = cv.imread(f_name, cv.IMREAD_GRAYSCALE)
blurred=median = cv.medianBlur(src,9)
ret,thresh2 = cv.threshold(src,127,255,cv.THRESH_BINARY_INV)
height, width = thresh2.shape[:2]
print(height,width)
res=[-1,0]
for angle in range(0,100,10):
rotated_temp=deskew(thresh2,angle)
cv.imshow('rotated_temp',rotated_temp)
cv.waitKey(0)
height,width=rotated_temp.shape[:2]
li=[]
for i in range(height):
sum=0
for j in range(width):
sum+=rotated_temp[i][j]
li.append(sum)
curr_variance=np.var(li)
print(curr_variance,angle)
if(curr_variance>res[0]):
res[0]=curr_variance
res[1]=angle
print(res)
final_rot=deskew(src,res[1])
cv.imshow('final_rot',final_rot)
cv.waitKey(0)
However the variance for a skewed image is coming to be more than the properly aligned image,is there any way to correct this
variance for the horizontal text aligned image(required):122449908.009789
variance for the vertical text aligned image :1840071444.404522
I have tried using HoughLines However since the spacing between the text is too less vertical lines are detected,hence this also fails
Any modifications or other approaches are appreciated
Working code for skew correction
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image as im
from scipy.ndimage import interpolation as inter
input_file = r'E:\flaskV8\test1.jpg'
img = im.open(input_file)
convert to binary
wd, ht = img.size
pix = np.array(img.convert('1').getdata(), np.uint8)
bin_img = 1 - (pix.reshape((ht, wd)) / 255.0)
plt.imshow(bin_img, cmap='gray')
plt.savefig(r'E:\flaskV8\binary.png')
def find_score(arr, angle):
data = inter.rotate(arr, angle, reshape=False, order=0)
hist = np.sum(data, axis=1)
score = np.sum((hist[1:] - hist[:-1]) ** 2)
return hist, score
delta = 1
limit = 5
angles = np.arange(-limit, limit+delta, delta)
scores = []
for angle in angles:
hist, score = find_score(bin_img, angle)
scores.append(score)
best_score = max(scores)
best_angle = angles[scores.index(best_score)]
print('Best angle: {}'.format(best_angle))
data = inter.rotate(bin_img, best_angle, reshape=False, order=0)
img = im.fromarray((255 * data).astype("uint8")).convert("RGB")
img.save(r'E:\flaskV8\skew_corrected.png')
I have this routine to do histogram equalization of a photo:
def histeq(im,nbr_bins=256):
#get image histogram
imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
cdf = imhist.cumsum() #cumulative distribution function
cdf = 255 * cdf / cdf[-1] #normalize
#use linear interpolation of cdf to find new pixel values
im2 = interp(im.flatten(),bins[:-1],cdf)
return im2.reshape(im.shape), cdf
#im = array(Image.open('AquaTermi_lowcontrast.jpg').convert('L'))
im = array(Image.open('Unequalized.jpg').convert('L'))
#Image.open('plant4.jpg').convert('L').save('inverted.jpg')
im2,cdf = histeq(im)
plt.imshow(im2)
plt.savefig("outputhisto.jpg")
When I run this with the picture from the wiki page for histogram equalization it results in this:
Instead of properly adjusting the contrast of image to something along the lines of this. What am I doing wrong?
Are you sure you're just not rendering with the wrong colormap? Try
plt.imshow(im2, cmap=plt.cm.gray)
Or
plt.imshow(im2, cmap=plt.get_cmap('gray'))