3

I'm having issues with finishing the segmentation in some photos of bio-samples, I'm trying to analyze growth of bacteria with image processing, as in theory it should work. Here is one of the original images I have:

biofilm of pseudomona

I'm trying to segment the area inside the circle and see how the values of the pixels change as time passes. I've been trying a lot of techniques, as I'm relative new to analyzing these kind of samples.Initially I was using opencv, but I wasn't getting the results I wanted so now I'm using scikit-image for all the techniques for image processing and segmentation. Here's the code I have until now:

from skimage import morphology, exposure, io, filters
from scipy import ndimage as ndi
from skimage.color import rgb2gray, label2rgb
from skimage.filters import sobel, rank
import matplotlib.pyplot as plt
y1=400
y2=1600
x1=700
x2=1900
test_img = io.imread(folders_path+hour_tested[0]+'5.jpg')
roi_test = test_img[y1:y2, x1:x2,:]
gray_img = rgb2gray(roi_test)
denoised_img = rank.median(gray_img, morphology.disk(5))
val = filters.threshold_otsu(denoised_img)
mask = denoised_img > val
elevation_map=sobel(denoised_img)
segmentation = morphology.watershed(elevation_map, mask=mask)
labeled_bio, num_seg = ndi.label(segmentation)
image_label_overlay = label2rgb(labeled_bio, image=gray_img)
plt.imshow(image_label_overlay)
plt.show()

On the last line I get to segment by different colors the areas of the sample and get the part I want to analyze in one label, is now that I don't know how to continue or at least how to just see that label and then create a mask.

I'm also sharing the labeled image for anyone to see and maybe help me in the next steps, I feel like or I'm really close to segment my area of interest or really far and confused.

Well here's the labeled image of the sample:

segmented area with labels

halfer
  • 19,824
  • 17
  • 99
  • 186

2 Answers2

0

Here's an approach using simple image processing techniques

  • Obtain binary image. Load image, convert to grayscale, then Otsu's threshold to get a binary image

  • Perform morphological operations. We create an elliptical shaped kernel then perform morph close to fill in the contours

  • Isolate area of interest. We find contours and filter using contour approximation + contour area. Once we isolate the contour, find a minimum enclosing circle to obtain a perfect circle then draw this onto a blank mask. The idea to obtain a perfect circle is from how to modify a mask to make a perfect circle

  • Isolate ROI. We find the bounding rectangle ROI on the mask then crop using Numpy slicing

  • Bitwise-and to obtain result. Finally we bitwise-and the two extracted ROIs


Here's a visualization of each step:

Input image

Binary image

Morph close

Isolated area of interest highlighted in green and filled contour drawn onto a blank mask

Isolated ROIs

Bitwise-and for result (two versions, one with black background and one with white background, depending on what you want)

Code

import cv2
import numpy as np

# Load image, create blank mask, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=5)

# Find contours and filter using contour area + contour approximation
# Determine perfect circle contour then draw onto blank mask
cnts = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.04 * peri, True)
    area = cv2.contourArea(c)
    if len(approx) > 4 and area > 10000 and area < 500000:
        ((x, y), r) = cv2.minEnclosingCircle(c)
        cv2.circle(mask, (int(x), int(y)), int(r), (255, 255, 255), -1)
        cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 3)

# Extract ROI
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
x,y,w,h = cv2.boundingRect(mask)
mask_ROI = mask[y:y+h, x:x+w]
image_ROI = original[y:y+h, x:x+w]

# Bitwise-and for result
result = cv2.bitwise_and(image_ROI, image_ROI, mask=mask_ROI)
result[mask_ROI==0] = (255,255,255) # Color background white

cv2.imwrite('close.png', close)
cv2.imwrite('thresh.png', thresh)
cv2.imwrite('image.png', image)
cv2.imwrite('mask.png', mask)
cv2.imwrite('result.png', result)
cv2.waitKey()

Note: Another approach to determine the circular region of interest is to use Hough Circle Transform already implemented as cv2.HoughCircles() but there are many parameters so it may not be the most practical method.

nathancy
  • 42,661
  • 14
  • 115
  • 137
  • I have an issue, that when I've tried the code with the same image, I end up with a mask completely black and no segmentation. I've seen that the issue appear in the for statement, do I have to change the parameters in peri, approx and area? Also if I have more images of samples, is this going to function for most of them ? or will I have to tune the code for each image. Thanks, don't want to bother, but I wanted to understand how to code works and then it didn't work. – Jose Zapana Jan 23 '20 at 15:43
  • which version of opencv have you used? I have 3.4.6 – Jose Zapana Jan 23 '20 at 16:04
  • I'm using `python 3.7.4`, `numpy==1.14.5`, `opencv-python==4.1.0.25`. You may need to change the lower/upper area bounds, although it should work for most of your images – nathancy Jan 23 '20 at 20:36
0

After fixing the code this is the correct answer for the biofilm segmentation:

import cv2
import numpy as np
import os
def resize_image(image, percentage):
   scale_percent=percentage
   width = int(image.shape[1] * scale_percent/100)
   height= int(image.shape[0] * scale_percent/100)
   dimensions = (width, height)
   resized = cv2.resize(image, dimensions, interpolation = cv2.INTER_AREA)
   return resized
#this path is changed for each image in the DB
path=folders_path+hour_tested[0]+'1.jpg'
image = cv2.imread(path)
s_image = resize_image(image,50)
original = s_image.copy()
mask = np.zeros(s_image.shape, dtype=np.uint8)
gray = cv2.cvtColor(s_image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
# Find contours and filter using contour area + contour approximation
# Determine perfect circle contour then draw onto blank mask
im,cnts,hierarchy = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.04*peri, True)
    area = cv2.contourArea(c)
    if len(approx) > 4 and (area > 8000 and area < 250000) and (peri<2000 and peri>1000):
        ((x, y), r) = cv2.minEnclosingCircle(c)
        x,y,r = int(x),int(y),int(r)
        blank_circle=cv2.circle(mask, (x, y), r, (255, 255, 255), -1)
        filled_circle=cv2.circle(s_image, (x, y), r, (36, 255, 12), 3)
        # Extract ROI
        mask_ROI = blank_circle[y-r:y+r, x-r:x+r]
        mask_ROI = cv2.cvtColor(mask_ROI, cv2.COLOR_BGR2GRAY)
        image_ROI = filled_circle[y-r:y+r, x-r:x+r]
        result = cv2.bitwise_and(image_ROI, image_ROI, mask=mask_ROI)
        cv2.imwrite('result.png',result)