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)
Related
I've got images like this one:
I need to detect the center of this circular element:
(More precisely - I'm looking for the midpoint of the circular element)
Currently my code detect the mold (the plastic circle that holds the circular element) and select the rectangle ROI to focus the image into the relevant area:
import cv2
import imutils
import numpy as np
if __name__ == "__main__":
image = cv2.imread('Dart - Overview Image - with Film.bmp')
img = imutils.resize(image, width=700)
image = img
output = image.copy()
roi = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# cv2.imshow("Gray", gray)
# cv2.waitKey(0)
# detect circles in the image
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
cv2.circle(output, (x, y), r, (0, 255, 0), 2)
cv2.rectangle(output, (x - 2, y - 2), (x + 2, y + 2), (0, 128, 255), -1)
roi = roi[y - r: y + r, x - r: x + r]
cv2.imshow("img", roi)
cv2.waitKey(0)
This will show this image:
Now, I'm trying to find the midpoint of the circular element inside this ROI image by detecting the circular element but It's challenged for me, I've try this methods:
gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray_roi, (3, 3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)[1]
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
(x, y), radius = cv2.minEnclosingCircle(c)
cv2.circle(roi, (int(x), int(y)), int(radius), (35, 255, 12), 3)
cv2.circle(roi, (int(x), int(y)), 1, (35, 255, 12), 2)
print(x, y)
break
# Find Canny edges
edged = cv2.Canny(roi, 30, 121, apertureSize=3, L2gradient=True)
cv2.waitKey(0)
# Finding Contours
# Use a copy of the image e.g. edged.copy()
# since findContours alters the image
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.imshow('Canny Edges After Contouring', edged)
cv2.waitKey(0)
And got this output:
I've also tried to change the threshold parameters in the cv2.Canny function but it's didn't give me better results.
Thanks a lot!
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.
I need to detect the points along these curves, in particular I need the position on the image of one of the two points starting from the left:
I tried to detect Hough Points like that:
import cv2
import numpy as np
# detect circles in the image
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
cv2.circle(output, (x, y), r, (0, 255, 0), 4)
cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
# show the output image
cv2.imshow("output", np.hstack([image, output]))
cv2.waitKey(0)
But it doesn't get the right points and I suppose that this is because the points are positioned along the two curves. Is there a way to detect points along a curve?
Just found the solution following the comment of #HansHirse. I followed these steps:
Color threshold
Erosion
Finding contours
This is the code:
import cv2
import numpy as np
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv_image, lowerb=(85, 0, 0), upperb=(95, 255, 255))
# Masking with green
imask = mask > 0
green = np.zeros_like(hsv_image, np.uint8)
green[imask] = hsv_image[imask]
kernel = np.ones((8, 8), np.uint8)
erosion = cv2.erode(green, kernel, iterations=1)
gray = cv2.cvtColor(erosion, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Filter out large non-connecting objects
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:
area = cv2.contourArea(c)
if area < 500:
cv2.drawContours(thresh,[c],0,0,-1)
# Morph open using elliptical shaped kernel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=3)
# Find circles
cnts = cv2.findContours(opening, cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)[-2]
for c in cnts:
area = cv2.contourArea(c)
if area > 0 and area < 50:
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 2)
cv2.imshow('image', image)
cv2.waitKey()
The result is the following image where the dots are represented by the green circles
I inserted an ellipse into the image, I know its center and I would like to add a line that divides the ellipse into two halves. This is my actuall code:
import cv2
import imutils
img = cv2.imread('labrador.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 100 , 255, cv2.THRESH_BINARY)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnt = max(cnts, key=cv2.contourArea)
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img, ellipse, (0, 255, 0), 3)
x, y = ellipse[0]
cv2.circle(img, (int(x), int(y)), 10, (255, 255, 255), -1)
cv2.imshow("dog_with_ellipse", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Original image:
Original image with an ellipse
And result should look like:
How could I do that? And how to calculate the two points that could be used to divide an ellipse by length?
Add rotated labrador image:
Here is one way in Python/OpenCV. I note that the findEllipse() function returns the center, minor and major axes diameters and the angle.
Input:
import cv2
import numpy as np
import math
# read input
img = cv2.imread('labrador.jpg')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 100 , 255, cv2.THRESH_BINARY)[1]
# find largest contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# fit contour to ellipse and get ellipse center, minor and major diameters and angle in degree
ellipse = cv2.fitEllipse(big_contour)
(xc,yc),(d1,d2),angle = ellipse
print(xc,yc,d1,d1,angle)
# draw ellipse
result = img.copy()
cv2.ellipse(result, ellipse, (0, 255, 0), 3)
# draw circle at center
xc, yc = ellipse[0]
cv2.circle(result, (int(xc),int(yc)), 10, (255, 255, 255), -1)
# draw vertical line
# compute major radius
rmajor = max(d1,d2)/2
if angle > 90:
angle = angle - 90
else:
angle = angle + 90
print(angle)
xtop = xc + math.cos(math.radians(angle))*rmajor
ytop = yc + math.sin(math.radians(angle))*rmajor
xbot = xc + math.cos(math.radians(angle+180))*rmajor
ybot = yc + math.sin(math.radians(angle+180))*rmajor
cv2.line(result, (int(xtop),int(ytop)), (int(xbot),int(ybot)), (0, 0, 255), 3)
# save results
cv2.imwrite("labrador_ellipse.jpg", result)
cv2.imshow("labrador_thresh", thresh)
cv2.imshow("labrador_ellipse", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
ADDITION:
For this image:
the code generates this result:
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?