简体   繁体   中英

Finding Teeth of Gear by python opencv

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.

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

产量

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM