【问题标题】:How can I correctly classify the number of positive (bright color) circles and negative (dark color) circles in the image如何正确分类图像中正(亮色)圆圈和负(深色)圆圈的数量
【发布时间】:2019-02-07 15:25:55
【问题描述】:

长篇大论 - 请多多包涵。

为了更好地理解目标是什么以及我到目前为止所做的事情,我发布了代码。如果需要任何进一步的信息,请告诉我。

我有一张图片(如图所示),目标是正确分类正面(蓝色)和负面(紫色)圆圈的数量我不关心图像中的半圆。如图所示,有29个圆(不包括半圆),其中有7个阳性。但我的代码只检测到 1 个阳性。这是我到目前为止所做的:

import cv2
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image
import math
import cv2.cv as cv

# --------Read Images--------------------------

I = cv2.imread('input_image.jpg')

# -----------Apply Contrast---------------------

lab = cv2.cvtColor(I, cv2.COLOR_BGR2LAB)  # Converting image to LAB Color model
l, a, b = cv2.split(lab)  # Splitting the LAB image to different channels

clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))  # Applying CLAHE to L-channel
cl = clahe.apply(l)

limg = cv2.merge((cl, a, b))  # Merge the CLAHE enhanced L-channel with the a and b channel

localContrast = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)  # Converting image from LAB Color model to RGB model

print("Local Contrast shape is", localContrast.shape)
print("Local Contrast shape is", type(localContrast))

cv2.imwrite('./Output/localContrast.jpg', localContrast)

# -------------Find Circles -----------------------

input_img = cv2.imread('./Output/localContrast.jpg')  # Read Contrast Image

gray_img = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)
blur_img = cv2.medianBlur(gray_img, 7)

circles = cv2.HoughCircles(blur_img, cv.CV_HOUGH_GRADIENT, dp=1, minDist=20, param1=50, param2=30, minRadius=5,
                           maxRadius=36)

circles = np.uint16(np.around(circles))

no_of_circles = 0 

radii = []
cx= []
cy = []

if circles is not None:

    # convert the (x, y) coordinates and radius of the circles to integers

    circles = np.round(circles[0, :]).astype("int")
    no_of_circles = len(circles)

    # loop over the (x, y) coordinates and radius of the circles

    for (x,y,r) in circles:

        radii.append(r)
        cx.append(x)
        cy.append(y)
        centers = [cx, cy]

        # draw the circle in the output image, then draw a rectangle
        # corresponding to the center of the circle

        cv2.circle(input_img, (x, y), r, (0, 0, 255), 2)
cv2.imwrite('/home/vr1019/Notebook/Output/circle_img.jpg', input_img) 
print ('no of circles',no_of_circles)

输出如下图所示:('no of circles', 30)

接下来,我通过获取像素值的前 10% 来计算每个圆圈的强度(这就是我需要计算强度的方式)。 想法来自createCirclesMask.m

def createCircleMask(localContrast, centers, radii):

      radii = np.reshape(radii, (len(radii),1))

      centers = np.asarray(centers)
      centers = np.transpose(centers)

      xdim = localContrast.shape[0]
      ydim = localContrast.shape[1]

      x = np.arange(0, xdim)
      y = np.arange(0, ydim)

      x = np.reshape(x, (1, len(x)))
      y = np.reshape(y, (1, len(y)))

     [xx,yy]= np.meshgrid(y, x)


      xc = centers[:,0]
      xc = np.reshape(xc, (len(xc),1))

      yc = centers[:,1]
      yc = np.reshape(yc, (len(yc),1))
      circle_intensity = []
      for ii in range(len(radii)):
           r_square = np.square(radii)
           var1= (np.square(y-xc[ii,0]))
           var2 = np.square(np.transpose(x)-yc[ii,0])
           cx,cy = np.where((var1 + var2)<r_square[ii])
           i1 =[]
           i2 =[]
           i3 =[]

           npixel = cx.shape[0]

           for j in range(npixel):

               i1.append(localContrast[cx[j],cy[j],0]);
               localContrast[cx[j],cy[j],0] = 0;

               i2.append(localContrast[cx[j],cy[j],1]);
               localContrast[cx[j],cy[j],1] = 0;

               i3.append(localContrast[cx[j],cy[j],2]);
               localContrast[cx[j],cy[j],2] = 0;

           s1= sorted(i1, reverse = True)
           s2=sorted(i2, reverse = True)
           s3=sorted(i3, reverse = True)

           # top 10 percent intensity

           m1 = np.asarray(s1[0:np.int(round(abs(len(s1)*0.1)))])
           m2 = np.asarray(s1[0:np.int(round(abs(len(s2)*0.1)))])
           m3 = np.asarray(s1[0:np.int(round(abs(len(s3)*0.1)))])

           m = np.mean((m1+m2+m3)/3)

           circle_intensity.append(m)

      print("The len of circle_intensty is", len(circle_intensity))

      return circle_intensity

