I am learning OpenCv. I have a helical gear image to find teeth.
Till now I have tried to find the contours, and then count the teeth. I am able to find the contour also the coordinates of the contour. But I stuck to count the teeth. As I am new in OpenCV may be the way I am trying to finding the teeth is not correct.
My code:
import cv2
import numpy as np
import scipy as sp
import imutils
from skimage.morphology import reconstruction
import csv
raw_image = cv2.imread('./Gear Image/new1.jpg')
#cv2.imshow('Original Image', raw_image)
#cv2.waitKey(0)
bilateral_filtered_image = cv2.bilateralFilter(raw_image, 5, 175, 175)
#cv2.imshow('Bilateral', bilateral_filtered_image)
#cv2.waitKey(0)
edge_detected_image = cv2.Canny(bilateral_filtered_image, 75, 200)
#cv2.imshow('Edge', edge_detected_image)
#cv2.waitKey(0)
contours, hierarchy = cv2.findContours(edge_detected_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_list = []
for contour in contours:
approx = cv2.approxPolyDP(contour,0.01*cv2.arcLength(contour,True),True)
area = cv2.contourArea(contour)
if ((len(approx) > 5) & (len(approx) < 25) & (area > 50) ):
contour_list.append(contour)
cv2.drawContours(raw_image, contour_list, -1, (255,0,0), 2)
c = max(contours, key = cv2.contourArea)
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
cv2.circle(raw_image, (cX, cY), 5, (142, 152, 100), -1)
cv2.putText(raw_image, "centroid", (cX - 25, cY - 25),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (142, 152, 100), 2)
contour_length = "Number of contours detected: {}".format(len(contours))
cv2.putText(raw_image,contour_length , (20,40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (142, 152, 100), 2)
for c in range(len(contours)):
n_contour = contours[c]
for d in range(len(n_contour)):
XY_Coordinates = n_contour[d]
print(len(coordinates))
print(XY_Coordinates)
print(type(XY_Coordinates))
print(XY_Coordinates[0,[0]])
print(XY_Coordinates[0,[1]])
cv2.imshow('Objects Detected',raw_image)
cv2.waitKey(0)
Input images:
Output Image I Got:
After this stage, how can I calculate the teeth? I can use the coordinates to calculate the interval and calculate the teeth.
or is there another way to calculate the teeth after this stage?
The first part of my solution is similar to the answer @HansHirse posted, but I used a different method to count the teeth. My full code can be found here: link to full code for python3 opencv4. Check that the outer contour of the gear is correctly detected before proceeding. If the gear is not correctly detected, the rest of the answer will not work.
Before counting the teeth, I 'unwrapped' the gear. I did this by sweeping around the gear, and computing the distance from the center of the gear to the outside of the tooth.
This is the code that I used to sweep around the gear and find the distance from the center of the gear to the outside of the gear:
# Start at angle 0, and increment the angle 1/200 rad
angle = 0
increment = 1/200
# Create a list for the distances from the centroid to the edge of the gear tooth
distances = []
# Create an image for display purposes
display_image = raw_image.copy()
# Sweep around the circle (until one full revolution)
while angle < 2*math.pi:
# Compute a ray from the center of the circle with the current angle
img_size = max(raw_image.shape)
ray_end = int(math.sin(angle) * img_size + cX), int(math.cos(angle) * img_size + cY)
center = cX, cY
# Create mask
mask = np.zeros((raw_image.shape[0], raw_image.shape[1]), np.uint8)
# Draw a line on the mask
cv2.line(mask, center, ray_end, 255, 2)
# Mask out the gear slice (this is the portion of the gear the us below the line)
gear_slice = cv2.bitwise_and(raw_image, raw_image, mask = mask)
# Threshold the image
_, thresh = cv2.threshold(cv2.cvtColor(gear_slice, cv2.COLOR_BGR2GRAY), 0 , 255, 0)
# Find the contours in the edge_slice
_, edge_slice_contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Get the center of the edge slice contours
M = cv2.moments(max(edge_slice_contours, key = cv2.contourArea))
edge_location = int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])
cv2.circle(display_image, edge_location, 0, (0,255,0), 4)
# Find the distance from the center of the gear to the edge of the gear...at this specific angle
edge_center_distance = distance(center, edge_location)
# Find the xy coordinates for this point on the graph - draw blue circle
graph_point = int(angle*0.5*raw_image.shape[1]/math.pi), int(edge_center_distance+ 1.5*gear_radius)
cv2.circle(display_image, graph_point, 0, (0,255,0), 2)
# Add this distance to the list of distances
distances.append(-edge_center_distance)
# Create a temporary image and draw the ray on it
temp = display_image.copy()
cv2.line(temp, ray_end, (cX,cY), (0,0,255), 2)
# Show the image and wait
cv2.imshow('raw_image', temp)
vid_writer.write(temp)
k = cv2.waitKey(1)
if k == 27: break
# Increment the angle
angle += increment
# Clean up
cv2.destroyAllWindows()
The result of this is tooth distance from the center of the gear as a function of angle.
import matplotlib.pyplot as plt
plt.plot(distances)
plt.show()
It is now much easier to count the teeth, because they are the peaks (or in this case the valleys - more about this later) in the function. To count the peaks, I took the Fourier transform of the tooth-distance function.
import scipy.fftpack
# Calculate the Fourier transform
yf = scipy.fftpack.fft(distances)
fig, ax = plt.subplots()
# Plot the relevant part of the Fourier transform (a gear will have between 2 and 200 teeth)
ax.plot(yf[2:200])
plt.show()
The peak of the Fourier transform occurs at 37. Therefore, there are 37 valleys and 38 gear teeth.
num_teeth = list(yf).index(max(yf[2:200])) - 1
print('Number of teeth in this gear: ' + str(num_teeth))
import numpy as np
import cv2
img=cv2.imread('in2.jpg')
img_copy=img.copy()
bilated=cv2.bilateralFilter(img,5,175,175)
median=cv2.medianBlur(bilated, 5)
edges=cv2.Canny(median,75,200)
contours,_=cv2.findContours(edges,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
c=max(contours,key=cv2.contourArea)
cv2.drawContours(img_copy,[c],-1,(255,0,0),2)
M=cv2.moments(c)
cX=int(M['m10']/M['m00'])
cY=int(M['m01']/M['m00'])
hull=cv2.convexHull(c,clockwise=True,returnPoints=False)
defects=cv2.convexityDefects(c,hull)
inner=[]
outter=[]
for i in range(defects.shape[0]):
start_index,end_index,farthest_index,distance=defects[i,0]
start,end,far=[tuple(c[x][0]) for x in (start_index,end_index,farthest_index)]
cv2.circle(img_copy,start,15,(0,0,255),1)
cv2.circle(img_copy,far,10,[0,255,0],1)
inner.append(far)
inner.append(start)
distance=lambda x,y:np.sqrt(pow(x[0]-y[0],2)+pow(x[1]-y[1],2))
inner_min=min([distance((cX,cY),x) for x in inner])
outter_max=max([distance((cX,cY),x) for x in inner])
for radius in (inner_min,outter_max):
cv2.circle(img_copy,(int(cX),int(cY)),int(radius),(0,0,255),2)
mid_radius=(inner_min+outter_max)//2
cv2.circle(img_copy,(int(cX),int(cY)),int(mid_radius),(0,255,0),2)
mask=np.zeros_like(edges)
cv2.drawContours(mask,[c],-1,255,-1)
cv2.circle(mask,(int(cX),int(cY)),int(mid_radius),0,-1)
contours,_=cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
mask=cv2.cvtColor(mask,cv2.COLOR_GRAY2BGR)
center_info=[]
for cnt in contours:
(px,py),r=cv2.minEnclosingCircle(cnt)
dx=px-cX
dy=py-cY
cv2.circle(mask,(int(px),int(py)),3,(0,0,255),-1)
angle=(180/np.pi)*np.arctan2(dy,dx)
center_info.append([px,py,angle])
center_info=sorted(center_info,key=lambda x:x[2])
min_angle=min([x[-1] for x in center_info])
for i,(x,y,angle) in enumerate(center_info):
angle=((angle-min_angle)/180)*np.pi
text=f'{i}'
font=cv2.FONT_HERSHEY_SIMPLEX
fontScale=0.6
thickness=1
(w,h),_=cv2.getTextSize(text,font,fontScale,thickness)
scale=40
tcenter=(int(x-scale*np.cos(angle)-w/2),int(y-scale*np.sin(angle)+h/2))
cv2.putText(mask,f'{i+1}',tcenter,font,fontScale,(0,0,255),thickness)
cv2.imwrite('result.jpg',np.hstack((img_copy,mask)))
cv2.imshow('img_copy',img_copy)
cv2.imshow('mask',mask)
cv2.waitKey()
cv2.destroyAllWindows()
Maybe the following solution works for you.
findContours
, I switched from RETR_TREE
to RETR_EXTERNAL
to get only the most outer contour(s).(I removed some unnecessary code of yours to keep the answer short.)
import cv2
import numpy as np
raw_image = cv2.imread('images/vChAL.jpg')
bilateral_filtered_image = cv2.bilateralFilter(raw_image, 5, 175, 175)
# Added median blurring to improve edge detection
median_blurred_images = cv2.medianBlur(bilateral_filtered_image, 5)
edge_detected_image = cv2.Canny(median_blurred_images, 75, 200)
# Switched from RETR_TREE to RETR_EXTERNAL to only extract most outer contours
contours, _ = cv2.findContours(edge_detected_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_list = []
for contour in contours:
approx = cv2.approxPolyDP(contour,0.01*cv2.arcLength(contour,True),True)
area = cv2.contourArea(contour)
if ((len(approx) > 5) & (len(approx) < 25) & (area > 50) ):
contour_list.append(contour)
cv2.drawContours(raw_image, contour_list, -1, (255, 0, 0), 2)
c = max(contours, key = cv2.contourArea)
contour_length = "Number of contours detected: {}".format(len(contours))
cv2.putText(raw_image,contour_length , (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (142, 152, 100), 2)
# Determine convex hull of largest contour
hull = cv2.convexHull(c, clockwise = True, returnPoints = False)
# Debug: Draw "raw" convex hull points (green)
cv2.drawContours(raw_image, c[hull], -1, (0, 255, 0), 3)
# Determine convex hull, such that nearby convex hull points are "grouped"
sparsehull = []
for idx in hull:
if (len(sparsehull) == 0):
sparsehull.append(idx)
else:
last = sparsehull[-1]
diff = c[idx] - c[last]
if (cv2.norm(diff) > 40):
sparsehull.append(idx)
sparsehull = np.asarray(sparsehull)
# Debug: Draw "sparse2 convex hull points (red)
cv2.drawContours(raw_image, c[sparsehull], -1, (0, 0, 255), 3)
# Additional output on image
teeth_length = "Number of teeth detected: {}".format(len(sparsehull))
cv2.putText(raw_image, teeth_length , (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (142, 152, 100), 2)
cv2.imshow('Objects Detected', raw_image)
cv2.waitKey(0)
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With