FFT image clearing - python

I am using python and I need to implement a function to clear a pattern noise from images, I write this function and I get an OK result on some images but on others, I get a bad result, can U guys help me to improve the code?
I can't use cv2, just 'numpy' and 'from scipy.signal import convolve'
def fft_clean(img1: np.array, img2: np.array, img3: np.array, img4: np.array) \
-> (np.array, np.array, np.array, np.array):
'''
This function receives 4 grayscale images and clean them using FFT algorithm.
Args:
img1: image array in float format (range: 0..1) - the source grayscale
image.
img2: image array in float format (range: 0..1) - the source grayscale
image.
img3: image array in float format (range: 0..1) - the source grayscale
image.
img4: image array in float format (range: 0..1) - the source grayscale
image.
Returns:
img_c1: array in int format (values: 0, 1) - the cleaned image.
img_c2: array in int format (values: 0, 1) - the cleaned image.
img_c3: array in int format (values: 0, 1) - the cleaned image.
img_c4: array in int format (values: 0, 1) - the cleaned image.
'''
######################################################################
# TODO: Implement The fft algorithm.
######################################################################
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# find the maximum magnitude value
max_val = np.max(mag)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = max_val * 0.1
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
# clean each of the input images and store the result
img_c1 = clean(img1)
img_c2 = clean(img2)
img_c3 = clean(img3)
img_c4 = clean(img4)
######################################################################
# END OF YOUR CODE #
######################################################################
return img_c1.real, img_c2.real, img_c3.real, img_c4.real
as u can see img1 have washed colors, img2 is great, img3 is ok but the color is little bit washed, and img4 look terrible
https://github.com/dnevo/ImageProcessing/tree/main/images
fft1 - ff4.tiff
I change the clean function to this:
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# find the maximum magnitude value
max_val = np.max(mag)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = max_val * 0.094
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
I just change this line
threshold = max_val * 0.094
instead of 0.1, I set 0.094 and the last pic is ok now
but the first is still washed.
I also try to change to this:
def clean(img):
# perform FFT on the input image
f = fft2(img)
# shift the FFT result to center the image
f = fftshift(f)
# get the magnitude of the FFT result
mag = np.abs(f)
# set a threshold that eliminates most of the noise and leaves the line patterns
threshold = np.max(mag) * 0.094
# create a binary mask with the same size as the image
mask = np.zeros_like(mag)
# set the values in the mask corresponding to the line patterns to 1
mask[np.where(mag < threshold)] = 1
# find the coordinates of the peaks in the magnitude spectrum that are above the threshold
peaks = np.array(np.where(mag > threshold)).T
# remove the significant peaks that are not at the origin by setting their values in the mask to 0
for peak in peaks:
if peak[0] != mag.shape[0]//2 or peak[1] != mag.shape[1]//2:
mask[peak[0], peak[1]] = 0
# apply the mask to the FFT result
f *= mask
# shift the result back to its original position
f = ifftshift(f)
# perform the inverse FFT to obtain the cleaned image
img_c = ifft2(f)
return img_c
like #CrisLuengo suggest but I get the same results
please let me know where I can improve the code and get better results.
thanks!!

Related

How Do I Develop a negative film image using python

