OpenCV 检测图像上的斑点

Posted

技术标签:

【中文标题】OpenCV 检测图像上的斑点【英文标题】:OpenCV detect blobs on the image 【发布时间】:2017-02-28 22:13:09 【问题描述】:

我需要在图像上找到 (并在周围绘制矩形)/获取最大和最小半径 斑点。 (以下示例)

问题是为图像找到正确的过滤器,以允许CannyThreshold 转换突出显示斑点。然后我将使用findContours 来查找矩形。

我试过了:

Threshold - 不同级别

blur->erode->erode->grayscale->canny

用各种“行”更改image tone

等等。更好的结果是检测到一块(20-30%)的斑点。并且此信息不允许在 blob 周围绘制矩形。另外,感谢检测到与斑点无关的阴影,这样也可以防止检测到该区域。

据我了解,我需要找到对比度强烈的计数器(不像阴影中那样平滑)。有没有办法用 openCV 做到这一点?

更新

案例分别为:image 1、image 2、image 3、image 4、image 5、image 6、image 7、image 8、image 9、@98765433@4、@98765433@4、 image 12

另一个更新

我相信斑点在边缘有对比区域。所以,我试图让边缘更强大:我创建了 2 个gray scale Mat: A and B,为第二个应用Gaussian blur - B (稍微降低噪音),然后我'我们做了一些计算:遍历每个像素并找到“A”的Xi,Yi 与“B”的附近点之间的最大差异:

并将max 差异应用于Xi,Yi。所以我得到这样的东西:

我走对了吗?顺便说一句,我可以通过 OpenCV 方法达到这样的目的吗?

更新 Image Denoising 有助于减少噪点,Sobel - 突出轮廓,然后threshold + findContourscustome convexHull 变得非常相似我正在寻找,但它不适合一些斑点。

【问题讨论】:

为什么输入是拼贴形式,这是原始输入格式吗?还是您自己创建了一个以展示不同的案例? @ZdaR 是的,我创建它是为了展示不同种类的案例。并非所有可能的变体(但我希望最流行) 可以单独上传所有案例吗? @ZdaR,将输入文件单独上传到i.stack.imgur.com,谢谢 恭喜,您刚刚(重新)发现了高通滤波器!当你从原始图像中减去高斯模糊时,你会在边缘得到很高的响应……特别是反射也有锐利的边缘。 【参考方案1】:

由于输入图像之间存在很大差异,因此算法应该能够适应这种情况。由于 Canny 是基于检测高频,所以我的算法将图像的清晰度作为预处理自适应的参数。我不想花一周时间找出所有数据的函数,所以我应用了一个基于 2 张图像的简单线性函数,然后用第三张图像进行测试。这是我的结果:

请记住,这是一种非常基本的方法,只是证明了一点。它将需要实验、测试和改进。这个想法是使用 Sobel 并对所有获取的像素求和。那,除以图像的大小,应该给你一个高频的基本估计。图像的响应。现在,通过实验,我找到了适用于 2 个测试用例的 CLAHE 过滤器的 clipLimit 值,并找到了一个连接高频的 linear function。使用 CLAHE 滤波器对输入进行响应,产生良好的结果。

sobel = get_sobel(img)
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

这就是自适应部分。现在是轮廓。我花了一段时间才找出过滤噪音的正确方法。我选择了一个简单的技巧:使用轮廓查找两次。首先,我用它来过滤掉不必要的、嘈杂的轮廓。然后我继续使用一些形态学魔法,最终为被检测到的对象提供正确的 blob(代码中的更多细节)。最后一步是根据计算的平均值过滤边界矩形,因为在所有样本上,斑点的大小都相对相似。

import cv2
import numpy as np


def unsharp_mask(img, blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    gaussian = cv2.GaussianBlur(img, (5,5), 0)
    return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)


def smoother_edges(img, first_blur_size, second_blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    img = cv2.GaussianBlur(img, first_blur_size, 0)
    return unsharp_mask(img, second_blur_size, imgWeight, gaussianWeight)


def close_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)


def open_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)


def shrink_rect(rect, scale = 0.8):
    center, (width, height), angle = rect
    width = width * scale
    height = height * scale
    rect = center, (width, height), angle
    return rect


def clahe(img, clip_limit = 2.0):
    clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(5,5))
    return clahe.apply(img)


