Why the contour is in reverse order? I use the RETR_EXTERNAL flag for contours hierarchy for detect only the external contours
_, contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
For example my my output is r,e B, why in reverse order?
You could use some heuristic to sort the contours. The following example uses the minimum x coordinate of each contour to sort them:
import cv2
import numpy as np
img = cv2.imread('BB7eu.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours.sort(key=lambda c: np.min(c[:,:,0]))
for i in range(len(contours)):
cur = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.drawContours(cur, contours, i, (0,0,255), -1)
cv2.putText(cur, "i=%d" % i, (140,90), cv2.FONT_HERSHEY_PLAIN, 1, (0,255,255), 1)
cv2.imshow("img", cur)
cv2.waitKey()
You'll see that the contours are highlighted "in order": B,e,r.
Related
i am new with CV2 with python.
i have many images, they have many big and small curvy structures.
and i have to get the biggest big one contour among all.
but i failed.
my codes and images are below...
import cv2 as cv
img_color = cv.imread('ex1.png')
img_gray = cv.cvtColor(img_color, cv.COLOR_BGR2GRAY)
ret, img_binary = cv.threshold(img_gray, 127, 255, 0)
#dbg contours, hierarchy = cv.findContours(img_binary, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
_, contours, hierarchy = cv.findContours(img_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for cnt in contours:
cv.drawContours(img_color, [cnt], 0, (255, 0, 0), 3) # blue
#dbg cv.imshow("result", img_color)
#dbg cv.waitKey(0)
cv.imwrite('save_image1.png', img_color)
for cnt in contours:
hull = cv.convexHull(cnt)
cv.drawContours(img_color, [hull], 0, (0, 0, 255), 5)
#dbg cv.imshow("result", img_color)
#dbg cv.waitKey(0)
cv.imwrite('save_image2.png', img_color)
example of input image ("ex1.png") is like...
the result output image ("save_image2.png") is like...
...
but,
what i want to retrieve is like below... (any of blue or red, i can use them ;)
i mean, the result contour must be just big one which includes everything.
thank you for reading util here(; )
You asked for outline/contour of all regions rather than just the largest. So here is how to do that in Python/OpenCV.
Read the input
Convert to gray
Threshold to binary
Get all the points where the value is greater than 0 and transpose (since numpy use y,x convention and OpenCV wants x,y)
Compute the convex hull of the points
Draw a poly line on a copy of the input
Draw a white filled polygon on a black image
Get the contour of the white filled polygon
Draw the contour on a copy of the input
Save results
Input:
import cv2
import numpy as np
img_color = cv2.imread('ex1.png')
img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# get convex hull
points = np.column_stack(np.where(img_binary.transpose() > 0))
hull = cv2.convexHull(points)
# draw convex hull on input image in green
result = img_color.copy()
cv2.polylines(result, [hull], True, (0,0,255), 2)
# draw white filled hull polygon on black background
mask = np.zeros_like(img_binary)
cv2.fillPoly(mask, [hull], 255)
# get the largest contour from result2
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw contour on copy of input
contr = img_color.copy()
contr = cv2.drawContours(contr, [big_contour], 0, (0,0,255), 2)
# save result2
cv2.imwrite('ex1_convex_hull.png', result)
cv2.imwrite('ex1_convex_hull_contour.png', contr)
# show result2
cv2.imshow('result', result)
cv2.imshow('contr', contr)
cv2.waitKey(0)
cv2.destroyAllWindows()
Resulting convex hull:
Contour of convex hull:
Here is one way to do that in Python/OpenCV.
Use max(contours, key=cv2.contourArea) to get the largest one.
Input:
import cv2
img_color = cv2.imread('ex1.png')
img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
img_binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
contours = cv2.findContours(img_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
img_contour = img_color.copy()
cv2.drawContours(img_contour, [big_contour], 0, (0,0,255), 2)
cv2.imwrite('ex1_contour.png', img_contour)
cv2.imshow('img_contour', img_contour)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am new in computer vision, and I want to create a program which helps me to detect box in the image and save as an image.
and etc...
I tried some code but did not get my desired result.
here is my code and its output.
import cv2
# Load iamge, grayscale, adaptive threshold
image = cv2.imread('image.jpeg')
result = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,9)
# Fill rectangular contours
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(thresh, [c], -1, (255,255,255), -1)
# Morph open
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=4)
# Draw rectangles
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 3)
cv2.imshow('thresh', thresh)
cv2.imshow('opening', opening)
cv2.imshow('image', image)
cv2.waitKey()
output:
All you need to do is simply first remove the outermost white area, that is, make it black so that we can detect the boxes without any issues using the cv2.RETR_EXTERNAL flag as they are not touching. Then we'll just extract the boxes one by one.
To remove the outmost area, I have used the point polygon test of the contours. If the point (1, 1) lies inside or on a contour, it is not drawn and every other contour will be drawn on a new image. From this new image, I have read the box contours and extracted them.
import cv2
import numpy as np
img = cv2.imread("2lscp.png", cv2.IMREAD_GRAYSCALE)
ret, img = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Contours = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2]
newImg = np.zeros(img.shape, dtype=np.uint8)
for Contour in Contours:
if cv2.pointPolygonTest(Contour, (1, 1), False) == -1:
cv2.drawContours(newImg, [Contour], -1, 255, 1)
Contours = cv2.findContours(newImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
for Contour in Contours:
[x, y, w, h] = cv2.boundingRect(Contour)
cv2.imshow("box extracted", img[y:y+h, x:x+w])
cv2.waitKey(0)
cv2.destroyAllWindows()
This case seems particularly simple because the image is quasi-binary. Detect the contours of the white regions and select those that have an area like 10 to 15% of the whole image. These are the desired boxes. Then fit a rectangle or rotated rectangle.
No need for additional processing.
Here is solution
try this:
import cv2
import numpy as np
#Read input image
img = cv2.imread('hw_data.png')
#convert from BGR to HSV color space
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#apply threshold
thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)[1]
# find contours and get one with area about 180*35
# draw all contours in green and accepted ones in red
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
#area_thresh = 0
min_area = 0.95*180*44
max_area = 1.05*180*44
print(min_area)
print(max_area)
result = img.copy()
i = 1
for c in contours:
# print(c)
area = cv2.contourArea(c)
cv2.drawContours(result, [c], -1, (0, 255, 0), 1)
x,y,w,h = cv2.boundingRect(c)
# crop region of img using bounding box
region = result[y:y+h, x:x+w]
# save region to new image
print(region.shape,' i ',i)
# cv2.imwrite("black_region_{0}.png".format(i), region)
i = i + 1
if region.shape[0]>70 and region.shape[1]<100:
cv2.imwrite("black_region_{0}.png".format(i), region)
# break
# if area > min_area and area < max_area:
# cv2.drawContours(result, [c], -1, (0, 0, 255), 1)
# break
# save result
# cv2.imwrite("box_found.png", result)
# show images
# cv2.imshow("GRAY", gray)
# cv2.imshow("THRESH", thresh)
# cv2.imshow("RESULT", result)
# cv2.waitKey(0)
i'm trying to find specific contours having red outlines. Below is the code, I'm trying on this image :
import numpy as np
import cv2
image = cv2.imread('C:/Users/htc/Desktop/image.png')
original = image.copy()
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0,50,50], dtype="uint8")
upper = np.array([10, 255,255], dtype="uint8")
mask = cv2.inRange(image, lower, upper)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Extract contours depending on OpenCV version
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
print(len(cnts))
# Iterate through contours and filter by the number of vertices
for c in cnts:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * perimeter, True)
if len(approx) > 5:
cv2.drawContours(original, [c], -1, (36, 255, 12), -1)
cv2.imshow('mask', mask)
cv2.imshow('original', original)
cv2.waitKey()
Output
the length of contour i'm getting is 14 which is not correct. The correct output will be 3. Where i'm doing wrong?
If you can notice, there are breaks in your mask image due to which many contours are being detected. To correct this (if you only want the count), you can dilate the mask image obtained before finding the contours as shown below.
mask = cv2.inRange(image, lower, upper)
# Dilating the mask
kernel = np.ones((3, 3), dtype=np.uint8)
mask = cv2.dilate(mask, kernel, iterations=2)
# Find contours
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Just add a step at the beginning where you blur the picture a little bit.
image = cv2.GaussianBlur(image, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
I have to find out automatically coordinates (only one point) where border (object) begin, I do not know how to handle function findContours.
testImage.jpg
image = cv2.imread("testImage.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Threshold
ret, thresh = cv2.threshold(gray,225,255,0)
# Contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Here are coordinates that I have to find out
coord = contours[0][1]
# Coordinates to point
point = (coord[0][0],coord[0][1])
# Draw circle on coordinates
cv2.circle(image,point,10,(0,255,0),1)
cv2.imshow("Image", image)
cv2.waitKey()
cv2.destroyAllWindows()
Output
And my goal is find out coordinates anywhere on the border (blue line) - see last image.
Goal
Thanks.
I tweaked your code a little. It seems to do the trick. Check out if it helps:
import cv2
import numpy as np
image = cv2.imread("test.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
t = 230 # threshold: tune this number to your needs
# Threshold
ret, thresh = cv2.threshold(gray,t,255,cv2.THRESH_BINARY_INV)
#kernel = np.ones((9,9),np.uint8)
#thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
cv2.imshow("thresh", thresh)
cv2.waitKey(1)
# Contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# find the coordinate with the smallest x value
for contour in contours:
coord = min(contour[0], key=lambda c: c[0])
# Coordinates to point
point = (coord[0],coord[1])
#draw circles on coordinates
cv2.circle(image,point,10,(0,255,0),5)
cv2.imshow("Image", image)
cv2.waitKey()
cv2.destroyAllWindows()
Note: Increase parameter t to move your green circle 'farther outside' of the contour. Decrease it to move inside.
#Sparkofska
Thanks for your idea, I use it with another way to find out.
image = cv2.imread('testImage.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
t=230
ret, thresh = cv2.threshold(gray,t,255,cv2.THRESH_BINARY_INV)
cv2.imshow("filter", thresh)
# Canny Edge detection
canny = cv2.Canny(thresh, 0, 100)
cv2.imshow("canny", canny)
# Find contours
cnts = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
# My coordinates
cv2.circle(image,(x,y), 10, (0,255,0), 5)
cv2.imshow("output", image)
cv2.waitKey()
cv2.destroyAllWindows()
I have gray scale image and want to convert to intensity contour with isotherm lines, in my code I am getting only one contour and how to apply the isotherm lines?
Goal:
import numpy as np
import cv2 as cv
img = cv2.imread(path)
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, -1, (0,255,0), 3)
plt.imshow(img)
You're on the right track, all you have to do is just take that 127 that you hard-coded into the code, and iterate over a couple of different values. So take what you have and just add a few things (including a plug for the viridis colormap):
import numpy as np
import cv2
# I don't have your image, so I will just create a similar one.
H, W = 480, 640
img = np.zeros([H, W, 3], dtype=np.uint8)
cv2.circle(img, (W//2, H//2), 200, (255,255,255), -1)
img = cv2.GaussianBlur(img, (551, 551), 0)
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# The viridis colormap is better than the jet one you have used.
img_viridis = cv2.applyColorMap(imgray, cv2.COLORMAP_VIRIDIS)
# This for-loop allows you to draw isotherm lines at any value you want.
THRESHES = [30, 90, 170]
for val in THRESHES:
ret, thresh = cv2.threshold(imgray, val, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img_viridis, contours, -1, (0, 0, 255), 2)
cv2.imshow('img', img_viridis)
k = cv2.waitKey(0)
output:
Here is another approach in Python/OpenCV by quantizing the gray image and then getting the contours.
Read the input
Convert it to gray
Quantize it
Get Canny edge
Apply morphology close to ensure they are closed
Get the contours
Filter the contours by perimeter to remove small extraneous ones
Draw the contours on the input
Save the results
Input:
import numpy as np
import cv2
# read input
img = cv2.imread('bright_blob.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# do color quantization
gray = 64*((gray/64).astype(np.uint8))
# get canny edges
edges = cv2.Canny(gray, 10, 250)
# apply morphology closed to ensure they are closed
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
# get contours
contours = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
contours = contours[0] if len(contours) == 2 else contours[1]
# filter contours to keep only large ones
result = img.copy()
for c in contours:
perimeter = cv2.arcLength(c, True)
if perimeter > 200:
cv2.drawContours(result, c, -1, (0,0,255), 1)
# save results
cv2.imwrite("bright_blob_gray.jpg", gray)
cv2.imwrite("bright_blob_edges.jpg", edges)
cv2.imwrite("bright_blob_isotherms.jpg", result)
# show images
cv2.imshow("gray", gray)
cv2.imshow("edges", edges)
cv2.imshow("result", result)
cv2.waitKey(0)
Quantized gray image:
Edge image:
Result: