OpenCV - visualize polygonal curve(s) extracted with cv2.approxPolyDP() - python

I want to visualize polygonal curve(s) extracted with cv2.approxPolyDP(). Here's the image I am using:
My code attempts to isolate the main island and define and plot the contour approximation and contour hull. I have plotted the contour found in green, the approximation in red:
import numpy as np
import cv2
# load image and shrink - it's massive
img = cv2.imread('../data/UK.png')
img = cv2.resize(img, None,fx=0.25, fy=0.25, interpolation = cv2.INTER_CUBIC)
# get a blank canvas for drawing contour on and convert img to grayscale
canvas = np.zeros(img.shape, np.uint8)
img2gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# filter out small lines between counties
kernel = np.ones((5,5),np.float32)/25
img2gray = cv2.filter2D(img2gray,-1,kernel)
# threshold the image and extract contours
ret,thresh = cv2.threshold(img2gray,250,255,cv2.THRESH_BINARY_INV)
im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# find the main island (biggest area)
cnt = contours[0]
max_area = cv2.contourArea(cnt)
for cont in contours:
if cv2.contourArea(cont) > max_area:
cnt = cont
max_area = cv2.contourArea(cont)
# define main island contour approx. and hull
perimeter = cv2.arcLength(cnt,True)
epsilon = 0.01*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
hull = cv2.convexHull(cnt)
# cv2.isContourConvex(cnt)
cv2.drawContours(canvas, cnt, -1, (0, 255, 0), 3)
cv2.drawContours(canvas, approx, -1, (0, 0, 255), 3)
## cv2.drawContours(canvas, hull, -1, (0, 0, 255), 3) # only displays a few points as well.
cv2.imshow("Contour", canvas)
k = cv2.waitKey(0)
if k == 27: # wait for ESC key to exit
cv2.destroyAllWindows()
Here are the resulting images:
The first image plots the contour in green. The second plots the approximation in red - how do I plot this approximation as a continuous closed curve?
The documentation isn't terribly clear and neither is the tutorial, but my understanding is that cv2.approxPolyDP() should define a continuous, closed curve, which I should be able to plot with cv2.drawContours(). Is that correct? If so, what am I doing wrong?

The problem is in visualization only: drawContours expects array (list in case of python) of contours, not just one numpy array (which is returned from approxPolyDP).
Solution is the following: replacing
cv2.drawContours(canvas, approx, -1, (0, 0, 255), 3)
to
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 3)

cv2.approxPolyDP()
approx = cv2.approxPolyDP(cnt, 0.03 * cv2.arcLength(cnt, True), True)

Related

How do I detect the corners of a low resolution shape?

I have a low resolution concave quadrilateral cursor. My goal is to find the point he points to. So I thought of finding 4 corners and getting the farthest corner from each other. usually works fine but doesn't always detect corners accurately. Using blur gives little results, but I'm not sure it's enough. What would you suggest I do to improve my results?
cursor = cv.bitwise_and(captureHSV, captureHSV, mask=mask)
#resizeCurs = cv.resize(cursor, (0, 0), fx=0.60, fy=0.60)
#blurCurs = blur = cv.blur(cursor, (3,3))
grayCurs = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
corners = cv.goodFeaturesToTrack(grayCurs, 4, 0.01, 1)
corners = np.int0(corners)
coordList = []
for corner in corners:
x, y = corner.ravel()
cv.circle(cursor, (x, y), 1, (255, 0, 0), 1)
coordList.append([x, y])
After detecting the canny edges of your image, converted to grayscale & blurred beforehand of course, you can dilate and erode the edges to get rid of noisy bumps and fill in little gaps.
But that wouldn't be enough to smooth out the results to 4 points, you'll need to use the cv2.approxPolyDP method to approximate the resulting contours.
Here is how it might go:
import cv2
import numpy as np
def process(img):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_blur = cv2.GaussianBlur(img_gray, (7, 7), 0)
img_canny = cv2.Canny(img_blur, 50, 50)
kernel = np.ones((0, 0))
img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
img_erode = cv2.erode(img_dilate, kernel, iterations=1)
return img_erode
def get_contours(img):
contours, hierarchies = cv2.findContours(process(img), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for cnt in contours:
if cv2.contourArea(cnt) > 10:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, peri * 0.04, True)
cv2.drawContours(img, approx, -1, (0, 0, 255), 8)
img1 = cv2.imread("cursor1.png")
img2 = cv2.imread("cursor2.png")
get_contours(img1)
get_contours(img2)
cv2.imshow("Image 1", img1)
cv2.imshow("Image 2", img2)
cv2.waitKey(0)
Output:
Where the red dots are the parts that were drawn on by the program.

