Как я могу правильно классифицировать количество положительных (светлый цвет) кругов и отрицательных (темный цвет) кругов на изображении - PullRequest
0 голосов
/ 07 февраля 2019

Длинный пост - пожалуйста, потерпите меня.

Для лучшего понимания цели и того, что я сделал до сих пор, я разместил код.Пожалуйста, дайте мне знать, если требуется какая-либо дополнительная информация.

У меня есть изображение (как показано на рисунке) и цель состоит в том, чтобы правильно классифицировать количество положительных (синих) и отрицательных (фиолетовых) кругов . Мне нет дела до полукругов на изображении .Как показано на рисунке, есть 29 кругов (исключая полукруги), в которых 7 положительных.Но мой код обнаруживает только 1 положительный.Вот что я сделал до сих пор:

input_image

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)

Вывод такой, как показано на рисунке ниже: ('нет кругов',30)

circle_detected

Затем я рассчитал интенсивность каждого круга, взяв верхние 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 дает:

histogram of circle_intensity

Я не знаю, что я делаю неправильно.Может ли кто-нибудь помочь мне здесь?Я искал онлайн решение (например, pyimagesearch или stackoverflow и т. Д.), Но не смог найти то, что искал.

1 Ответ

0 голосов
/ 07 февраля 2019

Вы получили почти все права, если вас не беспокоит один ошибочно классифицированный блоб, частичные капли вообще не обнаруживаются и (очевидно) неточный размер для некоторых блобов.

Последняя проблема, которую нужно решить, - это получить разумный порог между светлыми и темными каплями.Один из способов сделать это - использовать адаптивный порог, например, метод Отсу или др.

Здесь вы найдете другие пороговые методы от scikit-learn..

РЕДАКТИРОВАТЬ: Обновлено, чтобы лучше соответствовать тому, что вы просили.


Вкратце, по сравнению с вашим кодом, я сделал следующие изменения:

  • Поместите весь код в функции (это помогает мне лучше рассуждать)
  • Я определил функцию улучшения контраста, но она не используется в коде (потому что у меня получались худшие результаты.)
  • определяет функцию, которая генерирует маски, связанные с кругами (обратите внимание, что эта функция будет доступна, с немного другими параметрами, в 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

и соответствующее изображение:

circles_blobs.jpg

(и, конечно, вы можете адаптировать приведенный выше код, чтобы лучше соответствовать вашим потребностям).

РЕДАКТИРОВАТЬ: включены некоторые код и цифры.

Также можно построить график хороших / плохих результатов:

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()

circles_good_bad

Или для построения полной гистограммы, например:

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()

circles_histogram

РЕДАКТИРОВАТЬ: включены дополнительныегистограмма и гистограмма

...