def get_sobel(img, size = -1):
    sobelx64f = cv2.Sobel(img,cv2.CV_64F,2,0,size)
    abs_sobel64f = np.absolute(sobelx64f)
    return np.uint8(abs_sobel64f)


img = cv2.imread("blobs4.jpg")
# save color copy for visualizing
imgc = img.copy()
# resize image to make the analytics easier (a form of filtering)
resize_times = 5
img = cv2.resize(img, None, img, fx = 1 / resize_times, fy = 1 / resize_times)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# use sobel operator to evaluate high frequencies
sobel = get_sobel(img)
# experimentally calculated function - needs refining
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

# don't apply clahe if there is enough high freq to find blobs
if(clip_limit < 1.0):
    clip_limit = 0.1
# limit clahe if there's not enough details - needs more tests
if(clip_limit > 8.0):
    clip_limit = 8

# apply clahe and unsharp mask to improve high frequencies as much as possible
img = clahe(img, clip_limit)
img = unsharp_mask(img)

# filter the image to ensure edge continuity and perform Canny
# (values selected experimentally, using trackbars)
img_blurred = (cv2.GaussianBlur(img.copy(), (2*2+1,2*2+1), 0))
canny = cv2.Canny(img_blurred, 35, 95)

# find first contours
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

# prepare black image to draw contours
canvas = np.ones(img.shape, np.uint8)
for c in cnts:
    l = cv2.arcLength(c, False)
    x,y,w,h = cv2.boundingRect(c)
    aspect_ratio = float(w)/h

    # filter "bad" contours (values selected experimentally)
    if l > 500:
        continue
    if l < 20:
        continue
    if aspect_ratio < 0.2:
        continue
    if aspect_ratio > 5:
        continue
    if l > 150 and (aspect_ratio > 10 or aspect_ratio < 0.1):
        continue
    # draw all the other contours
    cv2.drawContours(canvas, [c], -1, (255, 255, 255), 2)

# perform closing and blurring, to close the gaps
canvas = close_image(canvas, (7,7))
img_blurred = cv2.GaussianBlur(canvas, (8*2+1,8*2+1), 0)
# smooth the edges a bit to make sure canny will find continuous edges
img_blurred = smoother_edges(img_blurred, (9,9))
kernel = np.ones((3,3), np.uint8)
# erode to make sure separate blobs are not touching each other
eroded = cv2.erode(img_blurred, kernel)
# perform necessary thresholding before Canny
_, im_th = cv2.threshold(eroded, 50, 255, cv2.THRESH_BINARY)
canny = cv2.Canny(im_th, 11, 33)

# find contours again. this time mostly the right ones
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# calculate the mean area of the contours' bounding rectangles
sum_area = 0
rect_list = []
for i,c in enumerate(cnts):
    rect = cv2.minAreaRect(c)
    _, (width, height), _ = rect
    area = width*height
    sum_area += area
    rect_list.append(rect)
mean_area = sum_area / len(cnts)

# choose only rectangles that fulfill requirement:
# area > mean_area*0.6
for rect in rect_list:
    _, (width, height), _ = rect
    box = cv2.boxPoints(rect)
    box = np.int0(box * 5)
    area = width * height

    if(area > mean_area*0.6):
        # shrink the rectangles, since the shadows and reflections
        # make the resulting rectangle a bit bigger
        # the value was guessed - might need refinig
        rect = shrink_rect(rect, 0.8)
        box = cv2.boxPoints(rect)
        box = np.int0(box * resize_times)
        cv2.drawContours(imgc, [box], 0, (0,255,0),1)

# resize for visualizing purposes
imgc = cv2.resize(imgc, None, imgc, fx = 0.5, fy = 0.5)
cv2.imshow("imgc", imgc)
cv2.imwrite("result3.png", imgc)
cv2.waitKey(0)

总的来说,我认为这是一个非常有趣的问题,有点太大了,无法在这里回答。我提出的方法应该被视为一个路标,而不是一个完整的解决方案。基本思想是:

    自适应预处理。

    两次寻找轮廓:过滤,然后进行实际分类。

    根据平均大小过滤 blob。

感谢您的乐趣和好运!

【讨论】:

谢谢!很好的答案。是否可以再增加一次赏金? @user5599807 我不知道 :) 不客气 - 这很有趣。【参考方案2】:

这是我使用的代码:

