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.
Related
I need to detect edges of any given Document. Tried using the famous Canny edge detection, but after trial and error found Laplacian to work better than it. For good contrast images, the detection works quite well, but for some scenarios like below 2 images, the detection logic doesn't work correctly.
Following is the logic:
Blurring the image
Convert to grayscale
Apply Laplacian
Find Top 3 largest Contour
Sort by largest
Check if one of those 3 Contours is a rectangle with 4 points.
If yes print the pink border, and always print the blue border of largest Contour
Below is the code and 3 Outputs:
def strokeEdges(src, dst, blurKsize = 7, edgeKsize = 5)
if blurKsize >= 3:
blurredSrc = cv2.medianBlur(src, blurKsize)
graySrc = cv2.cvtColor(blurredSrc, cv2.COLOR_BGR2GRAY)
cv2_imshow(graySrc)
else:
graySrc = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
img_lap = cv2.Laplacian(graySrc, cv2.CV_8U, graySrc, ksize = edgeKsize)
cv2_imshow(img_lap)
return img_lap
def draw_contours(img, img_edge):
contours, hierarchies = cv2.findContours(img_edge, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse = True)[:3]
for cnt in contours:
### Approximating the contour
#Calculates a contour perimeter or a curve length
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
if len(approx) == 4:
print("Found counter with 4 points")
screenCnt = approx
cv2.drawContours(img, [screenCnt], -1, (255, 0, 0), 2)
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,255),2)
break
#Draw the largest countour boundry
cv2.drawContours(img, contours, 0, (255, 0, 0), 2)
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.imread("8.jpg")
h, w, c = img.shape
img = cv2.resize(img, (w // 3, h // 3))
img1 = img
img1 = strokeEdges(img1,img1)
draw_contours(img,img1)
cv2_imshow(img)
cv2.waitKey(0)
Below are couple of images and respective final outputs:
Output works well, the blue line is largest Contour, and pink line is Contour with 4 points so a potential document found.
Output does not work well, the blue line is largest Contour which instead should have been wrapping the whole document.
Output does not work well, the blue line is largest Contour which instead should have been wrapping the right page of document.
Please help improve the code. Thank you.
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
Using OpenCV for Python, I am trying to get a mask of the noise elements in a image to be later used as input for the cv.inpaint() function.
I am given a greyscale image (2D matrix with values from 0 to 255) in the input_mtx_8u variable, with noise (isolated polygons of very low values).
So far what I did was:
get the edges in which the gradient is above 25:
laplacian = cv2.Laplacian(input_mtx_8u, cv2.CV_8UC1)
lapl_bin, lapl_bin_val = cv2.threshold(laplacian, 25, 255, cv2.THRESH_BINARY)
get the contours of the artifacts
contours, _ = cv2.findContours(lapl_bin_val, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
fill the contours identified
filled_mtx = input_mtx_8u.copy()
cv2.fillPoly(filled_mtx, contours, (255, 255, 0), 4)
For some reason, my 'filled polygons' are not completely filled (see figure).
What can I be doing wrong?
As pointed by #fmw42 , a solution to get the contours filled is using drawContours() instead of fillPoly().
The final working code I got is:
# input_mtx_8u = 2D matrix with uint8 values from 0 to 255
laplacian = cv2.Laplacian(input_mtx_8u, cv2.CV_8UC1)
lapl_bin, lapl_bin_val = cv2.threshold(laplacian, 25, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(lapl_bin_val, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
inpaint_mask = np.zeros(input_mtx_8u.shape, dtype=np.uint8)
for contour in contours:
cv2.drawContours(inpaint_mask, [contour], -1, (255, 0, 0), thickness=-1)
# inpaint_mask = can be used as the mask for cv2.inpaint()
Note that for some reason:
cv2.drawContours(input_mtx_cont, contours, -1, (255, 0, 0), thickness=-1)
does not work. One must loop and draw contour by contour...
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.
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)