Python/OpenCV——细菌聚簇重心确定

9
我目前正在开发一种算法,用于确定细菌聚集的(Brightfield)显微镜图像的质心位置。这是图像处理中一个重要的开放性问题。
这个问题是Python/OpenCV — Matching Centroid Points of Bacteria in Two Images的后续。
目前,该算法对于稀疏、分散的细菌非常有效。然而,当细菌聚集在一起时,它变得完全无效。
在这些图像中,注意到细菌的质心位置被有效地定位。 亮场图像#1 亮场图像#1 亮场图像#2 亮场图像#2

明场图像#3 明场图像#3

然而,当细菌在不同水平聚集时,算法会失败。

明场图像#4 明场图像#4

明场图像#5 明场图像#5

亮场图像#6 亮场图像#6

亮场图像#7 亮场图像#7

亮场图像#8 亮场图像#8

原始图像

亮场图像#1

明场图像#2

明场图像#3

明场图像#4

明场图像#5

明场图像#6

明场图像#7

明场图像#8

我希望优化我的当前算法,使其对这些类型的图像更加强健。 这是我正在运行的程序。

import cv2
import numpy as np
import os

kernel = np.array([[0, 0, 1, 0, 0],
                   [0, 1, 1, 1, 0],
                   [1, 1, 1, 1, 1],
                   [0, 1, 1, 1, 0],
                   [0, 0, 1, 0, 0]], dtype=np.uint8)


def e_d(image, it):
    image = cv2.erode(image, kernel, iterations=it)
    image = cv2.dilate(image, kernel, iterations=it)
    return image


path = r"(INSERT IMAGE DIRECTORY HERE)"
img_files = [file for file in os.listdir(path)]


def segment_index(index: int):
    segment_file(img_files[index])


