如何使用 OpenCV findContours 检测相交形状?

如何解决如何使用 OpenCV findContours 检测相交形状?

我在黑白图像中有两个相交的椭圆。我正在尝试使用 OpenCV findContours 使用此代码(以及下面的附图)将单独的形状识别为单独的轮廓。

original image

import numpy as np
import matplotlib.pyplot as plt

import cv2
import skimage.morphology

img_3d = cv2.imread("C:/temp/test_annotation_overlap.png")
img_grey = cv2.cvtColor(img_3d,cv2.COLOR_BGR2GRAY)
contours = cv2.findContours(img_grey,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[-2]

fig,ax = plt.subplots(len(contours)+1,1,figsize=(5,20))

thicker_img_grey = skimage.morphology.dilation(img_grey,skimage.morphology.disk(radius=3))
ax[0].set_title("ORIGINAL IMAGE")
ax[0].imshow(thicker_img_grey,cmap="Greys")

for i,contour in enumerate(contours):
    new_img = np.zeros_like(img_grey)
    cv2.drawContours(new_img,contour,-1,(255,255,255),10)
    ax[i+1].set_title(f"Contour {i}")
    ax[i+1].imshow(new_img,cmap="Greys")

plt.show()

然而找到了四个轮廓,没有一个是原始轮廓:

enter image description here

如何配置 OpenCV.findContours 来识别两个单独的形状? (注意我已经玩过霍夫圈,发现它对我正在分析的图像不可靠)

解决方法

从哲学上讲,您希望找到两个 ,因为您在搜索它们时,需要的是中心和半径。从图形上看,这些数字是相连的,我们可以看到它们是分开的,因为我们知道什么是“圆”,并推断出坐标,与重叠的部分相匹配。

那么如何找到每个轮廓的最小封闭圆(或者在某些情况下 fitEllipse 并使用它们的参数):https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html

然后说在清晰的图像中绘制该圆并获取不为零的像素坐标 - 通过掩码或通过逐步绘制圆来计算它们。

然后以某种精度将这些坐标与其他轮廓中的坐标进行比较,并将匹配的坐标附加到当前轮廓中。

最后:在清晰的画布上绘制扩展轮廓,并为单个非重叠圆应用 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,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,np.uint8)
mask3 = np.zeros((h,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],(255,255),-1)
cv2.drawContours(mask1,[cnt3],-1)

cv2.drawContours(mask2,[cnt2],-1)
cv2.drawContours(mask2,-1)

cv2.drawContours(mask3,-1)
cv2.drawContours(mask3,-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,kernel)
mask3 = cv2.morphologyEx(mask3,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.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,1: (0,0),2: (255,}

# 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.RETR_TREE)[0]
        cnt = max(cnts,key=lambda c: cv2.contourArea(c))
        cv2.drawContours(img_orig,[cnt],colors[i],1)

# Display result
cv2.imshow("img",img_orig)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果:

enter image description here

,

作为参考,我将根据这里的一些想法和其他一些想法发布我提出的解决方案。该解决方案的有效性为 99.9%,并且可以从包含许多重叠、包含和其他图像噪声(例如线条、文本等)的图像中恢复椭圆。

代码太长,散布在这里,但伪代码如下。

  1. 分割图像
    • 使用 RETR_EXTERNAL 运行 cv2 findContours 以获取图像中的单独区域
    • 对于每个图像,填充内部,应用蒙版,并独立于其他区域提取要处理的区域。
    • 对每个区域独立执行剩余的步骤
  2. 使用 RETR_LIST 运行 cv2 findContours 以获取所有内部和外部轮廓
  3. 对于找到的每个轮廓,应用多边形平滑以减少像素化的影响
  4. 对于每个平滑的轮廓,确定该轮廓内具有相同曲率符号的连续线段,即完全向右弯曲或向左弯曲的线段(仅计算角度和符号变化)
  5. 在每个段内,使用最小二乘法拟合椭圆模型 (scikit-learn EllipseModel)
  6. 对原始图像执行 Lee 算法,计算每个像素与白色像素的最小距离
  7. 对于每个模型,执行贪婪的局部邻域搜索以改进对原始模型的拟合 - 拟合是拟合椭圆到白色像素的最大距离,来自 lee 算法的输出

不简单也不优雅,但对我处理的内容高度准确,通过对大量图像的人工审核得到确认。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-