如何解决删除并测量一条线 openCV
链接到底部的所有图片 我在一个箭头上画了一条线来捕捉那个箭头的角度。我想然后删除箭头,只保留线,并使用 cv2.minAreaRect 来确定角度。到目前为止,除了删除原始箭头之外,我已经完成了所有工作,这会导致 cv2.minAreaRect 边界框生成的角度不正确。
真的,我只想用贯穿箭头的粗体黑线来测量角度,而不是箭头本身。如果有人有想法使这项工作或更简单的方法,请告诉我。谢谢
代码:
import numpy as np
import cv2
image = cv2.imread("templates/a_15.png")
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(image,127,255,0)
contours,hierarchy = cv2.findContours(thresh,1,2)
cont = contours[0]
rows,cols = image.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cont,cv2.DIST_L2,0.01,0.01)
leftish = int((-x*vy/vx) + y)
rightish = int(((cols-x)*vy/vx)+y)
line = cv2.line(image,(cols-1,rightish),(0,leftish),0),10)
# thresholding
thresh = cv2.threshold(line,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# compute rotated bounding box based on all pixel values > 0 and
# use coordinates to compute a rotated bounding box of those coordinates
coordinates = np.column_stack(np.where(thresh > 0))
w = coordinates[0]
h = coordinates[1]
# Compute minimum rotated angle that contains entire image.
# Return angle values in the range [-90,0).
# As the rectangle rotates clockwise,angle values increase towards 0.
# Once 0 is reached,angle is set back to -90 degrees.
angle = cv2.minAreaRect(coordinates)[-1]
# for angles less than -45 degrees,add 90 degrees to angle to take the inverse.
if angle < - 45:
angle = -(90 + angle)
else:
angle = -angle
# rotate image
(h,w) = image.shape[:2]
center = (w // 2,h // 2) # image center
RM = cv2.getRotationMatrix2D(center,angle,1.0)
rotated = cv2.warpAffine(image,RM,(w,h),flags=cv2.INTER_CUBIC,borderMode=cv2.BORDER_REPLICATE)
# correction angle for validation
cv2.putText(rotated,"Angle {:.2f} degrees".format(angle),(10,30),cv2.FONT_HERSHEY_DUPLEX,0.9,2)
# output
print("[INFO] angle: {:.3f}".format(angle))
cv2.imshow("Line",line)
cv2.imshow("Input",image)
cv2.imshow("Rotated",rotated)
cv2.waitKey(0)
图片
解决方法
这是一个可能的解决方案。主要思想是识别箭头的“尖端”和“尾部”近似一些关键点。确定两端后,您可以绘制一条连接两个点的线。知道哪个端点是尖端也是一个优势,因为这样您就可以从恒定点测量角度。
实现这一目标的方法不止一种。我选择了我过去应用过的东西:我将使用 this 方法来识别整体形状的端点。我的假设是尖端会比尾部产生更多的点。之后,我会将所有端点分为两组:tip 和 tail。我可以为此使用 K-Means,因为它将返回两个集群的平均中心。之后,我们有了可以用一条线轻松连接的尖端和尾部点。这些是步骤:
- 将图像转换为灰度
- 获取图像的骨架,将形状标准化为
width
像素的1
- 应用链接中描述的方法来获取箭头的端点
- 将端点划分为两个集群并使用K-Means 获得它们的中心
- 用一条线连接两个端点
让我们看看代码:
# imports:
import cv2
import numpy as np
# image path
path = "D://opencvImages//"
fileName = "CoXeb.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Grayscale conversion:
grayscaleImage = cv2.cvtColor(inputImage,cv2.COLOR_BGR2GRAY)
grayscaleImage = 255 - grayscaleImage
# Extend the borders for the skeleton:
extendedImg = cv2.copyMakeBorder(grayscaleImage,5,cv2.BORDER_CONSTANT)
# Store a deep copy of the crop for results:
grayscaleImageCopy = cv2.cvtColor(extendedImg,cv2.COLOR_GRAY2BGR)
# Compute the skeleton:
skeleton = cv2.ximgproc.thinning(extendedImg,None,1)
第一步是获取箭的骨架。正如我所说,在识别形状端点的基于卷积的方法之前需要此步骤。计算骨架将形状标准化为一个像素 width
。但是,有时,如果形状太靠近“画布”边界,骨架可能会显示一些伪影。使用边框扩展可以避免这种情况。箭头的骨架是这样的:
![](https://i.imgur.com/zfJ9mvQ.png)
检查该图像。如果我们确定端点,尖端将显示至少 3
个点,而尾部至少 1
。这很方便 - 尖端总是比尾部有更多的点。如果我们能检测到这些点就好了……幸运的是,我们可以:
# Threshold the image so that white pixels get a value of 0 and
# black pixels a value of 10:
_,binaryImage = cv2.threshold(skeleton,128,10,cv2.THRESH_BINARY)
# Set the end-points kernel:
h = np.array([[1,1,1],[1,1]])
# Convolve the image with the kernel:
imgFiltered = cv2.filter2D(binaryImage,-1,h)
# Extract only the end-points pixels,those with
# an intensity value of 110:
binaryImage = np.where(imgFiltered == 110,255,0)
# The above operation converted the image to 32-bit float,# convert back to 8-bit uint
binaryImage = binaryImage.astype(np.uint8)
此端点检测方法 convolves
骨架具有特殊的 kernel
标识端点。它返回一个二进制图像,其中所有端点的值都为 110
。在对这个中间结果进行阈值处理后,我们得到了这个图像,它代表了箭头端点:
![](https://i.imgur.com/ZZUXmHF.png)
很好,如您所见,我们可以将点分成两个集群并获得它们的集群中心。听起来像是 K-Means 的工作,因为这正是它所做的。不过,我们首先需要处理我们的数据,因为 K-Means 对 float
数据的定义形状数组进行操作:
# Find the X,Y location of all the end-points
# pixels:
Y,X = binaryImage.nonzero()
# Reshape the arrays for K-means
Y = Y.reshape(-1,1)
X = X.reshape(-1,1)
Z = np.hstack((X,Y))
# 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,1.0)
ret,label,center = cv2.kmeans(floatPoints,2,criteria,cv2.KMEANS_RANDOM_CENTERS)
# Set the cluster count,find the points belonging
# to cluster 0 and cluster 1:
cluster1Count = np.count_nonzero(label)
cluster0Count = np.shape(label)[0] - cluster1Count
print("Elements of Cluster 0: "+str(cluster0Count))
print("Elements of Cluster 1: " + str(cluster1Count))
最后两行分别打印分配给 Cluster 0
Cluster 1
的端点。输出这个:
Elements of Cluster 0: 3
Elements of Cluster 1: 2
正如预期的那样 - 好吧,有点。似乎簇 0
是尖端,簇 2
是尾部!但尾巴实际上得到了 2
分。如果你仔细观察骨架的图像,你会看到尾部有一个小分叉。这就是为什么我们实际上得到了两分而不是一分。好的,让我们获取中心点并在原始输入上绘制它们:
# Look for the cluster of max number of points
# That cluster will be the tip of the arrow:
maxCluster = 0
if cluster1Count > cluster0Count:
maxCluster = 1
# Check out the centers of each cluster:
matRows,matCols = center.shape
# Store the ordered end-points here:
orderedPoints = [None] * 2
# Let's identify and draw the two end-points
# of the arrow:
for b in range(matRows):
# Get cluster center:
pointX = int(center[b][0])
pointY = int(center[b][1])
# Get the "tip"
if b == maxCluster:
color = (0,255)
orderedPoints[0] = (pointX,pointY)
# Get the "tail"
else:
color = (255,0)
orderedPoints[1] = (pointX,pointY)
# Draw it:
cv2.circle(grayscaleImageCopy,(pointX,pointY),3,color,-1)
cv2.imshow("End-Points",grayscaleImageCopy)
cv2.waitKey(0)
这是生成的图像:
![](https://i.imgur.com/O9EreNB.png)
尖端总是以红色绘制,而尾部以蓝色绘制。非常酷,让我们将这些点存储在 orderedPoints
列表中,并在新的“画布”中绘制最后一条线,尺寸与原始图像相同:
# Store the tip and tail points:
p0x = orderedPoints[1][0]
p0y = orderedPoints[1][1]
p1x = orderedPoints[0][0]
p1y = orderedPoints[0][1]
# Create a new "canvas" (image) using the input dimensions:
imageHeight,imageWidth = binaryImage.shape[:2]
newImage = np.zeros((imageHeight,imageWidth),np.uint8)
newImage = 255 - newImage
# Draw a line using the detected points:
(x1,y1) = orderedPoints[0]
(x2,y2) = orderedPoints[1]
lineColor = (0,0)
cv2.line(newImage,(x1,y1),(x2,y2),lineColor,thickness=2)
cv2.imshow("Detected Line",newImage)
cv2.waitKey(0)
覆盖在原始图像上的线条和仅包含线条的新图像:
![](https://i.imgur.com/z1IkF7K.png)
![](https://i.imgur.com/DKoUFn1.png)
听起来您想测量线的角度,但是因为您正在测量在原始图像中绘制的线,所以现在必须过滤掉原始图像以获得该线的准确测量值...用你知道端点的坐标绘制?
我猜:
- 制作更好的过滤器?
- 在空白图像中绘制线条并检测那里的角度?
- 根据已知坐标确定角度?
因为你只要求一条线,所以我试过了......只是制作了一个空白图像,在上面画了你检测到的线,然后在下游使用......
blankIm = np.ones((height,width,channels),dtype=np.uint8)
blankIm.fill(255)
line = cv2.line(blankIm,(cols-1,rightish),(0,leftish),0),10)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。