I have a code that can detect ellipse like structures pretty well.
Essentially given this input image 1, my output would look something like output with segmented circles
but I have to manually alter 2 parameters (C value of adaptive threshold and morphology close value) depending on how bright the image is and tweak them every time after I run the program to get an optimal result.
In the code below, the values are 6 and 11, 11 respectively.
Given the code below:
import cv2
import numpy as np
import skimage.exposure
# read the input
img = cv2.imread('RedJan20.png')
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# blur
blur = cv2.GaussianBlur(gray, (0,0), sigmaX=99, sigmaY=99)
# do division normalization
normal = cv2.divide(gray, blur, scale=255)
# stretch to full dynamic range
stretch = skimage.exposure.rescale_intensity(normal, in_range='image', out_range=(0,255)).astype(np.uint8)
# adaptive threshold
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 25, 6)
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
# filter out small contours and fit ellipse
# draw contour on copy of image and also draw ellipse
result1 = img.copy()
result2 = img.copy()
i = 1
count = 0
for cntr in contours:
area = cv2.contourArea(cntr)
#if area > 10000:
if area > 5000:
ellipse = cv2.fitEllipse(cntr)
(xc,yc),(d1,d2),angle = ellipse
print('ellipse #:', i)
print('center:', xc,yc)
print('diameters:', d1,d2)
print('angle:', angle)
print('')
cv2.drawContours(result1, [cntr], 0, (0,0,255), 1)
cv2.ellipse(result2, (int(xc),int(yc)), (int(d1/2),int(d2/2)), angle, 0, 360, (0,0,255), 1)
i += 1
count += 1
print(count)
# show results
cv2.imshow('gray', gray)
cv2.imshow('blur', blur)
cv2.imshow('normalized', normal)
cv2.imshow('stretched', stretch)
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('contours', result1)
cv2.imshow('ellipses', result2)
cv2.waitKey(0)
For input image 1 above, having a C value of 6 and morph close value 11, 11 works, but let's say for another image input 2, the output will not detect all the circles. I have to increase the the C value to 10 and also the morph value to 50, 50 so all the ellipses can be detected.
After changing the values in the code, the output for image 2 looks like this - output image 2
For this other image only when I change the parameters can I obtain desirable results.
image3 ---> output3
All in all, for the aforementioned 3 images, these are the values I need for accurate circle detection.
Image 1: C argument - 6, morph values 11,11
Image 2: C argument - 10, morph values 50,50
Image 3: C argument - 6, morph values 50, 50
I want to be able to write an algorithm / script that be able to adjust the values depending on the input image. All the input images I will want to detect will have a constraint of 5 circles only, so what I am thinking is perhaps to start off with a C argument of 6 and morph values 11, 11 first. Then if the output image does not have 5 circles, then the program can increase/decrease the values accordingly until the output has 5 circles.
How should I do this, and is there a better way to approach this problem? Any help will be appreciated! Thank you so much.
Related
I want to retrieve all contours of the image below, but ignore text.
Image:
When I try to find the contours of the current image I get the following:
I have no idea how to go about this as I am new to using OpenCV and image processing. I want to get ignore the text, how can I achieve this? If ignoring is not possible but making a single bounding box surrounding the text is, than that would be good too.
Edit:
Criteria that I need to match:
The contours may very in size and shape.
The colors from the image may differ.
The colors and size of the text inside the image may differ.
Here is one way to do that in Python/OpenCV.
Read the input
Convert to grayscale
Get Canny edges
Apply morphology close to ensure they are closed
Get all contour hierarchy
Filter contours to keep only those above threshold in perimeter
Draw contours on input
Draw each contour on a black background
Save results
Input:
import numpy as np
import cv2
# read input
img = cv2.imread('short_title.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# get canny edges
edges = cv2.Canny(gray, 1, 50)
# apply morphology close to ensure they are closed
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# get contours
contours = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
# filter contours to keep only large ones
result = img.copy()
i = 1
for c in contours:
perimeter = cv2.arcLength(c, True)
if perimeter > 500:
cv2.drawContours(result, c, -1, (0,0,255), 1)
contour_img = np.zeros_like(img, dtype=np.uint8)
cv2.drawContours(contour_img, c, -1, (0,0,255), 1)
cv2.imwrite("short_title_contour_{0}.jpg".format(i),contour_img)
i = i + 1
# save results
cv2.imwrite("short_title_gray.jpg", gray)
cv2.imwrite("short_title_edges.jpg", edges)
cv2.imwrite("short_title_contours.jpg", result)
# show images
cv2.imshow("gray", gray)
cv2.imshow("edges", edges)
cv2.imshow("result", result)
cv2.waitKey(0)
Grayscale:
Edges:
All contours on input:
Contour 1:
Contour 2:
Contour 3:
Contour 4:
Here are two options for erasing the text:
Using pytesseract OCR.
Finding white (and small) connected components.
Both solution build a mask, dilate the mask and use cv2.inpaint for erasing the text.
Using pytesseract:
Find text boxes using pytesseract.image_to_boxes.
Fill the boxes in the mask with 255.
Code sample:
import cv2
import numpy as np
from pytesseract import pytesseract, Output
# Tesseract path
pytesseract.tesseract_cmd = "C:\\Program Files\\Tesseract-OCR\\tesseract.exe"
img = cv2.imread('ShortAndInteresting.png')
# https://stackoverflow.com/questions/20831612/getting-the-bounding-box-of-the-recognized-words-using-python-tesseract
boxes = pytesseract.image_to_boxes(img, lang='eng', config=' --psm 6') # Run tesseract, returning the bounding boxes
h, w, _ = img.shape # assumes color image
mask = np.zeros((h, w), np.uint8)
# Fill the bounding boxes on the image
for b in boxes.splitlines():
b = b.split(' ')
mask = cv2.rectangle(mask, (int(b[1]), h - int(b[2])), (int(b[3]), h - int(b[4])), 255, -1)
mask = cv2.dilate(mask, np.ones((5, 5), np.uint8)) # Dilate the boxes in the mask
clean_img = cv2.inpaint(img, mask, 2, cv2.INPAINT_NS) # Remove the text using inpaint (replace the masked pixels with the neighbor pixels).
# Show mask and clean_img for testing
cv2.imshow('mask', mask)
cv2.imshow('clean_img', clean_img)
cv2.waitKey()
cv2.destroyAllWindows()
Mask:
Finding white (and small) connected components:
Use mask = cv2.inRange(img, (230, 230, 230), (255, 255, 255)) for finding the text (assume the text is white).
Finding connected components in the mask using cv2.connectedComponentsWithStats(mask, 4)
Remove large components from the mask - fill components with large area with zeros.
Code sample:
import cv2
import numpy as np
img = cv2.imread('ShortAndInteresting.png')
mask = cv2.inRange(img, (230, 230, 230), (255, 255, 255))
nlabel, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, 4) # Finding connected components with statistics
# Remove large components from the mask (fill components with large area with zeros).
for i in range(1, nlabel):
area = stats[i, cv2.CC_STAT_AREA] # Get area
if area > 1000:
mask[labels == i] = 0 # Remove large connected components from the mask (fill with zero)
mask = cv2.dilate(mask, np.ones((5, 5), np.uint8)) # Dilate the text in the maks
cv2.imwrite('mask2.png', mask)
clean_img = cv2.inpaint(img, mask, 2, cv2.INPAINT_NS) # Remove the text using inpaint (replace the masked pixels with the neighbor pixels).
# Show mask and clean_img for testing
cv2.imshow('mask', mask)
cv2.imshow('clean_img', clean_img)
cv2.waitKey()
cv2.destroyAllWindows()
Mask:
Clean image:
Note:
My assumption is that you know how to split the image into contours, and the only issue is the present of the text.
I would recommend using flood fill, find the seed point for each color region, flood fill it to ignore the text values within. Hope that helps!
Refer to example of using floodfill here: https://www.programcreek.com/python/example/89425/cv2.floodFill
Example below copied from link above
def fillhole(input_image):
'''
input gray binary image get the filled image by floodfill method
Note: only holes surrounded in the connected regions will be filled.
:param input_image:
:return:
'''
im_flood_fill = input_image.copy()
h, w = input_image.shape[:2]
mask = np.zeros((h + 2, w + 2), np.uint8)
im_flood_fill = im_flood_fill.astype("uint8")
cv.floodFill(im_flood_fill, mask, (0, 0), 255)
im_flood_fill_inv = cv.bitwise_not(im_flood_fill)
img_out = input_image | im_flood_fill_inv
return img_out
This might be a bit too "general" question, but how do I perform GRAYSCALE image segmentation and keep the largest contour? I am trying to remove background noise (i.e. labels) from breast mammograms, but I am not successful. Here is the original image:
First, I applied AGCWD algorithm (based on paper "Efficient Contrast Enhancement Using Adaptive Gamma Correction With Weighting Distribution") in order to get better contrast of the image pixels, like so:
Afterwards, I tried executing following steps:
Image segmentation using OpenCV's KMeans clustering algorithm:
enhanced_image_cpy = enhanced_image.copy()
reshaped_image = np.float32(enhanced_image_cpy.reshape(-1, 1))
number_of_clusters = 10
stop_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.1)
ret, labels, clusters = cv2.kmeans(reshaped_image, number_of_clusters, None, stop_criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
clusters = np.uint8(clusters)
Canny Edge Detection:
removed_cluster = 1
canny_image = np.copy(enhanced_image_cpy).reshape((-1, 1))
canny_image[labels.flatten() == removed_cluster] = [0]
canny_image = cv2.Canny(canny_image,100,200).reshape(enhanced_image_cpy.shape)
show_images([canny_image])
Find and Draw Contours:
initial_contours_image = np.copy(canny_image)
initial_contours_image_bgr = cv2.cvtColor(initial_contours_image, cv2.COLOR_GRAY2BGR)
_, thresh = cv2.threshold(initial_contours_image, 50, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(initial_contours_image_bgr, contours, -1, (255,0,0), cv2.CHAIN_APPROX_SIMPLE)
show_images([initial_contours_image_bgr])
Here is how image looks after I draw 44004 contours:
I am not sure how can I get one BIG contour, instead of 44004 small ones. Any ideas how to fix my approach, or possibly any ideas on using alternative approach to get rid of label in top right corner.
Thanks in advance!
Here is one way to do that in Python OpenCV
Read the image
Threshold and invert so the borders are black
Remove the borders of the image as follows (so as to make it easier to get the relevant contours later):
Count the number of non-zero pixels in each column and find the first and last column that have counts greater than 0
Count the number of non-zero pixels in each row and find the first and last row that have counts greater than 0
Crop the image to remove the borders
Crop thresh1 and invert to make thresh2
Get the external contours from thresh2
Find the largest contour and draw as white filled on a black background as a mask
Make all pixels in the cropped image black where the mask is black
Save the results -
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('xray3.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold and invert
thresh1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)[1]
thresh1 = 255 - thresh1
# remove borders
# count number of white pixels in columns as new 1D array
count_cols = np.count_nonzero(thresh1, axis=0)
# get first and last x coordinate where black
first_x = np.where(count_cols>0)[0][0]
last_x = np.where(count_cols>0)[0][-1]
print(first_x,last_x)
# count number of white pixels in rows as new 1D array
count_rows = np.count_nonzero(thresh1, axis=1)
# get first and last y coordinate where black
first_y = np.where(count_rows>0)[0][0]
last_y = np.where(count_rows>0)[0][-1]
print(first_y,last_y)
# crop image
crop = img[first_y:last_y+1, first_x:last_x+1]
# crop thresh1 and invert
thresh2 = thresh1[first_y:last_y+1, first_x:last_x+1]
thresh2 = 255 - thresh2
# get external contours and keep largest one
contours = cv2.findContours(thresh2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# make mask from contour
mask = np.zeros_like(thresh2 , dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, -1)
# make crop black everywhere except where largest contour is white in mask
result = crop.copy()
result[mask==0] = (0,0,0)
# write result to disk
cv2.imwrite("xray3_thresh1.jpg", thresh1)
cv2.imwrite("xray3_crop.jpg", crop)
cv2.imwrite("xray3_thresh2.jpg", thresh2)
cv2.imwrite("xray3_mask.jpg", mask)
cv2.imwrite("xray3_result.png", result)
# display it
cv2.imshow("thresh1", thresh1)
cv2.imshow("crop", crop)
cv2.imshow("thresh2", thresh2)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
Threshold 1 image:
Cropped image:
Threshold 2 image:
Mask image:
Result:
I'm a beginner in image processing with Python so I need help.
I'm trying to remove areas of connected pixels from my pictures with the code posted below. Actually, it works but not well.
What I desire is the removing of areas of pixels, such as those marked in red in the pictures reported below, from my images, so as to obtain a cleaned picture.
Would be also great to set a minimum and a maximum limit for the dimensions of the detected areas of connected pixels.
Example of a picture with marked areas 1
Example of a picture with marked areas 2
This is my currently code:
### LOAD MODULES ###
import numpy as np
import imutils
import cv2
def is_contour_bad(c): # Decide what I want to find and its features
peri=cv2.contourArea(c, True) # Find areas
approx=cv2.approxPolyDP(c, 0.3*peri, True) # Set areas approximation
return not len(approx)>2 # Threshold to decide if add an area to the mask for its removing (if>2 remove)
### DATA PROCESSING ###
image=cv2.imread("025.jpg") # Load a picture
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert to grayscale
cv2.imshow("Original image", image) # Plot
edged=cv2.Canny(gray, 50, 200, 3) # Edges of areas detection
cnts=cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # Find contours: a curve joining all the continuous points (along the boundary), having same color or intensity
cnts=imutils.grab_contours(cnts)
mask=np.ones(image.shape[:2], dtype="uint8")*255 # Setup the mask with white background
# Loop over the detected contours
for c in cnts:
# If the contour satisfies "is_contour_bad", draw it on the mask
if is_contour_bad(c):
cv2.drawContours(mask, [c], -1, 0, -1) # (source image, list of contours, with -1 all contours in [c] pass, 0 is the intensity, -1 the thickness)
image_cleaned=cv2.bitwise_and(image, image, mask=mask) # Remove the contours from the original image
cv2.imshow("Adopted mask", mask) # Plot
cv2.imshow("Cleaned image", image_cleaned) # Plot
cv2.imwrite("cleaned_025.jpg", image_cleaned) # Write in a file
You may execute the following processing steps:
Threshold the image to binary image using cv2.threshold.
It's not a must, but in your case it looks like shades of gray are not important.
Use closing morphological operation, for closing small gaps in the binary image.
Use cv2.findContours with cv2.RETR_EXTERNAL parameter, for getting the contours (perimeter) surrounding the white clusters.
Modify the logic of "bad contour", to return true, only if area is large (assuming you only want to clean the large three contour).
Here is the updated code:
### LOAD MODULES ###
import numpy as np
import imutils
import cv2
def is_contour_bad(c): # Decide what I want to find and its features
peri = cv2.contourArea(c) # Find areas
return peri > 50 # Large area is considered "bad"
### DATA PROCESSING ###
image = cv2.imread("025.jpg") # Load a picture
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert to grayscale
# Convert to binary image (all values above 20 are converted to 1 and below to 0)
ret, thresh_gray = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)
# Use "close" morphological operation to close the gaps between contours
# https://stackoverflow.com/questions/18339988/implementing-imcloseim-se-in-opencv
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)));
#Find contours on thresh_gray, use cv2.RETR_EXTERNAL to get external perimeter
_, cnts, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # Find contours: a curve joining all the continuous points (along the boundary), having same color or intensity
image_cleaned = gray
# Loop over the detected contours
for c in cnts:
# If the contour satisfies "is_contour_bad", draw it on the mask
if is_contour_bad(c):
# Draw black contour on gray image, instead of using a mask
cv2.drawContours(image_cleaned, [c], -1, 0, -1)
#cv2.imshow("Adopted mask", mask) # Plot
cv2.imshow("Cleaned image", image_cleaned) # Plot
cv2.imwrite("cleaned_025.jpg", image_cleaned) # Write in a file
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
Marking contours found for testing:
for c in cnts:
if is_contour_bad(c):
# Draw green line for marking the contour
cv2.drawContours(image, [c], 0, (0, 255, 0), 1)
Result:
There is still work to be done...
Update
Two iterations approach:
First iteration - remove the large contour.
Second iteration - remove small but bright contours.
Here is the code:
import numpy as np
import imutils
import cv2
def is_contour_bad(c, thrs): # Decide what I want to find and its features
peri = cv2.contourArea(c) # Find areas
return peri > thrs # Large area is considered "bad"
image = cv2.imread("025.jpg") # Load a picture
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert to grayscale
# First iteration - remove the large contour
###########################################################################
# Convert to binary image (all values above 20 are converted to 1 and below to 0)
ret, thresh_gray = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)
# Use "close" morphological operation to close the gaps between contours
# https://stackoverflow.com/questions/18339988/implementing-imcloseim-se-in-opencv
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)));
#Find contours on thresh_gray, use cv2.RETR_EXTERNAL to get external perimeter
_, cnts, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # Find contours: a curve joining all the continuous points (along the boundary), having same color or intensity
image_cleaned = gray
# Loop over the detected contours
for c in cnts:
# If the contour satisfies "is_contour_bad", draw it on the mask
if is_contour_bad(c, 1000):
# Draw black contour on gray image, instead of using a mask
cv2.drawContours(image_cleaned, [c], -1, 0, -1)
###########################################################################
# Second iteration - remove small but bright contours
###########################################################################
# In the second iteration, use high threshold
ret, thresh_gray = cv2.threshold(image_cleaned, 150, 255, cv2.THRESH_BINARY)
# Use "dilate" with small radius
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_DILATE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2,2)));
#Find contours on thresh_gray, use cv2.RETR_EXTERNAL to get external perimeter
_, cnts, _ = cv2.findContours(thresh_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # Find contours: a curve joining all the continuous points (along the boundary), having same color or intensity
# Loop over the detected contours
for c in cnts:
# If the contour satisfies "is_contour_bad", draw it on the mask
# Remove contour if area is above 20 pixels
if is_contour_bad(c, 20):
# Draw black contour on gray image, instead of using a mask
cv2.drawContours(image_cleaned, [c], -1, 0, -1)
###########################################################################
Marked contours:
I am trying to detect all the squared shaped dice images so that i can crop them individually and use that for OCR.
Below is the Original image:
Here is the code i have got but it is missing some squares.
def find_squares(img):
img = cv2.GaussianBlur(img, (5, 5), 0)
squares = []
for gray in cv2.split(img):
for thrs in range(0, 255, 26):
if thrs == 0:
bin = cv2.Canny(gray, 0, 50, apertureSize=5)
bin = cv2.dilate(bin, None)
else:
_retval, bin = cv2.threshold(gray, thrs, 255, cv2.THRESH_BINARY)
bin, contours, _hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
cnt_len = cv2.arcLength(cnt, True)
cnt = cv2.approxPolyDP(cnt, 0.02*cnt_len, True)
if len(cnt) == 4 and cv2.contourArea(cnt) > 1000 and cv2.isContourConvex(cnt):
cnt = cnt.reshape(-1, 2)
max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in range(4)])
#print(cnt)
a = (cnt[1][1] - cnt[0][1])
if max_cos < 0.1 and a < img.shape[0]*0.8:
squares.append(cnt)
return squares
dice = cv2.imread('img1.png')
squares = find_squares(dice)
cv2.drawContours(dice, squares, -1, (0, 255, 0), 3)
Here are the Output images:
As per my analysis, some squares are missing due to missing canny edges along the dice because of smooth intensity transition between dice and background.
Given the constraint that there will always be 25 dices in square grid pattern (5*5) can we predict the missing square positions based on recognised squares?
Or can we modify above algorithm for square detection algorithm?
Sharpen square edges. Load the image, convert to grayscale, median blur to smooth, and sharpen to enhance edges.
Obtain binary image and remove noise. We threshold to obtain a black/white binary image. Depending on the image, Otsu's thresholding or adaptive thresholding would work. From here we create a rectangular kernel and perform morphological transformations to remove noise and enhance the square contours.
Detect and extract squares. Next we find contours and filter using minimum/maximum threshold area. Any contours that pass our filter will be our squares so to extract each ROI, we obtain the bounding rectangle coordinates, crop using Numpy slicing, and save each square image.
Sharpen image with
cv2.filter2D() using a generic sharpening kernel, other kernels can be found here.
Now threshold to get a binary image
There's little particles of noise so to remove them, we perform morphological operations
Next find contours and filter using cv2.contourArea() with minimum/maximum threshold values.
We can crop each desired square region using Numpy slicing and save each ROI like this
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
cv2.imwrite('ROI_{}.png'.format(image_number), ROI)
import cv2
import numpy as np
# Load image, grayscale, median blur, sharpen image
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
sharpen_kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
sharpen = cv2.filter2D(blur, -1, sharpen_kernel)
# Threshold and morph close
thresh = cv2.threshold(sharpen, 160, 255, cv2.THRESH_BINARY_INV)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
# Find contours and filter using threshold area
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
min_area = 100
max_area = 1500
image_number = 0
for c in cnts:
area = cv2.contourArea(c)
if area > min_area and area < max_area:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
cv2.imwrite('ROI_{}.png'.format(image_number), ROI)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
image_number += 1
cv2.imshow('sharpen', sharpen)
cv2.imshow('close', close)
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()
That extra piece of information is absolutely golden. Yes, given the 5x5 matrix of dice, you can nail the positions quite well. The dice you can identify give you the center, size, and orientation of the dice. Simply continue those patterns along both axes. For your second pass, increase the contrast in each "region of interest" where you expect to find the edge of a douse (never say die!). You know within a few pixels where the edges will be: simply attenuate the image until you identify those edges.
I am trying to have the circle detected in the following image.
So I did color thresholding and finally got this result.
Because of the lines in the center being removed, the circle is split into many small parts, so if I do contour detection on this, it can only give me each contour separately.
But is there a way I can somehow combine the contours so I could get a circle instead of just pieces of it?
Here is my code for color thresholding:
blurred = cv2.GaussianBlur(img, (9,9), 9)
ORANGE_MIN = np.array((12, 182, 221),np.uint8)
ORANGE_MAX = np.array((16, 227, 255),np.uint8)
hsv_disk = cv2.cvtColor(blurred,cv2.COLOR_BGR2HSV)
disk_threshed = cv2.inRange(hsv_disk, ORANGE_MIN, ORANGE_MAX)
The task is much easier when performed with the red plane only.
I guess there was problem with the thresholds for color segmentation, So the idea here was to generate a binary mask. By inspection your region of interest seems to be brighter than the other regions of input image, so thresholding can simply be done on a grayScale image to simplify the context. Note: You may change this step as per your requirement. After satisfying with the threshold output, you may use cv2.convexHull() to get the convex shape of your contour.
Also keep in mind to select the largest contour and ignore the small contours. The following code can be used to generate the required output:
import cv2
import numpy as np
# Loading the input_image
img = cv2.imread("/Users/anmoluppal/Downloads/3xGG4.jpg")
# Converting the input image to grayScale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Thresholding the image to get binary mask.
ret, img_thresh = cv2.threshold(img_gray, 145, 255, cv2.THRESH_BINARY)
# Dilating the mask image
kernel = np.ones((3,3),np.uint8)
dilation = cv2.dilate(img_thresh,kernel,iterations = 3)
# Getting all the contours
_, contours, __ = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Finding the largest contour Id
largest_contour_area = 0
largest_contour_area_idx = 0
for i in xrange(len(contours)):
if (cv2.contourArea(contours[i]) > largest_contour_area):
largest_contour_area = cv2.contourArea(contours[i])
largest_contour_area_idx = i
# Get the convex Hull for the largest contour
hull = cv2.convexHull(contours[largest_contour_area_idx])
# Drawing the contours for debugging purposes.
img = cv2.drawContours(img, [hull], 0, [0, 255, 0])
cv2.imwrite("./garbage.png", img)