How to draw bounding boxes on the detected contours in Scikit-Image? - bounding-box

I tried to find a way to draw a bounding box on the contours detected in scikit-image, but I didn't find any way as the find_contours in scikit-image method returns a 2-dimensional array of the co-ordinate points of the detected contours.

You can use this piece of code to draw bounds on your image:
# Loop through contours and draw the bounds in red
contours = skimage.measure.find_contours(img, level)
for contour in contours:
img = drawShape(img, contour, [255, 0, 0])
def drawShape(img, coordinates, color):
# In order to draw our line in red
img = skimage.color.gray2rgb(img)
# Make sure the coordinates are expressed as integers
coordinates = coordinates.astype(int)
img[coordinates[:, 0], coordinates[:, 1]] = color
return img

Related

Detect rectangular table from ASUS Xtion Live Pro IR image

I have an IR image with a resolution of (240 x 320), datatype: float32 as you can see here:
The raw npy image is here.
My objective is to detect the table (brown contour) in order to crop this region as a ROI.
What I have tried so far is to do:
Histogram equalization to increase contrast,
Gaussian Blurring to reduce the noise, and
contour detection to find the rectangular table in the middle of the image.
Note that the table in my case is installed on wheels, and hence it might slightly move so I want to detect it dynamically, and not use its fixed position inside the image.
The code I have used is:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import cv2
import random as rng
path = ""
# Read the numpy array
ir_raw = np.load(path+"ir.npy") # (240, 320) float32
ir = np.uint8((ir_raw/ir_raw.max()) * 255)
# Histogram equalization (Contrast Adjustment)
heq_ir = cv2.equalizeHist(ir)
# Gaussian smoothing (Noise Removal)
g_blur_ir = cv2.GaussianBlur(heq_ir, (5,5), 0)
# Otsu Thresholding
_, thresh_ir = cv2.threshold(g_blur_ir, 120, 255, cv2.THRESH_BINARY +
cv2.THRESH_OTSU)
# Find contours
contours, hierarchy = cv2.findContours(thresh_ir, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draw contours
rng.seed(12345)
drawing = np.zeros((thresh_ir.shape[0], thresh_ir.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
cv2.drawContours(drawing, contours, i, color, 2, cv2.LINE_8, hierarchy, 0)
plt.subplot(121)
plt.imshow(heq_ir)
plt.title("IR")
plt.subplot(122)
plt.imshow(drawing)
plt.title("IR contours")
plt.show()
Can you please tell me how can I detect the rectangular table in order to crop it as a ROI? thanks in advance.
Assuming the following, the table always has same area (well duh) but in the case below its always the largest object detected (the code should be easily to amend to filter out objects of different areas though).
I've filtered out all contours that have a different area to the table contour (process of elimination until I found the table then set the area threshold accordingly) & then fitted a rectangle to that contour. It is possible to fit skewed rectangles in OpenCV as well but I havent in this case. (EDIT: code for skewed rectangles added)
Your ROI boundary is in boundRect
for i in range(len(contours)):
if cv2.contourArea(contours[i]) > 10000:
color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
cv2.drawContours(drawing, contours, i, color, 2, cv2.LINE_8, hierarchy, 0)
# For unrotated bounding rectangle
boundRect = cv2.boundingRect(contours[i])
cv2.rectangle(drawing, (int(boundRect[0]), int(boundRect[1])), (int(boundRect[0]+boundRect[2]), int(boundRect[1]+boundRect[3])), (255, 255, 255), 2)
# For minimal bounding rectangle that will rotate with table rotation
rect = cv2.minAreaRect(contours[i])
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(drawing, [box], 0, (255, 0, 255), 2)
print(cv2.contourArea(contours[i]))
Ouput,

How to find bounding box of non white area of image with OpenCV Python

I have an image with a white border. I want to find the bounding box of the non-border part. I also have some images with a black border and need to do the same for them but for black. I do not want to crop the image just get the origin and size of the bounding box and then draw a green rectangle around it.
You should try to get the threshold image and calculate the bounding box coordinates from it:
img = cv2.imread('img.jpg')
gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gry,(3,3), 0)
th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
coords = cv2.findNonZero(th)
x,y,w,h = cv2.boundingRect(coords)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
Also, you can use 'cv2.THRESH_BINARY' instead of 'cv2.THRESH_BINARY_INV' in the threshold section for finding the black border bounding box.

Blurred Circle detection

I am new to opencv and want to detect the center point of these circles. I tried with Hough Circles with thresholding but it doesn't seem to generate good results all the time.
This image is easy to get using contours and threshloding:
It is harder to do this one:
The thresholding and Hough circle doesn't work with this image:
Adding more images for help
Can you suggest any method that will be reliable for all the images?
Since the circle is the only bright thing in the image, we can get the center by looking for the centroid of the white blob. We'll auto-threshold with otsu's and use findContours to get the centroid of the mask.
import cv2
import numpy as np
# load image
img = cv2.imread("circ1.png");
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
# threshold
gray = cv2.GaussianBlur(gray, (5,5), 0);
_, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# contour
_, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
# center
M = cv2.moments(contours[0]);
cx = int(M['m10']/M['m00']);
cy = int(M['m01']/M['m00']);
center = (int(cx), int(cy));
# draw
img = cv2.circle(img, center, 4, (0,0,200), -1);
# show
cv2.imshow("marked", img);
cv2.imshow("mask", mask);
cv2.waitKey(0);

How to get the area of the contours?

I have a picture like this:
And then I transform it into binary image and use canny to detect edge of the picture:
gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
edge = Image.fromarray(edges)
And then I get the result as:
I want to get the area of 2 like this:
My solution is to use HoughLines to find lines in the picture and calculate the area of triangle formed by lines. However, this way is not precise because the closed area is not a standard triangle. How to get the area of region 2?
A simple approach using floodFill and countNonZero could be the following code snippet. My standard quote on contourArea from the help:
The function computes a contour area. Similarly to moments, the area is computed using the Green formula. Thus, the returned area and the number of non-zero pixels, if you draw the contour using drawContours or fillPoly, can be different. Also, the function will most certainly give a wrong results for contours with self-intersections.
Code:
import cv2
import numpy as np
# Input image
img = cv2.imread('images/YMMEE.jpg', cv2.IMREAD_GRAYSCALE)
# Needed due to JPG artifacts
_, temp = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
# Dilate to better detect contours
temp = cv2.dilate(temp, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
# Find largest contour
cnts, _ = cv2.findContours(temp, cv2.RETR_EXTERNAL , cv2.CHAIN_APPROX_NONE)
largestCnt = []
for cnt in cnts:
if (len(cnt) > len(largestCnt)):
largestCnt = cnt
# Determine center of area of largest contour
M = cv2.moments(largestCnt)
x = int(M["m10"] / M["m00"])
y = int(M["m01"] / M["m00"])
# Initiale mask for flood filling
width, height = temp.shape
mask = img2 = np.ones((width + 2, height + 2), np.uint8) * 255
mask[1:width, 1:height] = 0
# Generate intermediate image, draw largest contour, flood filled
temp = np.zeros(temp.shape, np.uint8)
temp = cv2.drawContours(temp, largestCnt, -1, 255, cv2.FILLED)
_, temp, mask, _ = cv2.floodFill(temp, mask, (x, y), 255)
temp = cv2.morphologyEx(temp, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)))
# Count pixels in desired region
area = cv2.countNonZero(temp)
# Put result on original image
img = cv2.putText(img, str(area), (x, y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, 255)
cv2.imshow('Input', img)
cv2.imshow('Temp image', temp)
cv2.waitKey(0)
Temporary image:
Result image:
Caveat: findContours has some problems one the right side, where the line is very close to the bottom image border, resulting in possibly omitting some pixels.
Disclaimer: I'm new to Python in general, and specially to the Python API of OpenCV (C++ for the win). Comments, improvements, highlighting Python no-gos are highly welcome!
There is a very simple way to find this area, if you take some assumptions that are met in the example image:
The area to be found is bounded on top by a line
Any additional lines in the image are above the line of interest
There are no discontinuities in the line
In this case, the area of the region of interest is given by the sum of the lengths from the bottom of the image to the first set pixel. We can compute this with:
import numpy as np
import matplotlib.pyplot as pp
img = pp.imread('/home/cris/tmp/YMMEE.jpg')
img = np.flip(img, axis=0)
pos = np.argmax(img, axis=0)
area = np.sum(pos)
print('Area = %d\n'%area)
This prints Area = 22040.
np.argmax finds the first set pixel on each column of the image, returning the index. By first using np.flip, we flip this axis so that the first pixel is actually the one on the bottom. The index corresponds to the number of pixels between the bottom of the image and the line (not including the set pixel).
Thus, we're computing the area under the line. If you need to include the line itself in the area, add pos.shape[0] to the area (i.e. the number of columns).

openCV problem with detecting contours of shapes fully

I am doing this university project where i try to detect UI elements on screenshots of Android applications using openCV. I am not expecting a 100 percent accuracy for this detection of UI elements.
This is my code below. I convert the image to gray scale, apply Gaussian blur and then use adaptive threshold to convert the image to binary. After which i use the find contours method.
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image", help = "path to an image", required =
True)
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",gray)
cv2.waitKey(0)
blurred = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.adaptiveThreshold(blurred, 255,
cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 4)
cv2.imshow("thresh",thresh)
cv2.waitKey(0)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cv2.drawContours(image, cnts, -1, (0,255,0), 1)
cv2.imshow("contours", image)
cv2.waitKey(0)
for c in cnts:
area = cv2.contourArea(c)
print(area)
if area > 50:
M = cv2.moments(c)
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
#cv2.drawContours(image, [c], -1, (0,255,0), 2) # draw contours on image
(x,y,w,h) = cv2.boundingRect(c) # for each contour get a
bounding rectangle
mask = np.zeros(image.shape[:2], dtype = "uint8") # find
shape of the image dimensions and set up a mask
mask[y: y + h, x: x + w] = 255 # convert region of
interest into white
to_display = cv2.bitwise_and(image,image, mask = mask) # carry
out bitwise and
#cv2.putText(image, 'center', (c))
cv2.imshow("Image", to_display)
cv2.waitKey(0)
this is the screenshot that i am running my code on.
The leftmost screenshot represents the image after applying a threshold to it.
The middle image represents the image i get after drawing the contours.
The last image shows when i am examining each individual contour. The contour covers the line but does not encapsulate the rectangle.
I have a few questions.
1) Is it possible to sieve out the contours for the white rectangles. What alteration do i have to make to my code to be able to achieve this?
2) I am trying to sieve out the unimportant contours eg. the words and I was thinking if i could use the getArea() function to help me with it. The idea is that i would set a minimum contour size to filter out the smaller contours that account for the words.
This is another image that i have tried to identify the "objects" in this screenshots.
I face the same issue here where i cant identify the white rectangles. I am only identifying the borders of the rectangle.
Would appreciate any form of help as I am still new to openCv
Original images before processing:
There is no need to blur. In fact I makes it harder. Simple thresholding works best with hard transitions. The second image is easiest. There are white items on a grayish background. By selecting only very white values the items are selected.
Result:
Code:
# load image
img = cv2.imread("app.png")
# convert to gray
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# crate a mask that hold only white values (above 250)
ret,thresh1 = cv2.threshold(img2,250,255,cv2.THRESH_BINARY)
# find contours in mask
im2, contours, hierarchy = cv2.findContours(thresh1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select large contours (menu items only)
for cnt in contours:
print(cv2.contourArea(cnt))
if cv2.contourArea(cnt) > 5000:
# draw a rectangle around the items
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0),3)
#cv2.drawContours(img, [cnt], 0, (0,255,0), 3) #also works, but has issues with letters at the last item
#show image
cv2.imshow("img", img)
#cv2.imshow("mask", thresh) # shows mask
cv2.waitKey(0)
cv2.destroyAllWindows()
The first image is more complex, because it is divided in by a very thin red line. Selecting colors is easier in HSV colorspace. Next red values are used to create a mask, some noise is removed and then contours are detected.
Result:
# load image
img = cv2.imread("app2.png")
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# set lower and upper color limits
lower_val = np.array([0,0,0])
upper_val = np.array([20,50,255])
# Threshold the HSV image
mask = cv2.inRange(hsv, lower_val, upper_val)
# remove noise
kernel = np.ones((1,2),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
kernel = np.ones((1,5),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# find contours in mask
im2, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select large contours (menu items only)
for cnt in contours:
print(cv2.contourArea(cnt))
if cv2.contourArea(cnt) > 1000:
# draw a rectangle around the items
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0),3)
#show image
cv2.imshow("img", img)
cv2.imshow("mask", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

Resources