bitwise_and two images and counting the resulting objects - python

I'm trying to to get the number of correct answers given a sample of the model answer and an answer sheet example so i'm using cv2.bitwise_and function then i'm making erosion and dilation for the resulting image to count the objects that represent the correct answers but it's not working well.
Here is two examples images that i'm using:
That is the result:
so it detect 3 circles instead of 2. I tried to change the number of iterations in erosion and dilation and change the shape of the StructuringElement but still getting that wrong answers.
Here is my code:
import numpy as np
import cv2
from PIL import Image
img1 = cv2.imread("model_c3.png")
img2 = cv2.imread("ex_c3.png")
retval1, img1 = cv2.threshold(img1,225,255,cv2.THRESH_BINARY_INV)
retval2, img2 = cv2.threshold(img2,225,255,cv2.THRESH_BINARY_INV)
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
img1gray = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
mask = cv2.bitwise_and(img1gray, img2gray,img2)
el = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
e = cv2.erode(mask, el, iterations=2)
d = cv2.dilate(e, el, iterations=7)
im, contours, hierarchy = cv2.findContours(
d,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE
)
centers = []
radii = []
for contour in contours:
br = cv2.boundingRect(contour)
m = cv2.moments(contour)
center = (int(m['m10'] / m['m00']), int(m['m01'] / m['m00']))
centers.append(center)
print("There are {} circles".format(len(centers)))
cv2.imshow('result.png',d)
cv2.waitKey(0)
cv2.destroyAllWindows()

I changed a few things in your code.
It doesn't make much sense to threshold the RGB image and then convert it to gray.
Instead of ANDing both images and then do morphological operations to clean the result you should simply prepare both images for a proper AND operation. Make the circles in one image bigger and in the other image smaller. That way you also compensate the small shift you have between both images (the reason for all the artifacts in your AND result).
I'm not sure if you fully understand the functions you use because you use them in a weird manner with strange parameters...
Please note that I just changed a few things in your code to make it work. This is not intended to be a perfect solution.
import numpy as np
import cv2
#from PIL import Image
img1 = cv2.imread("d://1.png")
img2 = cv2.imread("d://2.png")
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
img1gray = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
retval1, img1bin = cv2.threshold(img1gray,128,255,cv2.THRESH_BINARY_INV)
retval2, img2bin = cv2.threshold(img2gray,128,255,cv2.THRESH_BINARY_INV)
el = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
e = cv2.erode(img1bin, el, iterations=2)
cv2.imshow("e", e)
d = cv2.dilate(img2bin, el, iterations=7)
result = cv2.bitwise_and(e, d)
cv2.imshow("d", d)
im, contours, hierarchy = cv2.findContours(
result,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE
)
centers = []
radii = []
for contour in contours:
br = cv2.boundingRect(contour)
m = cv2.moments(contour)
center = (int(m['m10'] / m['m00']), int(m['m01'] / m['m00']))
centers.append(center)
print("There are {} circles".format(len(centers)))
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Its seems the false positive blob is smaller than the other blobs. Filter the contours by area...
area = cv2.contourArea(contour)
http://docs.opencv.org/trunk/dd/d49/tutorial_py_contour_features.html

Related

Feather cropped edges