Removing small black contours/unwanted contours in Python

I have been at this all day, how to we remove the small back noise in the red circle? I would need it to work on other samples of pictures like this.
The idea I used is to findContours and then add a mask with all the small black noise that is less than a certain area (trial and error).
Removing noise in red ellipse
image = cv2.imread("11_Image_after_noise_removal.png")
# copy image
img = image.copy()
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0, cv2.THRESH_BINARY)
thresh = 255 - thresh
# Use cv2.CCOMP for two level hierarchy
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_CCOMP,
cv2.CHAIN_APPROX_SIMPLE) # Use cv2.CCOMP for two level hierarchy
cv2.drawContours(img, contours, -1, (0, 255, 0), 1)
cv2.imshow("First detection", img)
# loop through the contours
for i, cnt in enumerate(contours):
# if the contour has no other contours inside of it
if hierarchy[0][i][3] != -1: # basically look for holes
# if the size of the contour is less than a threshold (noise)
if cv2.contourArea(cnt) < 70:
# Fill the holes in the original image
cv2.drawContours(img, [cnt], 0, (0, 0, 0), -1)
# display result
# Visualize the image after the Otsu's method application
cv2.imshow("Image after noise removal", img)
cv2.waitKey(0)
cv2.destroyAllWindows().destroyAllWindows()
You might check the contour area using area = cv.contourArea(cnt) and if it is below some threshold, ignore it.
Here is the OpenCV documentations:
https://docs.opencv.org/4.3.0/dd/d49/tutorial_py_contour_features.html

How to draw Contours around the circular object and find its ROI using OpenCV Python?