I have tried inverting a negative film images color with the bitwise_not() function in python but it has this blue tint. I would like to know how I could develop a negative film image that looks somewhat good. Here's the outcome of what I did. (I just cropped the negative image for a new test I was doing so don't mind that)
If you don't use exact maximum and minimum, but 1st and 99th percentile, or something nearby (0.1%?), you'll get some nicer contrast. It'll cut away outliers due to noise, compression, etc.
Additionally, you should want to mess with gamma, or scale the values linearly, to achieve white balance.
I'll apply a "gray world assumption" and scale each plane so the mean is gray. I'll also mess with gamma, but that's just messing around.
And... all of that completely ignores gamma mapping, both of the "negative" and of the outputs.
import numpy as np
import cv2 as cv
import skimage
im = cv.imread("negative.png")
(bneg,gneg,rneg) = cv.split(im)
def stretch(plane):
# take 1st and 99th percentile
imin = np.percentile(plane, 1)
imax = np.percentile(plane, 99)
# stretch the image
plane = (plane - imin) / (imax - imin)
return plane
b = 1 - stretch(bneg)
g = 1 - stretch(gneg)
r = 1 - stretch(rneg)
bgr = cv.merge([b,g,r])
cv.imwrite("positive.png", bgr * 255)
b = 1 - stretch(bneg)
g = 1 - stretch(gneg)
r = 1 - stretch(rneg)
# gray world
b *= 0.5 / b.mean()
g *= 0.5 / g.mean()
r *= 0.5 / r.mean()
bgr = cv.merge([b,g,r])
cv.imwrite("positive_grayworld.png", bgr * 255)
b = 1 - np.clip(stretch(bneg), 0, 1)
g = 1 - np.clip(stretch(gneg), 0, 1)
r = 1 - np.clip(stretch(rneg), 0, 1)
# goes in the right direction
b = skimage.exposure.adjust_gamma(b, gamma=b.mean()/0.5)
g = skimage.exposure.adjust_gamma(g, gamma=g.mean()/0.5)
r = skimage.exposure.adjust_gamma(r, gamma=r.mean()/0.5)
bgr = cv.merge([b,g,r])
cv.imwrite("positive_gamma.png", bgr * 255)
Here's what happens when gamma is applied to the inverted picture... a reasonably tolerable transfer function results from applying the same factor twice, instead of applying its inverse.
Trying to "undo" the gamma while ignoring that the values were inverted... causes serious distortions:
And the min/max values for contrast stretching also affect the whole thing.
A simple photo of a negative simply won't do. It'll include stray light that offsets the black point, at the very least. You need a proper scan of the negative.
Here is one simple way to do that in Python/OpenCV. Basically one stretches each channel of the image to full dynamic range separately. Then recombines. Then inverts.
Input:
import cv2
import numpy as np
import skimage.exposure
# read image
img = cv2.imread('boys_negative.png')
# separate channels
r,g,b = cv2.split(img)
# stretch each channel
r_stretch = skimage.exposure.rescale_intensity(r, in_range='image', out_range=(0,255)).astype(np.uint8)
g_stretch = skimage.exposure.rescale_intensity(g, in_range='image', out_range=(0,255)).astype(np.uint8)
b_stretch = skimage.exposure.rescale_intensity(b, in_range='image', out_range=(0,255)).astype(np.uint8)
# combine channels
img_stretch = cv2.merge([r_stretch, g_stretch, b_stretch])
# invert
result = 255 - img_stretch
cv2.imshow('input', img)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save results
cv2.imwrite('boys_negative_inverted.jpg', result)
Result:
Caveat: This works for this image, but may not be a universal solution for all images.
ADDITION
In the above, I did not clip when stretching as I wanted to preserver all information. But if one wants to clip and use skimage.exposure.rescale_intensity for stretching, then it is easy enough by the following:
import cv2
import numpy as np
import skimage.exposure
# read image
img = cv2.imread('boys_negative.png')
# separate channels
r,g,b = cv2.split(img)
# compute clip points -- clip 1% only on high side
clip_rmax = np.percentile(r, 99)
clip_gmax = np.percentile(g, 99)
clip_bmax = np.percentile(b, 99)
clip_rmin = np.percentile(r, 0)
clip_gmin = np.percentile(g, 0)
clip_bmin = np.percentile(b, 0)
# stretch each channel
r_stretch = skimage.exposure.rescale_intensity(r, in_range=(clip_rmin,clip_rmax), out_range=(0,255)).astype(np.uint8)
g_stretch = skimage.exposure.rescale_intensity(g, in_range=(clip_gmin,clip_gmax), out_range=(0,255)).astype(np.uint8)
b_stretch = skimage.exposure.rescale_intensity(b, in_range=(clip_bmin,clip_bmax), out_range=(0,255)).astype(np.uint8)
# combine channels
img_stretch = cv2.merge([r_stretch, g_stretch, b_stretch])
# invert
result = 255 - img_stretch
cv2.imshow('input', img)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save results
cv2.imwrite('boys_negative_inverted2.jpg', result)
Result:

Computing a contrast map

I am trying to compute the contrast around each pixel in an NxN window and saving the results in a new image where each pixel in the new image is the contrast of the area around it in the old image. From another post I got this:
1) Convert the image to say LAB and get the L channel
2) Compute the max for an NxN neighborhood around each pixel
3) Compute the min for an NxN neighborhood around each pixel
4) Compute the contrast from the equation above at each pixel.
5) Insert the contrast as a pixel value in new image.
Currently I have the following:
def cmap(roi):
max = roi.reshape((roi.shape[0] * roi.shape[1], 3)).max(axis=0)
min = roi.reshape((roi.shape[0] * roi.shape[1], 3)).min(axis=0)
contrast = (max - min) / (max + min)
return contrast
def cm(img):
# convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# separate channels
L, A, B = cv2.split(lab)
img_shape = L.shape
size = 5
shape = (L.shape[0] - size + 1, L.shape[1] - size + 1, size, size)
strides = 2 * L.strides
patches = np.lib.stride_tricks.as_strided(L, shape=shape, strides=strides)
patches = patches.reshape(-1, size, size)
output_img = np.array([cmap(roi) for roi in patches])
cv2.imwrite("labtest.png", output_img)
The code complains about the size of roi. Is there a better (pythonic) way of doing what I want?
You may use Dilation and Erosion morphological operations for finding the max and min for NxN neighborhood.
Dilation of NxN is equivalent to maximum of NxN neighborhood.
Erosion of NxN is equivalent to minimum of NxN neighborhood.
Using morphological operations makes the solution much simpler than "manually" dividing the image into small blocks.
You may use the following stages:
Convert to LAB color space and get L channel.
Use "dilate" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood).
Use "erode" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood).
Convert images to type float (required before using division operation).
Compute contrast map (range of contrast map is [0, 1]).
Convert contrast map to type uint8 with rounding - the conversion loosed accuracy, so I can't recommend it (but I assume you need the conversion for getting the output as an image).
Here is a complete code sample:
import numpy as np
import cv2
size_n = 5 # NxN neighborhood around each pixel
# Read input image
img = cv2.imread('chelsea.png')
# Convert to LAB color space
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
# Get the L channel
L = lab[:, :, 0]
# Use "dilate" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
img_max = cv2.morphologyEx(L, cv2.MORPH_DILATE, np.ones((size_n, size_n)))
# Use "erode" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
img_min = cv2.morphologyEx(L, cv2.MORPH_ERODE, np.ones((size_n, size_n)))
# Convert to type float (required before using division operation)
img_max = img_max.astype(float)
img_min = img_min.astype(float)
# Compute contrast map (range of img_contrast is [0, 1])
img_contrast = (img_max - img_min) / (img_max + img_min)
# Convert contrast map to type uint8 with rounding - the conversion loosed accuracy, so I can't recommend it.
# Note: img_contrast_uint8 is scaled by 255 (scaled by 255 relative to the original formula).
img_contrast_uint8 = np.round(img_contrast*255).astype(np.uint8)
# Show img_contrast as output
cv2.imshow('img_contrast', img_contrast_uint8)
cv2.waitKey()
cv2.destroyAllWindows()
Input image:
L image:
img_max:
img_min:
Contrast map img_contrast_uint8:

How to align images by translation and rotation only (no warp) in Python

