这是另一种可能的解决方案。我们将尝试分割时钟指针并通过霍夫线变换运行它们以检测线。现在,这种检测将产生通过时钟指针像素的所有可能的直线 - 产生多条线。您可以尝试使用线变换参数来将结果缩小到目标线,但您最终可能会得到一组线。我将尝试使用K-Means 对这些行进行聚类,以仅获得两条线,而不管霍夫线变换的输出如何。这些是步骤:
- 获取图像的二进制掩码以隔离时钟指针
- 应用一些形态学来消除噪音
- 通过霍夫线检测运行二进制掩码
- 在多行上使用 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)
这是最后一行(绿色):