Finding Teeth of Gear by python opencv
Asked Answered
I

3

6

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: Input Image

Output Image I Got: OutPut Image

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?

Interleave answered 21/3, 2019 at 14:34 Comment(0)
C
7

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. sweeping around the gear

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()

gear teeth as a function of angle

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()

Fourier transform 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))
Chuipek answered 22/3, 2019 at 19:8 Comment(4)
Nice solution and animation! It would be more useful though if you showed your code - which you must surely have?Yseulte
hi @StephenMeschke, Its a great learning for me.Thanks for guiding by Step by Step. But I got error during running the code in ", contours, _ = cv2.findContours(edge_detected_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ValueError: not enough values to unpack (expected 3, got 2)" Any idea why?Interleave
I solved it as adding hierarchy in the line. but if I changed the image I saw and error which is "M = cv2.moments(max(edge_slice_contours, key = cv2.contourArea)) ValueError: max() arg is an empty sequence".. Any idea about this?Interleave
Hi @Subhasish1315. I changed code in Github Gist. You are getting an error because the gear wasn't detected correctly. I have also made some updates to my answer.Chuipek
N
4

enter image description here

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()
Novick answered 9/4, 2022 at 9:18 Comment(0)
S
2

Maybe the following solution works for you.

  • I added some slight median blurring after the bilateral filtering to improve the following edge detection (less tiny edges).
  • In findContours, I switched from RETR_TREE to RETR_EXTERNAL to get only the most outer contour(s).
  • For this, I determine the convex hull of the contour, and ensure, that per tooth, there is only one convex hull point.
  • The resulting number of these "sparse" convex hull points is the number of teeth.

(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)

Output

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!

Scuta answered 22/3, 2019 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.