【问题标题】:Python opencv detect shapes with intersectionsPython opencv检测具有交叉点的形状
【发布时间】:2021-04-06 22:31:00
【问题描述】:

我们正在使用 Python openCV 进行形状检测。 我们找到 approx_poly_dp 然后计算顶点数。但是,当对象有很多交叉点时,该过程不起作用(见下图第二张)。 Opencv 无法检测单个对象,只能找到一个大形状。在处理交叉点时,在给定一般图片输入的情况下,找到形状的最佳方法是什么。或者这不是 opencv 的功能,也许更适合机器学习?

    image_original = cv2.imread(file_name)
    image_gray = cv2.cvtColor(image_original, cv2.COLOR_BGR2GRAY)
    image_blur = cv2.GaussianBlur(image_gray, (5, 5), 0)
    image_morph = cv2.morphologyEx(image_blur, cv2.MORPH_OPEN, kernel)
    image_canny = cv2.Canny(image_morph, 50, 50)
    image_dilate = cv2.dilate(image_canny, kernel, iterations=2)
    image_final = cv2.erode(image_dilate, kernel, iterations=1)

    contours, hierarchy = cv2.findContours(image_final, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    peri = cv2.arcLength(contours, True)
    approx = cv2.approxPolyDP(contours, 0.04 * peri, True)

    if len(approx) == 3:
        shape = "triangle"
    elif len(approx) == 4:
        (x, y, w, h) = cv2.boundingRect(approx)
        ar = w / float(h)
        shape = "square" if ar >= 0.95 and ar <= 1.05 else "rectangle"
    elif len(approx) == 5:
        shape = "pentagon"
    else:
        shape = "circle"

路口图片:

【问题讨论】:

  • 获取每个形状的边界框并检查框之间可能的重叠。
  • @eldesgraciado 当我尝试获取轮廓或 polydp 时,我只在外面得到一个对象,它没有得到内部线条,你有这个吗?谢谢
  • 对于完全任意的图像,您很可能找不到仅涉及(基本)图像处理技术的通用解决方案。也许,如果您可以缩小图像的外观(例如,只有每两个形状相交,如下图所示),那么可能会有针对这种特殊情况的解决方案。照原样,要处理的域太模糊(或太大)。
  • @mattsmith5 如果你使用了cv2.RETR_EXTERNAL,你只会得到外线。你可以试试cv2.RETR_TREEcv2.RETR_LIST
  • 我建议先检测和擦除圆圈。使用线条更容易。如果图像像您的示例一样简单,这不是机器学习任务。

标签: python python-3.x opencv


【解决方案1】:

以下过程可能适用于简单的数字:

查找连通组件,并查找每个组件的相邻组件,忽略背景和轮廓组件。

尝试使用相邻组件组合的组件,并将其提供给您已有的形状分类器。作为预处理步骤,您可能必须使用形态闭合将组件组合成单个 blob。

import cv2 as cv
import numpy as np
from itertools import combinations

im = np.zeros((512, 512), dtype=np.uint8)
im = cv.rectangle(im, (100, 200), (400, 400), 255, 2)
im = cv.circle(im, (250, 380), 100, 255, 2)
im = cv.rectangle(im, (50, 50), (250, 250), 255, 2)

ret, bw = cv.threshold(im, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
ncomp, labels, stats, centroids = cv.connectedComponentsWithStats(bw)

def getNeighbors(labels, n):
    r = 4 # you might have to change this depending on outline thickness
    se = cv.getStructuringElement(cv.MORPH_CROSS, (2*r+1, 2*r+1))
    neighbors = cv.dilate((labels==n).astype(np.uint8), se)
    # ignore background, outline and the component of interest
    return np.setdiff1d(np.unique(labels[neighbors==1]), [0, 1, n])

def shapes(arr, i):
    p = arr.tolist()
    for r in range(1, len(p)+1):
        for l in list(combinations(p, r)):
            print l + (i, )

对于下图,您将获得已连接组件的列出组合。请注意,我没有费心删除重复项。

for i in range(2, ncomp):
    shapes(getNeighbors(labels, i), i)

(3, 2)
(2, 3)
(4, 3)
(2, 4, 3)
(3, 4)
(5, 4)
(3, 5, 4)
(4, 5)
(6, 5)
(4, 6, 5)
(5, 6)

我不会说这是一个有效的解决方案,因为它涉及尝试所有邻居组合,但我发布此内容以防有人发现此基于图像处理的解决方案有用。

【讨论】:

  • 很有趣,我也是这样。我正在考虑获取连接组件的所有可能组合,并从他的方法中使用cv2.approxPolyDP。但它并没有真正起作用,因为即使对于奇怪的图像,它也会返回一个形状,例如,只有标签 3 和 4 的图像会返回一个矩形。因此,需要一个更好的形状分类器。
【解决方案2】:

我想出了一个解决方案,通过使用父子轮廓关系和轮廓平滑方法从相交的图像中检测形状。

在上图中,红色边框是父轮廓,黄色是子轮廓的边框。使用父轮廓迭代所有子轮廓应用轮廓平滑方法来提取形状。 my questionian-chu 讨论了这种错误平滑方法

import cv2
import numpy as np


def get_parent_contour(contours, hierarchy):
    parent = hierarchy[0, :, 3]
    unique_parent, counts_parent = np.unique(parent, return_counts=True)

    parent_contour_id = -1
    max_area = 0

    for c in unique_parent:
        if c != -1:
            area = cv2.contourArea(contours[c])
            if area > max_area:
                max_area = area
                parent_contour_id = c

    return parent_contour_id, contours[parent_contour_id]


def get_child_contours(contours, hierarchy, parent_contour_id):
    parent = hierarchy[0, :, 3]

    # Select all contour whose parent is selected parent above
    child_contour_id = [i for i, v in enumerate(parent) if v == parent_contour_id]
    child_contours = [contours[i] for i in child_contour_id]

    return child_contour_id, child_contours


def smoothed(contour, dists, cutoff):
    smooth_con = []
    for a in range(len(dists)):
        if dists[a] < cutoff:
            smooth_con.append(contour[a])
    return np.asarray(smooth_con)


# get the distance list for an array of points
def distList(src, other):
    dists = []
    for point in src:
        point = point[0]  # drop extra brackets
        _, dist = closestPoint(point, other)
        dists.append(dist)
    return dists


# returns squared distance of two points
def squaredDist(one, two):
    dx = one[0] - two[0]
    dy = one[1] - two[1]
    return dx * dx + dy * dy


# find closest point (just do a linear search)
def closestPoint(point, arr):
    # init tracker vars
    closest = None
    best_dist = np.Inf

    # linear search
    for other in arr:
        other = other[0]  # remove extra brackets
        dist = squaredDist(point, other)
        if dist < best_dist:
            closest = other
            best_dist = dist
    return closest, best_dist


image = cv2.imread("image.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

_, threshold = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("threshold", threshold)

contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

blank_image = np.zeros_like(image)

if hierarchy is not None:

    for contour in contours:
        color = np.random.randint(0, 255, size=(3,))
        color = (int(color[0]), int(color[1]), int(color[2]))
        cv2.drawContours(blank_image, [contour], 0, color, 2)

cv2.imshow("contour image", blank_image)

parent_contour_id, parent_contour = get_parent_contour(contours, hierarchy)
child_contour_ids, child_contours = get_child_contours(contours, hierarchy, parent_contour_id)

blank_image_2 = np.zeros_like(image)
cv2.drawContours(blank_image_2, [parent_contour], 0, (0, 0, 255), 2)

for child_cnt in child_contours:
    cv2.drawContours(blank_image_2, [child_cnt], 0, (0, 255, 255), 1)
cv2.imshow("parent child contours", blank_image_2)

for child_cnt in child_contours:

    one = parent_contour
    two = child_cnt

    # get distances
    one_dists = distList(one, two)
    two_dists = distList(two, one)
    try:
        # dump values greater than 10
        smooth_one = smoothed(one, one_dists, 35)
        smooth_two = smoothed(two, two_dists, 35)

        # draw new contour
        blank_image_3 = np.zeros_like(threshold)
        cv2.drawContours(blank_image_3, [smooth_one], -1, 255, -1)
        cv2.drawContours(blank_image_3, [smooth_two], -1, 0, -1)

        cv2.imshow("smooth image", blank_image_3)
        
        # you can apply filters on smoothed contour image to accepting and reject smooth contour
        # And apply single shape detection algorithm on smoothed contour image to find shapes
        
    except:
        print("no near points. ")

    cv2.waitKey(0)

cv2.destroyAllWindows()

平滑输出图像

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 2017-07-01
    • 2016-06-01
    • 2019-02-25
    • 2015-03-12
    • 2021-03-31
    • 1970-01-01
    相关资源
    最近更新 更多