detecting contours in image with white background - python

I am having trouble detecting contours in this image
Class Diagram Image
I want to detect all border contours in the diagram but so far my program is only detecting the image border as shown in this image
Ignore the 0 in the center that is just to show that it is contour 0.
I am not sure where the problem lies in my code as it can detect contours in images with a black background.
filename = sys.argv[1]
t = int(sys.argv[2])
img = cv2.imread(filename)
resized = imutils.resize(img, width=300)
ratio = img.shape[0] / float(resized.shape[0])
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
(t, binary) = cv2.threshold(blur, t, 255, cv2.THRESH_BINARY)
_ ,cnts, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for (i,c) in enumerate(cnts):
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
cX, cY = 0, 0
(x,y,w,h) = cv2.boundingRect(c)
area = cv2.contourArea(c)
cv2.rectangle(img, (x, y), (x + w, y + h),(0, 255, 255), 2)
print("Object %d has dimensions x=%d, y=%d, w=%d, h=%d area=%d" % (i,x,y,w,h,int(w*h)))
cv2.putText(img, str(i), (cX, cY),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)

I am note sure where the problem lies in my code as it can detect contours in images with a black background.
This is almost definitly your problem. From the OpenCV tutorial
In OpenCV, finding contours is like finding white object from black background. So remember, object to be found should be white and background should be black.
This SO Q/A shows you how to invert your image.
You definitely need to be detecting white on black, as discussed here

If you don't want to invert your image, you can use cv2.THRESH_BINARY_INV instead.
(t, binary) = cv2.threshold(blur, t, 255, cv2.THRESH_BINARY_INV)

Related

How do I isolate the major white areas, Iterate through those contours, add a bounding box?

I'm am trying to get all white areas in the below image (first / second image), and draw a bounding box.
After thresholding, canny etc I get a decent mask, although needs improving. However not sure how to get all white areas once isolate. As you can see the current scrip finds the major contour (third image)
img = cv2.imread("img.png")
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)
cnts = cv2.findContours(img_canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
r1, r2 = sorted(cnts, 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)
cv2.imshow("img", img)
cv2.waitKey(0)
I have tried grabbing all contours using..., but still only adds a single box. This should sort big to small?
cnts = imutils.grab_contours(cnts)
(cnts, _) = contours.sort_contours(cnts)
for C in cnts:
if cv2.contourArea(C) < 200:
continue
box = cv2.minAreaRect(C)
box = cv2.boxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)
box = perspective.order_points(box)
orig = img.copy()
cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 1)
cv2.imshow("img", orig)
- Desired
To conclude, I'm trying to isolate the white spaces (yes needs some work), then add a box to all isolated white areas, eventually get box area etc.

problem with shared boundaries of contours in opencv

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.

Finding the center line and center point of rectangular region

I ran the following code to create a rectangle contour:
#import the necessary packages
import argparse
import imutils
import cv2
import numpy as np
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to the input image")
args = vars(ap.parse_args())
# load the image, convert it to grayscale, blur it slightly, and threshold it
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
# threshold the image, then perform a series of erosions + dilations to remove any small regions of noise
thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.erode(thresh, None, iterations=2)
thresh = cv2.dilate(thresh, None, iterations=2)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Find the index of the largest contour
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)
# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)
I would like to find the center line and center point of the rectangular contour. Please advise.
As you already have (x, y, w, h) of the desired contour using x,y,w,h = cv2.boundingRect(cnt) in above code, so center of the vertical mid line can be given by (x+w//2, y+h//2) and vertical line can be drawn using below code:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)
# center line
cv2.line(image, (x+w//2, y), (x+w//2, y+h), (0, 0, 255), 2)
# below circle to denote mid point of center line
center = (x+w//2, y+h//2)
radius = 2
cv2.circle(image, center, radius, (255, 255, 0), 2)
output:
Since you already have the bounding box, you can use cv2.moments() to find the center coordinates. This gives us the centroid (i.e., the center (x, y)-coordinates of the object)
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
The center point is simply (cX, cY) and you can draw this with cv2.circle()
cv2.circle(image, (cX, cY), 5, (36, 255, 12), -1)
Similarly, we can draw the center line using cv2.line() or Numpy slicing
cv2.line(image, (x + int(w/2), y), (x + int(w/2), y+h), (0, 0, 255), 3)
image[int(cY - h/2):int(cY+h/2), cX] = (0, 0, 255)
import imutils
import cv2
import numpy as np
# load the image, convert it to grayscale, blur it slightly, and threshold it
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
# threshold the image, then perform a series of erosions + dilations to remove any small regions of noise
thresh = cv2.threshold(gray, 45, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.erode(thresh, None, iterations=2)
thresh = cv2.dilate(thresh, None, iterations=2)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# Find the index of the largest contour
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.circle(image, (cX, cY), 5, (36, 255, 12), -1)
# To draw line you can use cv2.line or numpy slicing
cv2.line(image, (x + int(w/2), y), (x + int(w/2), y+h), (0, 0, 255), 3)
# image[int(cY - h/2):int(cY+h/2), cX] = (36, 255, 12)
# show the output image
cv2.imshow("Image", image)
cv2.imwrite("Image.png", image)
cv2.waitKey(0)

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.

OpenCV - Closing a contour against image edge

How can I highly accurately find the area of smooth surfaces. I am trying to find areas of bubbles in a cylinder.
This code works relatively well, but does not provide the area for the open surfaces.
It also finds a fake bubble (which you can see if running the code)
Image:
Is there a way to make the edge of the image the end point of the contour?
import cv2
import numpy as np
import imutils
image = cv2.imread('Image.png')
# Convert Image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.bitwise_not(gray)
(thresh, bw) = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# Show greyscale image
cv2.namedWindow("main", cv2.WINDOW_NORMAL)
cv2.imshow('main', bw)
cv2.waitKey(0)
cv2.destroyAllWindows()
_, cnts, _ = cv2.findContours(bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("no of shapes {}".format(len(contours)))
for c in cnts:
if cv2.contourArea(c) > 0:
# compute the center of the contour
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# draw the contour and center of the shape on the image
cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
cv2.circle(image, (cX, cY), 7, (255,0,0), -1)
cv2.putText(image, "center", (cX - 20, cY - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0), 2)
# show the image
cv2.namedWindow("main", cv2.WINDOW_NORMAL)
cv2.imshow("main", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
area = cv2.contourArea(c)
print(area)
I have full flexibility in the output of the image (colour of the lines, background colour, line thickness) as the output is from simulations.
What settings would work best for OpenCV?

Categories