problem with shared boundaries of contours in opencv - python

I am trying to get the maximum area object in an image.
I applied a Blur Kernel 5x5 then I applied the Canny algo to get the edges. Then I used the findContours method and the max contourArea but it returns the wrong object.
Base Image:
Canny Image:
Image with all contours found:
Max area object:
As you can see it has to return the left box but it returns the right one.
I think the problem is that left and right boxes share a common edge but it seems that belongs only to the left one.
This is the code snippet:
img_rgb = cv.imread(img_path)
gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
# blur with a kernel size of 5x5
blur = cv.GaussianBlur(gray, (5, 5), 0)
canny = cv.Canny(blur, 50, 50)
#saving canny image
cv.imwrite("canny.png", canny)
_, thresh = cv.threshold(canny, 127, 255, 0)
contours, _ = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
for c in contours:
cv.drawContours(img_rgb, [c], 0, (randrange(255), randrange(255), randrange(255)), 3)
#saving image with contours
cv.imwrite("contours.png", img_rgb)
max_area_contour = max(contours, key=cv.contourArea)
x, y, w, h = cv.boundingRect(max_area_contour)
cv.rectangle(img_rgb, (x, y), (x + w, y + h), (0, 255, 0), 3)
#saving the image with the biggest contour
cv.imwrite("max_contour.png", img_rgb)

I made key function for max as a square of bounding rect and then it works fine:
img_rgb = cv.imread(img_path)
img_rgb_init = img_rgb.copy()
gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
# blur with a kernel size of 5x5
blur = cv.GaussianBlur(gray, (5, 5), 0)
canny = cv.Canny(blur, 50, 50)
#saving canny image
# cv.imwrite("canny.png", canny)
_, thresh = cv.threshold(canny, 127, 255, 0)
contours, _ = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
for c in contours:
cv.drawContours(img_rgb, [c], 0, (random.randrange(255), random.randrange(255), random.randrange(255)), 3)
#saving image with contours
# cv.imwrite("contours.png", img_rgb)
def bounding_rect_size(in_cnt):
x, y, w, h =cv.boundingRect(in_cnt)
return w*h
x, y, w, h = cv.boundingRect(max(contours,key=bounding_rect_size))
cv.rectangle(img_rgb_init, (x, y), (x + w, y + h), (0, 255, 0), 3)
cv.imshow("",img_rgb_init)
cv.waitKey()
The reason it was working incorrect that the contour of bigest rectangle was looking like train railways. In one direction from one side of rectangle line and in other direction from other side (the line somewhere is broken). That is why the area of this contour was small.

Related

Detecting circle pattern in image using Python & OpenCV

I've got images like this one:
I need to detect the center of this circular element:
(More precisely - I'm looking for the midpoint of the circular element)
Currently my code detect the mold (the plastic circle that holds the circular element) and select the rectangle ROI to focus the image into the relevant area:
import cv2
import imutils
import numpy as np
if __name__ == "__main__":
image = cv2.imread('Dart - Overview Image - with Film.bmp')
img = imutils.resize(image, width=700)
image = img
output = image.copy()
roi = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# cv2.imshow("Gray", gray)
# cv2.waitKey(0)
# detect circles in the image
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
cv2.circle(output, (x, y), r, (0, 255, 0), 2)
cv2.rectangle(output, (x - 2, y - 2), (x + 2, y + 2), (0, 128, 255), -1)
roi = roi[y - r: y + r, x - r: x + r]
cv2.imshow("img", roi)
cv2.waitKey(0)
This will show this image:
Now, I'm trying to find the midpoint of the circular element inside this ROI image by detecting the circular element but It's challenged for me, I've try this methods:
gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray_roi, (3, 3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
(x, y), radius = cv2.minEnclosingCircle(c)
cv2.circle(roi, (int(x), int(y)), int(radius), (35, 255, 12), 3)
cv2.circle(roi, (int(x), int(y)), 1, (35, 255, 12), 2)
print(x, y)
break
# Find Canny edges
edged = cv2.Canny(roi, 30, 121, apertureSize=3, L2gradient=True)
cv2.waitKey(0)
# Finding Contours
# Use a copy of the image e.g. edged.copy()
# since findContours alters the image
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.imshow('Canny Edges After Contouring', edged)
cv2.waitKey(0)
And got this output:
I've also tried to change the threshold parameters in the cv2.Canny function but it's didn't give me better results.
Thanks a lot!

Opencv: how to draw a rotated bounding box in python

I am currently doing a project on painting recognition using opencv-python.
Right now I am able to detect most of the paintings decently however the bounding boxes are rectangles that include a lot of background.
This is because the cv2.boundingRect() function finds the bounding rectangle with a perpendicular projection (afaik). However I want to find the best bounding box without detecting any background.
The main part of my code:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hue, saturation, value = cv2.split(hsv)
blurred_sat = cv2.GaussianBlur(saturation, (5, 5), 0)
edges = cv2.Canny(blurred_sat, 45, 100)
kernel = np.ones((3, 3), np.uint8)
dilate = cv2.dilate(edges, kernel, iterations=6)
erode = cv2.erode(dilate, kernel, iterations=2)
contours, hierarchy = cv2.findContours(erode, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
# cv2.drawContours(frame, contours, -1, (255, 255, 0), 3)
for contour in contours:
area = cv2.contourArea(contour)
if area >= 3000:
x, y, w, h = cv2.boundingRect(contour)
subImg = frame[y:y+h, x:x+w]
cv2.rectangle(frame,
(x, y), (x + w, y + h),
(0, 255, 0),
2)
Current output image (video)
Desired output image with desired bounding box in red
After finding and sorting the contours, you need to use cv2.minAreaRect() function. This draws a rectangle enclosing each contour with the least area:
import numpy as np
# portion of code to find and sort contours
for contour in contours:
area = cv2.contourArea(contour)
if area >= 3000:
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(frame, [box], 0, (0, 255, 0), 2)
Note: cv2.minAreaRect() returns properties of the rotated rectangle (center, dimensions and angle of rotation). cv2.boxPoints() is used to obtain the vertices of the rectangle. This is then passed into cv2.drawContours() to draw them.
Have a look at the documentation
For details on the algorithm:
Link 1
Link 2
Update:
The following code approximates a quadrilateral around a contour and draws it on frame. The variable value determines how strong you want your approximations to be:
# use a value between 0 and 1
value = 0.02
for c in contours:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, value*perimeter, True)
if len(approx) == 4:
cv2.drawContours(frame, [approx], 0, (0, 0, 255), 5)

How to detect White region in an image with Opencv & Python?

I'm trying to extract the coordinates of a big white region in an image as follows:
Here's the original image:
Using a small square kernel, I applied a closing operation to fill small holes and help identify larger structures in the image as follows:
import cv2
import numpy as np
import imutils
original = cv2.imread("Plates\\24.png")
original = cv2.resize(original, None, fx=3, fy=3, interpolation=cv2.INTER_CUBIC)
gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)
# next, find regions in the image that are light
squareKern = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
light = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, squareKern)
light = cv2.threshold(light, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
the resulting image is as follows:
Here's another example:
What I wish to be able to do is to detect the large white region in the plate as follows:
Keeping in mind that contours will not work well with many examples
With the one image you provided:
I came up with 2 approaches as to how this problem can be solved:
Approach 1
Contour Area Comparison
As you can see there are 3 large contours in the image; the top rectangle and the two rectangles below it, of which you want to detect as a whole.
So I used a threshold on your image, detected the contours of the thresholded image, and indexed the second largest contour and the third largest contour (the largest is the top rectangle which you want to ignore).
Here is the thresholded image:
I stacked the two contours together and detected the bounding box of the two contours:
import cv2
import numpy as np
img = cv2.imread("image.png")
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 128, 255, cv2.THRESH_BINARY)
img_blur = cv2.GaussianBlur(thresh, (5, 5), 2)
img_canny = cv2.Canny(img_blur, 0, 0)
return img_canny
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
r1, r2 = sorted(contours, key=cv2.contourArea)[-3:-1]
x, y, w, h = cv2.boundingRect(np.r_[r1, r2])
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
get_contours(img)
cv2.imshow("img_processed", img)
cv2.waitKey(0)
Output:
Approach 2
Threshold Masking
As the 2 bottom rectangles are whiter than the top rectangle of the plate, I used a threshold to mask out the top of the plate:
I used the canny edge detector on the mask shown above.
import cv2
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 163, 255, cv2.THRESH_BINARY)
img_canny = cv2.Canny(thresh, 0, 0)
img_dilate = cv2.dilate(img_canny, None, iterations=7)
return cv2.erode(img_dilate, None, iterations=7)
def get_contours(img):
contours, _ = cv2.findContours(process(img), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
x, y, w, h = cv2.boundingRect(max(contours, key=cv2.contourArea))
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)
img = cv2.imread("egypt.png")
get_contours(img)
cv2.imshow("img_processed", img)
cv2.waitKey(0)
Output:
Of course, this method may not work properly if the top of the plate isn't brighter than the bottom.

