Python 如何使用OpenCV findContours检测相交形状?
在黑白图像中有两个相交的椭圆。我正试图使用OpenCV findContours,使用此代码(以及下面的附图)将单独的形状识别为单独的轮廓 但是,发现了四个轮廓,其中没有一个是原始轮廓:Python 如何使用OpenCV findContours检测相交形状?,python,opencv,contour,shape-recognition,Python,Opencv,Contour,Shape Recognition,在黑白图像中有两个相交的椭圆。我正试图使用OpenCV findContours,使用此代码(以及下面的附图)将单独的形状识别为单独的轮廓 但是,发现了四个轮廓,其中没有一个是原始轮廓: 如何配置OpenCV.findContours以识别这两个单独的形状?(注意,我已经玩过Hough圆,发现它对于我正在分析的图像不可靠)从哲学上讲,你想找到两个圆,因为你搜索它们,你需要中心和半径。从图形上看,这些图形是连接在一起的,我们可以看到它们是分开的,因为我们知道“圆”是什么,并推断出坐标,这些坐标
如何配置OpenCV.findContours以识别这两个单独的形状?(注意,我已经玩过Hough圆,发现它对于我正在分析的图像不可靠)从哲学上讲,你想找到两个圆,因为你搜索它们,你需要中心和半径。从图形上看,这些图形是连接在一起的,我们可以看到它们是分开的,因为我们知道“圆”是什么,并推断出坐标,这些坐标与重叠的部分相匹配 那么,如何为每个轮廓(或在某些情况下使用椭圆并使用其参数)找到最小封闭圆呢 然后说,在一张清晰的图像中画一个圆,然后用掩模取不为零的像素坐标,或者通过一步一步地画一个圆来计算它们 然后以一定精度将这些坐标与其他轮廓中的坐标进行比较,并将匹配坐标附加到当前轮廓
最后:在清晰的画布上绘制延伸轮廓,并将HoughCircles应用于单个不重叠的圆。(或者计算圆心和半径,一个圆的坐标,并与轮廓进行精确比较。)从哲学上讲,你想找到两个圆,因为你搜索它们,你需要圆心和半径。从图形上看,这些图形是连接在一起的,我们可以看到它们是分开的,因为我们知道“圆”是什么,并推断出坐标,这些坐标与重叠的部分相匹配 那么,如何为每个轮廓(或在某些情况下使用椭圆并使用其参数)找到最小封闭圆呢 然后说,在一张清晰的图像中画一个圆,然后用掩模取不为零的像素坐标,或者通过一步一步地画一个圆来计算它们 然后以一定精度将这些坐标与其他轮廓中的坐标进行比较,并将匹配坐标附加到当前轮廓
最后:在清晰的画布上绘制延伸轮廓,并将HoughCircles应用于单个不重叠的圆。(或者计算圆心和半径、圆的坐标并与轮廓进行精确比较。)也许我用这种方法做得过火了,但它可以作为一种工作方法。您可以找到图像上的所有轮廓-您将得到两个像“半圆”的轮廓,交点轮廓和两个连接圆的外部形状轮廓。最小的三条等高线应该是两个半圆和交点。如果从这三个轮廓中画出两个的组合,将得到三个遮罩,其中两个将具有一个半圆和交点的组合。如果在遮罩上执行闭合操作,将获得圆。然后你应该简单地做一个算法来检测哪两个掩模代表一个完整的圆,你就会得到你的结果。以下是示例解决方案:
import numpy as np
import cv2
# Function for returning solidity of contour - ratio of contour area to its
# convex hull area.
def checkSolidity(cnt):
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
return solidity
img_orig = cv2.imread("circles.png")
# Had to dilate the image so the contour was completly connected.
img = cv2.dilate(img_orig, np.ones((3, 3), np.uint8))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Grayscale transformation.
# Otsu threshold.
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# Search for contours.
contours = cv2.findContours(thresh, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
# Sorting contours from smallest to biggest.
contours.sort(key=lambda cnt: cv2.contourArea(cnt))
# Three contours - two semi circles and the intersection of the circles.
cnt1 = contours[0]
cnt2 = contours[1]
cnt3 = contours[2]
# Create three empty images
h, w = img.shape[:2]
mask1 = np.zeros((h, w), np.uint8)
mask2 = np.zeros((h, w), np.uint8)
mask3 = np.zeros((h, w), np.uint8)
# Draw all combinations of two out of three contours on the masks.
# The goal here is to draw one semicircle and the intersection together.
cv2.drawContours(mask1, [cnt1], 0, (255, 255, 255), -1)
cv2.drawContours(mask1, [cnt3], 0, (255, 255, 255), -1)
cv2.drawContours(mask2, [cnt2], 0, (255, 255, 255), -1)
cv2.drawContours(mask2, [cnt3], 0, (255, 255, 255), -1)
cv2.drawContours(mask3, [cnt1], 0, (255, 255, 255), -1)
cv2.drawContours(mask3, [cnt2], 0, (255, 255, 255), -1)
# Perform closing operation on the masks so that you get uniform contours.
kernel_size = 25
kernel = np.ones((kernel_size, kernel_size), np.uint8)
mask1 = cv2.morphologyEx(mask1, cv2.MORPH_CLOSE, kernel)
mask2 = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel)
mask3 = cv2.morphologyEx(mask3, cv2.MORPH_CLOSE, kernel)
masks = [] # List for storing all the masks.
masks.append(mask1)
masks.append(mask2)
masks.append(mask3)
# List where you will append solidity of the found biggest contour of every mask.
solidity = []
for mask in masks:
cnts = cv2.findContours(mask, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
cnt = max(cnts, key=lambda c: cv2.contourArea(c))
s = checkSolidity(cnt)
solidity.append(s)
# Index of the mask with smallest solidity.
min_solidity = solidity.index(min(solidity))
# The mask with the contour that has smallest solidity should be the one that
# has two semicirles drawn instead of one semicircle and the intersection.
#You could build a better function to check which mask is the one with
# two semicircles... like maybe the contour with the largest
# height and width of the bounding box etc.
# I chose solidity because it is enough for this example.
# Selection of colors.
colors = {
0: (0, 0, 255),
1: (0, 255, 0),
2: (255, 0, 0),
}
# Draw contours of the mask other two masks - those two that have the
# semicircle and the intersection.
for i, s in enumerate(solidity):
if s != solidity[min_solidity]:
cnts = cv2.findContours(
masks[i], cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
cnt = max(cnts, key=lambda c: cv2.contourArea(c))
cv2.drawContours(img_orig, [cnt], 0, colors[i], 1)
# Display result
cv2.imshow("img", img_orig)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
也许我用这种方法做得过火了,但它可以作为一种有效的方法。您可以找到图像上的所有轮廓-您将得到两个像“半圆”的轮廓,交点轮廓和两个连接圆的外部形状轮廓。最小的三条等高线应该是两个半圆和交点。如果从这三个轮廓中画出两个的组合,将得到三个遮罩,其中两个将具有一个半圆和交点的组合。如果在遮罩上执行闭合操作,将获得圆。然后你应该简单地做一个算法来检测哪两个掩模代表一个完整的圆,你就会得到你的结果。以下是示例解决方案:
import numpy as np
import cv2
# Function for returning solidity of contour - ratio of contour area to its
# convex hull area.
def checkSolidity(cnt):
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
return solidity
img_orig = cv2.imread("circles.png")
# Had to dilate the image so the contour was completly connected.
img = cv2.dilate(img_orig, np.ones((3, 3), np.uint8))
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Grayscale transformation.
# Otsu threshold.
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# Search for contours.
contours = cv2.findContours(thresh, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
# Sorting contours from smallest to biggest.
contours.sort(key=lambda cnt: cv2.contourArea(cnt))
# Three contours - two semi circles and the intersection of the circles.
cnt1 = contours[0]
cnt2 = contours[1]
cnt3 = contours[2]
# Create three empty images
h, w = img.shape[:2]
mask1 = np.zeros((h, w), np.uint8)
mask2 = np.zeros((h, w), np.uint8)
mask3 = np.zeros((h, w), np.uint8)
# Draw all combinations of two out of three contours on the masks.
# The goal here is to draw one semicircle and the intersection together.
cv2.drawContours(mask1, [cnt1], 0, (255, 255, 255), -1)
cv2.drawContours(mask1, [cnt3], 0, (255, 255, 255), -1)
cv2.drawContours(mask2, [cnt2], 0, (255, 255, 255), -1)
cv2.drawContours(mask2, [cnt3], 0, (255, 255, 255), -1)
cv2.drawContours(mask3, [cnt1], 0, (255, 255, 255), -1)
cv2.drawContours(mask3, [cnt2], 0, (255, 255, 255), -1)
# Perform closing operation on the masks so that you get uniform contours.
kernel_size = 25
kernel = np.ones((kernel_size, kernel_size), np.uint8)
mask1 = cv2.morphologyEx(mask1, cv2.MORPH_CLOSE, kernel)
mask2 = cv2.morphologyEx(mask2, cv2.MORPH_CLOSE, kernel)
mask3 = cv2.morphologyEx(mask3, cv2.MORPH_CLOSE, kernel)
masks = [] # List for storing all the masks.
masks.append(mask1)
masks.append(mask2)
masks.append(mask3)
# List where you will append solidity of the found biggest contour of every mask.
solidity = []
for mask in masks:
cnts = cv2.findContours(mask, cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
cnt = max(cnts, key=lambda c: cv2.contourArea(c))
s = checkSolidity(cnt)
solidity.append(s)
# Index of the mask with smallest solidity.
min_solidity = solidity.index(min(solidity))
# The mask with the contour that has smallest solidity should be the one that
# has two semicirles drawn instead of one semicircle and the intersection.
#You could build a better function to check which mask is the one with
# two semicircles... like maybe the contour with the largest
# height and width of the bounding box etc.
# I chose solidity because it is enough for this example.
# Selection of colors.
colors = {
0: (0, 0, 255),
1: (0, 255, 0),
2: (255, 0, 0),
}
# Draw contours of the mask other two masks - those two that have the
# semicircle and the intersection.
for i, s in enumerate(solidity):
if s != solidity[min_solidity]:
cnts = cv2.findContours(
masks[i], cv2.CHAIN_APPROX_NONE, cv2.RETR_TREE)[0]
cnt = max(cnts, key=lambda c: cv2.contourArea(c))
cv2.drawContours(img_orig, [cnt], 0, colors[i], 1)
# Display result
cv2.imshow("img", img_orig)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
为了便于参考,我将在这里发布我根据一些想法提出的解决方案以及其他一些想法。该解决方案具有99.9%的有效性,可以从图像中恢复椭圆,这些图像通常包含许多重叠、包含以及其他图像噪声,如线条、文本等 代码太长,分布太广,无法在此处发布,但伪代码如下所示
- 使用RETR_EXTERNAL运行cv2 findContours以获取图像中的单独区域
- 对于每个图像,填充内部,应用遮罩,并独立于其他区域提取要处理的区域
- 其余步骤分别针对每个区域执行
不简单也不优雅,但对于我正在处理的内容来说非常准确,这是通过对大量图像的手动查看确认的。为了便于参考,我将在这里发布我根据一些想法提出的解决方案,以及其他一些想法。该解决方案具有99.9%的有效性,可从通常包含许多重叠的图像中恢复椭圆,