Is there any numpy-like way to make my code faster? - python

I am making a code that partially normalizes intensity of an input image. Since I could not find corresponding function of Numpy library, I made a code like following:
def arealNorm(img,kernel_size = 7): #Img should me gray
img_norm = np.zeros(img.shape)
for v in range(kernel_size,img.shape[0]-kernel_size):
for u in range(kernel_size,img.shape[1]-kernel_size):
MaxV = np.max(img[v-kernel_size:v+kernel_size,u-kernel_size:u+kernel_size])
minV = np.min(img[v-kernel_size:v+kernel_size,u-kernel_size:u+kernel_size])
newval = (img[v,u]-minV)/(MaxV-minV)
img_norm[v,u] = newval
return img_norm
Since it should access every pixel of the image this code works very slowly.
Is there any numpy-like way to convert my code to work faster ?

One approach to speed up this code is to use the built-in function scipy.ndimage.filters.generic_filter. This function applies a user-defined function to each neighborhood in the input image.
Here is an example implementation using generic_filter:
from scipy.ndimage.filters import generic_filter
def arealNorm(img, kernel_size=7):
def local_norm(patch):
max_val = np.max(patch)
min_val = np.min(patch)
return (img - min_val) / (max_val - min_val)
img_norm = generic_filter(img, local_norm, size=kernel_size)
return img_norm

Thanks to #Michael Butscher , I finalized my code as :
def arealNorm(img,kernel_size = 7): #Img should me gray
#img_norm = np.zeros(img.shape)
img_max = ndi.filters.maximum_filter(img,size = kernel_size)
img_min = ndi.filters.minimum_filter(img,size = kernel_size)
img_norm = (img - img_min)/(img_max-img_min)
return img_norm

Related

What are the inaccuracies of this 'inverse map' function in OpenCV?

I am trying to horizontally stretch an image in a very specific way. Each x prime coordinate should follow a tangent path with respect to the original x coordinate. I believe there are two ways to do this:
Inverse the tangent function and map it normally
Map the tangent function and then inverse the mapping
Using this answer for map inversion, Im trying to figure out why the two images are not the same. I know that the first method gives me the correct image that I'm looking for, so why doesnt the second method work? Is it because of the "limited precision" that #ChristophRackwitz commented on the answer?
import cv2
import glob
import numpy as np
import math
A = -1010
B = -3.931
C = 5.258
D = 978.3
M = -193.8
N = 1740
def get_tan_func_value(x):
return A * math.tan((((x-N)/M)+B)/C) + D
def get_inverse_tan_func_value(x):
return M * (C*math.atan((x-D)/A) - B) + N
# answer from linked post
def invert_map(F, shape):
I = np.zeros_like(F)
I[:,:,1], I[:,:,0] = np.indices(shape)
P = np.copy(I)
for i in range(10):
P += I - cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR)
return P
# import image
images = glob.glob('*.jpg')
img = cv2.imread(images[0])
h, w = img.shape[:2]
map_x_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_x_inverse_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_y = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
# x tan function map
for i in range(map_x_tan.shape[0]):
map_x_tan[i,:] = [get_tan_func_value(x) for x in range(map_x_tan.shape[1])]
# x inverse tan function map
for i in range(map_x_inverse_tan.shape[0]):
map_x_inverse_tan[i,:] = [get_inverse_tan_func_value(x) for x in range(map_x_inverse_tan.shape[1])]
# default y map
for j in range(map_y.shape[1]):
map_y[:,j] = [y for y in range(map_y.shape[0])]
# convert x tan map to 2 channel (x,y) map
(xymap_tan, _) = cv2.convertMaps(map1=map_x_tan, map2=map_y, dstmap1type=cv2.CV_32FC2)
# invert the 2 channel x tan map
xymap_inverted = invert_map(xymap_tan, (h,w))
# remap and write the target image (inverse tan function with normal map)
target = cv2.remap(img, map_x_inverse_tan, map_y, cv2.INTER_LINEAR)
cv2.imwrite("target.jpg", target)
# remap and write the attempted image (normal tan function with inverted map)
attempt = cv2.remap(img, xymap_inverted, None, cv2.INTER_LINEAR)
cv2.imwrite("attempt.jpg", attempt)
Method 1: Target Image
Method 2: Attempt Image
The results show that the attempt (normal tan function with inverted map) has less stretching near the edges of the image than expected. Almost everywhere else on the images are identical except the edges. I did not post the original picture to save space.
I've played around with that invert_map procedure. It seems slightly susceptible to oscillation.
use this instead:
def invert_map(F):
(h, w) = F.shape[:2] # (h, w, 2), "xymap"
I = np.zeros_like(F)
I[:,:,1], I[:,:,0] = np.indices((h,w)) # identity map
P = np.copy(I)
for i in range(10):
correction = I - cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR)
P += correction * 0.5
return P
I simply damped the correction by 0.5, which makes the fixed point iteration tamer, converging a lot faster too.
In my experiments with your tan map, I've found that 5-10 iterations are good enough already, and there's no further progress in further iterations.
Entire notebook of my explorations: https://gist.github.com/crackwitz/67f76f8a9eff21476b080c06d20660d0
Feature request: https://github.com/opencv/opencv/issues/22120

imflatfield MATLAB for Python use

I am trying to find an equivalent Python function for MATLAB imflatfield function.
I have a section of code that modifies an image and I want to convert it to Python.
Here is the MATLAB code:
I = imread('lcs2.png');
out2 = imflatfield(I,30);
shadow_lab = rgb2lab(out2);
max_luminosity = 100;
L = shadow_lab(:,:,1)/max_luminosity;
shadow_adapthisteq = shadow_lab;
shadow_adapthisteq(:,:,1) = adapthisteq(L)*max_luminosity;
shadow_adapthisteq = lab2rgb(shadow_adapthisteq);
imwrite(shadow_adapthisteq,'lcs2_adap.jpg');
Original image
Final results from MATLAB
Since MATLAB releases the source code of imflatfield, it is not so difficult to implement it in Python using OpenCV.
Note: The implementation is specific to uint8 type and colored image (BGR format in Python).
Here is a MATLAB "manual" implementation of imflatfield:
function B = my_imflatfield(I, sigma)
A = im2single(I);
Ihsv = rgb2hsv(A);
A = Ihsv(:,:,3);
filterSize = 2*ceil(2*sigma)+1;
shading = imgaussfilt(A, sigma, 'Padding', 'symmetric', 'FilterSize', filterSize); % Calculate shading
meanVal = mean(A(:),'omitnan');
% Limit minimum to 1e-6 instead of testing using isnan and isinf after division.
shading = max(shading, 1e-6);
B = A*meanVal./shading;
%B(isnan(B)) = 0; % sometimes instances of 0/0 happen, making NaN values.
%B(isinf(B)) = 0; % sometimes values are divided by 0, making Inf values.
% Put processed V channel back into HSV image, convert to RGB
Ihsv(:,:,3) = B;
B = hsv2rgb(Ihsv);
B = im2uint8(B);
end
Here is an equivalent Python implementation (using OpenCV):
import cv2
import numpy as np
def imflatfield(I, sigma):
"""Python equivalent imflatfield implementation
I format must be BGR and type of I must be uint8"""
A = I.astype(np.float32) / 255 # A = im2single(I);
Ihsv = cv2.cvtColor(A, cv2.COLOR_BGR2HSV) # Ihsv = rgb2hsv(A);
A = Ihsv[:, :, 2] # A = Ihsv(:,:,3);
filterSize = int(2*np.ceil(2*sigma) + 1); # filterSize = 2*ceil(2*sigma)+1;
# shading = imgaussfilt(A, sigma, 'Padding', 'symmetric', 'FilterSize', filterSize); % Calculate shading
shading = cv2.GaussianBlur(A, (filterSize, filterSize), sigma, borderType=cv2.BORDER_REFLECT)
meanVal = np.mean(A) # meanVal = mean(A(:),'omitnan')
#% Limit minimum to 1e-6 instead of testing using isnan and isinf after division.
shading = np.maximum(shading, 1e-6) # shading = max(shading, 1e-6);
B = A*meanVal / shading # B = A*meanVal./shading;
#% Put processed V channel back into HSV image, convert to RGB
Ihsv[:, :, 2] = B # Ihsv(:,:,3) = B;
B = cv2.cvtColor(Ihsv, cv2.COLOR_HSV2BGR) # B = hsv2rgb(Ihsv);
B = np.round(np.clip(B*255, 0, 255)).astype(np.uint8) # B = im2uint8(B);
return B
# Read input imgae
I = cv2.imread('destroyer.jpg')
sigma = 30
out2 = imflatfield(I, sigma)
cv2.imwrite('imflatfield_py_destroyer.png', out2)
The above implementation reads the input image, and write the result to image file.
Comparing results using MATLAB (for testing):
I = imread('destroyer.jpg');
out1 = imflatfield(I, 30);
out2 = my_imflatfield(I, 30);
% Compare results of imflatfield and my_imflatfield:
max(max(max(imabsdiff(out1, out2))))
% figure;imshow(out2)
imwrite(out2, 'imflatfield_destroyer.png');
% Read Python result
out3 = imread('imflatfield_py_destroyer.png');
% Compare results of imflatfield and Python imflatfield:
max(max(max(imabsdiff(out1, out3))))
The maximum absolute difference between MATALB imflatfield and my_imflatfield is 0.
The maximum absolute difference between MATALB imflatfield and Python imflatfield is 1.
Converting the complete MATLAB code to Python:
sigma = 30
out2 = imflatfield(I, sigma)
# Conver out2 to float32 before converting to LAB
out2 = out2.astype(np.float32) / 255 # out2 = im2single(out2);
shadow_lab = cv2.cvtColor(out2, cv2.COLOR_BGR2Lab) # shadow_lab = rgb2lab(out2);
max_luminosity = 100
L = shadow_lab[:, :, 0] / max_luminosity # L = shadow_lab(:,:,1)/max_luminosity;
shadow_adapthisteq = shadow_lab.copy() # shadow_adapthisteq = shadow_lab;
# shadow_adapthisteq(:,:,1) = adapthisteq(L)*max_luminosity;
clahe = cv2.createCLAHE(clipLimit=20, tileGridSize=(8,8))
cl1 = clahe.apply((L*(2**16-1)).astype(np.uint16)) # CLAHE in OpenCV does not support float32 (convert to uint16 and back).
shadow_adapthisteq[:, :, 0] = cl1.astype(np.float32) * max_luminosity / (2**16-1)
shadow_adapthisteq = cv2.cvtColor(shadow_adapthisteq, cv2.COLOR_Lab2BGR) # shadow_adapthisteq = lab2rgb(shadow_adapthisteq);
# Convert shadow_adapthisteq to uint8
shadow_adapthisteq = np.round(np.clip(shadow_adapthisteq*255, 0, 255)).astype(np.uint8) # B = im2uint8(B);
cv2.imwrite('shadow_adapthisteq.jpg', shadow_adapthisteq) # imwrite(shadow_adapthisteq,'lcs2_adap.jpg');
Result is not going to be identical to MATLAB, because adapthisteq in MATLAB is not identical to CLAHE in OpenCV.
Result:

How to Transform Normalization Image Math Equation to Python?

I try to learn how to transform equation to python script.
I choose to start it from FingerPrint Enhancement from Academic resources here.
to start learn i search a fingerprint image to be enhance. I choose this image:
so, i do the first step is converting to gray:
import cv2
import numpy as np
input = 'PATH OF IMAGE'
img = cv2.imread(input)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
and below is the result:
ok the problem start from here...
please try to understood me, I try to learn how to convert math equation to python script.
not try to looking for another / existing script in Github (for example).
the equation is:
all detail from the academic research. Told that:
Let I(i, j) denote the gray-level value at pixel (i, j), M and
VAR denote the estimated mean and variance of I, respectively, and G(i, j) denote the normalized gray-level value at pixel (i, j).
A gray-level fingerprint image, I is defined as an N x N matrix, where I(i, j) represents the intensity of the pixel at the
i-th row and j-th column. We assume that all the images are
scanned at a resolution of 500 dots per inch (dpi). The mean and variance of a gray-level fingerprint image, I, are defined as
and
respectively
ok, we start to transform the equation:
def mean(gray):
rows, cols = gray.shape
sum = 0
for i in range(0,rows):
for j in range(0, cols):
pix = (gray[i,j].item())
sum += pix
M = sum/N
return M
def var(gray, M):
rows, cols = gray.shape
N = gray.size
sum = 0
for i in range(0,rows):
for j in range(0, cols):
vix = ((img[i,j].item()) - M)**2
sum += vix
VAR = sum/N
return VAR
def normalize(img, M0, VAR0):
M = mean(img)
VAR = var(img, M)
rows,cols = img.shape
normim = np.zeros((rows, cols))
for i in range(0, rows):
for j in range(0, cols):
if (gray[i,j].item()) > M:
G0 = M0 + ((((VAR0)*(((gray[i,j].item())-(M))**2))/(VAR))**(1/2))
normim[i,j] = int(G0)
else:
G1 = M0 - ((((VAR0)*(((gray[i,j].item())-(M))**2))/(VAR))**(1/2))
normim[i,j] = int(G1)
return normim
M0 = 100 #follow the academic research document
VAR0 = 100 #follow the academic research document
normgray = normalize(gray, 100,100)
cv2.imshow('test', normgray)
cv2.waitKey(1)
the result is out of expected:
all is white.
can somebody help me? please your advise.
to remind you, I'm not try to looking for the another script / another example. I try to understood how to transform a math equation to python script. about another script, i already have, even i already map it here.
This is a simple problem of not respecting the data types in between transformations. Specifically, when you load in the image, it is going to be unsigned 8-bit integer so the expected values should be within [0, 255], yet your calculations for the mean and variance will exceed this dynamic range and thus your calculations will overflow. The quickest way to resolve this problem is to convert your image so that it will respect a data type that can handle the precision of the calculations you want, like floating-point. Perform the calculations, and when you're done convert the image back to the expected data type, so unsigned 8-bit integer.
In addition, there are several errors in your code. For one thing, you didn't provide the variable N, which should be the total number of pixels in the image. In addition, your var function accepts gray as the variable yet you are using img to try and access pixel data, so this will also give off an error when you try and run it. Finally, you omitted the packages you're using so I added these in.
I've also downloaded your image locally so I can run the code to verify that it works. I've patched up the end of your code so that the image window that displays the result properly closes after you push a key and I've written the output image to file.
Therefore:
# Added so the code can run
import cv2
import numpy as np
# Added so the code can run
gray = cv2.imread('gnN4Q.png', 0)
gray = gray.astype(np.float) # Change to floating-point
N = gray.shape[0]*gray.shape[1]
def mean(gray):
rows, cols = gray.shape
sum = 0
for i in range(0,rows):
for j in range(0, cols):
pix = (gray[i,j].item())
sum += pix
M = sum/N # Added above
return M
def var(gray, M):
rows, cols = gray.shape
N = gray.size
sum = 0
for i in range(0,rows):
for j in range(0, cols):
vix = ((gray[i,j].item()) - M)**2 # Change
sum += vix
VAR = sum/N
return VAR
def normalize(img, M0, VAR0):
M = mean(img)
VAR = var(img, M)
rows,cols = img.shape
normim = np.zeros((rows, cols))
for i in range(0, rows):
for j in range(0, cols):
if (gray[i,j].item()) > M:
G0 = M0 + ((((VAR0)*(((gray[i,j].item())-(M))**2))/(VAR))**(1/2))
normim[i,j] = int(G0)
else:
G1 = M0 - ((((VAR0)*(((gray[i,j].item())-(M))**2))/(VAR))**(1/2))
normim[i,j] = int(G1)
return normim
M0 = 100 #follow the academic research document
VAR0 = 100 #follow the academic research document
normgray = normalize(gray, 100,100)
normgray = normgray.astype(np.uint8) # Added - convert back to uint8
cv2.imshow('test', normgray)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', normgray)
The output image we get is:
I didn't run your code but make sure G0 or G1 doesn't get too big. It could be that your value is above 255, thus the resulting all-white image.

Is there a nice way to slice Python numpy ndarrays without checking sign of the bounds?

This is part of some code I've been working on to quickly align images. It works well, but the syntax is ugly. Is there a better way to write this?
def shift_int(image, base, y, x):
"""
Quickly shift an image with respect to a base and return a parameter that
is minimized when the images are well aligned, and not biased towards
large shifts
image -- The input image that is shifted
base -- The second image to match
y -- An offset along axis 0
x -- An offset along axis 1
"""
new_image = image.copy()
new_base = base.copy()
if y > 0:
new_image = new_image[:-y]
new_base = new_base[y:]
if y < 0:
new_image = new_image[-y:]
new_base = new_base[:y]
if x > 0:
new_image = new_image[:,:-x]
new_base = new_base[:,x:]
if x < 0:
new_image = new_image[:,-x:]
new_base = new_base[:,:x]
return np.mean((new_im-new_base)**2)
h, w = np.shape(new_image)
new_image = new_image[max(0, -y):min(h, h-y),max(0, -x):min(w, w-x)]
h, w = np.shape(new_base)
new_base = new_base[max(0, y):min(h, h+y),max(0, x):min(w, w+x)]

Non local maxima suppression in python

Goal: To input an image (2d numpy array) and a window size, and output the same array with the local maxima remaining, but 0 elsewhere.
What I am struggling with: I think I made a stupid mistake in my code, maybe a few typos in my loop but I am not sure (the local maxima are only on the left side of the image, which is not true). As I note below I would also welcome any easy tricks with OpenCV or numpy to make this solution shorter.
similar questions: Finding Local Maxima in an Image
and Find local maxima in grayscale image using OpenCV
Mine is different because: I am looking to surpress all but local maxima. I don't have to use my code below, I haven't been able to find a built in function of opencv or numpy to do what I need to (harris corners in cv will implicitly do this as one of the steps, but I need something to perform the sole operation I need). I read a little bit on dilate and wasn't sure if that would be useful here as well.
What I have tried so far.
def nonMaximalSupress(image,NHoodSize):
#For
for x in range(0,image.shape[0]-1):
if x+NHoodSize[0]<image.shape[0]:
#while we can still take a square
#print "AHH ", image.shape
startWindow=0
for y in range(startWindow,image.shape[1]-NHoodSize[1]):
#try:
if np.sum(image[x:x+NHoodSize[0]][y:y+NHoodSize[1]])==0:
localMax=0
else:
localMax = np.amax(image[x:x+NHoodSize[0]][y:y+NHoodSize[1]])
#except ValueError:
#localMax=0
#print "local max is ", localMax
maxCoord=np.unravel_index(np.argmax((image[x:x+NHoodSize[0],y:y+NHoodSize[1]])),
image.shape)+np.array((x,y))
#print "X is %r, Y is %r, max coord is %r \n y+nhood is %r" %(x,y,maxCoord,y+NHoodSize[1])
#suppress everything
image[x:x+NHoodSize[0]][y:y+NHoodSize[1]]=0
#reset only the max
#print maxCoord
if localMax > 0:
print localMax
print "max coord is ", maxCoord[0], maxCoord[1]
image[maxCoord[0]][maxCoord[1]]=localMax
#increment y
x+=NHoodSize[0]
return image
How about something like this:
# Use the max filter to make a mask
roi = 3
size = 2 * roi + 1
image_max = ndimage.maximum_filter(image, size=size, mode='constant')
mask = (image == image_max)
image *= mask
# Remove the image borders
image[:roi] = 0
image[-roi:] = 0
image[:, :roi] = 0
image[:, -roi:] = 0
# Optionally find peaks above some threshold
image_t = (image > peak_threshold) * 1
# get coordinates of peaks
f = np.transpose(image_t.nonzero())
This may not be entirely correct, but it works better on a small test case
def nonMaximalSupress1(image,NHoodSize):
#
dX, dY = NHoodSize
M, N = image.shape
for x in range(0,M-dX+1):
for y in range(0,N-dY+1):
window = image[x:x+dX, y:y+dY]
if np.sum(window)==0:
localMax=0
else:
localMax = np.amax(window)
maxCoord=np.unravel_index(np.argmax(window), window.shape) + np.array((x,y))
#suppress everything
image[x:x+dX, y:y+dY]=0
#reset only the max
if localMax > 0:
print localMax
print "max coord is ", maxCoord
image[tuple(maxCoord)] = localMax
return image
I've used local variables to make things easier to read, and tweaked the loop ranges. But the big change is in how I index image. Especially when indexing with slices, you must use one set of brackets.
image[x:x+dX, y:y+dY] is the correct way to select a window, not image[x:x+dX][y:y+dY].
It can be cleaned up a bit more by modifying the window. Since it is a view, changing it changes image.
def nonMaximalSupress2(image,NHoodSize):
#
dX, dY = NHoodSize
M, N = image.shape
for x in range(0,M-dX+1):
for y in range(0,N-dY+1):
window = image[x:x+dX, y:y+dY]
if np.sum(window)==0:
localMax=0
else:
localMax = np.amax(window)
maxCoord = np.argmax(window)
# zero all but the localMax in the window
window[:] = 0
window.flat[maxCoord] = localMax
return image
A different approach using peak_local_max from skimage.feature.peak and center_of_mass from scipy.ndimage.measurements for localizing the centers of the peaks if a peak may consist of multiple pixels with the same pixel intensity:
from skimage.feature.peak import peak_local_max
from scipy.ndimage.measurements import center_of_mass
from scipy.ndimage import label
from scipy.ndimage.morphology import generate_binary_structure
footprint = generate_binary_structure(3, 3)
peaks = peak_local_max(img, indices=False, footprint=footprint)
lbl, num_features = label(maxima)
centers = center_of_mass(maxima, lbl, range(1, num_features + 1))
values = img[tuple(np.int0(np.transpose(centers)))]

Categories