J'essaie de compter la quantité de spores d'une maladie provenant d'un échantillon microscopique avec Pythony, mais jusqu'à présent sans grand succès.
Parce que la couleur de la spore est semblable à celle de l'arrière-plan et que beaucoup sont proches.
suivant la microscopie photographique de l'échantillon.
Code de traitement d'image:
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)
L'algorithme compté 11 spores
Mais dans l'image contient 27 spores
Le résultat du traitement de l’image montre que les spores sont regroupées
Comment puis-je rendre cela plus précis?
Tout d'abord, un code préliminaire que nous utiliserons ci-dessous:
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()
Pour référence, voici votre image d'origine:
#Read in image
img = cv2.imread('cells.jpg')
ShowImage('Original',img,'bgr')
La méthode d'Otsu est un moyen de segmenter les couleurs. La méthode suppose que l’intensité des pixels de l’image puisse être tracée dans un histogramme bimodal et trouve un séparateur optimal pour cet histogramme. J'applique la méthode ci-dessous.
#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')
Toutes ces petites taches sont gênantes, on peut s'en débarrasser en les dilatant:
#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')
Nous devons maintenant identifier les sommets du bassin versant et leur attribuer des étiquettes distinctes. L'objectif est de générer un ensemble de pixels tel que chacune des cellules contienne un pixel et qu'aucune cellule ne possède son identificateur de pixels en contact.
Pour ce faire, nous effectuons une transformation de distance puis filtrons les distances trop éloignées du centre de la cellule.
#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')
Chaque zone de blanc dans l'image ci-dessus est, en ce qui concerne l'algorithme, une cellule distincte.
Nous identifions maintenant les régions inconnues, celles qui seront étiquetées par l’algorithme de gestion des bassins versants, en soustrayant les maxima:
# Finding unknown region
unknown = cv2.subtract(dilated,sure_fg.astype(np.uint8))
ShowImage('Unknown',unknown,'gray')
Les régions inconnues doivent former des beignets complets autour de chaque cellule.
Ensuite, nous donnons à chacune des régions distinctes résultant de la transformation de distance des étiquettes uniques, puis marquons les régions inconnues avant de procéder à la transformation des bassins versants:
# 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')
Maintenant, le nombre total de cellules est le nombre de marqueurs uniques moins 1 (pour ignorer le fond):
len(set(markers.flatten()))-1
Dans ce cas, nous obtenons 23.
Vous pouvez rendre cela plus ou moins précis en ajustant le seuil de distance, le degré de dilatation, en utilisant éventuellement h-maxima (maxima à seuil local). Mais méfiez-vous de l'overfitting; En d'autres termes, ne supposez pas que le réglage d'une seule image vous donnera les meilleurs résultats partout.
Vous pouvez également modifier légèrement les paramètres de manière algorithmique pour avoir une idée de l’incertitude du décompte. Cela pourrait ressembler à ceci
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)))
Donner le résultat:
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
Prendre la médiane des valeurs de comptage est un moyen d'incorporer cette incertitude dans un nombre unique.
N'oubliez pas que la licence de StackOverflow nécessite que vous donniez appropriéattribution . Dans le travail académique, cela peut être fait via la citation.