如何沿着物体中心绘制一条直线?

6

我希望用这两条线来测量香蕉的宽度。第一条线是香蕉周围的轮廓线:

第二行是香蕉的中间行:

正如您在图片中所看到的,我尝试使用骨架化方法,但是它有一些噪声并且线条没有连接(实际上有多条线重叠在一起)。我希望红色线条成为像这样没有噪声的单一线条:

所以我可以从中计算出宽度。

更新:现在我可以去除所有杂乱的像素,结果如下:

no noisy banana

但这条线不连续,我需要一条连续的线。

我想要完成这条红线的原因有点难以解释,但我想通过画一条垂直线来找到最长的宽度,就像这样:

result

另一个更新:现在我可以通过绘制一条线连接最近的两个点来连接所有这些线,结果如下所示:done

也许把图片添加到问题中,而不是链接,这样更方便。 - Mohit Motwani
它说:“您需要至少10点声望才能发布图片。” - Patrick
骨架线定义上是连通的,且只有一个像素宽度,你应该质疑所使用的算法。为了消除虚假的线条,可以擦除短弧线。但我很好奇为什么你需要中心线来测量宽度。 - user1196549
@Patrick 计算最近的两个点是耗时的吗? - Lamp
@Lamp 是的,但时间非常短。这也取决于图像大小。 - Patrick
显示剩余5条评论
1个回答

9

本答案解释了如何找到轮廓的最厚部分。此答案分为四个步骤。您已经完成其中一些步骤,为了清晰起见,在此答案中我将重申它们。

步骤1:检测骨架

skeleton

import cv2
import numpy as np
import math

# Read image
src = cv2.imread('/home/stephen/Desktop/banana.png')
img = src.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mask = np.zeros_like(gray)

# Find contours in image
contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[1]

# Draw skeleton of banana on the mask
img = gray.copy()
size = np.size(img)
skel = np.zeros(img.shape,np.uint8)
ret,img = cv2.threshold(img,5,255,0)
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
done = False
while( not done):
    eroded = cv2.erode(img,element)
    temp = cv2.dilate(eroded,element)
    temp = cv2.subtract(img,temp)
    skel = cv2.bitwise_or(skel,temp)
    img = eroded.copy() 
    zeros = size - cv2.countNonZero(img)
    if zeros==size: done = True
kernel = np.ones((2,2), np.uint8)
skel = cv2.dilate(skel, kernel, iterations=1)
skeleton_contours, _ = cv2.findContours(skel, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
largest_skeleton_contour = max(skeleton_contours, key=cv2.contourArea)

步骤2:将轮廓延伸到图像的边缘。

scipy to extend skeleton

# Extend the skeleton past the edges of the banana
points = []
for point in largest_skeleton_contour: points.append(tuple(point[0]))
x,y = zip(*points)
z = np.polyfit(x,y,7)
f = np.poly1d(z)
x_new = np.linspace(0, img.shape[1],300)
y_new = f(x_new)
extension = list(zip(x_new, y_new))
img = src.copy()
for point in range(len(extension)-1):
    a = tuple(np.array(extension[point], int))
    b = tuple(np.array(extension[point+1], int))
    cv2.line(img, a, b, (0,0,255), 1)
    cv2.line(mask, a, b, 255, 1)   
mask_px = np.count_nonzero(mask)

步骤三:找到轮廓中点之间的距离,仅考虑穿过骨架线的距离。

distances that cross the skeleton line

# Find the distance between points in the contour of the banana
# Only look at distances that cross the mid line
def is_collision(mask_px, mask, a, b):
    temp_image = mask.copy()
    cv2.line(temp_image, a, b, 0, 2)
    new_total = np.count_nonzero(temp_image)
    if new_total != mask_px: return True
    else: return False

def distance(a,b): return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)

distances = []
for point_a in cnt[:int(len(cnt)/2)]:
    temp_distance = 0
    close_distance = img.shape[0] * img.shape[1]
    close_points = (0,0),(0,0)
    for point_b in cnt:
        A, B = tuple(point_a[0]), tuple(point_b[0])
        dist = distance(tuple(point_a[0]), tuple(point_b[0]))
        if is_collision(mask_px, mask, A, B):
            if dist < close_distance:
                close_points = A, B
                close_distance = dist
    cv2.line(img, close_points[0], close_points[1], (234,234,123), 1)
    distances.append((close_distance, close_points))
    cv2.imshow('img',img)
    cv2.waitKey(1)    

步骤4:找到最大距离:

maximum distance

max_thickness = max(distances)
a, b = max_thickness[1][0], max_thickness[1][1]
cv2.line(img, a, b, (0,255,0), 4)
print("maximum thickness = ", max_thickness[0])

1
@Patrick 当然可以!我很喜欢从这些有趣的问题中学习。如果这个回答能够令您满意,请点击我回答左侧的勾选标志以接受它。 - Stephen Meschke
1
我在代码的b = tuple(np.array(extension[point+1], int))行遇到了错误:OverflowError: Python int too large to convert to C long - Zorro
1
@Zorro 这个答案只适用于红线是一个函数的情况。香蕉必须与示例中的方向相同。 - Stephen Meschke

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