import cv2
from sympy import Point, Ellipse
import numpy as np
x1='C:\\Users\\Desktop\\python\\stack_over_flow\\XsXs9.png'    
image = cv2.imread(x1,0)
image1 = cv2.imread(x1,1)
x,y=image.shape
median = cv2.GaussianBlur(image,(9,9),0)
median1 = cv2.GaussianBlur(image,(21,21),0)
a=median1-median
c=255-a
ret,thresh1 = cv2.threshold(c,12,255,cv2.THRESH_BINARY)
kernel=np.ones((5,5),np.uint8)
dilation = cv2.dilate(thresh1,kernel,iterations = 1)
kernel=np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(dilation, cv2.MORPH_OPEN, kernel)
cv2.imwrite('D:\\test12345.jpg',opening)
ret,contours,hierarchy =    cv2.findContours(opening,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
c=np.size(contours[:])
Blank_window=np.zeros([x,y,3])
Blank_window=np.uint8(Blank_window)
for u in range(0,c-1):
    if (np.size(contours[u])>200):
        ellipse = cv2.fitEllipse(contours[u])
        (center,axes,orientation) =ellipse
        majoraxis_length = max(axes)
        minoraxis_length = min(axes)
        eccentricity=(np.sqrt(1-(minoraxis_length/majoraxis_length)**2))
        if (eccentricity<0.8):
             cv2.drawContours(image1, contours, u, (255,1,255), 3)
cv2.imwrite('D:\\marked.jpg',image1)

这里的问题是找到一个的圆形物体。这个简单的解决方案基于找到每个轮廓的偏心率。被检测到的此类物体是水滴。

【讨论】:

使用古怪的好方法。 我认为这并不能真正解决问题。这个答案的作者只使用了几个最简单的案例。这里的问题主要在于反射、不同的背景和整体噪声。在我看来,这些操作将在问题中显示的至少 30% 的情况下失败。 Used the code for one of the cases 是的,这并不适用于所有图像,但看起来是真的 - 它应该是几种算法和逻辑来检测哪个应该应用于某些类型的图像。此外,该解决方案在某些情况下获得了高精度。看起来我需要结合本主题中讨论的所有内容以获得更好的结果 =) 嗯,我想我有一个很好的解决方案给你。只需要将其转换为答案。我认为赏金不应该已经颁发。【参考方案3】:

我有一个部分解决方案。

第一

我最初将图像转换为 HSV 颜色空间并修改了 value 通道。在这样做时,我遇到了一些独特的东西。几乎在每张图像中,液滴都有微小的光反射。这在 value 频道中明显突出显示。

将其反转后,我能够获得以下信息:

样本 1:

示例 2:

示例 3:

第二次

现在我们必须提取这些点的位置。为此,我对获得的反转值通道进行了异常检测。我所说的异常是指其中的黑点。

为了做到这一点,我计算了倒置值通道的中值。我将中位数上下 70% 以内的像素值分配为正常像素。但是超出这个范围的每个像素值都是异常的。黑点非常适合那里。

样本 1:

示例 2:

示例 3:

对于几张图片来说效果并不好。

如您所见,黑点是水滴特有的光反射造成的。图像中可能存在其他圆形边缘,但反射将液滴与这些边缘区分开来。

第三

现在既然我们有了这些黑点的位置,我们就可以执行高斯差分(DoG)(在问题的更新中也提到过)并获得相关的边缘信息。如果获得的黑点位置位于发现的边缘内,则称其为水滴。

免责声明:此方法不适用于所有图像。您可以在此添加您的建议。

【讨论】:

谢谢!它可以帮助我分析图像类型并在需要时处理或去除眩光/过度曝光区域。谢谢。 当然,你必须结合你遇到的所有技术 好主意@JeruLuke。我也考虑过使用反射。但是更多地考虑填充,因为一些斑点的边缘没有很好地定义,所以这是行不通的。我实际上有自己的答案,但无法发布,因为 *** 给了我一个error。【参考方案4】:

美好的一天,我正在研究这个主题,我给你的建议是;首先,在使用了许多去噪滤波器(例如高斯滤波器)之后,再对图像进行处理。 您可以不使用计数器对这些圆圈进行斑点检测。

【讨论】:

你可以添加一个例子

以上是关于OpenCV 检测图像上的斑点的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV blob(斑点)特征,SimpleBlobDetector

基于opencv的纸张表面质量检测算法中

OpenCV中的斑点提取

从blob OpenCV侵蚀文本

使用 OpenCV 进行斑点检测

浅析OpenCV中的BlobDetector