I was trying to draw a contour, over a circular object present in the image and find its area and centroid. But I wasn't able to do it, because the contour was drawn all over the image as shown in the figure.
I want to draw contour only over the circular white object as shown in the figure.
Expected Result:
I want to draw the circular contour only over this white object shown in the image and show its centroid and area. OpenCV, ignore rest of the part.
Below is the code attached.
Code:
import cv2
import numpy as np
import imutils
cap = cv2.VideoCapture(0)
cap.set(3,640)
cap.set(4,480)
while True:
_,frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_blue = np.array([90,60,0])
upper_blue = np.array([121,255,255])
mask = cv2.inRange(hsv, lower_blue, upper_blue)
cnts = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts= imutils.grab_contours(cnts)
for c in cnts:
area = cv2.contourArea(c)
if area > 1500:
cv2.drawContours(frame, [c], -1, (0,255,0), 3)
M = cv2.moments(c)
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
cv2.circle(frame, (cx, cy), 7, (255, 255, 255), -1)
cv2.putText(frame, "Centre", (cx - 20, cy - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
cv2.imshow("frame", frame)
print("area is ...", area)
print("centroid is at ...", cx, cy)
k=cv2.waitKey(1000)
if k ==27:
break
cap.release()
cv2.destroyAllWindows()
Any help would be appreciated. Thanks in advance.
This can be done in many ways depending on the need. One simple way can be:
Firstly, filter the ROI. So we know 3 things, ROI is white, is a circle and we know its approx area.
For white color:
def detect_white(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY)
return thresh
Although you would need to play with white detection algorithm as per your need. Some good ways for white detection would be with threshold(like above), using HSL colorspace as whiteness is closely dependent on lightness(cv2.cvtColor(img, cv2.COLOR_BGR2HLS) etc.
So in the code below, first we filter out the ROI by white color, then by the shape and area of those white contours.
image = cv2.imread("path/to/your/image")
white_only = detect_white(image)
contours, hierarchy = cv2.findContours(white_only, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
circles = []
for contour in contours:
epsilon = 0.01 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
area = cv2.contourArea(contour)
if len(approx) > 12 and area > 1000 and area < 3000:
circles.append(contour)
# Now the list circles would have all such
# contours satisfying the above conditions
# If len(circles) != 1 or isn't the circle you
# desire, more filtering is required
cv2.drawContours(image, circles, -1, (0, 255, 0), 3)
Choosing the contour relies upon area and vertices(as returned by cv2. approxPolyDP()).
As in your image, the big white circle has a nice amount of area and is very close to a circle, I checked for it like: len(approx) > 12 and area > 1000 and area < 3000:. Tweak with this line according to your scenario and tell me if it solves your problem. If it doesn't, we can discuss some more nicer ways or play with this one to make it more accurate.

Triangle detection in bicycle image with opencv

I am trying to detect the triangle in different bicycle Images. For example this image
!(https://surlybikes.com/uploads/bikes/_medium_image/BridgeClub_BK9997.jpg)
I am new to OpenCV and am trying the cv.approxPolyDP method. However, I am not getting any results.
ret, thresh = cv.threshold(src, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
largest = None
for contour in contours:
approx = cv.approxPolyDP(contour, 0.01 * cv.arcLength(contour, True), True)
if len(approx) == 3:
# triangle found
if largest is None or cv.contourArea(contour) > cv.contourArea(largest):
largest = contour
print(largest)
cv.drawContours(src, [largest], 0, (0, 0, 255), 3)
cv.imshow("Source", src)
cv.waitKey()
I would like the triangles of the frame to be highlighted. Any help is appreciated.

Improve text area detection (OpenCV, Python)

I am working on a project which ask me to detect text area in an image. This is the result I achieved until now using the code below.
Original Image
Result
The code is the following:
import cv2
import numpy as np
# read and scale down image
img = cv2.pyrDown(cv2.imread('C:\\Users\\Work\\Desktop\\test.png', cv2.IMREAD_UNCHANGED))
# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
127, 255, cv2.THRESH_BINARY)
# find contours and get the external one
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
# with each contour, draw boundingRect in green
# a minAreaRect in red and
# a minEnclosingCircle in blue
for c in contours:
# get the bounding rect
x, y, w, h = cv2.boundingRect(c)
# draw a green rectangle to visualize the bounding rect
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness=1, lineType=8, shift=0)
# get the min area rect
#rect = cv2.minAreaRect(c)
#box = cv2.boxPoints(rect)
# convert all coordinates floating point values to int
#box = np.int0(box)
# draw a red 'nghien' rectangle
#cv2.drawContours(img, [box], 0, (0, 0, 255))
# finally, get the min enclosing circle
#(x, y), radius = cv2.minEnclosingCircle(c)
# convert all values to int
#center = (int(x), int(y))
#radius = int(radius)
# and draw the circle in blue
#img = cv2.circle(img, center, radius, (255, 0, 0), 2)
print(len(contours))
cv2.drawContours(img, contours, -1, (255, 255, 0), 1)
cv2.namedWindow('contours', 0)
cv2.imshow('contours', img)
while(cv2.waitKey()!=ord('q')):
continue
cv2.destroyAllWindows()
As you can see, this can do more than I need. Look for commented parts if you need more.
By the way, what I need is to bound every text area in a single rectangle not (near) every char which the script is finding. Filter the single number or letter and to round everything in a single box.
For example, the first sequence in a box, the second in another one and so on.
I searched a bit and I found something about "filter rectangle area". I don't know if it is useful for my purpose.
Tooked a look also at some of the first result on Google but most of them don't work very well. I guess the code need to be tweaked a bit but I am a newbie in OpenCV world.
Solved using the following code.
import cv2
# Load the image
img = cv2.imread('image.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# smooth the image to avoid noises
gray = cv2.medianBlur(gray,5)
# Apply adaptive threshold
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)
thresh_color = cv2.cvtColor(thresh,cv2.COLOR_GRAY2BGR)
# apply some dilation and erosion to join the gaps - change iteration to detect more or less area's
thresh = cv2.dilate(thresh,None,iterations = 15)
thresh = cv2.erode(thresh,None,iterations = 15)
# Find the contours
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# For each contour, find the bounding rectangle and draw it
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.rectangle(thresh_color,(x,y),(x+w,y+h),(0,255,0),2)
# Finally show the image
cv2.imshow('img',img)
cv2.imshow('res',thresh_color)
cv2.waitKey(0)
cv2.destroyAllWindows()
Parameters that need to be modified to obtain the result below is numbers of iterations in erode and dilate functions.
Lower values will create more bounding rectangles around (nearly) every digit/character.
Result

Categories

Resources