Поиск зубьев шестерни с помощью python opencv

#python #opencv #opencv-contour

#python #opencv #контур

Вопрос:

Я изучаю OpenCV. У меня есть изображение винтовой шестерни для поиска зубьев.

До сих пор я пытался найти контуры, а затем посчитать зубья. Я могу найти контур, а также координаты контура. Но я решил посчитать зубья. Поскольку я новичок в OpenCV, возможно, способ, которым я пытаюсь найти зубья, неверен.

Мой код:

 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) amp; (len(approx) < 25) amp; (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)
  

Входные изображения: Входное изображение

На выходе я получил изображение: Выходное изображение

После этого этапа, как я могу вычислить зубья? Я могу использовать координаты для вычисления интервала и вычисления зубьев.

или есть другой способ вычислить зубья после этого этапа?

Ответ №1:

Первая часть моего решения похожа на ответ, опубликованный @HansHirse, но я использовал другой метод для подсчета зубьев. Мой полный код можно найти здесь: ссылка на полный код для python3 opencv4. Прежде чем продолжить, убедитесь, что внешний контур шестерни определен правильно. Если шестерня обнаружена неправильно, остальная часть ответа не сработает.

Перед подсчетом зубьев я «развернул» шестерню. Я сделал это, обведя шестерню и вычислив расстояние от центра шестерни до внешней стороны зуба. обход шестеренки

Это код, который я использовал, чтобы обойти шестерню и найти расстояние от центра шестерни до внешней стороны шестерни:

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

Результатом этого является расстояние зуба от центра шестерни как функция угла.

 import matplotlib.pyplot as plt
plt.plot(distances)
plt.show()
  

зубья шестерни как функция угла

Теперь намного проще подсчитывать зубья, потому что они являются вершинами (или, в данном случае, впадинами — подробнее об этом позже) в функции. Чтобы подсчитать пики, я взял
Преобразование Фурье функции расстояния между зубьями.

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

Преобразование Фурье
Пик преобразования Фурье приходится на 37. Таким образом, имеется 37 впадин и 38 зубьев шестерни.

 num_teeth = list(yf).index(max(yf[2:200])) - 1
print('Number of teeth in this gear: '   str(num_teeth))
  

Комментарии:

1. Отличное решение и анимация! Однако было бы полезнее, если бы вы показали свой код — который у вас наверняка должен быть?

2. привет @StephenMeschke, это отличный урок для меня. Спасибо за пошаговое руководство. Но я получил ошибку при запуске кода в «, contours, _ = cv2.findContours(edge_detected_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ValueError: недостаточно значений для распаковки (ожидалось 3, получено 2)» Есть идеи, почему?

3. Я решил это как добавление иерархии в строку. но если бы я изменил изображение, которое я увидел, и ошибка была бы «M = cv2.moments(max(edge_slice_contours, key = cv2.contourrea)) Ошибка значения: max() arg — это пустая последовательность «.. Есть идеи по этому поводу?

4. Привет @Subhasish1315. Я изменил код в сути Github . Вы получаете сообщение об ошибке, потому что шестерня была обнаружена неправильно. Я также внес некоторые обновления в свой ответ.

Ответ №2:

введите описание изображения здесь

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

Ответ №3:

Возможно, вам подойдет следующее решение.

  • Я добавил небольшое размытие медианы после двусторонней фильтрации, чтобы улучшить последующее обнаружение краев (меньше крошечных краев).
  • В findContours я переключился с RETR_TREE на RETR_EXTERNAL , чтобы получить только самый внешний контур (ы).
  • Для этого я определяю выпуклую оболочку контура и гарантирую, что на каждый зуб имеется только одна точка выпуклой оболочки.
  • Результирующее количество этих «разреженных» точек выпуклой оболочки равно количеству зубьев.

(Я удалил ваш ненужный код, чтобы сделать ответ коротким.)

 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) amp; (len(approx) < 25) amp; (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)
  

Вывод

Отказ от ответственности: Я новичок в Python в целом, и особенно в Python API OpenCV (C для победы). Комментарии, улучшения, выделение недостатков Python настоятельно приветствуются!