简体   繁体   中英

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. 原始图像

阈值图像

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.

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. 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. 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 ) :

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:

在此处输入图像描述

Notice the variables defined above, y1_1 , y1_2 , y2_1 , and y2_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:

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:

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. 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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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