I have an image of a fluid that is being filled in a bottle. I’m happy with the detection of the ‘bubbles’ . But I need to ignore the chained detected bubbles.
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
ROI_LEFT = 200
ROI_RIGHT = 400
ROI_TOP = 200
ROI_BOTTOM = 400
def load_image(image_path, scale_factor=3):
"""Load an image from a file path, convert to grayscale, and resize"""
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return gray_image
def crop_image(image, roi_left, roi_right, roi_top, roi_bottom):
"""Crop an image to a region of interest"""
im_height, im_width = image.shape
return image[im_height - roi_bottom:im_height - roi_top, roi_left:roi_right]
def resize_image(image, scale_factor):
"""Resize an image"""
h, w = image.shape
return cv2.resize(image, (h * scale_factor, w * scale_factor), interpolation=cv2.INTER_AREA)
def apply_gaussian_blur(image, kernel_size):
"""Apply Gaussian blur to reduce noise"""
return cv2.GaussianBlur(image, None, sigmaX=20, sigmaY=20)
def threshold_image(image):
"""Apply Otsu's thresholding"""
_, thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
return thresh
def detect_cells(image):
"""Detect cells using watershed transform"""
thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
distance_map = ndimage.distance_transform_edt(thresh)
local_max = peak_local_max(distance_map, indices=False, min_distance=20, labels=thresh)
markers = ndimage.label(local_max, structure=np.ones((3, 3)))[0]
labels = watershed(-distance_map, markers, mask=thresh)
return labels
def calculate_cell_area(labels, image, max_area=10000):
"""Calculate the area of each cell, filter out areas larger than a threshold, and return the total area and number of cells"""
total_area = 0
num_cells = 0
for label in np.unique(labels):
if label == 0:
continue
mask = np.zeros(image.shape, dtype="uint8")
mask[labels == label] = 255
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > max_area:
continue
total_area += area
num_cells += 1
cv2.drawContours(image, [c], 0, (36,255,12), 2)
return total_area, num_cells
image = load_image('492.png')
img = crop_image(image, ROI_LEFT, ROI_RIGHT, ROI_TOP, ROI_BOTTOM)
img = resize_image(img, 5)
img_blur = apply_gaussian_blur(img, 5)
division = cv2.divide(img, img_blur, scale=255)
thresh = threshold_image(division)
labels = detect_cells(thresh)
total_area = calculate_cell_area(labels, img)
print("Total cell area:", total_area)
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].imshow(img, cmap='gray') # Grayscale image
ax[1].imshow(labels, cmap='bone') # Labeled image
plt.show()