Повышение точности обработки изображения для подсчета спор грибка - PullRequest
0 голосов
/ 28 ноября 2018

Я пытаюсь подсчитать количество спор заболевания из микроскопического образца с помощью Pythony, но пока без особого успеха.

Поскольку цвет споры похож на фон, и многиенаходятся рядом.

после фотографической микроскопии образца.

Microscopic photograph of spores

Код обработки изображения:

import numpy as np
import argparse
import imutils
import cv2

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
                help="path to the input image")
ap.add_argument("-o", "--output", required=True,
                help="path to the output image")
args = vars(ap.parse_args())

counter = {}

image_orig = cv2.imread(args["image"])
height_orig, width_orig = image_orig.shape[:2]

image_contours = image_orig.copy()

colors = ['Yellow']
for color in colors:

    image_to_process = image_orig.copy()

    counter[color] = 0

    if color == 'Yellow':
        lower = np.array([70, 150, 140])  #rgb(151, 143, 80)
        upper = np.array([110, 240, 210])  #rgb(212, 216, 106)

    image_mask = cv2.inRange(image_to_process, lower, upper)

    image_res = cv2.bitwise_and(
        image_to_process, image_to_process, mask=image_mask)

    image_gray = cv2.cvtColor(image_res, cv2.COLOR_BGR2GRAY)
    image_gray = cv2.GaussianBlur(image_gray, (5, 5), 50)

    image_edged = cv2.Canny(image_gray, 100, 200)
    image_edged = cv2.dilate(image_edged, None, iterations=1)
    image_edged = cv2.erode(image_edged, None, iterations=1)

    cnts = cv2.findContours(
        image_edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if imutils.is_cv2() else cnts[1]

    for c in cnts:

        if cv2.contourArea(c) < 1100:
            continue

        hull = cv2.convexHull(c)
        if color == 'Yellow':

            cv2.drawContours(image_contours, [hull], 0, (0, 0, 255), 1)

        counter[color] += 1      

print("{} esporos {}".format(counter[color], color))

cv2.imwrite(args["output"], image_contours)

Алгоритм насчитал 11 спор

Но на изображении содержится 27 спор

Результат обработки изображения показывает, что споры сгруппированы spores are grouped

Как мне сделать это более точным?

Ответы [ 2 ]

0 голосов
/ 01 декабря 2018

Во-первых, предварительный код, который мы будем использовать ниже:

import numpy as np
import cv2
from matplotlib import pyplot as plt
from skimage.morphology import extrema
from skimage.morphology import watershed as skwater

def ShowImage(title,img,ctype):
  if ctype=='bgr':
    b,g,r = cv2.split(img)       # get b,g,r
    rgb_img = cv2.merge([r,g,b])     # switch it to rgb
    plt.imshow(rgb_img)
  elif ctype=='hsv':
    rgb = cv2.cvtColor(img,cv2.COLOR_HSV2RGB)
    plt.imshow(rgb)
  elif ctype=='gray':
    plt.imshow(img,cmap='gray')
  elif ctype=='rgb':
    plt.imshow(img)
  else:
    raise Exception("Unknown colour type")
  plt.title(title)
  plt.show()

Для справки, вот ваше оригинальное изображение:

#Read in image
img         = cv2.imread('cells.jpg')
ShowImage('Original',img,'bgr')

Original image

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

#Convert to a single, grayscale channel
gray        = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#Threshold the image to binary using Otsu's method
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
ShowImage('Grayscale',gray,'gray')
ShowImage('Applying Otsu',thresh,'gray')

Grayscale cells Tresholded cells

Все эти маленькие пятнышки раздражают, мы можем избавитьсяиз них путем расширения:

#Adjust iterations until desired result is achieved
kernel  = np.ones((3,3),np.uint8)
dilated = cv2.dilate(thresh, kernel, iterations=5)
ShowImage('Dilated',dilated,'gray')

With noise eliminated

Теперь нам нужно идентифицировать пики водораздела и дать им отдельные метки.Цель этого состоит в том, чтобы сгенерировать набор пикселей так, чтобы каждая из ячеек имела пиксель внутри, и ни у двух ячеек не было соприкасающихся пикселей идентификатора.

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

#Calculate distance transformation
dist         = cv2.distanceTransform(dilated,cv2.DIST_L2,5)
ShowImage('Distance',dist,'gray')

Distance Transformation

#Adjust this parameter until desired separation occurs
fraction_foreground = 0.6
ret, sure_fg = cv2.threshold(dist,fraction_foreground*dist.max(),255,0)
ShowImage('Surely Foreground',sure_fg,'gray')

Foreground isolation

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

Теперь мы идентифицируем неизвестные области, области, которые будут помечены алгоритмом водораздела,вычитая максимумы:

# Finding unknown region
unknown = cv2.subtract(dilated,sure_fg.astype(np.uint8))
ShowImage('Unknown',unknown,'gray')

Unknown regions

Неизвестные области должны образовывать полные пончики вокруг каждой клетки.

Далее мыдайте каждой из отдельных областей, полученных в результате уникальных меток преобразования расстояния, а затем отметьте неизвестные области перед окончательным выполнением преобразования водораздела:

# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg.astype(np.uint8))
ShowImage('Connected Components',markers,'rgb')

# Add one to all labels so that sure background is not 0, but 1
markers = markers+1

# Now, mark the region of unknown with zero
markers[unknown==np.max(unknown)] = 0

ShowImage('markers',markers,'rgb')

dist    = cv2.distanceTransform(dilated,cv2.DIST_L2,5)
markers = skwater(-dist,markers,watershed_line=True)

ShowImage('Watershed',markers,'rgb')

Connected components Uncertain area Separate cells

Теперь общее количество ячеек равно количеству уникальных маркеров минус 1 (чтобы игнорировать фон):

len(set(markers.flatten()))-1

В этом случае мы получим 23.

Вы можете сделать это более или менее точным, отрегулировав порог расстояния, степень расширения, возможно, используя h-максимумы (локально-пороговые максимумы).Но остерегайтесь переоснащения;то есть, не думайте, что настройка для одного изображения даст вам лучшие результаты везде.

Оценка неопределенности

Вы также можете алгоритмически слегка изменить параметры, чтобы получить представление о неопределенностив подсчете.Это может выглядеть так:

import numpy as np
import cv2
import itertools
from matplotlib import pyplot as plt
from skimage.morphology import extrema
from skimage.morphology import watershed as skwater

def CountCells(dilation=5, fg_frac=0.6):
  #Read in image
  img         = cv2.imread('cells.jpg')
  #Convert to a single, grayscale channel
  gray        = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
  #Threshold the image to binary using Otsu's method
  ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
  #Adjust iterations until desired result is achieved
  kernel  = np.ones((3,3),np.uint8)
  dilated = cv2.dilate(thresh, kernel, iterations=dilation)
  #Calculate distance transformation
  dist         = cv2.distanceTransform(dilated,cv2.DIST_L2,5)
  #Adjust this parameter until desired separation occurs
  fraction_foreground = fg_frac
  ret, sure_fg = cv2.threshold(dist,fraction_foreground*dist.max(),255,0)
  # Finding unknown region
  unknown = cv2.subtract(dilated,sure_fg.astype(np.uint8))
  # Marker labelling
  ret, markers = cv2.connectedComponents(sure_fg.astype(np.uint8))
  # Add one to all labels so that sure background is not 0, but 1
  markers = markers+1
  # Now, mark the region of unknown with zero
  markers[unknown==np.max(unknown)] = 0    
  markers = skwater(-dist,markers,watershed_line=True)
  return len(set(markers.flatten()))-1

#Smaller numbers are noisier, which leads to many small blobs that get
#thresholded out (undercounting); larger numbers result in possibly fewer blobs,
#which can also cause undercounting.
dilations = [4,5,6] 
#Small numbers equal less separation, so undercounting; larger numbers equal
#more separation or drop-outs. This can lead to over-counting initially, but
#rapidly to under-counting.
fracs     = [0.5, 0.6, 0.7, 0.8] 

for params in itertools.product(dilations,fracs):
  print("Dilation={0}, FG frac={1}, Count={2}".format(*params,CountCells(*params)))

Получение результата:

Dilation=4, FG frac=0.5, Count=22
Dilation=4, FG frac=0.6, Count=23
Dilation=4, FG frac=0.7, Count=17
Dilation=4, FG frac=0.8, Count=12
Dilation=5, FG frac=0.5, Count=21
Dilation=5, FG frac=0.6, Count=23
Dilation=5, FG frac=0.7, Count=20
Dilation=5, FG frac=0.8, Count=13
Dilation=6, FG frac=0.5, Count=20
Dilation=6, FG frac=0.6, Count=23
Dilation=6, FG frac=0.7, Count=24
Dilation=6, FG frac=0.8, Count=14

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

Помните, что для лицензирования StackOverflow необходимо указать соответствующую атрибуцию .В академической работе это может быть сделано путем цитирования.

0 голосов
/ 01 декабря 2018

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

Споры в этой сцене, кажется, имеют схожий размер и примерно однородную форму.Учитывая это, вы можете использовать область ваших контуров, чтобы найти приблизительное количество спор, которые будут занимать указанную область, используя среднюю площадь спор.Споры не могут полностью заполнить эти произвольные формы, поэтому вам придется принять это во внимание.Этого можно добиться, найдя цвет фона и удалив область, цвет фона занимает область контура.В таких сценах вы должны получить очень близко к реальному ответу для области ячейки.

Итак, резюмируем:

Find average area of spore,

Find background color

Find contour area,

subtract background color pixels/area from contour

approximate_spore_count = ceil(contour_area / (average_area_of_spore))

Вы используете здесь ceil для того, чтобы позаботиться о том, что у вас могут быть споры, которые меньше, чем средние значения, которые можно найти по отдельности, хотя вы можете также поставить определенное условие для обработки этого, однакотогда вам нужно принять решение, если вы хотите посчитать долю споры или округления до целого числа с площадью контура> средней площадью споры.

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

Еще одна вещь, которую вы должны рассмотреть, хотя я не думаю, что это обязательно решит проблему с комкованием, - это использовать встроенного в Bloc обнаружения * OpenCV , который, если вы пойдете на районный подход,может быть в состоянии помочь вам с крайними случаями, которые может представлять градиент на вашем фоне.Используя обнаружение капли, вы можете просто обнаружить капли и разделить общую площадь капли на среднюю площадь споры.Вы можете следовать этому руководству , чтобы понять, как использовать его в Python.Вам также может пригодиться простой контурный подход с использованием контуров opencv, полезный для вашего случая использования.

TLDR: ваши споры примерно одинакового размера и оттенка цвета, вашифон примерно однородный, используйте среднюю площадь спор и разделите область, занимаемую цветами спор, чтобы получить гораздо более точный подсчет

Добавление:

Если у вас возникли проблемы с поискомсредняя площадь спор, тогда, если у вас было какое-либо представление о средней «одиночестве» споры (четко разделенная), вы могли бы использовать ее для сортировки контуров / пятен по площади, а затем взять нижний n% спор в соответствии сВероятность «одиночества» (n) и средняя.Пока «одиночество» не зависит в значительной степени от размера споры, это должно быть довольно точным измерением среднего размера споры.Это работает, потому что если вы предполагаете, что равномерное распределение спор является «одиноким», то вы можете думать о нем как о случайной выборке само по себе, и если вы знаете средний процент одиночества, то вы, вероятно, получите очень высокий процент одиночестваспоры, если вы берете% n отсортированных спор по размеру (или слегка уменьшаете n, чтобы уменьшить вероятность случайного захвата больших спор).Теоретически вам нужно будет сделать это только один раз, если вы знаете коэффициент масштабирования.

...