如何在分割中找到边界之间的最大值?

How to find max value between boundary in segmentation?

这里有个问题:我想在不规则形状的分割图像中找到实际的最大边界宽度(this) 下面我 post 一些我用于测试的示例图像 到目前为止,我设法获得了边界和骨架线,但是如何测量垂直于骨架线的等高线之间的距离?

def get_skeleton(image_path):

  im = cv2.imread(img_path , cv2.IMREAD_GRAYSCALE)
  binary = im > filters.threshold_otsu(im)
  skeleton = morphology.skeletonize(binary)
  return skeleton

skeleton = get_skeleton(img_path)
plt.imshow(skeleton, cmap="gray")

def get_boundary(image_path):
  reading_Img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
  reading_Img = cv2.cvtColor(reading_Img,cv2.COLOR_BGR2RGB)
  canny_Img = cv2.Canny(reading_Img,90,100)
  contours,_ = cv2.findContours(canny_Img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
  canvas = np.zeros_like(reading_Img)
  boundary = cv2.drawContours(canvas , contours, -1, (255, 0, 0), 1)
  return boundary

boundary = get_boundary(img_path)
plt.imshow(boundary)

Sample input image

编辑:

首先感谢您的回答,我想补充更多关于我正在尝试做的事情的细节。 所以我做了一个检测混凝土裂缝的分割模型(它们可以是任何形状,垂直的,水平的,对角线的等等)现在我需要确定它们的最大宽度并画一条线来显示它发生的位置。

我发现中轴 returns 与边界的距离,通过过滤最大值,我能够获得宽度(参见下面的 colab)及其在中轴上的坐标。现在我需要画一条线连接边界之间的宽度,但我不知道如何找到这样一条线的坐标。

我想到了一个算法,从中轴上最大距离出现的点开始,扩展直到找到边界,但我不知道如何实现它。

这张图片显示了我需要的东西:

找到点的 x 和 y 后,我将能够计算 2 点之间的欧氏距离

dist=sqrt((y2-y1)^2+(x2-x1)^2)

请查看我在 colab notebook 中的代码:https://colab.research.google.com/drive/1NvEyfrxpKGJ1kxjP48PGNB_UUSp6f6Ze?usp=sharing

示例输入图像:

https://imgur.com/ewTmH8M

https://imgur.com/JRAQCke

https://imgur.com/7QQFfAv

我做的第一件事就是让你的图像保持灰度,不需要转换到 3 个通道来寻找轮廓。其次是将 boundary 图像转换为二进制,使其与 skeleton 图像相同。然后我简单地将两者相加得到both图像。

然后我仔细查看组合 both 图像的每一行(因为您正在寻找垂直距离)并寻找 True 的元素,即边界或骨架像素。我在这一点上做了一个简化的假设 - 我只搜索了边界后跟 单个 骨架像素然后是第二个边界的情况,我明白这可能并不总是案例,但我把那个特别头疼的问题留给你解决。

之后,这只是跟踪逐行浏览图像时记录的最大和最小距离的情况。 (编辑:可能有比我做的更简洁的方法,但希望你能理解)

import numpy as np
import matplotlib.pyplot as plt
import cv2
from skimage import filters
from skimage import morphology


def get_skeleton(image_path):

    im       = cv2.imread(image_path , cv2.IMREAD_GRAYSCALE)
    binary   = im > filters.threshold_otsu(im)
    skeleton = morphology.skeletonize(binary)
    
    return skeleton


def get_boundary(image_path):
    
    reading_Img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    canny_Img   = cv2.Canny(reading_Img, 90, 100)
    contours,_  = cv2.findContours(canny_Img,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    canvas      = np.zeros_like(reading_Img)
    boundary    = cv2.drawContours(canvas, contours, -1, (255, 0, 0), 1)
    binary      = boundary > filters.threshold_otsu(boundary)
    
    return binary


skeleton = get_skeleton("LtqlM.png")
boundary = get_boundary("LtqlM.png")
both     = skeleton + boundary

max_dist = 0
min_dist = 100000

for idx in range(both.shape[0]): # counting through rows
    
    row   = both[idx, :]
    lines = np.where(row==True)[0]
    
    if len(lines) == 3:
        
        dist_1 = lines[1] - lines[0]
        dist_2 = lines[2] - lines[1]
        
        if (dist_1 > dist_2) and dist_1 > max_dist:
            max_dist = dist_1
            
        if (dist_2 > dist_1) and dist_2 > max_dist:
            max_dist = dist_2
            
        if (dist_1 < dist_2) and dist_1 < min_dist:
            min_dist = dist_1
            
        if (dist_2 < dist_1) and dist_2 < min_dist:
            min_dist = dist_2
  
print("Maximum distance = ", max_dist) 
print("Minimum distance = ", min_dist)      
plt.imshow(both)

从您的方法开始,您可以使用中轴函数

  • 在找到的点插入轴的方向
  • 从方向推导正交
  • 看正交到达边界的地方

下面的示例显示了原理并适用于您的示例图像。但我敢肯定会有一些尚未考虑的边界条件。我把它留给你让它对真实的实时数据具有鲁棒性。

import cv2
import numpy as np
from skimage.morphology import medial_axis
from skimage import img_as_ubyte

delta = 3  # delta index for interpolation

# get crack
im = cv2.imread("img.png", cv2.IMREAD_GRAYSCALE)
rgb = cv2.cvtColor(im, cv2.COLOR_GRAY2RGB)  # rgb just for demo purpose
_, crack = cv2.threshold(im, 127, 255, cv2.THRESH_BINARY)

# get medial axis
medial, distance = medial_axis(im, return_distance=True)
med_img = img_as_ubyte(medial)
med_contours, _ = cv2.findContours(med_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(rgb, med_contours, -1, (255, 0, 0), 1)
med_pts = [v[0] for v in med_contours[0]]

# get point with maximal distance from medial axis
max_idx = np.argmax(distance)
max_pos = np.unravel_index(max_idx, distance.shape)
max_dist = distance[max_pos]
coords = np.array([max_pos[1], max_pos[0]])
print(f"max distance from medial axis to boundary = {max_dist} at {coords}")

# interpolate orthogonal of medial axis at coords
idx = next(i for i, v in enumerate(med_pts) if (v == coords).all())
px1, py1 = med_pts[(idx-delta) % len(med_pts)]
px2, py2 = med_pts[(idx+delta) % len(med_pts)]
orth = np.array([py1 - py2, px2 - px1]) * max(im.shape)

# intersect orthogonal with crack and get contour
orth_img = np.zeros(crack.shape, dtype=np.uint8)
cv2.line(orth_img, coords + orth, coords - orth, color=255, thickness=1)
gap_img = cv2.bitwise_and(orth_img, crack)
gap_contours, _ = cv2.findContours(gap_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
gap_pts = [v[0] for v in gap_contours[0]]

# determine the end points of the gap contour by negative dot product
n = len(gap_pts)
gap_ends = [
    p for i, p in enumerate(gap_pts)
    if np.dot(p - gap_pts[(i-1) % n], gap_pts[(i+1) % n] - p) < 0
]
print(f"Maximum gap found from {gap_ends[0]} to {gap_ends[1]}")
cv2.line(rgb, gap_ends[0], gap_ends[1], color=(0, 0, 255), thickness=1)

cv2.imwrite("test_out.png", rgb)

从距离最大的像素开始,您可以探索同心方形图层,直到找到背景像素。然后在最后一层找到欧氏距离最短的背景像素。第二个端点是对称的。