查找多个连接对象的每个质心

Finding each centroid of multiple connected objects

我是 python 编码的超级新手,需要一些帮助。我能够分割生物组织内的每个细胞轮廓(超级酷!),现在我正在尝试使用以下方法找到组织内每个细胞的质心:

我正在使用此代码:

img = cv2.imread('/Users/kate/Desktop/SegmenterTest/SegmentedCells/Seg1.png')
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(image, 60, 255, cv2.THRESH_BINARY)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

# loop over the contours
for c in cnts:
    # compute the center of the contour
    M = cv2.moments(c)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    # draw the contour and center of the shape on the image
    cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
    cv2.circle(image, (cX, cY), 7, (255, 255, 255), -1)
    cv2.putText(image, "center", (cX - 20, cY - 20),
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
    # show the image
    cv2.imshow("Image", image)
    cv2.waitKey(0)

但是,当我使用这段代码时,它给出了整个对象的质心,而不是每个单独的对象。

我不知道从这里到哪里去,所以非常感谢向正确的方向轻推!

问题

cv2.findContours 使用的算法有一些不同 'retrieval modes'。这些会影响 returned 的轮廓以及 returned 的方式。这已记录在案 here。这些作为 findContours 的第二个参数给出。您的代码使用 cv2.RETR_EXTERNAL,这意味着 findContours 只会 return 单独对象的最外层边框。

解决方案

将此参数更改为 cv2.RETR_LIST 将为您提供图像中的所有轮廓(包括最外层的边框)。这是最简单的解决方案。

例如

import cv2
import imutils

img = cv2.imread('/Users/kate/Desktop/SegmenterTest/SegmentedCells/Seg1.png')
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(image, 60, 255, cv2.THRESH_BINARY)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

# loop over the contours
for c in cnts:
    # compute the center of the contour
    M = cv2.moments(c)
    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    # draw the contour and center of the shape on the image
    cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
    cv2.circle(image, (cX, cY), 7, (255, 255, 255), -1)
    cv2.putText(image, "center", (cX - 20, cY - 20),
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
    # show the image
    cv2.imshow("Image", image)
    cv2.waitKey(0)

只选择最里面的对象

要可靠地忽略外部轮廓,您可以利用 findContours 的功能来 return 它检测到的轮廓的层次结构。为此,您可以再次将检索模式参数更改为 RETR_TREE,这将生成完整的层次结构。

层次结构是一个数组,其中包含图像中每个轮廓的 4 个值的数组。每个值都是轮廓数组中轮廓的索引。来自 docs:

For each i-th contour contours[i], the elements hierarchy[i][0] , hierarchy[i][1] , hierarchy[i][2], and hierarchy[i][3] are set to 0-based indices in contours of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively. If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative.

当我们说 'innermost' 时,我们的意思是没有子级的轮廓(它们内部的轮廓)。所以我们想要那些在层次结构中的条目具有负的第三值的轮廓。即 contours[i],使得 hierarchy[i][2] < 0

一个小问题是,尽管 findContours return 是一个包含层次结构的元组,但 imutils.grabContours 丢弃了层次结构,而 return 只是轮廓数组。这意味着如果我们打算使用不同版本的 OpenCV,我们必须自己完成 grabContours 的工作。这只是一个简单的 if else 语句。

res = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# switch for different versions of OpenCV
if len(cnts) == 3:
    _, cnts, hierarchy = res
else:
    cnts, hierarchy = res

一旦你有 hierarchy,检查轮廓是否 cnts[i] 是 'innermost' 可以用 hierarchy[0][i][2] < 0 完成,对于轮廓应该是 False包含其他轮廓。

基于您问题代码的完整示例:

import cv2
import imutils

img = cv2.imread('/Users/kate/Desktop/SegmenterTest/SegmentedCells/Seg1.png')
image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(image, 60, 255, cv2.THRESH_BINARY)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# switch for different versions of OpenCV
if len(cnts) == 3:
    _, cnts, hierarchy = cnts
else:
    cnts, hierarchy = cnts

# loop over the contours
for i, c in enumerate(cnts):
    # check that it is 'innermost'
    if hierarchy[0][i][2] < 0:
        # compute the center of the contour
        M = cv2.moments(c)
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])

        # draw the contour and center of the shape on the image
        cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
        cv2.circle(image, (cX, cY), 7, (255, 255, 255), -1)
        cv2.putText(image, "center", (cX - 20, cY - 20),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
        # show the image
        cv2.imshow("Image", image)
        cv2.waitKey(0)

您可以在您的案例中使用模块 scikit-image 中的函数 regionprops。这是我得到的。

这是我使用的代码。

import cv2
import matplotlib.pyplot as plt
from skimage import measure
import numpy as np

cells = cv2.imread('cells.png',0)

ret,thresh = cv2.threshold(cells,20,255,cv2.THRESH_BINARY_INV)


labels= measure.label(thresh, background=0)
bg_label = labels[0,0] 
labels[labels==bg_label] = 0 # Assign background label to 0

props = measure.regionprops(labels)

fig,ax = plt.subplots(1,1)
plt.axis('off')
ax.imshow(cells,cmap='gray')
centroids = np.zeros(shape=(len(np.unique(labels)),2)) # Access the coordinates of centroids
for i,prop in enumerate(props):
    my_centroid = prop.centroid
    centroids[i,:]= my_centroid
    ax.plot(my_centroid[1],my_centroid[0],'r.')

# print(centroids)
# fig.savefig('out.png', bbox_inches='tight', pad_inches=0)
plt.show()

祝你研究顺利!