Count number of cells in the image
Asked Answered
S

1

7

I need code for counting the number of cells in the image and only the cells that are in pink color should be counted .I have used thresholding and watershed method.

enter image description here

import cv2
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import imutils

image = cv2.imread("cellorigin.jpg")

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
    cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv2.imshow("Thresh", thresh)


D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=20,
    labels=thresh)
cv2.imshow("D image", D)

markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) -     1))

for label in np.unique(labels):
    # if the label is zero, we are examining the 'background'
    # so simply ignore it
    if label == 0:
        continue

    # otherwise, allocate memory for the label region and draw
    # it on the mask
    mask = np.zeros(gray.shape, dtype="uint8")
    mask[labels == label] = 255

    # detect contours in the mask and grab the largest one
    cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
        cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)
    c = max(cnts, key=cv2.contourArea)

    # draw a circle enclosing the object
    ((x, y), r) = cv2.minEnclosingCircle(c)
    cv2.circle(image, (int(x), int(y)), int(r), (0, 255, 0), 2)
    cv2.putText(image, "#{}".format(label), (int(x) - 10, int(y)),
        cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)



cv2.imshow("input",image

cv2.waitKey(0)

I am not able to segment the pink cells properly.At some places two pink cells are attached together those also should be separated.

output:

enter image description here

Subequatorial answered 7/11, 2019 at 14:39 Comment(2)
Does this answer your question? How to calculate nucleus amount of cell?Sutlej
partially i too saw that question @CrisLuengoSubequatorial
G
8

Since the cells seem to be visibility different from the nucleus (dark purple) and the background (light pink), color thresholding should work here. The idea is to convert the image to HSV format then use a lower and upper color threshold to isolate the cells. This will give us a binary mask which we can use to count the number of cells.


We begin by converting the image to HSV format then use a lower/upper color threshold to create a binary mask. From here we perform morphological operations to smooth the image and remove small bits of noise.

enter image description here

Now that we have the mask, we find contours with the cv2.RETR_EXTERNAL parameter to ensure that we only take the outer contours. We define several area thresholds to filter out the cells

minimum_area = 200
average_cell_area = 650
connected_cell_area = 1000

The minimum_area threshold ensures that we do not count tiny sections of a cell. Since some of the cells are connected, some contours may have multiple connected cells represented as a single contour so to estimate the cells better, we define an average_cell_area parameter which estimates the area of a single cell. The connected_cell_area parameter detects connected cells where use math.ceil() on a connected cell contour to estimate the number of cells in that contour. To count the number of cells, we iterate through the contours and sum up the contours based on their area. Here's the detected cells highlighted in green

enter image description here

Cells: 75

Code

import cv2
import numpy as np
import math

image = cv2.imread("1.jpg")
original = image.copy()
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

hsv_lower = np.array([156,60,0])
hsv_upper = np.array([179,115,255])
mask = cv2.inRange(hsv, hsv_lower, hsv_upper)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations=2)

cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

minimum_area = 200
average_cell_area = 650
connected_cell_area = 1000
cells = 0
for c in cnts:
    area = cv2.contourArea(c)
    if area > minimum_area:
        cv2.drawContours(original, [c], -1, (36,255,12), 2)
        if area > connected_cell_area:
            cells += math.ceil(area / average_cell_area)
        else:
            cells += 1
print('Cells: {}'.format(cells))
cv2.imshow('close', close)
cv2.imshow('original', original)
cv2.waitKey()
Greaten answered 7/11, 2019 at 22:35 Comment(10)
Brilliant approach! Great explanation.Shinny
Thats a great approach is it possible to separate the bonded pink cells in to two different cells at some places .(I used water shed segmentation for that )Subequatorial
You could use watershed on the connected cells but since they are not a "standard" circle since the shape of the cell can differ greatly, I doubt you will get good results. Watershed would probably give you an estimate of the cell contour in the best case scenario. If you look at your current watershed results, it detects two connected cells as a single contour. For a human its difficult to exactly specific the divide between two cells so watershed would have a hard time too. At best you will get a rough estimate. I recommend looking into other properties such as area or aspect ratioGreaten
I would look into those approaches, they will give you the best resultsGreaten
I did not try yet. However, I think an opening operation (cv2.MORPH_OPEN) could split these connected cells. What is your opinion?Lafave
@Lafave yes you could simply do a morph open but you will also erode a lot of the detail of the connected cells. The blob will get smaller and smaller until there are more than one contour. It would work but there is a tradeoff from detail lossGreaten
For an opening, an erosion followed by a dilation. I am not sure which detail will be missing?Lafave
@Lafave With each iteration the area of the object decreases so eventually the object will disappear. Each dilation does not bring back the same amount of detail loss, closing may work when the connected joint is small but in cases where they are almost overlapping, you will have to constantly erode/dilate until the cell becomes extremely small (detail is loss)Greaten
I see. So maybe just apply for a small iteration, so disjoin mere overlap cells? Anyway, seeing is believing, I need to try this at the weekend to confirm.Lafave
If you only use a small iteration it should work. If you use any kernel larger than 1x1 it will eventually erode the object to nothingGreaten

© 2022 - 2024 — McMap. All rights reserved.