【问题标题】:computer vision - Counting small circles in an image计算机视觉 - 计算图像中的小圆圈
【发布时间】:2018-05-13 22:44:42
【问题描述】:

下图有很多圈。单击并放大以查看圆圈。

https://drive.google.com/open?id=1ox3kiRX5hf2tHDptWfgcbMTAHKCDizSI

我想要的是使用任何免费语言(例如 python)计算圆圈。

有什么功能或想法可以做到吗?

【问题讨论】:

  • 这就是我的建议:将两种不同的颜色阈值或分割成二值图像。 floodFill() 背景,你只会得到每个圆圈的中心。然后您可以轻松地从connectedComponents() 获取标签数量。
  • 每个小圆圈的大小是否相同/相似?在计算连接组件之后,您可能希望通过假设它们是大小为 X 的 N (=nearest-round(size/X)) 连接的圆来拆分太大的组件,其中 X 是所有连接组件大小的中值或用户输入。
  • 您有未压缩的图片(不是 jpg)吗?
  • @Micka 所有的圆圈大小都差不多。
  • 最好的图片质量最好,最好是未压缩的:)

标签: opencv computer-vision object-detection


【解决方案1】:

一种可能的解决方案是使用 OpenCV 读取图像,获取其灰度,然后使用 Canny 边缘检测并在 OpenCV 中执行计数查找。这将返回一个计数列表。它看起来像:

import cv2
image = cv2.imread('path-to-your-image')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# tweak the parameters of the GaussianBlur for best performance
blurred = cv2.GaussianBlur(gray, (7, 7), 0)
# again, try different values here
edged = cv2.Canny(blurred, 20, 140)
(_, contours, _) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))

【讨论】:

  • 如果两个圆圈重叠怎么办?这被认为是一个或两个轮廓?
  • 它被认为是一个轮廓。如果要计算重叠的圆,问题就比较难了。
【解决方案2】:

编辑:我想出了一个更好的解决方案,部分灵感来自this answer below。我最初想到了这种方法(如 OP cmets 中所述),但我决定反对它。原始图像的质量不够好。不过,我改进了该方法,它非常出色地工作,以获得更好的图像质量。原来的方法是第一位的,然后是底部的新方法。


第一种方法

所以这里有一个通用的方法,看起来效果很好,但肯定只是给出了估计。这假设圆圈的大小大致相同。

首先,图像大部分是蓝色的——所以只对蓝色通道进行分析似乎是合理的。对蓝色通道进行阈值处理,在这种情况下,使用 Otsu 阈值(无需输入即可确定最佳阈值)似乎效果很好。这并不奇怪,因为颜色值的分布几乎是二进制的。检查由此产生的掩码!

然后,对 mask 进行连通分量分析,得到每个分量的面积(分量 = mask 中的白色斑点)。从connectedComponentsWithStats() 返回的统计信息(除其他外)给出了该区域,这正是我们所需要的。然后,我们可以通过根据其面积估计给定组件中有多少圆适合来简单地计算圆。另请注意,除了第一个标签之外,我正在统计每个标签:这是背景标签 0,而不是任何白色斑点。

现在,一个圆的面积有多大?最好让数据告诉我们。因此,您可以计算所有区域的直方图,并且由于单个圆圈比其他任何东西都多,因此该区域将高度集中在 250-270 像素左右。或者,您可以只取 50 到 350 之间的所有区域的平均值,这也应该让您进入类似的范围。

真的,在这个直方图中,您可以很容易地看到单圈、双圈、三圈等之间的分界线。只有较大的组件才会给出相当粗略的估计。事实上,该区域似乎并没有完全线性地缩放。两个圆的斑点比两个单圆稍大,三个圆的斑点仍然比三个单圆大,依此类推,因此很难很好地估计,但四舍五入仍应使我们保持接近。如果您愿意,您可以包含一个小的乘法参数,该参数会随着面积的增加而增加以说明这一点,但如果不通过直方图进行分析,这将很难量化......所以,我不担心这个。

单圈面积除以平均单圈面积应该接近1。而5圈组的面积除以平均圈面积应该接近5。这也意味着小的不显着的成分,面积为 1 或 10 甚至 100 像素,自round(50/avg_circle_size) < 1/2 以来将不计入总数,因此这些将向下舍入为 0。因此我应该能够获取所有组件区域,将它们除以平均圆的大小,圆形,并通过将它们全部加起来得到一个不错的估计。

import cv2
import numpy as np

img = cv2.imread('circles.png')
mask = cv2.threshold(img[:, :, 0], 255, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

stats = cv2.connectedComponentsWithStats(mask, 8)[2]
label_area = stats[1:, cv2.CC_STAT_AREA]

min_area, max_area = 50, 350  # min/max for a single circle
singular_mask = (min_area < label_area) & (label_area <= max_area)
circle_area = np.mean(label_area[singular_mask])

n_circles = int(np.sum(np.round(label_area / circle_area)))

print('Total circles:', n_circles)

此代码简单有效,可用于粗略计数。

但是,与正常的圆圈大小相比,这里肯定有一些关于圆圈组的假设,并且存在无法正确计算边界处的圆圈的问题(这些没有明确定义---a两个半截掉的圆形斑点看起来更像一个圆圈——没有明确的方法来计算或不计算这些用这种方法)。此外,我在这里通过 Otsu 使用了自动阈值;通过更仔细的颜色过滤,您可以获得(可能更好)的结果。此外,在 Otsu 生成的蒙版中,一些被蒙版的圆圈从其中心移除了一些像素。形态学可以重新添加这些像素,这将为您提供(稍大)更准确的单个圆形组件区域。无论哪种方式,我只是想就如何用最少的代码轻松估计这一点给出一个总体思路。


新方法

以前,目标是计算圈数。这种新方法改为计算圆的中心。一般的想法是你设置阈值,然后从背景像素填充填充以填充背景(填充填充就像照片编辑应用程序中的油漆桶工具一样),这样你只能看到中心,如this answer below 所示。

但是,这依赖于全局阈值,它对局部光照变化并不可靠。这意味着由于某些中心比其他中心更亮/更暗,因此您不会总是通过单一阈值获得良好的结果。

在这里,我创建了一个动画来显示循环通过不同的阈值;观察一些中心在不同时间出现和消失,这意味着您会根据您选择的阈值获得不同的计数(这只是图像的一小部分,它无处不在):

请注意,出现在左上角的第一个斑点实际上会随着阈值的增加而消失。但是,如果我们实际上将每一帧 OR 在一起,那么每个检测到的像素都会持续存在:

但是现在每一个斑点都出现了,所以我们应该清理每一帧的遮罩,以便在它们出现时移除单个像素(否则它们可能会堆积起来,以后很难移除)。带有小内核的简单morphological opening 将删除它们:

应用于整个图像,此方法效果非常好,几乎可以找到每个单元格。我可以发现只有三个误报(检测到的不是中心的 blob)和两个未命中,而且代码非常简单。创建蒙版后要做的最后一件事是简单地计算组件,减去背景。此处唯一需要的用户输入是在后台(代码中的seed_pt)填充填充的单点。

img = cv2.imread('circles.png', 0)
seed_pt = (25, 25)
fill_color = 0
mask = np.zeros_like(img)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
for th in range(60, 120):
    prev_mask = mask.copy()
    mask = cv2.threshold(img, th, 255, cv2.THRESH_BINARY)[1]
    mask = cv2.floodFill(mask, None, seed_pt, fill_color)[1]
    mask = cv2.bitwise_or(mask, prev_mask)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

n_centers = cv2.connectedComponents(mask)[0] - 1
print('There are %d cells in the image.'%n_centers)

图像中有 874 个单元格。

【讨论】:

    【解决方案3】:

    如果您拥有这样的所有图像 - 考虑对其进行阈值化,不一定通过像 Otsu 这样的自动阈值搜索算法,而是通过给定阈值使用最简单的阈值。是的,在阈值化之前,您必须将颜色输入转换为灰度,或采用一种颜色通道。然后基于对通道和阈值的少量实验 - 确定阈值以在单色阈值结果中具有带孔的圆圈。根据您的 png 图像,我发现 81 的值(灰度强度从 0 到 255 不等)非常适合阈值输入的灰度版本,以使此类二进制图像具有适当的孔,如上所述。

    然后简单地计算那些孔。

    可以通过填充种子的白色区域来确定孔,连接到图像边框。结果,您将在黑色背景上有白洞连接的组件 - 所以只需计算它们。

    您可以在这里找到更多详细信息 http://www.leptonica.com/filling.html 并使用 leptonica 基元进行阈值处理、孔计数等。

    【讨论】:

    • 这也是一个好方法。这是我开始采用的最初方法(您可以在 OP 的 cmets 中看到建议)。我只是在获取中心的阈值方面遇到了问题——部分原因是 OP 从质量较差的图像开始。我不喜欢这种方法的唯一一点是圆圈并不总是封闭的,因此洪水填充会消除这些圆圈。这肯定只会低估,而我上面的方法可能会低估或高估(并不是说一个比另一个更好)。
    • 也许如果你估计单个圆的大小,你可以生成一个适当大小的形态内核,它是一个环(不在乎中间)。这将有助于关闭对背景开放的圆圈,以便洪水填充移除更少的圆圈。这意味着您可以稍微减少对阈值的限制,这意味着也有更少的中心被黑色遮盖。
    • 带有圆形图案的Hit-And-Miss变换可以提供帮助,但图案大小非常重要。如果图像采集系统能够在小尺寸方差方面提供稳定的圆尺寸,我认为 HMT 具有以下计数 HMT 结果的连通分量似乎是一个很好的解决方案。
    • 确实,我认为在这种情况下可以使用直方图来找到单个圆的合适大小;在某个最小尺寸之后的第一个局部峰值将对应于一个很好的估计。无论哪种方式,您循环通过不同阈值的方法都会产生非常好的结果(请参阅我的答案中的编辑)。
    猜你喜欢
    • 2020-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 2018-03-19
    • 1970-01-01
    • 2011-10-18
    • 2017-08-26
    相关资源
    最近更新 更多