def segment_file(img_file: str):
    img_path = path + "\\" + img_file
    print(img_path)
    img = cv2.imread(img_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Applying adaptive mean thresholding
    th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    # Removing small noise
    th = e_d(th.copy(), 1)

    # Finding contours with RETR_EXTERNAL flag and removing undesired contours and
    # drawing them on a new image.
    cnt, hie = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cntImg = th.copy()
    for contour in cnt:
        x, y, w, h = cv2.boundingRect(contour)
        # Eliminating the contour if its width is more than half of image width
        # (bacteria will not be that big).
        if w > img.shape[1] / 2:
            continue
        cntImg = cv2.drawContours(cntImg, [cv2.convexHull(contour)], -1, 255, -1)

    # Removing almost all the remaining noise.
    # (Some big circular noise will remain along with bacteria contours)
    cntImg = e_d(cntImg, 3)

    # Finding new filtered contours again
    cnt2, hie2 = cv2.findContours(cntImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    # Now eliminating circular type noise contours by comparing each contour's
    # extent of overlap with its enclosing circle.
    finalContours = []  # This will contain the final bacteria contours
    for contour in cnt2:
        # Finding minimum enclosing circle
        (x, y), radius = cv2.minEnclosingCircle(contour)
        center = (int(x), int(y))
        radius = int(radius)

        # creating a image with only this circle drawn on it(filled with white colour)
        circleImg = np.zeros(img.shape, dtype=np.uint8)
        circleImg = cv2.circle(circleImg, center, radius, 255, -1)

        # creating a image with only the contour drawn on it(filled with white colour)
        contourImg = np.zeros(img.shape, dtype=np.uint8)
        contourImg = cv2.drawContours(contourImg, [contour], -1, 255, -1)

        # White pixels not common in both contour and circle will remain white
        # else will become black.
        union_inter = cv2.bitwise_xor(circleImg, contourImg)

        # Finding ratio of the extent of overlap of contour to its enclosing circle.
        # Smaller the ratio, more circular the contour.
        ratio = np.sum(union_inter == 255) / np.sum(circleImg == 255)

        # Storing only non circular contours(bacteria)
        if ratio > 0.55:
            finalContours.append(contour)

    finalContours = np.asarray(finalContours)

    # Finding center of bacteria and showing it.
    bacteriaImg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

    for bacteria in finalContours:
        M = cv2.moments(bacteria)
        cx = int(M['m10'] / M['m00'])
        cy = int(M['m01'] / M['m00'])

        bacteriaImg = cv2.circle(bacteriaImg, (cx, cy), 5, (0, 0, 255), -1)

    cv2.imshow("bacteriaImg", bacteriaImg)
    cv2.waitKey(0)

# Segment Each Image
for i in range(len(img_files)):
    segment_index(i)

理想情况下,我希望至少能在发布的几幅图像中进行改进。

1
哥们,我现在无法为你的问题提供完整的解决方案,但我会采用更复杂的技术,比如分水岭算法。不过还需要一些工作来分离背景。 - dpetrini
2个回答

5
口罩始终是识别物体的弱点,也是最重要的一步。这将提高识别高数量细菌图像的能力。我通过添加一个OPEN和另一个带有核的ERODE通道,并更改it(迭代次数)变量(从1,3更改为1,2),修改了您的e_d函数以执行此操作。这绝不是一个完成的努力,但我希望它能给您一个关于如何进一步增强它的想法。我使用了您提供的图像,由于它们已经有了红点,这可能会干扰我的结果图像...但您可以看到它能够在大多数情况下识别更多的细菌。我的一些结果显示出两个点,而仅有一个细菌的图像则被我错过了,这很可能是因为它已经被标记了。请尝试使用原始图像并查看其效果。
此外,由于细菌在大小和形状上相对均匀,我认为您可以使用每个细菌的高度与宽度之比和/或平均值来过滤极端形状(小型或大型)以及瘦长形状。您可以测量足够多的细菌,以确定平均轮廓长度、高度和宽度,或高度/宽度比等,以找到合理的公差,而不是与图像大小本身的比例。另一个建议是重新思考如何一起遮罩这些图像,可能尝试分两步进行。第一步是找到包含细菌的长形状的边界,然后在其中找到细菌。这假设所有图像都与这些类似,如果是这样,它可能有助于消除永远不是细菌的边界外的杂散命中。

Bright-Field Image #1

Bright-Field Image #2

Bright-Field Image #3

Bright-Field Image #4

Bright-Field Image #5

Bright-Field Image #6

Bright-Field Image #7

Bright-Field Image #8

#!usr/bin/env python
# https://dev59.com/Ubzpa4cB1Zd3GeqPL3wB
import cv2
import numpy as np
import os

kernel = np.array([[0, 0, 1, 0, 0],
                   [0, 1, 1, 1, 0],
                   [1, 1, 1, 1, 1],
                   [0, 1, 1, 1, 0],
                   [0, 0, 1, 0, 0]], dtype=np.uint8)


def e_d(image, it):
    print(it)
    image = cv2.erode(image, kernel, iterations=it)
    image = cv2.dilate(image, kernel, iterations=it)
    image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel, iterations = 1)
    image = cv2.morphologyEx(image, cv2.MORPH_ERODE, kernel, iterations = 1)
    return image


#path = r"(INSERT IMAGE DIRECTORY HERE)"
path = r"E:\stackimages"
img_files = [file for file in os.listdir(path)]


def segment_index(index: int):
    segment_file(img_files[index])


def segment_file(img_file: str):
    img_path = path + "\\" + img_file
    print(img_path)
    head, tail = os.path.split(img_path)
    img = cv2.imread(img_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.imshow("bacteriaImg-1", img)
    cv2.waitKey(0)
    # Applying adaptive mean thresholding
    th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    # Removing small noise
    th = e_d(th.copy(), 1)

    # Finding contours with RETR_EXTERNAL flag and removing undesired contours and
    # drawing them on a new image.
    cnt, hie = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cntImg = th.copy()
    for contour in cnt:
        x, y, w, h = cv2.boundingRect(contour)
        # Eliminating the contour if its width is more than half of image width
        # (bacteria will not be that big).
        
        if w > img.shape[1] / 2:
            continue
  
        else:
           
            cntImg = cv2.drawContours(cntImg, [cv2.convexHull(contour)], -1, 255, -1)


    # Removing almost all the remaining noise.
    # (Some big circular noise will remain along with bacteria contours)
    cntImg = e_d(cntImg, 2)
    cv2.imshow("bacteriaImg-2", cntImg)
    cv2.waitKey(0)

    # Finding new filtered contours again
    cnt2, hie2 = cv2.findContours(cntImg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

    # Now eliminating circular type noise contours by comparing each contour's
    # extent of overlap with its enclosing circle.
    finalContours = []  # This will contain the final bacteria contours
    for contour in cnt2:
        # Finding minimum enclosing circle
        (x, y), radius = cv2.minEnclosingCircle(contour)
        center = (int(x), int(y))
        radius = int(radius)

        # creating a image with only this circle drawn on it(filled with white colour)
        circleImg = np.zeros(img.shape, dtype=np.uint8)
        circleImg = cv2.circle(circleImg, center, radius, 255, -1)

        # creating a image with only the contour drawn on it(filled with white colour)
        contourImg = np.zeros(img.shape, dtype=np.uint8)
        contourImg = cv2.drawContours(contourImg, [contour], -1, 255, -1)

        # White pixels not common in both contour and circle will remain white
        # else will become black.
        union_inter = cv2.bitwise_xor(circleImg, contourImg)

        # Finding ratio of the extent of overlap of contour to its enclosing circle.
        # Smaller the ratio, more circular the contour.
        ratio = np.sum(union_inter == 255) / np.sum(circleImg == 255)

        # Storing only non circular contours(bacteria)
        if ratio > 0.55:
            finalContours.append(contour)

    finalContours = np.asarray(finalContours)

    # Finding center of bacteria and showing it.
    bacteriaImg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

    for bacteria in finalContours:
        M = cv2.moments(bacteria)
        cx = int(M['m10'] / M['m00'])
        cy = int(M['m01'] / M['m00'])

        bacteriaImg = cv2.circle(bacteriaImg, (cx, cy), 5, (0, 0, 255), -1)

    cv2.imshow("bacteriaImg", bacteriaImg)
    cv2.waitKey(0)


# Segment Each Image
for i in range(len(img_files)):
    segment_index(i)

1
太棒了!我认为我可以改进确定中心和消除背景噪音的方式,但主要难题是检测细菌。现在我正在努力弄清楚为什么标记没有放置在细菌的中心。你有什么想法吗? - Raiyan Chowdhury
虽然我在发布的图像上得到了与您相同的结果,但在原始图像上复制此结果有些困难。我已将原始图像添加到问题中。能否请您看一下?如果我能找到有效解决方法,我愿意为此问题提高悬赏金额。 - Raiyan Chowdhury

2

这里有一些代码,你可以尝试并查看是否适用于你。它使用了一种替代方法来分割图像。你可以调整参数来看哪种组合会给你最可接受的结果。

import numpy as np
import cv2
import matplotlib.pyplot as plt


# Adaptive threshold params
gw = 11
bs = 7
offset = 5

bact_aspect_min = 2.0
bact_aspect_max = 10.0
bact_area_min = 20 # in pixels
bact_area_max = 1000

url = "/path/to/image"
img_color = cv2.imread(url)
img = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
rows, cols = img.shape

img_eq = img.copy()
cv2.equalizeHist(img, img_eq)

img_blur = cv2.medianBlur(img_eq, gw)
th = cv2.adaptiveThreshold(img_blur, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, bs, offset)

_, contours, hier = cv2.findContours(th.copy(), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for i in range(len(contours)):
    # Filter closed contours
    rect = cv2.minAreaRect(contours[i])
    area = cv2.contourArea(contours[i])
    (x, y), (width, height), angle = rect
    if min(width, height) == 0:
        continue
        
    aspect_ratio = max(width, height) / min(width, height)
    
    if hier[0][i][3] != -1 and \
    bact_aspect_min < aspect_ratio < bact_aspect_max and \
    bact_area_min < area < bact_area_max:
        M = cv2.moments(contours[i])
        cx = int(M['m10'] / M['m00'])
        cy = int(M['m01'] / M['m00'])
        img_color = cv2.circle(img_color, (cx, cy), 3, (255, 0, 0), cv2.FILLED)

plt.imshow(img_color)

在大多数图像中,您的细菌似乎重叠在一起,很难判断它们的大小并将它们分开。最好的方法是在Jupyter / ipywidgets中使用一系列参数值运行此代码片段,并查看哪个效果最好。祝你好运!
编辑1:
我已更新代码,使用了稍微不同的技术和思路。基本上使用L2轮廓(孔)来确定细菌,这更符合细菌的形状。您可以再次调整参数以查看哪个效果最佳。代码中的参数集给我带来了令人满意的结果。您可能需要对图像进行更多过滤以消除虚假阳性。
除了最新代码中的技巧之外,还可以使用其他几种技巧:
  1. 尝试ADAPTIVE_THRESH_GAUSSIAN_C
  2. 尝试均衡化图像而不模糊
  3. 与一级轮廓一起使用二级轮廓
  4. 使用不同的L1和L2轮廓尺寸约束。
我认为所有这些的组合应该为您提供相当不错的结果。

谢谢你!当细菌“融合”时,它们实际上只是在分裂的过程中。因此,它仍应被视为单个细菌。 - Raiyan Chowdhury
在这种情况下,您可以将#4-L1轮廓作为主要约束条件,将L2轮廓作为次要约束条件。例如,一旦您发现某个L2级别的轮廓ctr_index是细菌的一部分,那么您所需要做的就是在其上运行形态学操作(腐蚀/开放),并将父级hier [0] [ctr_index] [3]添加为您的细菌。这样,您将有更多的“命中”,而不是错过,仅计算融合的细菌一次。如果我表达不清楚,请告诉我。 - Knight Forked

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接