如何在 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。第二条边产生于第二个零交叉点,从0255。您是否正在检测边缘以检测三角形的中间部分(最后一张图像中的点 2 到 5)?您是否只对获取点列表(上一张图像中的 1 到 4 点)感兴趣,还是需要完整的轮廓? 【参考方案1】:

这是我的方法。它主要是基于形态的。它涉及使用特殊内核卷积图像。此卷积识别三角形的端点以及中间线所在的交点。这将产生一个 points mask,其中包含与您要查找的点匹配的像素。之后,我们可以应用一点形态学来连接可能的重复点。剩下的就是获取这些点的坐标列表以供进一步处理。

这些是步骤:

    通过 Otsu 的阈值处理获取输入的二进制图像 获取二值图像的骨架 定义特殊的kernelconvolve骨架图像 应用形态扩张来连接可能的重复点 获取点的质心并将它们存储在列表中

代码如下:

# 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 上的信息。这篇文章主要展示了寻找形状端点的方法,但我将其扩展为也识别线的交叉点,例如三角形的中间部分。主要思想是卷积产生一个非常具体的值,其中在输入图像中找到黑白像素的模式。有关此想法背后的理论,请参阅帖子,但在这里,我们正在寻找两个值:11040。第一个发生在找到终点时。找到线交叉点时的第二个。让我们设置卷积:

# 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:如何从实时视频流中获取最新帧或跳过旧帧

如何使用 Python/Opencv 分割二进制图像中的附近元素

如何在Python中使用OpenCV的

如何将 keras 模型文件加载到 OpenCV 代码中?