I'm trying to crop an object from an image, and paste it on another image. Examining the method in this answer, I've successfully managed to do that. For example:
The code (show_mask_applied.py):
import sys
from pathlib import Path
from helpers_cv2 import *
import cv2
import numpy
img_path = Path(sys.argv[1])
img = cmyk_to_bgr(str(img_path))
threshed = threshold(img, 240, type=cv2.THRESH_BINARY_INV)
contours = find_contours(threshed)
mask = mask_from_contours(img, contours)
mask = dilate_mask(mask, 50)
crop = cv2.bitwise_or(img, img, mask=mask)
bg = cv2.imread("bg.jpg")
bg_mask = cv2.bitwise_not(mask)
bg_crop = cv2.bitwise_or(bg, bg, mask=bg_mask)
final = cv2.bitwise_or(crop, bg_crop)
cv2.imshow("debug", final)
cv2.waitKey(0)
cv2.destroyAllWindows()
helpers_cv2.py:
from pathlib import Path
import cv2
import numpy
from PIL import Image
from PIL import ImageCms
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
def cmyk_to_bgr(cmyk_img):
img = Image.open(cmyk_img)
if img.mode == "CMYK":
img = ImageCms.profileToProfile(img, "Color Profiles\\USWebCoatedSWOP.icc", "Color Profiles\\sRGB_Color_Space_Profile.icm", outputMode="RGB")
return cv2.cvtColor(numpy.array(img), cv2.COLOR_RGB2BGR)
def threshold(img, thresh=128, maxval=255, type=cv2.THRESH_BINARY):
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
threshed = cv2.threshold(img, thresh, maxval, type)[1]
return threshed
def find_contours(img):
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morphed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
contours = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
return contours[-2]
def mask_from_contours(ref_img, contours):
mask = numpy.zeros(ref_img.shape, numpy.uint8)
mask = cv2.drawContours(mask, contours, -1, (255,255,255), -1)
return cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
def dilate_mask(mask, kernel_size=11):
kernel = numpy.ones((kernel_size,kernel_size), numpy.uint8)
dilated = cv2.dilate(mask, kernel, iterations=1)
return dilated
Now, instead of sharp edges, I want to crop with feathered/smooth edges. For example (the right one; created in Photoshop):
How can I do that?
All images and codes can be found that at this repository.
You are using a mask to select parts of the overlay image. The mask currently looks like this:
Let's first add a Gaussian blur to this mask.
mask_blurred = cv2.GaussianBlur(mask,(99,99),0)
We get to this:
Now, the remaining task it to blend the images using the alpha value in the mask, rather than using it as a logical operator like you do currently.
mask_blurred_3chan = cv2.cvtColor(mask_blurred, cv2.COLOR_GRAY2BGR).astype('float') / 255.
img = img.astype('float') / 255.
bg = bg.astype('float') / 255.
out = bg * (1 - mask_blurred_3chan) + img * mask_blurred_3chan
The above snippet is quite simple. First, transform the mask into a 3 channel image (since we want to mask all the channels). Then transform the images to float, since the masking is done in floating point. The last line does the actual work: for each pixel, blends the bg and img images according to the value in the mask. The result looks like this:
The amount of feathering is controlled by the size of the kernel in the Gaussian blur. Note that it has to be an odd number.
After this, out (the final image) is still in floating point. It can be converted back to int using:
out = (out * 255).astype('uint8')
While Paul92's answer is more than enough, I wanted to post my code anyway for any future visitor.
I'm doing this cropping to get rid of white background in some product photos. So, the main goal is to get rid of the whites while keeping the product intact. Most of the product photos have shadows on the ground. They are either the ground itself (faded), or the product's shadow, or both.
While the object detection works fine, these shadows also count as part of the object. Differentiating the shadows from the objects is not really necessary, but it results in some images that are not so desired. For example, examine the left and bottom sides of the image (shadow). The cut/crop is obviously visible, and doesn't look all that nice.
To get around this problem, I wanted to do non-rectangular crops. Using masks seems to do the job just fine. The next problem was to do the cropping with feathered/blurred edges so that I can get rid of these visible shadow cuts. With the help of Paul92, I've managed to do that. Example output (notice the missing shadow cuts, the edges are softer):
Operations on the image(s):
The code (show_mask_feathered.py, helpers_cv2.py)
import sys
from pathlib import Path
import cv2
import numpy
from helpers_cv2 import *
img_path = Path(sys.argv[1])
img = cmyk_to_bgr(str(img_path))
threshed = threshold(img, 240, type=cv2.THRESH_BINARY_INV)
contours = find_contours(threshed)
dilation_length = 51
blur_length = 51
mask = mask_from_contours(img, contours)
mask_dilated = dilate_mask(mask, dilation_length)
mask_smooth = smooth_mask(mask_dilated, odd(dilation_length * 1.5))
mask_blurred = cv2.GaussianBlur(mask_smooth, (blur_length, blur_length), 0)
mask_blurred = cv2.cvtColor(mask_blurred, cv2.COLOR_GRAY2BGR)
mask_threshed = threshold(mask_blurred, 1)
mask_contours = find_contours(mask_threshed)
mask_contour = max_contour(mask_contours)
x, y, w, h = cv2.boundingRect(mask_contour)
img_cropped = img[y:y+h, x:x+w]
mask_cropped = mask_blurred[y:y+h, x:x+w]
background = numpy.full(img_cropped.shape, (200,240,200), dtype=numpy.uint8)
output = alpha_blend(background, img_cropped, mask_cropped)

python opencv crop using contour hierarchy

