简体   繁体   English

通过 python opencv 找到齿轮的齿

[英]Finding Teeth of Gear by python opencv

I am learning OpenCv.我正在学习 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.因为我是 OpenCV 的新手,所以我试图找到牙齿的方式可能不正确。

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:我得到的 Output 图像: 输出图像

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. 我的解决方案的第一部分类似于@HansHirse发布的答案,但我使用了一种不同的方法来计算牙齿。 My full code can be found here: link to full code for python3 opencv4 . 我的完整代码可以在这里找到: 链接到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. 傅立叶变换的峰值出现在37处。因此,有37个谷和38个齿轮齿。

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). findContours ,我从RETR_TREE切换到RETR_EXTERNAL以仅获得最外部的轮廓。
  • 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). 免责声明:我是Python的新手,特别是OpenCV的Python API(获胜的C ++)。 Comments, improvements, highlighting Python no-gos are highly welcome! 评论,改进,突出Python no-gos非常受欢迎!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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