【问题标题】:How to detect clock hands with hough lines detection如何使用霍夫线检测来检测时钟指针
【发布时间】:2021-08-04 20:02:57
【问题描述】:

我想从模拟时钟中获取时间。现在我有点卡住了,我设法得到了分段图像(虽然我无法删除它的底部......),并进行了 Canny 检测。我遇到的问题是,我无法移除的底部,以及时钟指针的检测。我的目标是以一种我可以计算角度然后从这些角度计算时间的方式来检测手。我知道我需要霍夫线变换,但我不太明白它是如何工作的,如何设置参数。

原始、分割和 Canny 检测到的图片:

这是我用来到达那里的代码:

img = cv2.imread('clock.jpg')
cv2.imshow('img', img)
cv2.waitKey(0)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
cv2.imshow('blur', blur)
cv2.waitKey(0)

circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, 1, 20, param1=20, param2=100, minRadius=0, maxRadius=0)
detected_cricles = np.uint16(np.around(circles))
circle = detected_cricles[0][0]

x = circle[0]
y = circle[1]
r = circle[2]

rect = (x - r, y - r, x+r, y+(r-10))
mask = np.zeros(img.shape[:2], dtype = np.uint8)
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)

cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 1, cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 1) + (mask == 3), 255, 0).astype('uint8')
segmented = cv2.bitwise_and(img, img, mask=mask2)
cv2.imshow('segmented', segmented)
cv2.waitKey(0)

blur = cv2.GaussianBlur(segmented, (11,11), 0)
cv2.imshow('blur2', blur)
cv2.waitKey(0)

canny = cv2.Canny(blur, 30, 150, None, 3)
cv2.imshow('canny', canny)
cv2.waitKey(0)

【问题讨论】:

  • 霍夫圆不会得到时钟臂。你需要霍夫线
  • 是的,我使用霍夫圆来获取钟面,但我不知道如何在这里实现霍夫线
  • 可以发原图吗?
  • 是的,已添加到帖子中

标签: python opencv image-processing hough-transform


【解决方案1】:

这是在 Python/OpenCV 中使用 HoughLinesP 的一种方法。该方法在获得霍夫线之前使用阈值、轮廓和细化。我会留给你计算线端点的角度。

输入:

import cv2
import numpy as np
from skimage.morphology import skeletonize

# Read image
img = cv2.imread('clock.jpg')
hh, ww = img.shape[:2]

# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# threshold
thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]

# invert so shapes are white on black background
thresh = 255 - thresh

# get contours and save area
cntrs_info = []
contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
index=0
for cntr in contours:
    area = cv2.contourArea(cntr)
    cntrs_info.append((index,area))
    index = index + 1

# sort contours by area
def takeSecond(elem):
    return elem[1]
cntrs_info.sort(key=takeSecond, reverse=True)

# get third largest contour
arms = np.zeros_like(thresh)
index_third = cntrs_info[2][0]
cv2.drawContours(arms,[contours[index_third]],0,(1),-1)

#arms=cv2.ximgproc.thinning(arms)
arms_thin = skeletonize(arms)
arms_thin = (255*arms_thin).clip(0,255).astype(np.uint8)

# get hough lines and draw on copy of input
result = img.copy()
lineThresh = 15
minLineLength = 20
maxLineGap = 100
max
lines = cv2.HoughLinesP(arms_thin, 1, np.pi/180, lineThresh, None, minLineLength, maxLineGap)

for [line] in lines:
    x1 = line[0]
    y1 = line[1]
    x2 = line[2]
    y2 = line[3]
    cv2.line(result, (x1,y1), (x2,y2), (0,0,255), 2)   

# save results
cv2.imwrite('clock_thresh.jpg', thresh)
cv2.imwrite('clock_arms.jpg', (255*arms).clip(0,255).astype(np.uint8))
cv2.imwrite('clock_arms_thin.jpg', arms_thin)
cv2.imwrite('clock_lines.jpg', result)

cv2.imshow('thresh', thresh)
cv2.imshow('arms', (255*arms).clip(0,255).astype(np.uint8))
cv2.imshow('arms_thin', arms_thin)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

阈值图像:

手臂轮廓:

细化(骨架):

输入的霍夫线段:

【讨论】:

    【解决方案2】:

    这是另一种可能的解决方案。我们将尝试分割时钟指针并通过霍夫线变换运行它们以检测线。现在,这种检测将产生通过时钟指针像素的所有可能的直线 - 产生多条线。您可以尝试使用线变换参数来将结果缩小到目标线,但您最终可能会得到一组线。我将尝试使用K-Means 对这些行进行聚类,以仅获得两条线,而不管霍夫线变换的输出如何。这些是步骤:

    1. 获取图像的二进制掩码以隔离时钟指针
    2. 应用一些形态学来消除噪音
    3. 通过霍夫线检测运行二进制掩码
    4. 在多行上使用 K-means 以得到仅 2(平均)行(每个时针一个)

    让我们看看代码:

    # Imports
    import cv2
    import numpy as np
    
    # Read image
    imagePath = "D://opencvImages//"
    inputImage = cv2.imread(imagePath+"orFGl.jpg")
    
    # Store deep copy for results:
    originalImg = inputImage.copy()
    
    # Convert BGR back to grayscale:
    grayInput = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
    
    # Threshold via Otsu + bias adjustment:
    threshValue, binaryImage = cv2.threshold(grayInput, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
    

    第一位是微不足道的,并产生这个二进制掩码:

    我们可以通过一些形态来摆脱小元素。让我们应用 erosion 后跟 dilation 来过滤除较大组件(时钟指针)之外的所有内容:

    # Set morph operation iterations:
    opIterations = 1
    
    # Get the structuring element:
    structuringElement = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    
    # Perform Erode:
    erodeImg = cv2.morphologyEx(binaryImage, cv2.MORPH_ERODE, structuringElement, None, None, opIterations, cv2.BORDER_REFLECT101)
    # Perform Dilate:
    dilateImg = cv2.morphologyEx(erodeImg, cv2.MORPH_DILATE, structuringElement, None, None, opIterations, cv2.BORDER_REFLECT101)
    

    这会产生这个图像:

    非常好,几乎所有的噪音都消失了。让我们直接通过线路检测运行它,看看我们得到了什么样的结果。此外,我还准备了一些列表来存储每个开始 (x1, y1) 和结束 (x2, y2) 的行:

    # Set HoughLinesP parameters:
    lineThresh = 50
    minLineLength = 20
    maxLineGap = 100
    # Run the line detection:
    lines = cv2.HoughLinesP(dilateImg, 1, np.pi/180, lineThresh, None, minLineLength, maxLineGap)
    
    # Prepare some lists to store every coordinate of the detected lines:
    X1 = []
    X2 = []
    Y1 = []
    Y2 = []
    
    # Store and draw the lines:
    for [currentLine] in lines:
    
        # First point:
        x1 = currentLine[0]
        y1 = currentLine[1]
        X1.append(x1)
        Y1.append(y1)
    
        # Second point:
        x2 = currentLine[2]
        y2 = currentLine[3]
        X2.append(x2)
        Y2.append(y2)
    
        # Draw the lines:
        cv2.line(originalImg, (x1,y1), (x2,y2), (0,0,255), 2)
        cv2.imshow("Lines", originalImg)
        cv2.waitKey(0)
    

    这是结果:

    如您所见,有多行。幸运的是,这些线条聚集在两个非常明显的组中:左手和右手。如果我们将四个坐标聚类分成两组,我们可以得到每只手的平均起点和终点。这可以通过应用聚类算法来完成,在本例中为K-Means。 K-means 将需要四个数组来保存数据以生成两个聚类中心。在给它我们的数据之前,我们需要按照K-means 期望的方式重塑它:

    # Reshape the arrays for K-means
    X1 = np.array(X1)
    Y1 = np.array(Y1)
    X2 = np.array(X2)
    Y2 = np.array(Y2)
    
    X1dash = X1.reshape(-1,1)
    Y1dash = Y1.reshape(-1,1)
    X2dash = X2.reshape(-1,1)
    Y2dash = Y2.reshape(-1,1)
    
    # Stack the data
    Z = np.hstack((X1dash, Y1dash, X2dash, Y2dash))
    
    # K-means operates on 32-bit float data:
    floatPoints = np.float32(Z)
    
    # Set the convergence criteria and call K-means:
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    # Set the desired number of clusters
    K = 2
    ret, label, center = cv2.kmeans(floatPoints, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
    

    结果在center 数组中。在这里,我们给出了最后一对线。让我们遍历它并在原始图像上绘制它们:

    # Loop through the center points
    # and draw the lines:
    for p in range(len(center)):
    
        # Get line points:
        print(center[p])
        x1 = int(center[p][0])
        y1 = int(center[p][1])
        x2 = int(center[p][2])
        y2 = int(center[p][3])
    
        cv2.line(originalImg, (x1, y1), (x2, y2), (0, 255, 0), 1)
        cv2.imshow("Lines", originalImg)
        cv2.waitKey(0)
    

    这是最后一行(绿色):

    【讨论】:

    • 不错的方法。但是为什么有 4 条红线而不是 2 条呢?两条平行但偏移的线集。
    • 谢谢,@fmw42。红线是霍夫线检测的原始输出 - 将被分为两组。聚类的结果是两条绿线。我把所有东西都画在同一张图片上,绿线有点难看。
    • 好的。但我什至看不到绿线。也许更好地在输入的另一个副本上再次绘制它们。但是使用 kmeans 方法做得很好
    • 谢谢,@fmw42。你说的对!我至少应该使用更粗的线条。我会记住这一点,以便将来得到答案。
    • 在你提到它之后,我现在可以看到细的绿线了。很抱歉投诉。
    猜你喜欢
    • 2013-03-09
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多