【问题标题】:Python/OpenCV — Centroid Determination in Bacterial ClustersPython/OpenCV — 细菌簇中的质心测定
【发布时间】:2020-11-20 17:23:10
【问题描述】:

我目前正在开发一种算法,用于从细菌簇的(明场)显微镜图像中确定质心位置。这是目前图像处理中的一个主要开放问题。

此问题是对Python/OpenCV — Matching Centroid Points of Bacteria in Two Images 的后续问题。

目前,该算法对稀疏、间隔的细菌有效。但是,当细菌聚集在一起时,它就完全失效了。

在这些图像中,注意细菌质心是如何有效定位的。

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

原始图片

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

我想优化我当前的算法,使其更适合这些类型的图像。这是我正在运行的程序。

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)

理想情况下,我希望至少改进几张已发布的图片。

【问题讨论】:

  • 伙计,我现在无法为您的问题找到完整的解决方案,但我会采用更复杂的技术,例如分水岭。但是需要一些工作来分离背景。

标签: python python-3.x opencv image-processing computer-vision


【解决方案1】:

面具永远是识别物体的薄弱环节,也是最重要的一步。这将改进识别具有大量细菌的图像。我已经通过添加一个 OPEN 和另一个带有内核的 ERODE 传递来修改您的 e_d 函数,并为您的代码更改了它(迭代次数)变量(改为 1、2 而不是 1,3)来执行此操作。这绝不是一项完成的工作,但我希望它能让您了解您可能会尝试进一步增强它的内容。我使用了您提供的图像,并且由于它们已经有一个红点,这可能会干扰我的结果图像……但是您可以看到它能够识别大多数细菌。我的一些结果显示了两个点,而只有一个细菌的图像,我错过了,每个都很可能是因为它已经被标记了。尝试使用原始图像,看看效果如何。

另外,由于细菌的大小和形状都相对一致,我认为您可以使用每种细菌的高度与宽度的比率和/或平均值来过滤掉极端形状(小或大)和瘦的, 长的形状。您可以测量足够多的细菌,以查看平均轮廓长度,或高度和宽度,或高度/宽度比等,以找到合理的公差而不是与图像尺寸本身的比例。另一个建议是重新考虑如何将图像全部遮盖起来,可能分两步尝试。找到包含细菌的长形边界,然后找到其中的细菌。这假设您的所有图像都与这些图像相似,如果是这样,它可能有助于消除此边界之外的杂散命中,它们绝不是细菌。

#!usr/bin/env python
# https://stackoverflow.com/questions/63182075/python-opencv-centroid-determination-in-bacterial-clusters
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)

【讨论】:

  • 这太棒了!它认为我可以改进确定中心的方式并消除背景噪音,但主要障碍是检测细菌。现在我正试图弄清楚为什么标记没有被放置在细菌的中心。任何想法为什么?
  • 虽然我得到的结果与您发布的图像相同,但在原始图像上重现此问题时遇到了一些麻烦。我已将原始图像添加到问题中。你能看看吗?如果我能找到有效解决这个问题的方法,我愿意在这个问题上提出赏金。
【解决方案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 中运行此代码 sn-p 并查看最有效的方法。祝你好运!

编辑 1

我已经更新了代码以使用稍微不同的技术和想法。基本上使用l2轮廓(孔)来确定细菌,这更符合细菌的形状。您可以再次摆弄参数以查看最有效的参数。代码中的一组参数给了我满意的结果。您可能需要对图像进行更多过滤以消除误报。

除了最新代码中的技巧之外,还可以使用其他技巧:

  1. 试用 ADAPTIVE_THRESH_GAUSSIAN_C
  2. 尝试均衡图像而不模糊
  3. 将 1 级轮廓与 2 级一起使用
  4. 对 l1 和 l2 轮廓使用不同的尺寸约束。

我认为所有这些的组合应该可以为您提供相当不错的结果。

【讨论】:

  • 谢谢!当细菌“融合”时,它们实际上只是处于分裂过程中。因此,它仍应算作单一细菌。
  • @RaiyanChowdhury 在这种情况下,您可以使用 #4 - L1 轮廓作为主要约束,L2 轮廓作为次要约束。例如,一旦您发现某个 L2 级别轮廓 ctr_index 是细菌的一部分,那么您需要做的就是在其上运行形态运算符(侵蚀/打开)并将父级 hier[0][ctr_index][3] 添加为你的细菌。这样,您将获得比未命中更多的“命中”,只计算一次融合细菌。如果我的想法不清楚,请告诉我。
猜你喜欢
  • 2020-11-16
  • 2020-11-08
  • 2012-11-03
  • 2012-11-17
  • 2018-08-19
  • 2021-04-04
  • 2018-09-09
  • 2021-03-03
  • 2017-10-07
相关资源
最近更新 更多