首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用DipLib (PyDIP)测量两条线的距离

用DipLib (PyDIP)测量两条线的距离
EN

Stack Overflow用户
提问于 2022-01-02 22:16:27
回答 2查看 479关注 0票数 3

我目前正在开发一个测量系统,它使用定量图像分析来找到塑料长丝的直径。下面是原始图像和处理过的二值图像,使用DipLib (PyDIP变体)这样做。

问题

好吧,在我个人看来,这看起来很棒。下一个问题是,我试图计算在二值图像中灯丝的上边缘和下边缘之间的距离。使用OpenCV很容易做到这一点,但由于PyDIP变体DipLib的功能有限,我遇到了很多麻烦。

势解

从逻辑上讲,我认为我可以向下扫描像素列,查找第一行像素从0到255,反之亦然。然后我可以取这些值,以某种方式创建一条最合适的线,然后计算它们之间的距离。不幸的是,我正为这件事的第一部分而挣扎。我希望有经验的人能帮我解决问题。

背景故事

我使用DipLib是因为OpenCV很适合检测,而不是量化。我见过其他例子,如这里,它使用度量衡函数从类似的设置中获得直径。

我的代码:

代码语言:javascript
复制
import diplib as dip
import math
import cv2


# import image as opencv object
img = cv2.imread('img.jpg') 

# convert the image to grayscale using opencv (1D tensor)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


# convert image to diplib object
dip_img = dip.Image(img_gray)

# set pixel size
dip_img.SetPixelSize(dip.PixelSize(dip.PixelSize(0.042*dip.Units("mm"))))

# threshold the image
dip_img = dip.Gauss(dip_img)
dip_img = ~dip.Threshold(dip_img)[0]

关于Cris Luengo的问题

First,在这一行中:

代码语言:javascript
复制
# binarize
mask = dip.Threshold(edges)[0] '

您如何知道输出映像包含在索引中,因为几乎没有关于PyDIP的文档,所以我想知道,如果我自己做的话,我可能在哪里找到了它。我可能找错地方了。我知道我这么做是在我最初的文章中,但我肯定是在一些示例代码中找到的。

这些行中的第二个

代码语言:javascript
复制
normal1 = np.array(msr[1]['GreyMajorAxes'])[0:2]  # first axis is perpendicular to edge
normal2 = np.array(msr[2]['GreyMajorAxes'])[0:2]

据我所知,你们正在找到这两条线的主轴,它们只是特征向量。我在这里的经验非常有限--我是一个工程专业的本科生,所以我从抽象的意义上理解了特征向量,但我仍然在学习它们是如何有用的。我不太理解的是,行msr[1]['GreyMajorAxes'])[0:2]只返回两个值,这将只定义一个点。这是如何定义一条与检测到的线正常的线,如果它只是一个点?第一个点引用是(0,0)还是图像的中心?另外,如果你有任何相关的资源来阅读关于在图像处理中使用特征值的内容,我想再深入一点。谢谢!

第三代,您已经设置好了,如果我理解正确的话,mask[0]包含顶部,mask[1]包含底线。因此,如果我想解释灯丝的弯曲,我可以取这两个矩阵,并得到每条正确的最佳拟合线?目前,我只是简单地缩小了我的捕获面积相当大,主要是不需要担心任何挠曲,但这不是一个完美的解决方案-特别是当长丝相当下垂和曲率是不可避免的。让我知道你的想法,再次感谢你的时间。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-01-03 08:17:17

我认为衡量灯丝两边距离的最精确方法是:

  1. 用高斯梯度幅值检测两条边缘,
  2. 确定两条边的重心,这是每条边上的一个点,
  3. 确定两个边的角度,和
  4. 用三角函数来找出两边之间的距离。

这假设这两个边是完全直的和平行的,但情况似乎并非如此。

使用DIPlib,您可以这样做:

代码语言:javascript
复制
import diplib as dip
import numpy as np
import matplotlib.pyplot as pp

# load
img = dip.ImageRead('wDnU6.jpg') 
img = img(1)  # use green channel
img.SetPixelSize(0.042, "mm")

# find edges
edges = dip.GradientMagnitude(img)

# binarize
mask = dip.Threshold(edges)[0]
mask = dip.Dilation(mask, 9)  # we want the mask to include the "tails" of the Gaussian
mask = dip.AreaOpening(mask, filterSize=1000)  # remove small regions

# measure the two edges
mask = dip.Label(mask)
msr = dip.MeasurementTool.Measure(mask, edges, ['Gravity','GreyMajorAxes'])
# msr[n] is the measurements for object with ID n, if we have two objects, n can be 1 or 2.

# get distance between edges
center1 = np.array(msr[1]['Gravity'])
center2 = np.array(msr[2]['Gravity'])

normal1 = np.array(msr[1]['GreyMajorAxes'])[0:2]  # first axis is perpendicular to edge
normal2 = np.array(msr[2]['GreyMajorAxes'])[0:2]
normal = (normal1 + normal2) / 2  # we average the two normals, assuming the edges are parallel

distance = abs((center1 - center2) @ normal)
units = msr['Gravity'].Values()[0].units
print("Distance between lines:", distance, units)

这一产出如下:

代码语言:javascript
复制
Distance between lines: 21.491425398007312 mm

您可以用以下方式显示这两条边:

代码语言:javascript
复制
mmpp = img.PixelSize()[0].magnitude
center1 = center1 / mmpp  # position in pixels
center2 = center2 / mmpp
L = 1000
v = L * np.array([normal[1], -normal[0]])
img.Show()
pt1 = center1 - v
pt2 = center1 + v
pp.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]])
pt1 = center2 - v
pt2 = center2 + v
pp.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]])

另一种方法是使用距离变换,它将与最近的背景像素的距离分配给每个对象像素。由于灯丝大致是水平的,所以很容易将每个图像列的最大值作为沿着灯丝某一点宽度的一半。这种测量有点噪音,因为它计算像素之间的距离,并使用二值化的图像。但是,我们可以对每一幅图像列的宽度进行平均,以获得更精确的测量结果,尽管它可能存在偏差(估计值可能小于真实值):

代码语言:javascript
复制
mask = dip.Threshold(img)[0]
dt = dip.EuclideanDistanceTransform(mask, border='object')
width = 2 * np.amax(dt, axis=0)
width = width[100:-100]  # close to the image edges the distance could be off
print("Distance between lines:", np.mean(width), img.PixelSize()[0].units)

这一产出如下:

代码语言:javascript
复制
Distance between lines: 21.393684 mm

如果怀疑灯丝的宽度不均匀,也可以计算本地平均宽度:

代码语言:javascript
复制
width_smooth = dip.Gauss(width, 100)

然后,您可以绘制估计宽度以查看您的估计:

代码语言:javascript
复制
pp.plot(width)
pp.plot(width_smooth)
pp.show()

对附加问题的答复

您如何知道输出映像包含在索引中,因为关于PyDIP的文档很少.

事实上,文档太少了,我们在这方面肯定需要一些帮助!大多数函数都是从C++函数转换而来的,因此C++文档提供了适当的信息。但是有些函数(如dip.Threshold() )与C++稍有偏离;在这种情况下,输出在元组中提供阈值图像和选定的阈值。在Python中运行out = dip.Threshold(edges),然后检查out变量,您可以看到它是一个元组,图像作为第一个值,浮点作为第二个值。您需要从C++文档中猜测这两个值的含义。源代码将是最好的发现地点,但这对用户来说一点也不友好。

据我所知,你们正在找到这两条线的主轴,它们只是特征向量。..。我不太理解的是,行msr[1]['GreyMajorAxes'])[0:2]只返回两个值,这将只定义一个点。这是如何定义一条与检测到的线正常的线,如果它只是一个点?

2D中的向量由两个数定义,向量从原点到由这两个数给出的点。msr[1]['GreyMajorAxes'])给出了四个值( 2D),前两个是惯性张量的最大特征向量,第二个是最小特征向量。在这种情况下,最大特征向量对应于法向量。

正如您已经设置的,如果我理解正确的话,掩码包含顶线,mask1包含底线。

不,mask是一个标记图像,其中值为0的像素为背景,值为1的像素为第一个对象(本例中为边缘),值为2的像素为第二个对象。因此,mask==1将给出一个二值图像与像素的第一个边缘选择。但是mask不是一条细线,它很宽。其思想是覆盖edge中高斯轮廓的全宽度,这是图像的梯度幅度。我上面所做的计算都是基于这个高斯轮廓,提供了一个比处理二进制图像或单个像素集所得到的更精确的结果。

因此,如果我想解释灯丝的弯曲,我可以取这两个矩阵,并得到每条正确的最佳拟合线?

您可以做的一件事是为edge中的每一列在mask==1像素内找到最大值的亚像素位置(例如,将高斯与每列中的值进行拟合)。这将给你一组点,你可以拟合一条曲线。这将涉及到更多的代码,但我认为并不难。

票数 2
EN

Stack Overflow用户

发布于 2022-01-02 22:56:55

下面是如何使用np.diff()方法从像素从0到255的位置查找第一行的索引,反之亦然( cv2只在那里读取图像和阈值,您已经使用diplib__完成了)

代码语言:javascript
复制
import cv2
import numpy as np

img = cv2.imread("img.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

x1, x2 = 0, img.shape[1] - 1
diff1, diff2 = np.diff(thresh[:, [x1, x2]].T, 1)
y1_1, y2_1 = np.where(diff1)[0][:2]
y1_2, y2_2 = np.where(diff2)[0][:2]

cv2.line(img, (x1, y1_1), (x2, y1_2), 0, 10)
cv2.line(img, (x1, y2_1), (x2, y2_2), 0, 10)

cv2.imshow("Image", img)
cv2.waitKey(0)

输出:

注意上面定义的变量,y1_1y1_2y2_1y2_2。使用它们,你可以从灯丝的两端得到直径:

代码语言:javascript
复制
print(y1_2 - y1_1)
print(y2_2 - y2_1)

输出:

代码语言:javascript
复制
100
105
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70560162

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档