然后绘制 circle_intensity 的直方图给出:

我不知道我做错了什么。有人可以帮我吗?我在网上寻找解决方案(如 pyimagesearch 或 stackoverflow 等),但找不到我想要的。

【问题讨论】:

    标签: python opencv detection hough-transform


    【解决方案1】:

    如果您不担心一个错误分类的 blob、根本没有检测到部分 blob 以及某些 blob 的(显然)大小不准确,那么您几乎完全正确。

    最后一个要解决的问题是在明暗斑点之间设置一个合理的阈值。一种方法是使用自适应阈值,例如Otsu's method 或其他人。

    在这里查看更多来自 scikit-learn 的 threshold methods

    编辑:更新以更好地匹配您的要求。


    简单来说,对比你的代码,我做了如下修改:

    • 将所有代码放入函数中(这有助于我更好地推理)
    • 我已经定义了一个对比度增强函数,但是在代码中没有使用它(因为我得到了更糟糕的结果。)
    • 定义一个生成与圆圈关联的掩码的函数(请注意,此函数可以在PyMRT 中使用,但参数略有不同 - 免责声明:我是它的主要作者。)
    • 使用上面的掩码和 Otsu 方法来确定最佳阈值的阈值

    (小注:我将输入图像保存为blobs.jpg)。

    我会这样做,但我确信通过调整参数可以提高其稳健性。

    import numpy as np
    import cv2
    import matplotlib.pyplot as plt
    
    from skimage.filters import threshold_otsu
    
    
    # based on: https://stackoverflow.com/questions/46626267/how-to-generate-a-sphere-in-3d-numpy-array/46626448#46626448
    def circle(shape, radius, position):
        semisizes = (radius,) * 2
        grid = [slice(-x0, dim - x0) for x0, dim in zip(position, shape)]
        position = np.ogrid[grid]
        arr = np.zeros(shape, dtype=float)
        for x_i, semisize in zip(position, semisizes):
            arr += (np.abs(x_i / semisize) ** 2)
        return arr <= 1.0
    
    
    def enhance_contrast(
            in_img,
            save_filepath=None):
        """Enhance contrast."""
        lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)  
        l_ch, a_ch, b_ch = cv2.split(lab_img)
        # Applying CLAHE to L-channel
        clahe_filter = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
        l_ch = clahe_filter.apply(l_ch)
        out_img = cv2.merge((l_ch, a_ch, b_ch))
        out_img = cv2.cvtColor(out_img, cv2.COLOR_LAB2BGR)
        if save_filepath:
            cv2.imwrite(save_filepath, out_img)
        return out_img
    
    
    def find_circles(
            in_filepath,
            out_filepath='circles_{in_filepath}',
            enh_filepath='enh_{in_filepath}',
            hough_circles_kws=(
                ('dp', 1), ('minDist', 15), ('param1', 30), ('param2', 30),
                ('minRadius', 5), ('maxRadius', 25)),
            verbose=True):
        """Find circles in image."""
        out_filepath = out_filepath.format(**locals())
        enh_filepath = enh_filepath.format(**locals())
        hough_circles_kws = dict(hough_circles_kws) if hough_circles_kws else {}
    
        in_img = cv2.imread(in_filepath)
        lab_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2LAB)
        l_ch, a_ch, b_ch = cv2.split(lab_img)
        blur_l_ch = cv2.medianBlur(l_ch, 1)
        circles = cv2.HoughCircles(blur_l_ch, cv2.HOUGH_GRADIENT, **hough_circles_kws)
        if circles is not None:
            values_img = l_ch
            # compute means
            if verbose:
                print('Image size: ', values_img.shape)
            circles = np.squeeze(circles)
            values = []
            for x0, y0, r in circles:
                mask = circle(values_img.shape, r, (y0, x0))
                values.append(np.percentile(values_img[mask], 90))
            circles = np.concatenate((circles, np.array(values).reshape(-1, 1)), -1)
            threshold = threshold_otsu(np.array(values))
            if verbose:
                print('Threshold: ', threshold)
            # plot circles
            for x0, y0, r, mean in circles:
                if mean > threshold:
                    # good circles in green
                    cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 255, 0), 2)
                else:
                    # bad circles in red
                    cv2.circle(in_img, (int(x0), int(y0)), int(r), (0, 0, 255), 2)
            if verbose:
                print('Circles:')
                print(circles)
                print('Num Circles: ', circles.shape[0])
                print('Good Circles: ', np.sum(values > threshold))
        if out_filepath:
            cv2.imwrite(out_filepath.format(**locals()), in_img)
        return out_filepath, circles, threshold
    
    
    out_filepath, circles, threshold = find_circles('blobs.jpg')
    

    这将生成以下输出:

    Image size:  (230, 294)
    Threshold:  96.1328125
    Circles:
    [[ 36.5        108.5         21.10000038 155.5       ]
     [170.5        124.5         24.39999962 170.        ]
     [ 43.5        156.5         21.10000038 156.5       ]
     [ 33.5         57.5         22.20000076 190.        ]
     [101.5         40.5         19.89999962  90.        ]
     [ 75.5         78.5         18.79999924  88.        ]
     [254.5        171.5         16.60000038  82.        ]
     [138.5         52.5         15.39999962  90.        ]
     [123.5        148.5         14.39999962  90.        ]
     [ 42.5        199.5         15.39999962 174.        ]
     [138.5         15.5         14.10000038  88.        ]
     [ 86.5        176.5         15.39999962  90.        ]
     [256.5         23.5         15.5        146.        ]
     [211.5        140.5         14.39999962  87.        ]
     [132.5        193.5         13.19999981  90.1       ]
     [174.5         35.5          9.60000038  93.        ]
     [ 81.5        129.5         11.          93.        ]
     [223.5         54.5          9.60000038  87.        ]
     [177.5         75.5         13.19999981 146.        ]
     [214.5        195.5         11.          90.        ]
     [259.5        126.5          9.60000038  90.        ]
     [ 62.5         22.5         11.          96.        ]
     [220.5         98.5          9.60000038  89.        ]
     [263.5         77.5         12.10000038  84.1       ]
     [116.5        101.5          9.60000038  92.        ]
     [170.5        177.5         11.          91.        ]
     [251.5        215.5         11.          91.        ]
     [167.5        215.5         11.          87.        ]
     [214.5         14.5          9.60000038  92.        ]]
    Num Circles:  29
    Good Circles:  7
    

    以及对应的图片:

    (当然您可以调整上面的代码以更好地满足您的需求)。

    编辑:包括一些代码和数字。

    还可以绘制好/坏结果的条形图:

    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots()
    
    values = circles[:, -1]
    data = [np.sum(values <= threshold), np.sum(values > threshold)]
    
    labels = ['Bad', 'Good']
    colors = ['red', 'green']
    
    ax.bar(labels, data, color=colors)
    plt.show()
    

    或者绘制一个完整的直方图,例如:

    fig, ax = plt.subplots()
    
    hist, edges = np.histogram(values, bins=40)
    widths = (edges[1:] - edges[:-1])
    ax.bar(edges[:-1] + widths / 2, hist, widths)  # plots the histogram
    ax.axvline(x=threshold, color='black')  # plots the threshold (optional)
    plt.show()
    

    编辑:包括额外的条形图和直方图

    【讨论】:

    • 您好,感谢您的回复。所以,你的意思是我应该先设置阈值,然后再做createCircleMask函数还是在检测圆之前?
    • 不,我的意思是,一旦找到圆圈,并计算出它们的平均(或其他特征)强度,您就需要一个阈值(如 Otsu 的阈值)来区分正负。跨度>
    • @Riya208 没问题。 (1)不,不需要它,我只是包括因为你的代码中有它; (2) np.percentile() 如您所指实现“获取像素值的前 10%”(有关更多信息,请参阅其文档); (3) 我不确定我是否理解histogram 问题.. 如果你想绘制条形图,你可以使用例如matplotlib; (4) verbose 位的存在只是因为您可能不想每次使用它时都打印。最后,您可能想要投票/接受答案。 :-)
    • @Riya208:我认为你想要的实际上是一个好/坏结果的条形图。但我还包含了正确的值直方图(我只想使用values 而不是circles)。请注意,我还修改了函数以返回 circlesthreshold
    • @norok2 非常感谢!我希望我能做的不仅仅是投票和接受答案!
    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-10
    • 1970-01-01
    • 2017-05-30
    • 1970-01-01
    相关资源
    最近更新 更多