【问题标题】:Calculate the radius of an enclosing circle over a binary mask array计算二进制掩码数组上的封闭圆的半径
【发布时间】:2019-09-21 00:45:33
【问题描述】:

我有一个图像的 numpy 数组:

红色是特定部分的二进制掩码,我的目标是计算此掩码中的最大直径长度。我曾尝试使用边界框并测量框的直径(从一端到另一端),但它并不像我想要的那样准确,尤其是当面具的形状接近圆形时。

我的下一个想法是使用边界圆而不是框,并测量非常准确的半径。问题是我不知道任何包或功能可以给我一个边界圈。即使有这样的功能,我也不知道如何测量给定圆的半径,因为圆中没有尖点可以端到端。

欣赏任何想法。

【问题讨论】:

  • 您正在寻找计算形状的最大 Feret 直径(最大投影长度)。我在这里解释一个算法:crisluengo.net/archives/408
  • 可能与这个问题重复,stackoverflow.com/questions/13240615/…
  • 我认为考虑到相对较小的周边尺寸(以像素为单位),最有效的解决方案可能最终会成为一种蛮力解决方案,即在周边像素 p_1 上计算一些排序,... , p_n 并比较所有对。最坏情况时间 O(n^2) 其中 n 是区域的总大小(长瘦区域 = 所有像素都在周长上)

标签: python numpy image-processing


【解决方案1】:

关于我的解决方案的一些说明:

(0) 它接受二值图像作为输入。

(1) 它为图像中的所有区域找到它。

(2) 找到该区域凸包的最大直径。我认为这是一个合理的做法,但您可以根据需要调整实现。

(3) 我使用 skimage.data “硬币”图像,以便您可以复制自己。

import sys

# To find the diameters
from skimage.measure import regionprops, label
from sklearn.metrics import pairwise_distances
from scipy import ndimage as ndi
import numpy as np

# To generate test data
from skimage import data
from skimage.filters import sobel
from skimage.morphology import watershed

# For visualization
import matplotlib.pyplot as plt

STREL_4 = np.array([[0, 1, 0],
                    [1, 1, 1],
                    [0, 1, 0]], dtype=np.bool)


def get_border_image(region):
    convex_hull_mask = region.convex_image
    eroded_image = ndi.binary_erosion(convex_hull_mask, STREL_4, border_value=0)
    border_image = np.logical_xor(convex_hull_mask, eroded_image)
    return border_image

def get_region_diameters(img):

    assert img.dtype == np.bool and len(img.shape) == 2

    label_img = label(img, connectivity=img.ndim)

    for region in regionprops(label_img):
        border_image = get_border_image(region)
        perimeter_coordinates = np.transpose(np.nonzero(border_image))
        pairwise_distances_matrix = pairwise_distances(perimeter_coordinates)
        i, j = np.unravel_index(np.argmax(pairwise_distances_matrix), pairwise_distances_matrix.shape)
        ptA, ptB = perimeter_coordinates[i], perimeter_coordinates[j]
        region_offset = np.asarray([region.bbox[0], region.bbox[1]])
        ptA += region_offset
        ptB += region_offset
        yield pairwise_distances_matrix[i, j], ptA, ptB

if __name__ == "__main__":

    # Create a segmentation of the coins image, for testing purposes.
    # You should create a binary image
    coins = data.coins()
    elevation_map = sobel(coins)
    markers = np.zeros_like(coins)
    markers[coins < 30] = 1
    markers[coins > 150] = 2
    segmentation = (watershed(elevation_map, markers) > 1)

    for distance, ptA, ptB in get_region_diameters(segmentation):
        plt.imshow(segmentation)
        x1, x2, y1, y2 = ptA[1], ptB[1], ptA[0], ptB[0]
        plt.plot([x1, x2], [y1, y2], color='k', linestyle='-', linewidth=2)
        print(distance, ptA, ptB)

    plt.show()

【讨论】:

  • 我的解决方案的一些注意事项:(1)它为图像中的所有区域找到它。 (2) 找到该区域凸包的最大直径。我认为这是一个合理的做法,但您可以根据需要调整实现。
  • 按照@CrisLuengo 的建议编辑
  • 感谢您的努力,感激不尽。您的逻辑似乎很准确,但是对于这样的简单任务,代码太多了。尤其是当我看到您使用二进制腐蚀和 XOR 来获得似乎不必要的边界时。我终于按照@Dr.H.Lecter 的建议使用了openCV(cv2)。使用“contours, hierarchy = cv2.findContours(image, 1, 2)”获取轮廓,使用“(x,y),radius = cv2.minEnclosureCircle(contours[0])”获取边界圆的半径跨度>
【解决方案2】:

如果您愿意接受近似值,请使用此方法并使用属性“major_axis_length”:https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.regionprops

【讨论】:

  • 谢谢你的回答,但我已经有了一个近似值。我想要一个更准确的值。
猜你喜欢
  • 1970-01-01
  • 2017-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-09
  • 2018-07-28
相关资源
最近更新 更多