I was looking to remove the borders from the below image
what I have tried till now is using OpenCV to get edges
code:
def autocrop(image, threshold=0):
"""Crops any edges below or equal to threshold
Crops blank image to 1x1.
Returns cropped image.
"""
if len(image.shape) == 3:
flatImage = np.max(image, 2)
else:
flatImage = image
assert len(flatImage.shape) == 2
rows = np.where(np.max(flatImage, 0) > threshold)[0]
if rows.size:
cols = np.where(np.max(flatImage, 1) > threshold)[0]
image = image[cols[0]: cols[-1] + 1, rows[0]: rows[-1] + 1]
else:
image = image[:1, :1]
return image
no_border = autocrop(new_image)
cv2.imwrite('no_border.png',no_border)
the result is this image , next how to remove those boxes
Update :
I have found that the solution works for a white background but when I change the background color border are not removed
Edited
I have tried the solution on this image
But the result was like this
How I can achieve a complete removal of the boundary boxes .
For this we use floodFill function.
import cv2
import numpy as np
if __name__ == '__main__':
# read image and convert to gray
img = cv2.imread('image.png',cv2.IMREAD_UNCHANGED)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold the gray image to binarize, and negate it
_,binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
binary = cv2.bitwise_not(binary)
# find external contours of all shapes
_,contours,_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# create a mask for floodfill function, see documentation
h,w,_ = img.shape
mask = np.zeros((h+2,w+2), np.uint8)
# determine which contour belongs to a square or rectangle
for cnt in contours:
poly = cv2.approxPolyDP(cnt, 0.02*cv2.arcLength(cnt,True),True)
if len(poly) == 4:
# if the contour has 4 vertices then floodfill that contour with black color
cnt = np.vstack(cnt).squeeze()
_,binary,_,_ = cv2.floodFill(binary, mask, tuple(cnt[0]), 0)
# convert image back to original color
binary = cv2.bitwise_not(binary)
cv2.imshow('Image', binary)
cv2.waitKey(0)
cv2.destroyAllWindows()
There is another to find the characters within the image. This using the concept of hierarchy in contours.
The implementation is in python:
path = r'C:\Desktop\Stack'
filename = '2.png'
img = cv2.imread(os.path.join(path, filename), 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
_, contours2, hierarchy2 = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
Notice that in the cv2.findContours() function is passed in the RETR_CCOMP parameter to store contours according to their different levels of hierarchy. Hierarchy is useful when one contour lies inside another contour, thus enabling and parent-child relationship. RETR_CCOMP helps identify this relationship.
img2 = img.copy()
l = []
for h in hierarchy2[0]:
if h[0] > -1 and h[2] > -1:
l.append(h[2])
In the snippet above I am passing all contours that have a child into the list l. Using l I am drawing those contours in the snippet below.
for cnt in l:
if cnt > 0:
cv2.drawContours(img2, [contours2[cnt]], 0, (0,255,0), 2)
cv2.imshow('img2', img2)
Have a look at the DOCUMENTATION HERE to learn more about hierarchy in contours.

OCR, Cropping the letters

I'm building a simple OCR, I'm facing a problem of not being to crop the letters after segmenting them using OpenCV. Can anyone help me with a simple way to crop the letters?
Here's the segmenting code.
import cv2
import numpy as np
mser = cv2.MSER_create()
# original image
# -1 loads as-is so if it will be 3 or 4 channel as the original
image = cv2.imread('1.jpg', -1)
# mask defaulting to black for 3-channel and transparent for 4-channel
# (of course replace corners with yours)
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
vis = image.copy()
regions = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions[0]]
channel_count = image.shape[2] # i.e. 3 or 4 depending on your image
ignore_mask_color = (255,)*channel_count
cv2.fillConvexPoly(mask, hulls, ignore_mask_color)
# from Masterfool: use cv2.fillConvexPoly if you know it's convex
masked_image = cv2.bitwise_and(vis, hulls)
cv2.imwrite('img')
#for m in range(len(hulls)):
#masked_image = cv2.bitwise_and(vis, ignore_mask_color)
# save the result
#cv2.imwrite('img'+m, masked_image)
This results:
I need each letter to be cropped using the same hulls. Any help?
You can't crop and directly save the hulls as you can see them in the example you posted. Or, better, you can crop and paste them in a square/rectangle canvas. But it's not the answer you want for this question.
So, if you have all the text which is computer written, best option to begin is to apply cv2.findContours() to the image. There are also other specific tools you can use, but for now (and relatively to this question) use this.
import cv2
import numpy as np
#import image
image = cv2.imread('image.png')
#grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)
cv2.waitKey(0)
#binary
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
cv2.imshow('second', thresh)
cv2.waitKey(0)
#dilation
kernel = np.ones((1,1), np.uint8)
img_dilation = cv2.dilate(thresh, kernel, iterations=1)
cv2.imshow('dilated', img_dilation)
cv2.waitKey(0)
#find contours
im2,ctrs, hier = cv2.findContours(img_dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
for i, ctr in enumerate(sorted_ctrs):
# Get bounding box
x, y, w, h = cv2.boundingRect(ctr)
# Getting ROI
roi = image[y:y+h, x:x+w]
# show ROI
#cv2.imshow('segment no:'+str(i),roi)
cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2)
#cv2.waitKey(0)
if w > 15 and h > 15:
cv2.imwrite('roi{}.png'.format(i), roi)
cv2.imshow('marked areas',image)
cv2.waitKey(0)
You can tweak the kernel for more or less wide of the rectangle detection.

