简体   繁体   English

使用 DipLib (PyDIP) 测量两条线之间的距离

[英]Measuring the distance between two lines using DipLib (PyDIP)

I am currently working on a measurement system that uses quantitative image analysis to find the diameter of plastic filament.我目前正在研究一种使用定量图像分析来查找塑料细丝直径的测量系统。 Below are the original image and the processed binary image, using DipLib (PyDIP variant) to do so.下面是原始图像和处理后的二进制图像,使用 DipLib(PyDIP 变体)来执行此操作。 原始图像

阈值图像

The Problem问题

Okay so that looks great, in my personal opinion.好吧,在我个人看来,这看起来很棒。 the next issue is I am trying to calculate the distance between the top edge and the bottom edge of the filament in the binary image.下一个问题是我正在尝试计算二进制图像中灯丝的顶部边缘和底部边缘之间的距离。 This was pretty simple to do using OpenCV, but with the limited functionality in the PyDIP variant of DipLib, I'm having a lot of trouble.使用 OpenCV 非常简单,但是由于 DipLib 的 PyDIP 变体功能有限,我遇到了很多麻烦。

Potential Solution潜在解决方案

Logically I think I can just scan down the columns of pixels and look for the first row the pixel changes from 0 to 255, and vice-versa for the bottom edge.从逻辑上讲,我认为我可以向下扫描像素列并查找像素从 0 变为 255 的第一行,反之亦然。 Then I could take those values, somehow create a best-fit line, and then calculate the distance between them.然后我可以取这些值,以某种方式创建一条最佳拟合线,然后计算它们之间的距离。 Unfortunately I'm struggling with the first part of this.不幸的是,我正在努力解决这个问题的第一部分。 I was hoping someone with some experience might be able to help me out.我希望有经验的人可以帮助我。

Backstory背景故事

I am using DipLib because OpenCV is great for detection, but not quantification.我正在使用 DipLib,因为 OpenCV 非常适合检测,但不适用于量化。 I have seen other examples such as this one here that uses the measure functions to get diameter from a similar setup.我在这里看到了其他示例,例如使用测量功能从类似设置中获取直径的示例。

My code:我的代码:

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]

Here is how you can use the np.diff() method to find the index of first row from where the pixel changes from 0 to 255, and vice-versa for the bottom edge (the cv2 is only there to read in the image and threshold it, which you have already accomplished using diplib ) :这是您如何使用np.diff()方法查找像素从 0 变为 255 的第一行的索引,反之亦然的底部边缘cv2仅用于读取图像和阈值,您已经使用diplib完成)

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)

Output: Output:

在此处输入图像描述

Notice the variables defined above, y1_1 , y1_2 , y2_1 , and y2_2 .请注意上面定义的变量y1_1y1_2y2_1y2_2 Using them, you can get the diameter from both ends of the filament:使用它们,您可以获得灯丝两端的直径:

print(y1_2 - y1_1)
print(y2_2 - y2_1)

Output: Output:

100
105

I think the most precise approach to measure the distance between the two edges of the filament is to:我认为测量灯丝两个边缘之间距离的最精确方法是:

  1. detect the two edges using the Gaussian gradient magnitude,使用高斯梯度幅度检测两个边缘,
  2. determine the center of gravity of the two edges, which will be a point on each of the edges,确定两条边的重心,这将是每条边上的一个点,
  3. determine the angle of the two edges, and确定两条边的角度,以及
  4. use trigonometry to find the distance between the two edges.使用三角函数求两条边之间的距离。

This assumes that the two edges are perfectly straight and parallel, which doesn't seem to be the case though.这假设两条边完全笔直且平行,但情况似乎并非如此。

Using DIPlib you could do it this way:使用 DIPlib 你可以这样做:

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)

This outputs:这输出:

Distance between lines: 21.491425398007312 mm

You can show the two edges with:您可以显示两条边:

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]])

Another approach uses the distance transform, which assigns to each object pixel the distance to the nearest background pixel.另一种方法使用距离变换,它为每个 object 像素分配到最近背景像素的距离。 Because the filament is approximately horizontal, it is easy to use the maximum value for each image column as half the width at one point along the filament.因为灯丝近似水平,所以很容易将每个图像列的最大值用作沿灯丝某一点的宽度的一半。 This measurement is a bit noisy, because it computes distances between pixels, and uses a binarized image.这种测量有点嘈杂,因为它计算像素之间的距离,并使用二值化图像。 But we can average the width for each image column to obtain a more precise measurement, though it is likely biased (the estimated value is likely smaller than the true value):但是我们可以平均每个图像列的宽度以获得更精确的测量值,尽管它可能存在偏差(估计值可能小于真实值):

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)

This outputs:这输出:

Distance between lines: 21.393684 mm

You could also compute locally averaged widths, if you suspect that the filament is not uniform in width:如果您怀疑灯丝的宽度不均匀,您还可以计算局部平均宽度:

width_smooth = dip.Gauss(width, 100)

You could then plot the estimated widths to see your estimates:然后,您可以 plot 估计的宽度来查看您的估计:

pp.plot(width)
pp.plot(width_smooth)
pp.show()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM