Segmentation of small particles using Watershed and Distance Transform - python

I’m trying to separate the dust particles in a picture. To do so, I was thinking of using the watershed algorithm (it’s the first time for me using it).
The picture I’m working with is the following:
Following the tutorial on the OpenCV documentation named “Image Segmentation with Distance Transform and Watershed Algorithm” (link), I wrote the following python code:
img = cv2.imread(img_path)
# sharpen the image to acute the edges of the foreground objects
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
imgLaplacian = cv2.filter2D(img, cv2.CV_32F, kernel)
sharp = np.float32(img)
imgResult = sharp - imgLaplacian
# Binarization
gray = cv2.cvtColor(imgResult, cv2.COLOR_BGR2GRAY)
_, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
dist = cv2.distanceTransform(img_bin, cv2.DIST_L2, 5)
# Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
cv2.normalize(dist, dist, 0, 1.0, cv2.NORM_MINMAX)
# Threshold to obtain the peaks
# This will be the markers for the foreground objects
_, dist = cv2.threshold(dist, min_norm, max_norm, cv2.THRESH_BINARY)
# Dilate a bit the dist image
dist = cv2.dilate(dist, np.ones((3,3), dtype=np.uint8))
# Create the CV_8U version of the distance image
# It is needed for findContours()
dist_8u = dist.astype('uint8')
# Find total markers
contours, _ = cv2.findContours(dist_8u, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create the marker image for the watershed algorithm
markers = np.zeros(dist.shape, dtype=np.int32)
# Draw the foreground markers
for i in range(len(contours)):
cv2.drawContours(markers, contours, i, (i+1), -1)
cv2.watershed(imgResult, markers)
img[markers == -1] = [0,0,255] # Draw the contours
ImgResult is the visualized as following:
As you can see in the picture, many of the bigger particles are still considered as just one particle, while some of the smaller particles are not even considered.
I’m sure there’s something I’m missing, but I can’t figure out what.
Let me know if something is not clear or if the pictures are not understandable. (I used another function to make the contours more visible, the code posted generates a red border as large as a pixel).
P.S.
I also tried to follow “Image Segmentation with Watershed Algorithm” (link) tutorial from OpenCV docs. The results are a little bit better for the bigger particles (they get separated), but it doesn’t identify many particles (I assume due to the morphological operations). The picture follows.
P.P.S.
In both cases, the algorithm generates a weird border around the whole picture.
Desired result
The desired output is something similar to this:
Drawing function
To draw on the pictures I used:
for label in no.unique(markers):
if label == 0:
continue
mask = np.zeros(image.shape[:2], dtype="uint8")
mask[markers == label] = 255
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
c = max(cnts, key=cv2.contourArea)
cv2.drawContours(image, [c], -1, (36,255,12), 2)

Related

How to find the junction points or segments in a skeletonized image Python OpenCV?

I am trying to convert the result of a skeletonization into a set of line segments, where the vertices correspond to the junction points of the skeleton. The shape is not a closed polygon and it may be somewhat noisy (the segments are not as straight as they should be).
Here is an example input image:
And here are the points I want to retrieve:
I have tried using the harris corner detector, but it has trouble in some areas even after trying to tweak the algorithm's parameters (such as the angled section on the bottom of the image). Here are the results:
Do you know of any method capable of doing this? I am using python with mostly OpenCV and Numpy but I am not bound to any library. Thanks in advance.
Edit: I've gotten some good responses regarding the junction points, I am really grateful. I would also appreciate any solutions regarding extracting line segments from the junction points. I think #nathancy's answer could be used to extract line segments by subtracting the masks with the intersection mask, but I am not sure.
My approach is based on my previous answer here. It involves convolving the image with a special kernel. This convolution identifies the end-points of the lines, as well as the intersections. This will result in a points mask containing the pixel that matches the points you are looking for. After that, apply a little bit of morphology to join possible duplicated points. The method is sensible to the corners produced by the skeleton.
This is the code:
import cv2
import numpy as np
# image path
path = "D://opencvImages//"
fileName = "Repn3.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
inputImageCopy = inputImage.copy()
# Convert to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Compute the skeleton:
skeleton = cv2.ximgproc.thinning(grayscaleImage, None, 1)
# Threshold the image so that white pixels get a value of 10 and
# black pixels a value of 0:
_, binaryImage = cv2.threshold(skeleton, 128, 10, cv2.THRESH_BINARY)
# Set the convolution kernel:
h = np.array([[1, 1, 1],
[1, 10, 1],
[1, 1, 1]])
# Convolve the image with the kernel:
imgFiltered = cv2.filter2D(binaryImage, -1, h)
So far I convolved the skeleton image with my special kernel. You can inspect the image produced and search for the numerical values at the corners and intersections.
This is the output so far:
Next, identify a corner or an intersection. This bit is tricky, because the threshold value depends directly on the skeleton image, which sometimes doesn't produce good (close to straight) corners:
# Create list of thresholds:
thresh = [130, 110, 40]
# Prepare the final mask of points:
(height, width) = binaryImage.shape
pointsMask = np.zeros((height, width, 1), np.uint8)
# Perform convolution and create points mask:
for t in range(len(thresh)):
# Get current threshold:
currentThresh = thresh[t]
# Locate the threshold in the filtered image:
tempMat = np.where(imgFiltered == currentThresh, 255, 0)
# Convert and shape the image to a uint8 height x width x channels
# numpy array:
tempMat = tempMat.astype(np.uint8)
tempMat = tempMat.reshape(height,width,1)
# Accumulate mask:
pointsMask = cv2.bitwise_or(pointsMask, tempMat)
This is the binary mask:
Let's dilate to join close points:
# Set kernel (structuring element) size:
kernelSize = 3
# Set operation iterations:
opIterations = 4
# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform Dilate:
pointsMask = cv2.morphologyEx(pointsMask, cv2.MORPH_DILATE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
This is the output:
Now simple extract external contours. Get their bounding boxes and calculate their centroid:
# Look for the outer contours (no children):
contours, _ = cv2.findContours(pointsMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Store the points here:
pointsList = []
# Loop through the contours:
for i, c in enumerate(contours):
# Get the contours bounding rectangle:
boundRect = cv2.boundingRect(c)
# Get the centroid of the rectangle:
cx = int(boundRect[0] + 0.5 * boundRect[2])
cy = int(boundRect[1] + 0.5 * boundRect[3])
# Store centroid into list:
pointsList.append( (cx,cy) )
# Set centroid circle and text:
color = (0, 0, 255)
cv2.circle(inputImageCopy, (cx, cy), 3, color, -1)
font = cv2.FONT_HERSHEY_COMPLEX
cv2.putText(inputImageCopy, str(i), (cx, cy), font, 0.5, (0, 255, 0), 1)
# Show image:
cv2.imshow("Circles", inputImageCopy)
cv2.waitKey(0)
This is the result. Some corners are missed, you might one to improve the solution before computing the skeleton.
Here's a simple approach, the idea is:
Obtain binary image. Load image, convert to grayscale, Gaussian blur, then Otsu's threshold.
Obtain horizontal and vertical line masks. Create horizontal and vertical structuring elements with cv2.getStructuringElement then perform cv2.morphologyEx to isolate the lines.
Find joints. We cv2.bitwise_and the two masks together to get the joints. The idea is that the intersection points on the two masks are the joints.
Find centroid on joint mask. We find contours then calculate the centroid.
Find leftover endpoints. Endpoints do not correspond to an intersection so to find those, we can use the Shi-Tomasi Corner Detector
Horizontal and vertical line masks
Results (joints in green and endpoints in blue)
Code
import cv2
import numpy as np
# Load image, grayscale, Gaussian blur, Otsus threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Find horizonal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,1))
horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
# Find vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,5))
vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=1)
# Find joint intersections then the centroid of each joint
joints = cv2.bitwise_and(horizontal, vertical)
cnts = cv2.findContours(joints, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
# Find centroid and draw center point
x,y,w,h = cv2.boundingRect(c)
centroid, coord, area = cv2.minAreaRect(c)
cx, cy = int(centroid[0]), int(centroid[1])
cv2.circle(image, (cx, cy), 5, (36,255,12), -1)
# Find endpoints
corners = cv2.goodFeaturesToTrack(thresh, 5, 0.5, 10)
corners = np.int0(corners)
for corner in corners:
x, y = corner.ravel()
cv2.circle(image, (x, y), 5, (255,100,0), -1)
cv2.imshow('thresh', thresh)
cv2.imshow('joints', joints)
cv2.imshow('horizontal', horizontal)
cv2.imshow('vertical', vertical)
cv2.imshow('image', image)
cv2.waitKey()

Python/OpenCV — Intelligent Centroid Tracking in Bacterial Images?

I'm currently working on an algorithm to detect bacterial centroids in microscopy images.
This question is a continuation of: OpenCV/Python — Matching Centroid Points of Bacteria in Two Images: Python/OpenCV — Matching Centroid Points of Bacteria in Two Images
I am using a modified version of the program proposed by Rahul Kedia.
https://stackoverflow.com/a/63049277/13696853
Currently, the issues in segmentation I am working on are:
Low Contrast
Clustering
The images below are sampled a second apart. However, in the latter image, one of the bacteria does not get detected.
Bright-field Image #1
Bright-Field Image #2
Bright-Field Contour Image #1
Bright-Field Contour Image #2
Bright-Field Image #1 (Unsegmented)
Bright-Field Image #2 (Unsegmented)
I want to know, given that I can successfully determine bacterial centroids in an image, can I use the data to intelligently look for the same bacteria in the subsequent image?
I haven't been able to find anything substantial online; I believe SIFT/SURF would likely be ineffective as the bacteria have the same appearance. Moreover, I am looking for specific points in the images. You can view my program below. Insert a specific path as indicated if you'd like to run the program.
import cv2
import numpy as np
import os
kernel = np.array([[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0]], dtype=np.uint8)
def e_d(image, it):
image = cv2.erode(image, kernel, iterations=it)
image = cv2.dilate(image, kernel, iterations=it)
return image
path = r"[INSERT PATH]"
img_files = [file for file in os.listdir(path)]
def segment_index(index: int):
segment_file(img_files[index])
def segment_file(img_file: str):
img_path = path + "\\" + img_file
print(img_path)
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Applying adaptive mean thresholding
th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
# Removing small noise
th = e_d(th.copy(), 1)
# Finding contours with RETR_EXTERNAL flag and removing undesired contours and
# drawing them on a new image.
cnt, hie = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cntImg = th.copy()
for contour in cnt:
x, y, w, h = cv2.boundingRect(contour)
# Eliminating the contour if its width is more than half of image width
# (bacteria will not be that big).
if w > img.shape[1] / 2:
continue
cntImg = cv2.drawContours(cntImg, [cv2.convexHull(contour)], -1, 255, -1)
# Removing almost all the remaining noise.
# (Some big circular noise will remain along with bacteria contours)
cntImg = e_d(cntImg, 3)
# Finding new filtered contours again
cnt2, hie2 = cv2.findContours(cntImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Now eliminating circular type noise contours by comparing each contour's
# extent of overlap with its enclosing circle.
finalContours = [] # This will contain the final bacteria contours
for contour in cnt2:
# Finding minimum enclosing circle
(x, y), radius = cv2.minEnclosingCircle(contour)
center = (int(x), int(y))
radius = int(radius)
# creating a image with only this circle drawn on it(filled with white colour)
circleImg = np.zeros(img.shape, dtype=np.uint8)
circleImg = cv2.circle(circleImg, center, radius, 255, -1)
# creating a image with only the contour drawn on it(filled with white colour)
contourImg = np.zeros(img.shape, dtype=np.uint8)
contourImg = cv2.drawContours(contourImg, [contour], -1, 255, -1)
# White pixels not common in both contour and circle will remain white
# else will become black.
union_inter = cv2.bitwise_xor(circleImg, contourImg)
# Finding ratio of the extent of overlap of contour to its enclosing circle.
# Smaller the ratio, more circular the contour.
ratio = np.sum(union_inter == 255) / np.sum(circleImg == 255)
# Storing only non circular contours(bacteria)
if ratio > 0.55:
finalContours.append(contour)
finalContours = np.asarray(finalContours)
# Finding center of bacteria and showing it.
bacteriaImg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for bacteria in finalContours:
M = cv2.moments(bacteria)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
bacteriaImg = cv2.circle(bacteriaImg, (cx, cy), 5, (0, 0, 255), -1)
cv2.imshow("bacteriaImg", bacteriaImg)
cv2.waitKey(0)
# Segment Each Image
for i in range(len(img_files)):
segment_index(i)
Edit #1: Applying frmw42's approach, this image seems to get lost. I have tried adjusting a number of parameters but the image does not seem to show up.
Bright-Field Image #3
Bright-Field Image #4
Here is my Python/OpenCV code to extract your bacteria. I simply threshold, then get the contours and draw filled contours for those within a certain area range. I will let you do any further processing that you want. I simply viewed each step to make sure I have tuned the arguments appropriately before moving to the next step.
Input 1:
Input 2:
import cv2
import numpy as np
# read image
#img = cv2.imread("bacteria1.png")
img = cv2.imread("bacteria2.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = 255 - gray
# do adaptive threshold on inverted gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21, 5)
result = np.zeros_like(img)
contours = cv2.findContours(thresh , cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
area = cv2.contourArea(cntr)
if area > 600 and area < 1100:
cv2.drawContours(result, [cntr], 0, (255,255,255), -1)
# write results to disk
#cv2.imwrite("bacteria_filled_contours1.png", result)
cv2.imwrite("bacteria_filled_contours2.png", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("result", result)
cv2.waitKey(0)
Result 1:
Result 2:
Adjust as desired.
It would seem that adaptive threshold is not able to handle all your various images. I suspect nothing simple will. You may need to use AI with training. Nevertheless, this works for your images: 1, 2 and 4 in Python/OpenCV. I make no guarantee that it will work for any of your other images.
First I found a simple threshold that seems to work, but brings in other regions. So since all your bacteria have similar shapes and range of orientations, I fit and ellipse to your bacteria and get the orientation of the major axis and filter the contours with area and angle.
import cv2
import numpy as np
# read image
#img = cv2.imread("bacteria1.png")
#img = cv2.imread("bacteria2.png")
img = cv2.imread("bacteria4.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = 255 - gray
# median filter
#gray = cv2.medianBlur(gray, 1)
# do simple threshold on inverted gray image
thresh = cv2.threshold(gray, 170, 255, cv2.THRESH_BINARY)[1]
result = np.zeros_like(img)
contours = cv2.findContours(thresh , cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
area = cv2.contourArea(cntr)
if area > 600 and area < 1100:
ellipse = cv2.fitEllipse(cntr)
(xc,yc),(d1,d2),angle = ellipse
if angle > 90:
angle = angle - 90
else:
angle = angle + 90
print(angle,area)
if angle >= 150 and angle <= 250:
cv2.drawContours(result, [cntr], 0, (255,255,255), -1)
# write results to disk
#cv2.imwrite("bacteria_filled_contours1.png", result)
#cv2.imwrite("bacteria_filled_contours2.png", result)
cv2.imwrite("bacteria_filled_contours4.png", result)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("result", result)
cv2.waitKey(0)
Result for image 1:
Result for image 2:
Result for image 4:
You might explore noise reduction before thresholding. I had some success with using some of ImageMagick tools and there is a Python version called Python Wand that uses ImageMagick.

How to fill a circle contour at the edge of an image?

In the image above, I want to eventually be able to fill in the contours of the colored circles. Unfortunately, the yellow circle on the bottom is right at the edge of the image, so cv2.findContours() doesn't work:
_, green_contours, _ = cv2.findContours(green_seg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Does anyone know how I can somehow fill in the yellow circle contour even though it's at the edge of the image? Since it's at the edge of the image, the pixel value on the bottom edge of the image doesn't complete the circle, isn't valued at 255, if that makes sense.
I looked online and some people say it's possible to draw a big box around the image and then do the contours, but if I do that, then opencv only draws the big contour around the entire image and not the circle.
Any other thoughts would be greatly appreciated!
EDIT: The image I show above is just one case I'm considering. I'm trying to think how I can make this general enough such that for any contour that is at the edge of the border, I can still fill in the contour with cv2.drawContour().
If you have (more or less) convex polygons, you actually CAN use cv2.findContours. Having the contours, try to find the center of mass, e.g. using cv2.moments, and then use this as the seed point in cv2.floodFill.
Please see the following code snippet. I assume, you can identify your polygons by color. Also, instead of some advanced finding of the center of mass, I just used the center point of the bounding rectangle of each contour. Maybe, that's also sufficient for your use case!?
import cv2
import numpy as np
# Set up test image
colors = [(0, 255, 0), (0, 0, 255)]
input = np.zeros((400, 400, 3), np.uint8)
cv2.circle(input, (100, 100), 50, colors[0], 10)
cv2.circle(input, (150, 350), 75, colors[1], 10)
output = input.copy()
# Iterate all colors...
for i, c in enumerate(colors):
# Mask color
img = np.all(output == c, axis=2).astype(np.uint8) * 255
# Find contours with respect to OpenCV version
cnts = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Get bounding rectangles; derive seed points for flood filling; flood fill
rects = [cv2.boundingRect(c) for c in cnts]
seeds = [(np.int32(r[0] + r[2] / 2), np.int32(r[1] + r[3] / 2)) for r in rects]
[cv2.floodFill(output, mask=None, seedPoint=s, newVal=c) for s in seeds]
cv2.imshow('input', input)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
That's the input:
And, that's the output:
Hope that helps!
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.1
NumPy: 1.18.1
OpenCV: 4.1.2
----------------------------------------
A simple way, perhaps not so precise, is to compute the convex hull for each contour and draw the interior with a fill color:
import cv2
import numpy as np
import sys
# load input image from the cmd-line
img = cv2.imread('test_images/partial_contour.png')
if (img is None):
print('!!! Failed imread')
sys.exit(-1)
output_img = img.copy()
# isolate just the colored drawings
ret, thres_bgr = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY)
thres_bgr[np.where((thres_bgr == [255,255,255]).all(axis=2))] = [0,0,0] # replace white pixels for black
# convert from 3-channels (BGR) to a single channel (gray)
gray_img = cv2.cvtColor(thres_bgr, cv2.COLOR_BGR2GRAY)
# this loop processes all the contours found in the image
contours, hierarchy = cv2.findContours(gray_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
# compute a convex hull
hull = cv2.convexHull(cnt)
# fill the inside with red
cv2.fillPoly(output_img, pts=[hull], color=(0, 0, 255))
cv2.imshow('output_img', output_img)
cv2.imwrite('fill_partial_cnt_output.png', output_img)
cv2.waitKey(0)

Counting the number of circular rods in an image

I want to count the number of metal rods in an image using image processing. The images are all similar to this one:
I thought of using Hough Circle Transform, but the rods aren't precisely circular and also there are imperfections on the faces. Another idea was to use watershed algorithm. I took following steps:
Grayscale conversion
a CLAHE enhancement
Pyramid Mean Shift Filter to remove the texture and irregularities
Applied a gaussian blur.
Otsu's Thresholding
Result:
upper image is after 4, lower after 5
Clearly, I cannot use this image for Watershed.
I then tried using a simple thresholding, which gives better results, but still not accurate enough. Also I want the counting algorithm to be independent of the image used, and that means simple thresholding won't do.
I searched the net for algorithms to detect circular objects, but they all seem to be dependent on the segmentation or edge detection. I don't understand how to proceed from this point. Am I missing something basic here?
Edit: The basic preprocessing code:
img = cv2.imread('testc.jpg')
img = cv2.pyrMeanShiftFiltering(img, 21, 51)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(7,7))
cl1 = clahe.apply(img)
cl1 = cv2.GaussianBlur(cl1, (5,5), 1)
ret3,thresh = cv2.threshold(cl1,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Watershed:
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=35,
labels=thresh)
# perform a connected component analysis on the local peaks,
# using 8-connectivity, then appy the Watershed algorithm
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
# loop over the unique labels returned by the Watershed
# algorithm
for label in np.unique(labels):
# if the label is zero, we are examining the 'background'
# so simply ignore it
if label == 0:
continue
# otherwise, allocate memory for the label region and draw
# it on the mask
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# detect contours in the mask and grab the largest one
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
c = max(cnts, key=cv2.contourArea)
# draw a circle enclosing the object
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(img, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(img, "#{}".format(label), (int(x) - 10, int(y)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
Code for Watershed algorithm I took mostly from pyimagesearch I am sorry for not giving the link, I am allowed to post only two links currently (and No Images).
This is not the only thing that I have tried, but is the best approach currently.

Highlight all possible circles ( Bubble sheet choices ) in opencv

I am working on automatically correcting a bubble-sheet tests that are scanned.
Currently, I can extract the solutions part of the sheet and fix its rotation.
So I have this image.
The output image with detected contours
Running the following code yields in the output image
def get_answers(image):
display_normal("Just image",image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurry = cv2.GaussianBlur(gray, (3, 3), 1)
thresh = cv2.threshold(blurry, 225, 255,
cv2.THRESH_BINARY_INV)[1]
display_normal("Binary", thresh)
# find contours in the thresholded image, then initialize
# the list of contours that correspond to questions
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[1]
questionCnts = []
# loop over the contours
for c in cnts:
# compute the bounding box of the contour, then use the
# bounding box to derive the aspect ratio
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# in order to label the contour as a question, region
# should be sufficiently wide, sufficiently tall, and
# have an aspect ratio approximately equal to 1
if w >= 18 and h >= 18 and 0.9 <= ar and ar <= 1.2:
questionCnts.append(c)
cv2.drawContours(image, questionCnts, -1, (255, 0, 0), 1)
display_normal("Image with contours",image.copy())
if(questionCnts < 45*4):
raise Exception("Didn't found all possible answers")
Here is the problem : I convert the input image to binary and try to find contours that looks like a circle, but I can't find the whole possible 45*4 choices.. I fail to detect some of these circles..
So is there any better idea/algorithm to do this specific task ?
You could have tried using adaptive threshold:
adapt_thresh = cv2.adaptiveThreshold(equ, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
cv2.imshow('adapt_thresh.jpg', adapt_thresh)
(I resized the original image to keep it smaller)
UPDATE:
Another approach that I just performed.......
I equalized the gray scale image using histogram equalization:
equalized_img = cv2.equalizeHist(gray)
cv2.imshow('Equalized Image.jpg', equalized_img )
I then obtained the median of the equalized image using np.median(equalized_img) and applied a binary threshold by selecting all pixel values below [0.6 * median]
ret, thresh = cv2.threshold(equalized_img, lower, 255, 1)
cv2.imwrite("Final Image.jpg", thresh)
Now you can go ahead and find your desired contours on this image.
Hope it helps .. :)

Categories