Batch processing and breaking up an image

I'm trying to segment each charm item in this collective image.
The shapes are irregular and inconsistent:
https://i.imgur.com/sf8nOau.jpg
I have another image where there is some consistency with the top row of items, but ideally I'd be able to process and brake up all items in one go:
http://i.imgur.com/WiiYBay.jpg
I have no experience with opencv so I'm just looking for best possible tool or approach to take. I've read about background subtraction as well as color clustering, but I'm not sure about those either.
Any ideas on how to best approach this? Thanks.
Code
import cv2
import numpy as np
im=cv2.imread('so1.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
res = cv2.dilate(thresh,kernel,iterations = 1)
res = cv2.erode(res,kernel,iterations = 1)
res = cv2.dilate(res,kernel,iterations = 1)
cv2.imshow('thresh',res)
_,contours, hierarchy = cv2.findContours(res.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('image',im)
cv2.waitKey(0)
cv2.destroyAllWindows()
Output
Now having the contours you can crop them out
Code Update
import cv2
import numpy as np
im=cv2.imread('so.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
kernel = np.ones((3,3),np.uint8)
res = cv2.dilate(thresh,kernel,iterations = 1)
res = cv2.erode(res,kernel,iterations = 1)
res = cv2.dilate(res,kernel,iterations = 8)
cv2.imshow('thresh',res)
_,contours, hierarchy = cv2.findContours(res.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
count=0
for cnt in contours:
blank=np.zeros(im.shape,dtype=np.uint8)
x,y,w,h = cv2.boundingRect(cnt)
epsilon = 0.001*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
cv2.fillConvexPoly(blank,approx,(255, 255, 255))
masked_image = cv2.bitwise_and(im, blank)
cv2.imwrite('results_so/im'+str(count)+'.jpg',masked_image[y:y+h,x:x+w])
count+=1
cv2.imshow('image',im)
cv2.waitKey(0)
cv2.destroyAllWindows()
results
good one
bad one
Some small noises are also detected as objects you can eliminate those by only taking contours whose area is greater than a certain value .

Finding properties of sloppy hand-drawn rectangles

Image I'm working with:
I'm trying to find each of the boxes in this image. The results don't have to be 100% accurate, just as long as the boxes found are approximately correct in position/size. From playing with the example for square detection, I've managed to get contours, bounding boxes, corners and the centers of boxes.
There are a few issues I'm running into here:
bounding rectangles are detected for both the inside and the outside of the drawn lines.
some extraneous corners/centers are detected.
I'm not sure how to match corners/centers with the related contours/bounding boxes, especially when taking nested boxes into account.
Image resulting from code:
Here's the code I'm using to generate the image above:
import numpy as np
import cv2
from operator import itemgetter
from glob import glob
def angle_cos(p0, p1, p2):
d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) )
def makebin(gray):
bin = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 2)
return cv2.bitwise_not(bin)
def find_squares(img):
img = cv2.GaussianBlur(img, (11, 11), 0)
squares = []
points = []`
for gray in cv2.split(img):
bin = makebin(gray)
contours, hierarchy = cv2.findContours(bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
corners = cv2.goodFeaturesToTrack(gray,len(contours)*4,0.2,15)
cv2.cornerSubPix(gray,corners,(6,6),(-1,-1),(cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS,10, 0.1))
for cnt in contours:
cnt_len = cv2.arcLength(cnt, True)
if len(cnt) >= 4 and cv2.contourArea(cnt) > 200:
rect = cv2.boundingRect(cnt)
if rect not in squares:
squares.append(rect)
return squares, corners, contours
if __name__ == '__main__':
for fn in glob('../1 (Small).jpg'):
img = cv2.imread(fn)
squares, corners, contours = find_squares(img)
for p in corners:
cv2.circle(img, (p[0][0],p[0][3]), 3, (0,0,255),2)
squares = sorted(squares,key=itemgetter(1,0,2,3))
areas = []
moments = []
centers = []
for s in squares:
areas.append(s[2]*s[3])
cv2.rectangle( img, (s[0],s[1]),(s[0]+s[2],s[1]+s[3]),(0,255,0),1)
for c in contours:
moments.append(cv2.moments(np.array(c)))
for m in moments:
centers.append((int(m["m10"] // m["m00"]), int(m["m01"] // m["m00"])))
for cent in centers:
print cent
cv2.circle(img, (cent[0],cent[1]), 3, (0,255,0),2)
cv2.imshow('squares', img)
ch = 0xFF & cv2.waitKey()
if ch == 27:
break
cv2.destroyAllWindows()
I suggest a simpler approach as a starting point. For instance, morphological gradient can serve as a good local detector of strong edges, and threshold on it tends to be simple. Then, you can remove too small components, which is relatively easy for your problem too. In your example, each remaining connected component is a single box, so the problem is solved in this instance.
Here is what you would obtain with this simple procedure:
The red points represent the centroid of the component, so you could grow another box from there that is contained in the yellow one if the yellow ones are bad for you.
Here is the code for achieving that:
import sys
import numpy
from PIL import Image, ImageOps, ImageDraw
from scipy.ndimage import morphology, label
def boxes(orig):
img = ImageOps.grayscale(orig)
im = numpy.array(img)
# Inner morphological gradient.
im = morphology.grey_dilation(im, (3, 3)) - im
# Binarize.
mean, std = im.mean(), im.std()
t = mean + std
im[im < t] = 0
im[im >= t] = 1
# Connected components.
lbl, numcc = label(im)
# Size threshold.
min_size = 200 # pixels
box = []
for i in range(1, numcc + 1):
py, px = numpy.nonzero(lbl == i)
if len(py) < min_size:
im[lbl == i] = 0
continue
xmin, xmax, ymin, ymax = px.min(), px.max(), py.min(), py.max()
# Four corners and centroid.
box.append([
[(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)],
(numpy.mean(px), numpy.mean(py))])
return im.astype(numpy.uint8) * 255, box
orig = Image.open(sys.argv[1])
im, box = boxes(orig)
# Boxes found.
Image.fromarray(im).save(sys.argv[2])
# Draw perfect rectangles and the component centroid.
img = Image.fromarray(im)
visual = img.convert('RGB')
draw = ImageDraw.Draw(visual)
for b, centroid in box:
draw.line(b + [b[0]], fill='yellow')
cx, cy = centroid
draw.ellipse((cx - 2, cy - 2, cx + 2, cy + 2), fill='red')
visual.save(sys.argv[3])
I see you have already got the answer. But I think there is a much more simpler,shorter and better method available in OpenCV to resolve this problem.
While finding contours, you are also finding the hierarchy of the contours. Hierarchy of the contours is the relation between different contours.
So the flag you used in your code, cv2.RETR_TREE provides all the hierarchical relationship.
cv2.RETR_LIST provides no hierarchy while cv2.RETR_EXTERNAL gives you only external contours.
The best one for you is cv2.RETR_CCOMP which provides you all the contour, and a two-level hierarchical relationship. ie outer contour is always parent and inner hole contour always is child.
Please read following article for more information on hierarchy : Contour - 5 : Hierarchy
So hierarchy of a contour is a 4 element array in which last element is the index pointer to its parent. If a contour has no parent, it is external contour and it has a value -1. If it is a inner contour, it is a child and it will have some value which points to its parent. We are going to exploit this feature in your problem.
import cv2
import numpy as np
# Normal routines
img = cv2.imread('square.JPG')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,50,255,1)
# Remove some small noise if any.
dilate = cv2.dilate(thresh,None)
erode = cv2.erode(dilate,None)
# Find contours with cv2.RETR_CCOMP
contours,hierarchy = cv2.findContours(erode,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
for i,cnt in enumerate(contours):
# Check if it is an external contour and its area is more than 100
if hierarchy[0,i,3] == -1 and cv2.contourArea(cnt)>100:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
m = cv2.moments(cnt)
cx,cy = m['m10']/m['m00'],m['m01']/m['m00']
cv2.circle(img,(int(cx),int(cy)),3,255,-1)
cv2.imshow('img',img)
cv2.imwrite('sofsqure.png',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result :
This question is related to python image recognition. A solution is given in squres.py demo.

Categories

Resources