Set white color outside boundingBox (Python, OpenCV)

I have this image:
(or this..)
How can I set to white all the area outside the boundingBox'es ?
I would like to obtain this result:
Thanks
As mentioned in the comments if you have the positions of the ROIs, you can use them to paste them on the an image with white background having the same shape as the original.
import cv2
import numpy as np
image = cv2.imread(r'C:\Users\Desktop\rus.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
white_bg = 255*np.ones_like(image)
ret, thresh = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY_INV)
blur = cv2.medianBlur(thresh, 1)
kernel = np.ones((10, 20), np.uint8)
img_dilation = cv2.dilate(blur, kernel, iterations=1)
im2, ctrs, hier = cv2.findContours(img_dilation.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
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)
roi = image[y:y + h, x:x + w]
if (h > 50 and w > 50) and h < 200:
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), 1)
cv2.imshow('{}.png'.format(i), roi)
#--- paste ROIs on image with white background
white_bg[y:y+h, x:x+w] = roi
cv2.imshow('white_bg_new', white_bg)
cv2.waitKey(0)
cv2.destroyAllWindows()
The result:
Take a mask with the dimensions same as that of your image. The mask is an array with values 255 (white). I assume if you are drawing the bounding box, you definitely have the coordinates for each of them. For each of the bounding box you simply replace the region in the mask with the region bounded by the bounding box as below:
mask[y:y+h,x:x+w] = image[y:y+h,x:x+w], where mask is your final output with your desired result and image is your input image on which the processing is to take place. The values x,y,w,h is the same for both the image as we have made sure that the dimensions for the mask and input image are the same.

Extract MSER detected areas (Python, OpenCV)

I can't extract the detected regions by MSER in this image:
What I want to do is to save the green bounded areas.
My actual code is this:
import cv2
import numpy as np
mser = cv2.MSER_create()
img = cv2.imread('C:\\Users\\Link\\img.tif')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()
regions, _ = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions]
cv2.polylines(vis, hulls, 1, (0, 255, 0))
mask = np.zeros((img.shape[0], img.shape[1], 1), dtype=np.uint8)
mask = cv2.dilate(mask, np.ones((150, 150), np.uint8))
for contour in hulls:
cv2.drawContours(mask, [contour], -1, (255, 255, 255), -1)
text_only = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('img', vis)
cv2.waitKey(0)
cv2.imshow('mask', mask)
cv2.waitKey(0)
cv2.imshow('text', text_only)
cv2.waitKey(0)
Expected result should be a ROI like image.
Source image:
detectRegions also returns bounding boxes:
regions, boundingBoxes = mser.detectRegions(gray)
for box in boundingBoxes:
x, y, w, h = box;
cv2.rectangle(vis, (x, y), (x+w, y+h), (0, 255, 0), 1)
This draws green rectangles, or save them as mentioned in GPhilo's answer.
Just get the bounding box for each contour, use that as a ROI to extract the area and save it out:
for i, contour in enumerate(hulls):
x,y,w,h = cv2.boundingRect(contour)
cv2.imwrite('{}.png'.format(i), img[y:y+h,x:x+w])
Hey found a cleaner way to get the bounding boxes
regions, _ = mser.detectRegions(roi_gray)
bounding_boxes = [cv2.boundingRect(p.reshape(-1, 1, 2)) for p in regions]

Categories