Ich versuche, die Anzahl der Sporen einer Krankheit aus einer mikroskopischen Probe mit Pythony zu zählen, aber bisher ohne großen Erfolg.
Weil die Farbe der Spore dem Hintergrund ähnlich ist und viele davon nahe beieinander liegen.
nach der fotografischen Mikroskopie der Probe.
Bildverarbeitungscode:
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)
Der Algorithmus gezählt 11 Sporen
Aber im Bild sind 27 Sporen enthalten
Ergebnis der Bildverarbeitung zeigt, dass Sporen gruppiert sind
Wie mache ich das genauer?
Zunächst ein paar vorläufige Codes, die wir unten verwenden werden:
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()
Hier sehen Sie Ihr Originalbild:
#Read in image
img = cv2.imread('cells.jpg')
ShowImage('Original',img,'bgr')
Otsus Methode ist eine Möglichkeit, Farben zu segmentieren. Das Verfahren geht davon aus, dass die Intensität der Pixel des Bildes in ein bimodales Histogramm eingetragen werden kann, und er findet ein optimales Trennzeichen für dieses Histogramm. Ich wende die Methode unten an.
#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')
Alle diese kleinen Flecken sind ärgerlich, wir können sie durch Dehnen loswerden:
#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')
Wir müssen nun die Gipfel der Wasserscheide identifizieren und ihnen separate Etiketten geben. Das Ziel hiervon ist es, einen Satz von Pixeln zu erzeugen, so dass jede der Zellen ein Pixel in sich hat und dass sich nicht zwei Zellen mit ihren identifizierenden Pixeln berühren.
Um dies zu erreichen, führen wir eine Entfernungstransformation durch und filtern dann Entfernungen heraus, die zu weit vom Zentrum der Zelle entfernt sind.
#Calculate distance transformation
dist = cv2.distanceTransform(dilated,cv2.DIST_L2,5)
ShowImage('Distance',dist,'gray')
#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')
Jeder weiße Bereich im obigen Bild ist für den Algorithmus eine separate Zelle.
Nun identifizieren wir unbekannte Regionen, dh die Regionen, die durch den Algorithmus der Wasserscheide markiert werden, indem wir die Maxima abziehen:
# Finding unknown region
unknown = cv2.subtract(dilated,sure_fg.astype(np.uint8))
ShowImage('Unknown',unknown,'gray')
Die unbekannten Regionen sollten vollständige Donuts um jede Zelle bilden.
Als Nächstes geben wir den einzelnen Regionen, die sich aus der Distanztransformation ergeben, eindeutige Bezeichnungen und markieren dann die unbekannten Regionen, bevor sie die Wasserscheidetransformation durchführen:
# 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')
Jetzt ist die Gesamtanzahl der Zellen die Anzahl der eindeutigen Marker minus 1 (um den Hintergrund zu ignorieren):
len(set(markers.flatten()))-1
In diesem Fall bekommen wir 23.
Sie können dies mehr oder weniger genau tun, indem Sie den Entfernungsschwellenwert und den Erweiterungsgrad anpassen und möglicherweise h-Maxima (lokal Schwellenwert-Maxima) verwenden. Aber hüte dich vor Überanpassung; Nehmen Sie also nicht an, dass Sie durch die Optimierung eines einzelnen Bildes überall die besten Ergebnisse erzielen.
Sie können die Parameter auch algorithmisch geringfügig ändern, um ein Gefühl für die Unsicherheit in der Zählung zu erhalten. Das könnte so aussehen
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)))
Das Ergebnis geben:
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
Der Mittelwert der Zählwerte ist eine Möglichkeit, diese Unsicherheit in eine einzige Zahl zu integrieren.
Beachten Sie, dass für die Lizenzierung von StackOverflow geeignetattribution erforderlich ist. In der akademischen Arbeit kann dies durch Zitieren erfolgen.
Diese Pilzsporen haben ungefähr die gleiche Größe. Wenn Sie sich nicht für die exakte Genauigkeit interessieren, anstatt durch das Kaninchenloch zu springen und Grenzen und Wasserscheide zu erweitern, können Sie könnte ein sehr gutes Ergebnis erzielen einfache Änderung Ihrer aktuellen Algorithmen und eine Tonne mehr Genauigkeit.
Die Sporen in dieser Szene scheinen eine ähnliche Größe und ungefähr homogene Form zu haben. Unter der Voraussetzung, dass Sie möglicherweise in der Lage sind, den Bereich Ihrer Konturen zu verwenden, um die ungefähre Anzahl von Sporen zu ermitteln, die diesen Bereich einnehmen würden, wobei die durchschnittliche Sporenfläche verwendet wird. Sporen können diese willkürlichen Formen nicht vollständig ausfüllen, daher müssen Sie dies berücksichtigen. Sie erreichen dies, indem Sie die Hintergrundfarbe suchen und den Bereich entfernen, den die Hintergrundfarbe aus dem Konturbereich einnimmt. In solchen Szenen solltest du ganz nah dran an der realen Antwort für den Zellenbereich sein.
so um es zusammenzufassen:
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))
Sie verwenden hier ceil, um die Tatsache zu berücksichtigen, dass Sie möglicherweise Sporen haben, die kleiner als der Durchschnitt sind, die einzeln gefunden werden. Sie können jedoch auch eine bestimmte Bedingung festlegen, um dies zu handhaben. Dann müssen Sie jedoch eine Entscheidung treffen, wenn Sie möchten Zählen Sie den Bruchteil einer Spore oder Runde zu einer ganzen Zahl mit Konturfläche> durchschnittliche Sporenfläche.
Sie können jedoch feststellen, dass, wenn Sie die Hintergrundfarbe herausfinden können und Ihre Sporen ungefähr gleich geformt und von homogener Farbe sind, Sie eine bessere Leistung erzielen, wenn Sie einfach den Bereich der Hintergrundfarbe vom gesamten Bild subtrahieren und dividiere die durchschnittliche Sporengröße aus dem verbleibenden Bereich. Dies wäre viel schneller als die Verwendung von Dilatation.
Eine andere Sache, die Sie in Betracht ziehen sollten, obwohl ich nicht glaube, dass sie Ihr Problem mit der Klumpenbildung unbedingt beheben wird, ist die Verwendung von OpenCV eingebaute Blob-Erkennung , was möglicherweise hilfreich sein kann, wenn Sie sich für den Gebietsansatz entscheiden Sie mit den Randfällen, die der Farbverlauf in Ihrem Hintergrund darstellen könnte. Mit der Blob-Erkennung können Sie nur Blobs erkennen und die gesamte Blob-Fläche durch die durchschnittliche Sporenfläche teilen. Sie können dieses Tutorial folgen, um zu verstehen, wie man es in Python verwendet. Möglicherweise stellen Sie auch den Erfolg eines einfachen Konturansatzes fest, indem Sie die Konturen von opencv verwenden, die für Ihren Anwendungsfall hilfreich sind.
TLDR: Ihre Sporen haben ungefähr die gleiche Größe und Farbe, Ihr Hintergrund ist ungefähr homogen, verwenden Sie die durchschnittliche Sporenfläche und teilen Sie die Fläche durch die Sporenfarben, um eine genauere Zählung zu erhalten
Wenn Sie Schwierigkeiten haben, die durchschnittliche Sporenfläche zu finden, können Sie, wenn Sie eine Vorstellung von der durchschnittlichen "Einsamkeit" einer Spore haben (deutlich getrennt), die Konturen/Kleckse nach Fläche sortieren und dann den Boden abnehmen n% der Sporen nach der "Einsamkeitswahrscheinlichkeit" (n) und deren Durchschnitt. Solange "Einsamkeit" nicht stark von der Sporengröße abhängt, sollte dies eine ziemlich genaue Messung der durchschnittlichen Sporengröße sein. Dies funktioniert, denn wenn Sie davon ausgehen, dass die Sporen gleichmäßig "einsam" sind, können Sie sich das als Zufallsstichprobe vorstellen. Wenn Sie den durchschnittlichen Prozentsatz an Einsamkeit kennen, werden Sie wahrscheinlich einen sehr hohen Prozentsatz an Einsamkeit bekommen Sporen, wenn Sie diese% n der sortierten Sporen nach Größe sortieren (oder n leicht verkleinern, um die Wahrscheinlichkeit zu verringern, versehentlich große Sporen zu ergreifen). Sie müssten dies theoretisch nur einmal tun, wenn Sie den Zoomfaktor kennen.