如何在 OpenCV - Python 中获得没有冗余的内部轮廓点
Posted
技术标签:
【中文标题】如何在 OpenCV - Python 中获得没有冗余的内部轮廓点【英文标题】:How can i get the inner contour points without redundancy in OpenCV - Python 【发布时间】:2021-06-30 22:12:06 【问题描述】:我是 OpenCV 新手,问题是我需要获取所有轮廓点。这很容易在 findContours 方法中设置 cv2.RETR_TREE 模式。事情是这样,返回冗余坐标。所以,例如,在这个多边形中,我不想得到这样的轮廓点:
但是像这样:
所以根据第一张图像,绿色是使用 RETR_TREE 模式检测到的轮廓,而点 1-2、3-5、4-6、... 是多余的,因为它们彼此非常接近。我需要将这些冗余点合二为一,并将其附加到 customContours 数组中。 目前,我只有第一张图片的代码,设置点之间的距离和点坐标:
def getContours(img, minArea=20000, cThr=[100, 100]):
font = cv2.FONT_HERSHEY_COMPLEX
imgColor = img
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgBlur = cv2.GaussianBlur(imgGray, (5, 5), 1)
imgCanny = cv2.Canny(imgBlur, cThr[0], cThr[1])
kernel = np.ones((5, 5))
imgDial = cv2.dilate(imgCanny, kernel, iterations=3)
imgThre = cv2.erode(imgDial, kernel, iterations=2)
cv2.imshow('threshold', imgThre)
contours, hierachy = cv2.findContours(imgThre, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
customContours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area > minArea:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.009*peri, True)
bbox = cv2.boundingRect(approx)
customContours.append([len(approx), area, approx, bbox, cnt])
print('points: ', len(approx))
n = approx.ravel()
i = 0
for j in n:
if i % 2 == 0:
x = n[i]
y = n[i + 1]
string = str(x)+" " + str(y)
cv2.putText(imgColor, str(i//2+1) + ': ' + string, (x, y), font, 2, (0, 0, 0), 2)
i = i + 1
customContours = sorted(customContours, key=lambda x: x[1], reverse=True)
for cnt in customContours:
cv2.drawContours(imgColor, [cnt[2]], 0, (0, 0, 255), 5)
return imgColor, customContours
你能帮我了解一下关于第二张图片的真正要点吗?
(编辑 01/07/21)
我想要一个通用的解决方案,因为图像可能更复杂,例如下图: 注意: 注意中间的箭头(点 17 和 18)没有封闭区域,所以不是要研究的多边形。那么,那个区域就没有兴趣获得他的积分。另外,注意点的顺序并不重要,但如果入口是孔图像,它应该知道有 4 个多边形,所以每个多边形的点从 0 开始,然后是 1,等等。
【问题讨论】:
获得“内部”轮廓的原因是边缘检测器 - 这会产生两个边缘:第一个是第一个零交叉,当像素强度沿一个维度(水平或垂直)从255
更改为0
。第二条边产生于第二个零交叉点,从0
到255
。您是否正在检测边缘以检测三角形的中间部分(最后一张图像中的点 2 到 5)?您是否只对获取点列表(上一张图像中的 1 到 4 点)感兴趣,还是需要完整的轮廓?
【参考方案1】:
这是我的方法。它主要是基于形态的。它涉及使用特殊内核卷积图像。此卷积识别三角形的端点以及中间线所在的交点。这将产生一个 points mask,其中包含与您要查找的点匹配的像素。之后,我们可以应用一点形态学来连接可能的重复点。剩下的就是获取这些点的坐标列表以供进一步处理。
这些是步骤:
-
通过 Otsu 的阈值处理获取输入的二进制图像
获取二值图像的骨架
定义特殊的kernel和convolve骨架图像
应用形态扩张来连接可能的重复点
获取点的质心并将它们存储在列表中
代码如下:
# Imports:
import numpy as np
import cv2
# image path
path = "D://opencvImages//"
fileName = "triangle.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Prepare a deep copy for results:
inputImageCopy = inputImage.copy()
# Convert BGR to Grayscale
grayImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Threshold via Otsu:
_, binaryImage = cv2.threshold(grayImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
第一位计算二进制图像。非常简单。我使用this image 作为基础,这只是您发布的没有注释的内容的清理版本。这是生成的二进制图像:
现在,要执行卷积,我们必须首先获得图像“骨架”。 skeleton 是二进制图像的一个版本,其中行已被规范化为具有1 pixel
的宽度。这很有用,因为我们可以使用3 x 3
内核对图像进行卷积并寻找特定的像素模式。让我们使用 OpenCV 的扩展图像处理模块来计算骨架:
# Get image skeleton:
skeleton = cv2.ximgproc.thinning(binaryImage, None, 1)
这是获得的图像:
我们现在可以应用卷积了。该方法基于Mark Setchell 在此post 上的信息。这篇文章主要展示了寻找形状端点的方法,但我将其扩展为也识别线的交叉点,例如三角形的中间部分。主要思想是卷积产生一个非常具体的值,其中在输入图像中找到黑白像素的模式。有关此想法背后的理论,请参阅帖子,但在这里,我们正在寻找两个值:110
和 40
。第一个发生在找到终点时。找到线交叉点时的第二个。让我们设置卷积:
# 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 convolution kernel:
h = np.array([[1, 1, 1],
[1, 10, 1],
[1, 1, 1]])
# Convolve the image with the kernel:
imgFiltered = cv2.filter2D(binaryImage, -1, h)
# Create list of thresholds:
thresh = [110, 40]
第一部分已经完成。我们将分两步检测端点和交叉点。每一步都会产生一个部分结果,我们可以OR
这两个结果得到一个最终的掩码:
# Prepare the final mask of points:
(height, width) = binaryImage.shape
pointsMask = np.zeros((height, width, 1), np.uint8)
# Perform convolution and create points mask:
for t in range(len(thresh)):
# Get current threshold:
currentThresh = thresh[t]
# Locate the threshold in the filtered image:
tempMat = np.where(imgFiltered == currentThresh, 255, 0)
# Convert and shape the image to a uint8 height x width x channels
# numpy array:
tempMat = tempMat.astype(np.uint8)
tempMat = tempMat.reshape(height,width,1)
# Accumulate mask:
pointsMask = cv2.bitwise_or(pointsMask, tempMat)
这是点的最终掩码:
请注意,白色像素是与我们的目标模式匹配的位置。这些是我们正在寻找的点。由于形状不是一个完美的三角形,有些点可能会重复。我们可以通过应用形态膨胀来“合并”相邻的 blob:
# Set kernel (structuring element) size:
kernelSize = 7
# Set operation iterations:
opIterations = 3
# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
# Perform Dilate:
morphoImage = cv2.morphologyEx(pointsMask, cv2.MORPH_DILATE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
这是结果:
非常好,我们现在有了大的像素簇(或斑点)。要获取它们的坐标,一种可能的方法是获取这些轮廓的边界矩形并计算它们的质心:
# Look for the outer contours (no children):
contours, _ = cv2.findContours(morphoImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Store the points here:
pointsList = []
# Loop through the contours:
for i, c in enumerate(contours):
# Get the contours bounding rectangle:
boundRect = cv2.boundingRect(c)
# Get the centroid of the rectangle:
cx = int(boundRect[0] + 0.5 * boundRect[2])
cy = int(boundRect[1] + 0.5 * boundRect[3])
# Store centroid into list:
pointsList.append( (cx,cy) )
# Set centroid circle and text:
color = (0, 0, 255)
cv2.circle(inputImageCopy, (cx, cy), 3, color, -1)
font = cv2.FONT_HERSHEY_COMPLEX
string = str(cx) + ", " + str(cy)
cv2.putText(inputImageCopy, str(i) + ':' + string, (cx, cy), font, 0.5, (255, 0, 0), 1)
# Show image:
cv2.imshow("Circles", inputImageCopy)
cv2.waitKey(0)
这些是位于原始输入中的点:
另请注意,我已将它们的坐标存储在 pointsList
列表中:
# Print the list of points:
print(pointsList)
这会将质心打印为元组 (centroidX, centroidY)
:
[(717, 971), (22, 960), (183, 587), (568, 586), (388, 98)]
【讨论】:
嗨,这太棒了,但应该只适用于这个例子。我想要一些通用的东西,我设置了一个带有中间边框的三角形,但可能是不同且复杂的东西,请检查编辑。以上是关于如何在 OpenCV - Python 中获得没有冗余的内部轮廓点的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 opencv 和多线程 (logitech c920) 在 python 中捕获视频
获得最大置信度的边界框 pandas opencv python
OpenCV-Python:如何从实时视频流中获取最新帧或跳过旧帧