I need to align two images which are slightly shifted and rotated 180 deg. relative to each other. I tried several ways using opencv (in Python), but no luck.
Method 1 was using MOTION_AFFINE:
im1 = cv2.imread(file1) # Reference image.
im2 = cv2.imread(file2) # Image to be aligned.
# Convert images to grayscale for computing the rotation via ECC method
im1_gray = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
im2_gray = cv2.cvtColor(im2,cv2.COLOR_BGR2GRAY)
# Find size of image1
sz = im1.shape
# Define the motion model - euclidean is rigid (SRT)
warp_mode = cv2.MOTION_AFFINE
# Define 2x3 matrix and initialize the matrix to identity matrix I (eye)
warp_matrix = np.eye(2, 3, dtype=np.float32)
# Specify the number of iterations.
number_of_iterations = 5000;
# Specify the threshold of the increment
# in the correlation coefficient between two iterations
termination_eps = 1e-3;
# Define termination criteria
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, number_of_iterations, termination_eps)
# Run the ECC algorithm. The results are stored in warp_matrix.
(cc, warp_matrix) = cv2.findTransformECC (im1_gray, im2_gray, warp_matrix, warp_mode, criteria, None, 1)
# Warp im2 using affine
im2_aligned = cv2.warpAffine(im2, warp_matrix, (sz[1],sz[0]))#, flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP);
# Save the output.
cv2.imwrite(outfile, im2_aligned)
This didn't even converge.
Method 2 was using feature matching, like so:
im1 = cv2.imread(file1) # Reference image.
im2 = cv2.imread(file2) # Image to be aligned.
img1 = cv2.cvtColor(img1_color, cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(img2_color, cv2.COLOR_BGR2GRAY)
height, width = img2.shape
# Create ORB detector with 4000 features.
orb_detector = cv2.ORB_create(4000)
# The first arg is the image, second arg is the mask
# (which is not reqiured in this case).
kp1, d1 = orb_detector.detectAndCompute(img1, None)
kp2, d2 = orb_detector.detectAndCompute(img2, None)
# Match features between the two images.
# We create a Brute Force matcher with
# Hamming distance as measurement mode.
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck = True)
# Match the two sets of descriptors.
matches = matcher.match(d1, d2)
# Sort matches on the basis of their Hamming distance.
matches.sort(key = lambda x: x.distance)
# Take the top 90 % matches forward.
matches = matches[:int(len(matches)*90)]
no_of_matches = len(matches)
# Define empty matrices of shape no_of_matches * 2.
p1 = np.zeros((no_of_matches, 2))
p2 = np.zeros((no_of_matches, 2))
for i in range(len(matches)):
p1[i, :] = kp1[matches[i].queryIdx].pt
p2[i, :] = kp2[matches[i].trainIdx].pt
# Find the homography matrix.
homography, mask = cv2.findHomography(p1, p2, cv2.RANSAC)
# Use this matrix to transform the
# colored image wrt the reference image.
transformed_img = cv2.warpPerspective(img1_color,
homography, (width, height))
# Save the output.
cv2.imwrite(outfile, transformed_img)
This ended up rotating the second image to the first image's orientation, but warping it too much, so it looks like it's not even in the same plane.
Is there any way to combine feature-based matching of two images with a transform that only rotates and translates, but does not warp perspective?
Thank you!

why does cv2.Sobel function returns a black and white image instead of grayscale image

