Non local maxima suppression in python - 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)))]

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

How to filter specific image coordinates from an image

I am reading an image, getting objects that have a certain brightness value, and then plotting the X and Y coords to the image.
But, there is a huge group of outliers, which are all located in a rectangular part of the image, Its X and Y coords are 1110-1977 (width) and 1069-1905 (height). From here, I'm looping through this little square portion of the image, and from my pre-created x and y arrays any values that have the same coords as shown there are removed.
However, this removes a lot more coords, which, for example, have X in the range 1110-1977. So the end result is a cross pattern filtering when I only want the square in the center to be filtered. How would I do this?
Code
from PIL import Image, ImageDraw
import numpy as np
from math import sqrt
imag = Image.open("Centaurus_A-DeNoiseAI-denoise.jpg")
imag = imag.convert ('RGB')
x=[]
y=[]
imag2=Image.open("Cen_A_cropped.jpg")
imag2=imag2.convert('RGB')
r=[]
g=[]
b=[]
width2, height2=imag2.size
for count2 in range(width2):
for i2 in range(height2):
X,Y=count2,i2
(R,G,B)=imag2.getpixel((X,Y))
r.append(R)
g.append(G)
b.append(B)
average_r=sum(r)/len(r)
average_g=sum(g)/len(g)
average_b=sum(b)/len(b)
brightness_average=sqrt(0.299*(average_r**2) + 0.587*(average_g**2) + 0.114*(average_b**2))
print("Avg. brightness "+str(brightness_average))
def calculate_brightness(galaxy,ref_clus,clus_mag):
delta_b=(galaxy/ref_clus)
bright=delta_b**2
mag=np.log(bright)/np.log(2.512)
return mag+clus_mag
count=0
X,Y = 1556,1568
(R,G,B) = imag.getpixel((X,Y))
width, height=imag.size
brightness = sqrt(0.299*(R**2) + 0.587*(G**2) + 0.114*(B**2))
print("Magnitude: "+str((calculate_brightness(13050, 15.79,3.7))))
reference=brightness_average/(calculate_brightness(13050, 15.79,3.7)/6.84)
print("Reference: "+str(reference))
for count in range(width):
for i in range(height):
X,Y = count,i
(R,G,B) = imag.getpixel((X,Y))
brightness = sqrt(0.299*(R**2) + 0.587*(G**2) + 0.114*(B**2))
if(reference<=brightness<=reference+3):
x.append(X)
y.append(Y)
#post processing----------------------------------------------------------------------------------------------------
for x2 in range(1110, 1977):
for y2 in range(1069, 1905):
X,Y=x2,y2
if(X in x and Y in y):
x.remove(X)
y.remove(Y)
#-------------------------------------------------------------------------------------------------------------------
with imag as im:
delta = 19
draw = ImageDraw.Draw(im)
for i in range(len(x)):
draw.rectangle([x[i-delta],y[i-delta],x[i-delta],y[i-delta]], fill=(0,255,0))
im.save("your_image.png")
Centaurus_A-DeNoiseAI-denoise.jpg
Cen_A_cropped.jpg
Your post-processing logic is flawed. You remove a bunch of X values in the range 1110-1977, without checking whether its corresponding Y value is also in the range of the box. Remove this code section instead and add that logic the first time you loop to gather your x and y coords.
for count in range(width):
for i in range(height):
X,Y = count,i
if 1110 <= X < 1977 and 1069 <= Y < 1905: # add these
continue # two lines
(R,G,B) = imag.getpixel((X,Y))
However, there is a better way of doing the exact same thing by using numpy arrays. Instead of writing explicit loops, you can vectorise a lot of your computations.
import numpy as np
from PIL import Image, ImageDraw
image = Image.open('Centaurus_A-DeNoiseAI-denoise.jpg').convert('RGB')
img1 = np.array(image)
img2 = np.array(Image.open('Cen_A_cropped.jpg').convert('RGB'))
coeffs = np.array([.299, .587, .114])
average = img2.mean(axis=(0, 1))
brightness_average = np.sqrt(np.sum(average**2 * coeffs))
reference = brightness_average / (calculate_brightness(13050, 15.79,3.7) / 6.84)
print(f'Avg. brightness: {brightness_average}')
print(f'Reference: {reference}')
brightness = np.sqrt(np.sum(img1.astype(int)**2 * coeffs, axis=-1))
accepted_brightness = (brightness >= reference) * (brightness <= reference + 3)
pixels_used = np.ones((img1.shape[:2]), dtype=bool)
pixels_used[1069:1905,1110:1977] = False
rows, cols = np.where(accepted_brightness * pixels_used)
with image as im:
draw = ImageDraw.Draw(im)
draw.point(list(zip(cols, rows)), fill=(0, 255, 0))
image.save('out.png')
The main trick used here is in the line
rows, cols = np.where(accepted_brightness * pixels_used)
accepted_brightess is a 2d array of each pixel with a boolean value whether its brightness is within your preferred range. pixels_used is another 2d boolean array, where every pixel is True, except from the pixels in the box near the centre you want to ignore. The combination of those two gives you the pixel coordinates that have the correct brightness and are not in the square in the centre.

Most efficient way to transfrom a 2d array to a different coordinate system using a function, then interpolate the resultant holes

To start, Im basically trying to go from this:
To this:
Given that each coordinate [x,y] correspond with a given point in the second image after a function is applied to x and y. f(x,y)=coords of the second image for the value of [x,y]. The way Im handling this part as of now is to make a "map" array of x and y and the lookup in that array to find the new point. so mapArrayX[x] will give the new x value and mapArray[y] will give the new Y value. The Issue with this is that I have to iterate over the entire image (256,000 points) and that takes roughly .4 seconds. Is there a better way to do this?
The second issue is after transforming the coordinates I get an image with holes in it that looks like this:
which I make look like the image above without the holes by doing this:
dewarpedImage[dewarpedImage == 0] = np.nan
x = np.arange(0, dewarpedImage.shape[1])
y = np.arange(0, dewarpedImage.shape[0])
# mask invalid values
dewarpedImage = np.ma.masked_invalid(dewarpedImage)
xx, yy = np.meshgrid(x, y)
# get only the valid values
x1 = xx[~dewarpedImage.mask]
y1 = yy[~dewarpedImage.mask]
newarr = dewarpedImage[~dewarpedImage.mask]
startTime = time.time()
dewarpedImage = interpolate.griddata((x1, y1), newarr.ravel(),
(xx, yy),
method='linear')
This takes roughly 3 seconds to perform. Is there a faster way to do this maybe. I ideally need to get this whole process to go from taking 3+seconds to less than 1 second.
Here is my conversion function/how I generate my mapping:
RANGE_BIN_SIZE = .39
def rangeBinToRange(rangeBin):
return rangeBin * RANGE_BIN_SIZE
def azToDegree(azBin):
degree = math.degrees(math.asin((azBin - 127.5) * 0.3771/(0.19812*255)))
return degree
def makeWarpMap():
print("making warp maps")
xMap = np.zeros((1024, 256))
yMap = np.zeros((1024, 256))
for az in range(256):
for rang in range(1024):
azDegree = azToDegree(az)
dist = rangeBinToRange(rang)
x = round(dist * math.sin(math.radians(azDegree)) + 381)
y = round(dist * math.cos(math.radians(azDegree)))
xMap[rang][az] = x
yMap[rang][az] = y
np.save("warpmapX", xMap)
np.save("warpmapY", yMap)
print(azToDegree(0))
if not path.exists("warpmapX.npy") or not path.exists("warpmapY.npy"):
makeWarpMap()
data = np.load(filename)
xMap = np.load("warpmapX.npy")
yMap = np.load("warpmapY.npy")
dewarpedImage = np.zeros((400, 762))
print(data.shape)
for az in range(256):
azslice = data[:, az]
for rang in range(1024):
intensity = azslice[rang]
x = xMap[rang][az]
y = yMap[rang][az]
dewarpedImage[int(y)][int(x)] = intensity
You have holes in your converted image because your conversion does not span the entire polar image. I would recommend to do the reverse conversion. In other words, for each (X,Y) in polar image, find corresponding point (x,y) in cartesian image and get that color. That way you won't need to deal with holes at all and it will give you a full image (it will get rid of 3sec conversion). If you provide your conversion function, I can help you do the reverse conversion.

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.

Detect a simple circle in an image and find its radius using numpy

I have an image with a simple circle in it.
How to detect the circle using numpy and find its radius ?
I tried learning but couldnt. I would like to learn from the program that you answer here.
Only thing I know as of now is:
from scipy import misc
f = misc.imread("/path/to/file.png")
# then dont know what to do
Here is the image - https://c2.staticflickr.com/6/5476/14135136623_3973d3f03c_b.jpg
I'm assuming for now that the image always consists of exactly one non-black circle in an otherwise black image.
I'm also assuming you don't care particularly about efficiency of the algorithm.
Loop over all of the pixels in the image, keeping a sum, count, min, and max tally of all white pixel's x- and y-coordinates (min/max needed only for x or y, not both). Something like this (just roughly -- I don't have scipy installed atm):
circle_count = 0
circle_sum_x = 0
circle_sum_y = 0
circle_min_x = 0
circle_max_x = 0
for y in range(image_height):
for x in range(image_width):
if pixel(x,y) is not black:
circle_count += 1
circle_sum_x += x
circle_sum_y += y
circle_min_x = min(circle_min_x, x)
circle_max_x = max(circle_max_y, y)
if circle_count == 0:
print "No circle found!"
else:
circle_center_x = circle_sum_x / circle_count
circle_center_y = circle_sum_y / circle_count
circle_radius = (circle_max_x - circle_min_x) / 2

Categories