找到通过质心的每条线段的长度,以及如何将线收缩到外轮廓

Finding length of each line segment passing through centroid , and how to constrict the line till outer contour

Input Image

Processed Image

import numpy as np
import cv2


img = cv2.imread('Image(i).png', 0)
ret, img =cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
img_bw = img<=120
img_bw =img_bw.astype('uint8') 
#Fit the ellipses
contours0, hierarchy = cv2.findContours( img.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
outer_ellipse = [cv2.approxPolyDP(contours0[0], 0.1, True)]
inner_ellipse = [cv2.approxPolyDP(contours0[0], 0.1, True)]
ref = np.zeros_like(img_bw)
out=img.copy()
h, w = img.shape[:2]
vis = np.zeros((h, w, 3), np.uint8)
cv2.drawContours( vis, outer_ellipse, -1, (255,0,0), 1)
cv2.drawContours( vis, inner_ellipse, -1, (0,0,255), 1)

##Extract contour of ellipses
cnt_outer = np.vstack(outer_ellipse).squeeze()
cnt_inner = np.vstack(inner_ellipse).squeeze()

#Determine centroid
M = cv2.moments(cnt_inner)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print (cx, cy)

#Draw full segment lines 
#cv2.line(vis,(cx,0),(cx,w),(150,0,0),1)

width = img.shape[1]
height = img.shape[0]
N = 20
for i in range(N):
 tmp = np.zeros_like(img_bw)
 theta = i*(360/N)
 theta *= np.pi/180.0
 cv2.line(tmp, (cx, cy),
       (int(cx-np.cos(theta)*w),
         int(cy+np.sin(theta)*h)), (150,0,0), 1)
    
(row,col) = np.nonzero(np.logical_and(tmp, ref))
     
#cv2.line(out, (cx, cy), (col,row),(255,0,0), 1)
    
    # Show the image
    
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
  

正如在处理过的图像中看到的那样,穿过质心的线没有收缩到外轮廓并且正在穿过它。

我希望线条在外轮廓处停止,以便我可以测量从质心到外轮廓的距离。 第一张图片是输入图片,第二张图片是通过质心的线段。

我有一个不是最好的方法,但这是我现在能想到的。

在上图中绘制线条的同时,修改代码并执行以下操作:

  • 在for循环之前,先画一个同样大小的二值图,只包含外轮廓圆。保存这张图片以备后用。
  • 现在在 for 循环中,在单独的二进制空白图像中绘制每一行。因此,现在您将有两个图像,第一个图像只有外圈,第二个图像只包含直线。
  • 现在对这两张图片执行bitwise_and操作。
  • 现在你只会得到一个白色像素点,它是直线和外圆的交点。
  • 现在找到找到的图像中白色像素的坐标,因此您将得到交点的坐标。

显然这不是最有效的方式,但它是实时的。另外,请记住,图像中的外圆宽度应至少为 2,线的宽度应为 1。在某些情况下,您可能会得到一个以上的交点,取其中的任意一个。它们之间的差异将只有1-2个像素,可以忽略不计。

这是一个可能的方法:

  • 在白色背景上用黑色画出你的外轮廓

你现在有一个黑色椭圆。然后,实际上没有画任何东西:

  • 使用skimage.draw.line获取沿所有半径的点列表
  • 使用 Numpy argmax() 沿半径获取第一个白色像素

代码如下:

#!/usr/bin/env python3

import cv2
import math
from skimage.draw import line
import numpy as np


# Load image as greyscale
img = cv2.imread('ellipses.png', cv2.IMREAD_GRAYSCALE)
_, img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY)
h, w = img.shape

#Fit the ellipses
contours, hierarchy = cv2.findContours( img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
outer_ellipse = [cv2.approxPolyDP(contours[0], 0.1, True)]

# Draw outer contour filled with black on white background
vis = np.zeros_like(img) + 255
cnt = cv2.drawContours(vis, outer_ellipse, -1, 0, -1)

# Centroid by existing method
cx, cy = 365, 335

maxThickness = 0
# Take 10 points along top
for x in range(0,w,int(w/10)):
    # ... and bottom
    for y in 0, h-1:
        # Get y and x of all pixels between centroid and top and bottom edge
        yy, xx = line(cy, cx, 0, x)
        firstWhiteIndex = np.argmax(vis[yy,xx])
        fx, fy = xx[firstWhiteIndex], yy[firstWhiteIndex]
        # Get length of this radial line
        length = np.sqrt((cx-fx)**2 + (cy-fy)**2)
        # Remember if longer than all others so far seen
        if length > maxThickness:
            maxThickness = length
            fxMax, fyMax = fx, fy
    
# Take 10 points down left side
for y in range(0,h,int(h/10)):
    # ... and right
    for x in 0, w-1:
        # Get y and x of all pixels between centroid and left and right edge
        yy, xx = line(cy, cx, 0, x)
        firstWhiteIndex = np.argmax(vis[yy,xx])
        fx, fy = xx[firstWhiteIndex], yy[firstWhiteIndex]
        # Get length of this radial line
        length = np.sqrt((cx-fx)**2 + (cy-fy)**2)
        # Remember if longer than all others so far seen
        if length > maxThickness:
            maxThickness = length
            fxMax, fyMax = fx, fy

print(f'Max thickness: {maxThickness}')
# Draw thickest radius in mid-grey
cv2.line(img, (cx,cy), (fxMax, fyMax), 128, 5)
cv2.imwrite('result.png', img)