code -
frame=cv2.imread('ball2.jpg',0)
frame_blur=cv2.GaussianBlur(frame,(7,7),0)
sobelx=cv2.Sobel(frame_blur,cv2.CV_64F,1,0,dst=None,ksize=5)
sobely=cv2.Sobel(frame_blur,cv2.CV_64F,0,1,dst=None,ksize=5)
lap=cv2.Laplacian(frame_blur,cv2.CV_64F)
cv2.imshow('sobelx',sobelx)
cv2.imshow('sobely',sobely)
cv2.imshow('laplecian',lap)
why does cv2.solber returns a black and white image instead of grayscale and what is the threshold the cv2.solber function uses after finding the gradient in the image.
same thing happens with the cv2.laplecian function ^^^
one more question why do we normalise the image after applying solber kernel . i understand that sometimes we get a negative value after applying the solder kernel but doesn't opencv converts the negative no. to positive automatically
for eg: -1 to 255 , -2 to 244
Here are two ways to create the sobel and normalize for saving in Python/OpenCV. Method 1: stretch min and max values to range -255 to 255, then clip negative values so range is 0 to 255. Method 2: stretch min and max values to range 0 to 255 (so zero in Sobel becomes mid gray).
Input:
import cv2
import numpy as np
import skimage.exposure as exposure
# read the image
img = cv2.imread('barn.jpg')
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# blur
blur = cv2.GaussianBlur(gray,(3,3),0)
# apply sobel x derivative
sobelx = cv2.Sobel(blur,cv2.CV_64F,1,0,ksize=5)
# normalize to range -1 to 1
sobelx_norm1a = exposure.rescale_intensity(sobelx, in_range='image', out_range=(-1,1))
# normalize to range -255 to 255 and clip negatives
sobelx_norm1b = exposure.rescale_intensity(sobelx, in_range='image', out_range=(-255,255)).clip(0,255).astype(np.uint8)
# normalize to range 0 to 255
sobelx_norm8 = exposure.rescale_intensity(sobelx, in_range='image', out_range=(0,255)).astype(np.uint8)
# save results
cv2.imwrite('barn_sobel_norm1b.jpg', sobelx_norm1b)
cv2.imwrite('barn_sobel_norm8.jpg', sobelx_norm8)
# show results
cv2.imshow('sobelx_norm1a', sobelx_norm1a)
cv2.imshow('sobelx_norm1b', sobelx_norm1b)
cv2.imshow('sobelx_norm8', sobelx_norm8)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result 1:
Result 2:
I suspect you want the second method. But are getting the first method when you use float values that get negative values clipped as in my displayed result 1a.

How can I improve this disparity map?

I have been working on a piece of code to create a disparity map.
I don't want to use OpenCV for more than loading / saving the images converting them to grayscale.
So far, I've managed to implement the algorithm explained in this website. I'm using the version of the algorithm that uses the Sum of Absolute Differences (SAD). To test my implementation, I'm using the stereo images from this dataset.
Here's my code:
import cv2
import numpy as np
# Load the stereo images
img = cv2.imread('bow-view1.png')
img2 = cv2.imread('bow-view5.png')
# convert stereo images to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
# get the size of the images
# l -> lines
# c -> columns
# v -> channel (RGB)
l,c,v = img.shape
# initialize arrays
minSAD = np.ones((l,c)) * 1000
sad = np.ones((l,c))
winsad = np.ones((l,c))
disp = np.zeros((l,c))
max_shift = 30
# set size of the SAD window
w_l = 2
w_c = 2
for shift in range(max_shift):
print("New Shift: %d"%(shift))
for u in range(0,l):
for v in range(0,c):
# calculate SAD
if(u+shift < l):
sad[u,v] = np.abs((int(gray[u,v]) - int(gray2[u+shift,v])))
sum_sad = 0
for d in range(w_l):
for e in range(w_c):
if(u+d < l and v+e < c):
sum_sad += sad[u+d,v+e]
winsad[u,v] = sum_sad
# Save disparity
if(sad[u,v] < minSAD[u,v]):
minSAD[u,v] = winsad[u,v]
disp[u,v] = shift
print("Process Complete")
# write disparity map to image
cv2.imwrite('outputHT/disparity/sad.png',disp)
print("Disparity Map Generated")
This is the output generated by that code:
I should get an output similar (or very close to) this:
I've tried several window sizes (in the SAD step), but I keep getting results like this one or images that are all black.
Any answer that helps me figure out the problem or that at least points me in the right direction will be very appreciated!
One thing you are missing here is that all the values in the disp array will be between 0 and 30 which correspond to black pixel, so in order to map these values between 0 and 255 you have to multiply the shift by 8.

Categories