I have an image like following, I want to find four coordinate (corners) from this image.
I have tried with below code:
# dilate thresholded image - merges top/bottom
kernel = np.ones((3,3), np.uint8)
dilated = cv2.dilate(img, kernel, iterations=3)
# Finding contours for the thresholded image
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
First, I dilated the image so that it fill up the scatter portion and tried to find out contours from there. But it gives me wrong output.
What I can do this for finding out four corner coordinates?
I have found your points by putting a regression line threw each of your sides and taking their interception points.
First I import stuff and find the contour points with open cv.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from scipy.stats import linregress
from sympy import solve, symbols
import itertools
img = cv2.imread('ZrSqG.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
threshold, binarized_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
contours, hierarchy = cv2.findContours(binarized_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = (contours[1].reshape(-1,2)).T
Now I get a few of the top most, left most etc. points and put a line threw them. Then I calculate their interceptions and plot it all.
def interpolate_function(x,y):
line = interpolate(x,y)
return lambda x: x*line.slope+line.intercept
def interpolate(x,y):
idx = np.argsort(x)
line = linregress(x[idx], y[idx])
return line
def interception(line1, line2):
x = symbols('x')
x = solve(x*line1.slope+line1.intercept-(x*line2.slope+line2.intercept))[0]
return (x,x*line1[0]+line1[1])
idx_x = np.argsort(contours[0])
idx_y = np.argsort(contours[1])
left = [contours[0][idx_x[:30]], contours[1][idx_x[:30]]]
right = contours[0][idx_x[-10:]], contours[1][idx_x[-10:]]
top = contours[0][idx_y[:10]], contours[1][idx_y[:10]]
bottom = contours[0][idx_y[-30:]], contours[1][idx_y[-30:]]
contour_functions = [interpolate_function(*left), interpolate_function(*right),interpolate_function(*top), interpolate_function(*bottom)]
contour_function_eqs = [[interpolate(*left), interpolate(*right)],
[interpolate(*top), interpolate(*bottom)]]
for f in contour_functions:
t = np.linspace(0, img.shape[1], 10**4)
t = t[(0 < f(t)) & (f(t) < img.shape[0])]
plt.plot(t,f(t))
itersections = np.array([interception(line1, line2)
for line1, line2 in itertools.product(contour_function_eqs[0], contour_function_eqs[1])])
plt.scatter(itersections[:,0], itersections[:,1])
plt.imshow(img, cmap='gray')
And I get
Or if you prefer to follow the bottom left part you just reduce the points in the bottom by replacing
bottom = contours[0][idx_y[-30:]], contours[1][idx_y[-30:]]
with
bottom = contours[0][idx_y[-10:]], contours[1][idx_y[-10:]]
and you get
Related
I am wondering if there is a way to inscribe an ellipse in almost rectangular contour? I am using openCV findcontours() method in my image and my aim is to fit biggest possible ellipses inside every object in the following image:
Code I am using:
import cv2
import numpy as np
def find_contours(preprocessed, min_perimeter):
contours, hierarchy = cv2.findContours(preprocessed,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) #find contours
contours_new = [] #new empty list of contours
for cnts in contours: #looping trough contours
perimeter = cv2.arcLength(cnts,True) #arc length
if perimeter > min_perimeter: #leave only significant contours
contours_new.append(cnts)
return contours_new
min_perimeter = 7000
img_num = 6
results_path = 'results/'
image_path = 'images/'
mask = image_path+str(img_num)+'_predicted_mask.bmp'
empty = np.empty([9452, 6144], dtype=np.uint16)
empty = cv2.cvtColor(empty, cv2.COLOR_GRAY2BGR)
gray = cv2.imread(mask)
ret,thresholded_mask = cv2.threshold(gray,150,255,0)
gray_mask = cv2.cvtColor(thresholded_mask, cv2.COLOR_BGR2GRAY)
contours_new = find_contours(gray_mask, min_perimeter)
cv2.drawContours(empty, contours_new, -1, (255,255,255), 3)
for cnt in contours_new:
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(empty, ellipse, (0, 0, 255), 3)
cv2.imwrite('contours.bmp',empty)
Result (as you can see elipses are not inscribed):
I have a code that detects sunspots but I want to count the number of sunspot groups instead of the individual spots like the current output I have here ("actual").
Here is my code. How do I get my output to group the sunspots and look like this ("desired") instead?
import os
import cv2 # opencv library
import numpy as np
import matplotlib.pyplot as plt
"""Make the pwd implementation"""
cwd = os.getcwd()
file = "/sunspot1.jpg"
path = cwd + file
image = cv2.imread(path,0)
image_1 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
#plt.show()
#plot the image in graycolor
#gray = cv2.cvtColor(image,cv2.COLOR_BGR2HSV)
#plt.imshow(gray)
#plt.show()
# perform image thresholding
ret, thresh = cv2.threshold(image, 90, 255, cv2.THRESH_BINARY)
#plt.imshow(thresh, cmap = 'gray')
#plt.show()
#circle = cv2.circle(thresh, (249,249),(238),(0, 255, 0),1)
# plt.imshow(circle)
# plt.show()
# find taches contours
contours, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
valid_cntrs = []
for i,cntr in enumerate(contours):
x,y,w,h = cv2.boundingRect(cntr)
#print("x = ",x,"y = ",y,"w = ",w,"h = ",h)
if ((x-249)**2 + (y-249)**2)<= 238**2:
valid_cntrs.append(cntr)
"""implement image size detection for the contour LINE 36"""
#count the taches number
taches= len(valid_cntrs);
#sunspot= 1*(10*groups+taches);
# count the number of dicovered sunspots
print("The number of taches is: ",taches)
if taches == 0:
plt.imshow(image_1)
plt.show()
else:
contour_sizes = [(cv2.contourArea(contour), contour) for contour in valid_cntrs]
for i in range(len(valid_cntrs)):
x,y,w,h = cv2.boundingRect(contour_sizes[i][1])
prevtaches = cv2.rectangle(image_1,(x,y),(x+w,y+h),(0,255,0),1)
plt.imshow(prevtaches)
plt.show()
Actual:
Desired:
What you can do is after thresholding (and before detecting the individual contours), you can perform morphological operations like dilation, to make the white area more broader, such that the nearby ones get connected and make one big contour. To do that, you can adjust you kernel size to fit your needs in the best way and can also play with the iterations argument.
You can refer this
After that, you can draw your contours.
I am working with images with objects in it. I used canny edge detection and contours to detect and draw the edges of the objects in it. Then I used both SIFT and SURF to detect key points in the object. Here is the sample code I've been working on.
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread(image)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 100,200)
image, contours, hierarchy = cv.findContours(edges, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
outimg = cv.drawContours(img, contours, -1, (0,255,0), 3)
sift = cv.xfeatures2d_SIFT.create()
kp, des = sift.detectAndCompute(outimg,None)
Is there any way to remove the key points that are on the edge? Answer with example will be really helpful.
Thanks.
You can use pointPolygonTest method to filter detected keypoints. Use detected contours as boundary polygon. You will also be able to define desired margin.
Simaple example (fot 4 point contour):
def inside_point(self, point, rect):
# point is a list (x, y)
# rect is a contour with shape [4, 2]
rect = rect.reshape([4, 1, 2]).astype(np.int64)
dist = cv2.pointPolygonTest(rect,(point[0], point[1]),True)
if dist>=0:
# print(dist)
return True
else:
return False
You can also draw contours on mask image and to check if the point is inside contours, just check the pixel value with the point coordinates, and if it not 0 then point is valid.
Seems everythig woeks fine:
I have no xfeatures2d, so used ORB features here.
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('image.jpg')
#img = cv.resize(img,(512,512))
img = cv.copyMakeBorder(img,20,20,20,20, 0)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_ , gray = cv.threshold(gray,20,255,cv.THRESH_TOZERO)
gray=cv.erode(gray,np.ones( (5,5), np.int8) )
edges = cv.Canny(gray, 100,200)
contours, hierarchy = cv.findContours(edges, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
orb = cv.ORB_create(nfeatures=10000)
kp, des = orb.detectAndCompute(gray,None)
outimg = cv.drawContours(img, contours, -1, (0,255,0), 3)
k = []
for cont in contours:
for i in kp:
(x, y) =i.pt
dist = cv.pointPolygonTest(cont, (x,y), True)
if dist>=0:
k.append(i)
for i in k:
pt=(int(i.pt[0]),int(i.pt[1]) )
cv.circle(outimg,pt,3, (255,255,255),-1)
cv.imwrite('result.jpg',outimg)
cv.imshow('outimg',outimg)
cv.waitKey()
I am still finding it difficult to remove key points from the given image. I tried to append the key points in a new list if it's not in the contour but shows an error when I use cv2.drawKeypoints function because the new list is not of type keypoint. This is the work I have done so far.
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread(image)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 100,200)
image, contours, hierarchy = cv.findContours(edges, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
outimg = cv.drawContours(img, contours, -1, (0,255,0), 3)
sift = cv.xfeatures2d_SIFT.create()
kp, des = sift.detectAndCompute(outimg,None)
k = cv.KeyPoint()
for i in kp:
(x, y) =i.pt
dist = cv.pointPolygonTest(contours[0], (x,y), True)
if dist>=0:
k1.append(k)
I am trying to count the number of drops in this image and the coverage percentage of the area covered by those drops.
I tried to convert this image into black and white, but the center color of those drops seems too similar to the background. So I only got something like the second picture.
Is there any way to solve this problem or any better ideas?
Thanks a lot.
You can fill the holes of your binary image using scipy.ndimage.binary_fill_holes. I also recommend using an automatic thresholding method such as Otsu's (avaible in scikit-image).
from skimage import io, filters
from scipy import ndimage
import matplotlib.pyplot as plt
im = io.imread('ba3g0.jpg', as_grey=True)
val = filters.threshold_otsu(im)
drops = ndimage.binary_fill_holes(im < val)
plt.imshow(drops, cmap='gray')
plt.show()
For the number of drops you can use another function of scikit-image
from skimage import measure
labels = measure.label(drops)
print(labels.max())
And for the coverage
print('coverage is %f' %(drops.mean()))
I used the following code to detect the number of contours in the image using OpenCV and python.
import cv2
import numpy as np
img = cv2.imread('ba3g0.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,1)
contours,h = cv2.findContours(thresh,1,2)
for cnt in contours:
cv2.drawContours(img,[cnt],0,(0,0,255),1)
For further removing the contours inside another contour, you need to iterate over the entire list and compare and remove the internal contours. After that, the size of "contours" will give you the count
The idea is to isolate the background form the inside of the drops that look like the background.
Therefore i found the connected components for the background and the inside drops took the largest connected component and change its value to be like the foreground value which left me with an image which he inside drops as a different value than the background.
Than i used this image to fill in the original threshold image.
In the end using the filled image i calculated the relevant values
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Read image
I = cv2.imread('drops.jpg',0);
# Threshold
IThresh = (I>=118).astype(np.uint8)*255
# Remove from the image the biggest conneced componnet
# Find the area of each connected component
connectedComponentProps = cv2.connectedComponentsWithStats(IThresh, 8, cv2.CV_32S)
IThreshOnlyInsideDrops = np.zeros_like(connectedComponentProps[1])
IThreshOnlyInsideDrops = connectedComponentProps[1]
stat = connectedComponentProps[2]
maxArea = 0
for label in range(connectedComponentProps[0]):
cc = stat[label,:]
if cc[cv2.CC_STAT_AREA] > maxArea:
maxArea = cc[cv2.CC_STAT_AREA]
maxIndex = label
# Convert the background value to the foreground value
for label in range(connectedComponentProps[0]):
cc = stat[label,:]
if cc[cv2.CC_STAT_AREA] == maxArea:
IThreshOnlyInsideDrops[IThreshOnlyInsideDrops==label] = 0
else:
IThreshOnlyInsideDrops[IThreshOnlyInsideDrops == label] = 255
# Fill in all the IThreshOnlyInsideDrops as 0 in original IThresh
IThreshFill = IThresh
IThreshFill[IThreshOnlyInsideDrops==255] = 0
IThreshFill = np.logical_not(IThreshFill/255).astype(np.uint8)*255
plt.imshow(IThreshFill)
# Get numberof drops and cover precntage
connectedComponentPropsFinal = cv2.connectedComponentsWithStats(IThreshFill, 8, cv2.CV_32S)
NumberOfDrops = connectedComponentPropsFinal[0]
CoverPresntage = float(np.count_nonzero(IThreshFill==0)/float(IThreshFill.size))
# Print
print "Number of drops = " + str(NumberOfDrops)
print "Cover precntage = " + str(CoverPresntage)
Solution
image = cv2.imread('image path.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# (thresh, blackAndWhiteImage) = cv2.threshold(gray, 127, 255,
cv2.THRESH_BINARY)
plt.imshow(gray, cmap='gray')
blur = cv2.GaussianBlur(gray, (11, 11), 0)
plt.imshow(blur, cmap='gray')
canny = cv2.Canny(blur, 30, 40, 3)
plt.imshow(canny, cmap='gray')
dilated = cv2.dilate(canny, (1, 1), iterations=0)
plt.imshow(dilated, cmap='gray')
(cnt, hierarchy) = cv2.findContours(
dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.drawContours(rgb, cnt, -1, (0, 255, 0), 2)
plt.imshow(rgb)
print("No of circles: ", len(cnt))
I need to get a matrix with the coordinates (x,y) of the contour of the following image with python.
I try it with opencv canny detector and find contours but I get a lot of contours and I don't know how to get the one I want.
import numpy as np
from matplotlib import pyplot as plt
import cv2
#from skimage import measure, feature, io
#from skimage import img_as_ubyte
x1 = 330
xf = 690
y1 = 0
yf = 400
img = cv2.imread('test.tif')
img = img[y1:yf, x1:xf]
edge = cv2.Canny(img, 100, 200)
image, contours, hierarchy = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
I just need an array with the (x,y) coordinates of the contour. I think it is in the contours output of cv2.findContours() but I don't find the contour that I want…
I also tried with the matplotlib.pyplot.contour function :
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('test.tif', 0) # read image
img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)[1] # threshold image
img = cv2.medianBlur(img, 15) # remove noise
# skeletonize
size = np.size(img) # get number of pixels
skel = np.zeros(img.shape, np.uint8) # create an array of zeros with the same shape as the image and 256 gray levels
element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # create a structurant element (cross)
done = False
while(not done):
eroded = cv2.erode(img, element)
temp = cv2.dilate(eroded, element)
temp = cv2.subtract(img, temp)
skel = cv2.bitwise_or(skel, temp)
img = eroded.copy()
zeros = size - cv2.countNonZero(img)
if zeros == size:
done = True
cs = plt.contour(skel, 1)
p = cs.collections[0].get_paths()[0]
v = p.vertices
x = v[:, 0]
y = v[:, 1]
But I just have closed contours and not the open contour which is going from the left to the right of the image.
Thanks a lot for your answers.
You almost found the answer to your question. First of all, there is a difference between edge detection and contour detection. Fundamentally, edge detection results in what you call (improperly) "open contour" (i.e. edge) and contour detection results in what you call "closed contour" (i.e. contour).
The Canny edge detection is a popular edge detection algorithm. Since you want to detect the edge in the form of an array with the (x,y) coordinates going from the left to the right of the image, the Canny edge detection is a good idea.
The answer is edge which is not in the wanted format.
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread('test.tif')
edge = cv2.Canny(img, 100, 200)
ans = []
for y in range(0, edge.shape[0]):
for x in range(0, edge.shape[1]):
if edge[y, x] != 0:
ans = ans + [[x, y]]
ans = np.array(ans)
print(ans.shape)
print(ans[0:10, :])
The array ans (shape equal to (n, 2)) stores the (x, y)-coordinate of the n pixels which compose the detected edge. This is the result you are looking for.
Here is an image where I have plotted in white these n pixels:
I hope this will help you.