Red dot coordinates detection - python

I'm trying to follow movement of a part using red dots. I tried with white dots and thresholding before, but there is too much reflection from the smartphone I'm using. The plan is to recognize a dot as a contour, find the center and fill the array with the coordinates of all contour centers for further calculation.
The code is posted bellow, it recognizes the correct number of dots, but I get the division by zero error. Does anyone know what I'm doing wrong?
Image:https://imgur.com/a/GLXGCPP
import cv2
import numpy as np
from matplotlib import pyplot as plt
import imutils
#load image
img = cv2.imread('dot4_red.jpg')
#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)
#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
#color definition
red_lower = np.array([0,0,240])
red_upper = np.array([10,10,255])
#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)
#copy image for, .findContours distorts the source image
mask_copy = mask.copy()
#find contours
cnts = cv2.findContours(mask_copy,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
#extract contours from the list??
cnts = imutils.grab_contours(cnts)
#count number of conoturs of specific size
s1 = 500
s2 = 10000
xcnts = []
for cnt in cnts:
if s1<cv2.contourArea(cnt)<s2:
xcnts.append(cnt)
n = len(xcnts)
#pre-allocate array for extraction of centers of contours
s = (n,2)
array = np.zeros(s)
#fill array of center coordinates
for i in range(0,n):
cnt = cnts[i]
moment = cv2.moments(cnt)
c_x = int(moment["m10"]/moment["m00"])
c_y = int(moment["m01"]/moment["m00"])
array[i,:] = [c_x, c_y]
#display image
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', mask)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
#print results
print ('number of dots, should be 4:',n)
print ('array of dot center coordinates:',array)

The problem was the wrong color range. Because of this, there were holes in the mask of the circles. Due to division by zero. M00. You can choose the correct color range or pre-fill the holes in the mask. Or use this code:
import cv2
import numpy as np
#load image
img = cv2.imread('ZayrMep.jpg')
#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)
#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
#color definition
red_lower = np.array([160,210,230])
red_upper = np.array([180,255,255])
#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)
connectivity = 4
# Perform the operation
output = cv2.connectedComponentsWithStats(mask, connectivity, cv2.CV_32S)
# Get the results
num_labels = output[0]-1
centroids = output[3][1:]
#print results
print ('number of dots, should be 4:',num_labels )
print ('array of dot center coordinates:',centroids)

moments00 (area) can be 0 for some shapes according to cv documentation. This is probably what is happening here:
Note Since the contour moments are computed using Green formula, you
may get seemingly odd results for contours with self-intersections,
e.g. a zero area (m00) for butterfly-shaped contours.
From: https://docs.opencv.org/3.4/d8/d23/classcv_1_1Moments.html#a8b1b4917d1123abc3a3c16b007a7319b
You need to ensure the area (m00) is not 0 before using it for division.

Related

OpenCV Contour Detection in color

I'm trying to detect the optic disc in an image of the back of the eye using OpenCV and findContour, then fitEllipse, but my program isn't detecting the optic disc at all. How do I fix this? Code and images are below
import cv2
import numpy as np
from sklearn.linear_model import LinearRegression
import math
from decimal import Decimal
def find_elongation(image):
img = cv2.imread(image,0)
ret,thresh = cv2.threshold(img,127,255,0)
contour,hierarchy = cv2.findContours(thresh, 1, 2)
contours = []
for i in range(len(contour)):
if len(contour[i])>=5:
contours.append(contour[i])
cnt = contours[0]
k = cv2.isContourConvex(cnt)
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(img,ellipse,(0,255,0),2)
(x,y),(ma,Ma),angle = cv2.fitEllipse(cnt)
return Ma/ma
print(find_elongation('eye.png'))
print(find_elongation('eye2.png'))
print(find_elongation('eye3.png'))
Image (one of them):
I'm trying to get the brightly colored circle in the middle:
Thanks for the help!
I have developed a piece of code to implement what you have asked. It mainly uses de Value channel of the HSV color space followed by some morphological operations.
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Read the image
img = cv2.imread('so.png')
# Transform the image to HSV color-space and keep only the value channel
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
# Threshold the iamge with the 95% of the brightest possible pixels
maxVal = np.max(v)
per = 0.95
_, th = cv2.threshold(v, maxVal*per, 255, cv2.THRESH_BINARY)
# Erode the image and find the connected components
th = cv2.erode(th, np.ones((2,2), np.uint8))
n, conComp, stats, centroids = cv2.connectedComponentsWithStats(th)
# Obtain the sizes of the connectedComponents skipping the background
sizes = stats[1:,-1]
# Obtain the number of the connectedComponent with biggest size
nComp = np.argmax(sizes) + 1
# Isolate the connectedComponent and apply closing
out = np.zeros((img.shape[0], img.shape[1]), np.uint8)
out[conComp==nComp] = 1
out = cv2.morphologyEx(out, cv2.MORPH_CLOSE, np.ones((10,10)))
# Apply gradient to mask to obtain the border of the ellipse
out = cv2.morphologyEx(out, cv2.MORPH_GRADIENT, np.ones((2,2)))
# Join the border of the ellipse with the image to display it
img[out==1] = (0,0,0)
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.show()
I attach the output I have obtained with the picture you posted:

Area of a closed contour on a plot using python openCV

I am attempting to find the area inside an arbitrarily-shaped closed curve plotted in python (example image below). So far, I have tried to use both the alphashape and polygon methods to acheive this, but both have failed. I am now attempting to use OpenCV and the floodfill method to count the number of pixels inside the curve and then I will later convert that to an area given the area that a single pixel encloses on the plot.
Example image:
testplot.jpg
In order to do this, I am doing the following, which I adapted from another post about OpenCV.
import cv2
import numpy as np
# Input image
img = cv2.imread('testplot.jpg', cv2.IMREAD_GRAYSCALE)
# Dilate to better detect contours
temp = cv2.dilate(temp, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
# Find largest contour
cnts, _ = cv2.findContours(255-temp, cv2.RETR_TREE , cv2.CHAIN_APPROX_NONE) #255-img and cv2.RETR_TREE is to account for how cv2 expects the background to be black, not white, so I convert the background to black.
largestCnt = [] #I expect this to yield the blue contour
for cnt in cnts:
if (len(cnt) > len(largestCnt)):
largestCnt = cnt
# Determine center of area of largest contour
M = cv2.moments(largestCnt)
x = int(M["m10"] / M["m00"])
y = int(M["m01"] / M["m00"])
# Initial mask for flood filling, should cover entire figure
width, height = temp.shape
mask = img2 = np.ones((width + 2, height + 2), np.uint8) * 255
mask[1:width, 1:height] = 0
# Generate intermediate image, draw largest contour onto it, flood fill this contour
temp = np.zeros(temp.shape, np.uint8)
temp = cv2.drawContours(temp, largestCnt, -1, 255, cv2.FILLED)
_, temp, mask, _ = cv2.floodFill(temp, mask, (x, y), 255)
temp = cv2.morphologyEx(temp, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
area = cv2.countNonZero(temp) #Number of pixels encircled by blue line
I expect from this to get to a place where I have the same image as above, but with the center of the contour filled in white and the background and original blue contour in black. I end up with this:
result.jpg
While this at first glance appears to have accurately turned the area inside the contour white, the white area is actually larger than the area inside the contour and so the result I get is overestimating the number of pixels inside it.
Any input on this would be greatly appreciated. I am fairly new to OpenCV so I may have misunderstood something.
EDIT:
Thanks to a comment below, I made some edits and this is now my code, with edits noted:
import cv2
import numpy as np
# EDITED INPUT IMAGE: Input image
img = cv2.imread('testplot2.jpg', cv2.IMREAD_GRAYSCALE)
# EDIT: threshold
_, temp = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY_INV)
# EDIT, REMOVED: Dilate to better detect contours
# Find largest contour
cnts, _ = cv2.findContours(temp, cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_NONE)
largestCnt = [] #I expect this to yield the blue contour
for cnt in cnts:
if (len(cnt) > len(largestCnt)):
largestCnt = cnt
# Determine center of area of largest contour
M = cv2.moments(largestCnt)
x = int(M["m10"] / M["m00"])
y = int(M["m01"] / M["m00"])
# Initial mask for flood filling, should cover entire figure
width, height = temp.shape
mask = img2 = np.ones((width + 2, height + 2), np.uint8) * 255
mask[1:width, 1:height] = 0
# Generate intermediate image, draw largest contour, flood filled
temp = np.zeros(temp.shape, np.uint8)
temp = cv2.drawContours(temp, largestCnt, -1, 255, cv2.FILLED)
_, temp, mask, _ = cv2.floodFill(temp, mask, (x, y), 255)
temp = cv2.morphologyEx(temp, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
area = cv2.countNonZero(temp) #Number of pixels encircled by blue line
I input a different image with the axes and the frame that python adds by default removed for ease. I get what I expect at the second step, so this image. However, in the enter image description here both the original contour and the area it encircles appear to have been made white, whereas I want the original contour to be black and only the area it encircles to be white. How might I acheive this?
The problem is your opening operation at the end. This morphological operation includes a dilation at the end that expands the white contour, increasing its area. Let’s try a different approach where no morphology is involved. These are the steps:
Convert your image to grayscale
Apply Otsu’s thresholding to get a binary image, let’s work with black and white pixels only.
Apply a first flood-fill operation at image location (0,0) to get rid of the outer white space.
Filter small blobs using an area filter
Find the “Curve Canvas” (The white space that encloses the curve) and locate and store its starting point at (targetX, targetY)
Apply a second flood-fill al location (targetX, targetY)
Get the area of the isolated blob with cv2.countNonZero
Let’s take a look at the code:
import cv2
import numpy as np
# Set image path
path = "C:/opencvImages/"
fileName = "cLIjM.jpg"
# Read Input image
inputImage = cv2.imread(path+fileName)
inputCopy = inputImage.copy()
# Convert BGR to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Threshold via Otsu + bias adjustment:
threshValue, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
This is the binary image you get:
Now, let’s flood-fill at the corner located at (0,0) with a black color to get rid of the first white space. This step is very straightforward:
# Flood-fill background, seed at (0,0) and use black color:
cv2.floodFill(binaryImage, None, (0, 0), 0)
This is the result, note how the first big white area is gone:
Let’s get rid of the small blobs applying an area filter. Everything below an area of 100 is gonna be deleted:
# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(binaryImage, connectivity=4)
# Set the minimum pixels for the area filter:
minArea = 100
# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
This is the result of the filter:
Now, what remains is the second white area, I need to locate its starting point because I want to apply a second flood-fill operation at this location. I’ll traverse the image to find the first white pixel. Like this:
# Get Image dimensions:
height, width = filteredImage.shape
# Store the flood-fill point here:
targetX = -1
targetY = -1
for i in range(0, width):
for j in range(0, height):
# Get current binary pixel:
currentPixel = filteredImage[j, i]
# Check if it is the first white pixel:
if targetX == -1 and targetY == -1 and currentPixel == 255:
targetX = i
targetY = j
print("Flooding in X = "+str(targetX)+" Y: "+str(targetY))
There’s probably a more elegant, Python-oriented way of doing this, but I’m still learning the language. Feel free to improve the script (and share it here). The loop, however, gets me the location of the first white pixel, so I can now apply a second flood-fill at this exact location:
# Flood-fill background, seed at (targetX, targetY) and use black color:
cv2.floodFill(filteredImage, None, (targetX, targetY), 0)
You end up with this:
As you see, just count the number of non-zero pixels:
# Get the area of the target curve:
area = cv2.countNonZero(filteredImage)
print("Curve Area is: "+str(area))
The result is:
Curve Area is: 1510
Here is another approach using Python/OpenCV.
Read Input
convert to HSV colorspace
Threshold on color range of blue
Find the largest contour
Get its area and print that
draw the contour as a white filled contour on black background
Save the results
Input:
import cv2
import numpy as np
# read image as grayscale
img = cv2.imread('closed_curve.jpg')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#select blu color range in hsv
lower = (24,128,115)
upper = (164,255,255)
# threshold on blue in hsv
thresh = cv2.inRange(hsv, lower, upper)
# get largest contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
area = cv2.contourArea(c)
print("Area =",area)
# draw filled contour on black background
result = np.zeros_like(thresh)
cv2.drawContours(result, [c], -1, 255, cv2.FILLED)
# save result
cv2.imwrite("closed_curve_thresh.jpg", thresh)
cv2.imwrite("closed_curve_result.jpg", result)
# view result
cv2.imshow("threshold", thresh)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Result Filled Contour On Black Background:
Area Result:
Area = 2347.0

Separating cell boundaries in the following image and also do nuclear counting using python

I tried to use watershed with Otsu for thresholding but its only picking up nuclear boundaries,I want to segment cells boundaries
I used Otsu followed by noise removal with opening ,identifying sure background, applying distance transform, using it to define sure foreground, defining unknown, creating markers
import cv2
import numpy as np
img = cv2.imread("images/bio_watershed/Osteosarcoma_01.tif")
cells=img[:,:,0]
#Threshold image to binary using OTSU. ALl thresholded pixels will be set
#to 255
ret1, thresh = cv2.threshold(cells, 0, 255,
cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Morphological operations to remove small noise - opening
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,
iterations = 2)
# finding sure background
sure_bg = cv2.dilate(opening,kernel,iterations=10)
#applying dustance transform
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret2, sure_fg
=cv2.threshold(dist_transform,0.5*dist_transform.max(),255,0)
# Unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
#Now we create a marker and label the regions inside.
ret3, markers = cv2.connectedComponents(sure_fg)
#add 10 to all labels so that sure background is not 0, but 10
markers = markers+10
# Now, mark the region of unknown with zero
markers[unknown==255] = 0
#applying watershed
markers = cv2.watershed(img,markers)
# color boundaries in yellow.
img[markers == -1] = [0,255,255]
img2 = color.label2rgb(markers, bg_label=0)
cv2.imshow('Overlay on original image', img)
cv2.imshow('Colored Cells', img2)
cv2.waitKey(0)
by running this code I get following nuclear boundary segmentation but I want to get the cell boundaries
Thanks so much for your help
I'm not sure if you are still looking for an answer but I have edited your code to segment the cell boundaries. You need to select the image slice that shows the actin filaments, which is in index 1.
I have also used an edge detector followed by contour drawing to outline the cell boundaries.
Here is my code:
import cv2
import numpy as np
import skimage.io as skio
img = cv2.imread("cells.png")
img_read = img[:,:,1] #Shows the actin filaments
#Denoising
img_denoise = cv2.fastNlMeansDenoising(img_read, 45, 7, 35)
#Canny edge detector
img_canny = cv2.Canny(img_denoise, 10, 200)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(2,2))
img_dilate = cv2.dilate(img_canny, kernel, iterations = 2)
#Contour finding
contours, _ = cv2.findContours(img_dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
img_med = cv2.cvtColor(img_denoise, cv2.COLOR_GRAY2RGB)
img_final = cv2.drawContours(img_med, contours, -1, (0,128,128), 2, 4)
skio.imsave("img_output.tif", img_final)
cv2.imshow('Overlay on original image', img_final)
cv2.waitKey(0)
The example you have is good for color-based segmentation as it is (better resolution will improve the result though).
Contrast is good enough (and can be improved), so did a very quick test without using OpenCV (so no code to share).
Nuclear boundary:
Cell boundary:
Combined:
Or as a separate masks:
So I'd say it is all about delta E and proper segmentation.

Calculating the area covered by the objects of irregular shapes in an image

I am working on a project where I'm estimating the wheat yield based on the wheat spikes in the image. After detecting spikes using Faster-RCNN and color based segmentation, the following is the resultant image where there are only spikes in the image.
Now my goal is to estimate the yield produced by the spikes in the image using python. For this, we may have to calculate the area covered by the objects of polygon shapes or we may have to work around the pixel values to calculate the area. But I don't know how we can do this. Please Let me know If anyone has the solution. Thanks
The area in pixels of the image that are not black can be found from creating a binary mask. The area in pixels is equal to the total number of white pixels in the mask. One way to get that is to compute the fraction of white pixels in the image. The number of white pixels will then be the fraction * width * height of the image. The fraction is just the average of the image divided by the maximum possible gray level (255). So
area in pixels of white pixels = (average/255)widthheight
Thus, get the fractional average (average/255) of the binary mask image (by thresholding at 0). The result for the average will be one single value. Then multiply that by the Width of the image and then by the Height of the image. That result will be equal to the total number of white pixels in the mask and thus the total pixels that are not black (i.e. are colored) in your image. The number of white pixels is the pixel area of the non-black pixels in your image.
Input:
import cv2
import numpy as np
img = cv2.imread('img.jpg')
height = img.shape[0]
width = img.shape[1]
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
cv2.imshow("Mask", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
ave = cv2.mean(thresh)[0]/255
print(ave)
0.310184375
area = ave*height*width
print(area)
198518.0
Note that this is the non-black pixel area. Some of your rectangles have black inside them. So this is not the area of the rectangle. You would have ensure that your image had no black pixels before isolating the rectangles to get the area of the rectangles.
ADDITION
A simpler approach, suggested by Mark Setchell, is to simply count the number of nonzero pixels in the thresholded image. It computes the same number as above.
import cv2
import numpy as np
img = cv2.imread('img.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
cv2.imshow("Mask", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
area2 = cv2.countNonZero(thresh)
print(area2)
198518
ADDITION 2
If you know the ground area or dimensions in meters (0.8 m on aside as per your comment) corresponding to the area covered by the image, then the ground area corresponding to the count of non-zero pixels will be:
area on ground for nonzero pixels = count * 0.8 * 0.8 / (width * height)
where width and height are the pixel dimensions of the image.
import cv2
import numpy as np
img = cv2.imread('img.jpg')
height = img.shape[0]
width = img.shape[1]
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
cv2.imshow("Mask", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
count = cv2.countNonZero(thresh)
area = count*0.8*0.8/(width*height)
print(area)
0.19851800000000003
So the result is 0.198518 square meters
Hope this helps 😉
Loads the image in grayscale (0[Black] - 255[White])
Extract pixels above certain level. I have for more than 60 using cv2.threshold
Fill in small white blobs using smallest kernel (3,3) using cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
Extract the contours from threshold image
Iterate through each contour in reverse order(right top to bottom right corner)
As you iterate through the each contour calculate its properties
Code
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('RIUXF.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
# Area occupied by black region
black_area = np.true_divide(hist[0],np.prod(img.shape))[0]*100
# extract no black parts
thresh = cv2.threshold(img,60,255,cv2.THRESH_BINARY)[1]
kernel = np.ones((3,3),np.uint8)
# fill in the small white spots
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
# extract the contours
contours = cv2.findContours(opening, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
blank_image = np.zeros((img.shape),np.uint8)
image_area = np.prod(img.shape)
# iterate through the contours detected from right top corner
for i,c in enumerate(contours[::-1]):
# turn blank_image black
blank_image *= 0
# draw filled contour
cv2.drawContours(blank_image, [c], 0, (255), thickness=cv2.FILLED)
contour_area = cv2.contourArea(c)
# percentage of area contour
contour_area_pc = np.true_divide(int(contour_area),image_area)*100 if int(contour_area) > 1 else 0
text = ' '.join(['Contour:',str(i),'Area:',str(round(contour_area,2)),'Percentage Area:',str(round(contour_area_pc,2))])
cv2.putText(blank_image,text,(10,60), cv2.FONT_HERSHEY_SIMPLEX, 1,(255),2,cv2.LINE_AA)
plt.imshow(blank_image, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([]) # to hide tick values on X and Y axis
plt.show()
Sample output
PS: I doubt if area cv2 calculates is correct 🤔

Find positions of objects on the noisy image

I have a bunch of images and I need to determine positions of crosses for further transformation of the image and the alignment procedure. The problem is that images are quite noisy and I'm new to all these things of computer vision. Generally, I'm trying to solve the task via opencv and python. I have tried several approaches described in the tutorial of opencv library but I did not get the appropriate result.
Consider: I need to determine the exact positions of centers of the crosses (which I can do with about pixel accuracy by hand). The best result I have obtained via findContours function. I have adopted code from the tutorial and I got:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
import random
random.seed(42)
img = cv.imread("sample.png")
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_gray = cv.blur(img_gray, (3,3))
threshold = 150
dst = cv.Canny(img_gray, threshold, threshold * 2)
_, contours, hierarchy = cv.findContours(dst, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
result = np.zeros((dst.shape[0], dst.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (random.randint(0, 256), random.randint(0, 256), random.randint(0, 256))
cv.drawContours(result, contours, i, color, 2, cv.LINE_8, hierarchy, 0)
cv.imwrite("result.png", result)
fig, ax = plt.subplots()
fig.set_size_inches(10, 10);
ax.imshow(result, interpolation='none', cmap='gray');
which results in: Now I'm confused with the following steps. How can I define which contour is cross and which is not? What to do with crosses consisting of multiple contours?
Any help is really appreated!
A simple way on which you can determine what is a cross and what isn't is by making a bouning box x,y,w,h = cv2.boundingRect(cnt) over each contour and selecting those that have h (height) and w (weight) bigger than treshold you provide. If you observe the noises on the image arent as big as the crosses.
I have also made an example on how I would try to tackle such a task. You can try denoising the image by performing histogram equalization followed by thresholding with OTSU threshold and performing an opening to the threshold (erosion followed by dilation). Then you can filter out crosses with height and weight of the contour and then calculate the middle points of every bounding box of the contours that is in the mentioned criteria. Hope it helps a bit. Cheers!
Example:
import cv2
import numpy as np
img = cv2.imread('croses.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
equ = cv2.equalizeHist(gray)
_, thresh = cv2.threshold(equ,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
if w > 40 and h > 40:
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.circle(img,(int(x+(w/2)), int(y+(h/2))),3,(0,0,255),-1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Categories