CasualPythoner
CasualPythoner

Reputation: 69

Measure a length of a line segment in pixels from an image

I have a ton of images like the one shown below. I am trying to detect nanoparticles from transmission electron microscopy images which I have successfully achieved using OpenCV-Python. The image is just a crop of the bottom left corner of a bigger transmission electron microscopy image. I would like to extract the length of the white colored line segment under the '100 nm'. Currently, I am manually measuring the length of this line and I would like to automate this. I think this could be done with OpenCV-Python by contouring, but I have lot of other features which are also contoured in the image. I am new to Python and I am not very sure on how to implement this or if this can be implemented at all. My idea about what the code should do:

  1. Load the image
  2. Find the white line segment
  3. Return it's length in pixels.
  4. It would also be nice, if it detects what the number just above the line and returns it too.

Any suggestions, directions or 'where to start' comments are greatly welcome and appreciated.

The line is segment is very unique in its features and there is only one line in every image.

enter image description here

Upvotes: 1

Views: 6729

Answers (2)

Ahx
Ahx

Reputation: 7985

One approach is using the probabilistic Hough line method for detecting the line.

The result will be:

enter image description here

Now the problem is multiple lines are detected. We know their coordinates. If we print them all:

Pixel Length: 296
Pixel Length: 197
Pixel Length: 308
Pixel Length: 175
Pixel Length: 292
Pixel Length: 229
Pixel Length: 103
Pixel Length: 109

Since there are a lot of lengths detected, maybe finding its average is meaningful:

Average Pixel Length: 109 pixel

Though I'm not sure how to convert 109 pixel to 100nm.

import cv2
import numpy as np

img = cv2.imread("4HQtp.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
base = cv2.HoughLinesP(edges, 1, np.pi / 180, 80, minLineLength=1, maxLineGap=6)
pixel_array = []
pixel_length = 0
if base is not None:
    for line in base:
        x1, y1, x2, y2 = line[0]
        cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
        pixel_length = np.abs(x2 - x1)
        pixel_array.append(pixel_length)
        print("Pixel Length: {}".format(pixel_length))
    cv2.imshow("img", img)
    cv2.imwrite("hough_img.png", img)
    cv2.waitKey(0)
print("Average Pixel Length: {:.0f} pixel".format(np.average(pixel_array)))

Upvotes: 1

thibsc
thibsc

Reputation: 4049

You can binarize your image to find contour of the scale segment:

processing

scale = cv2.imread('scalenanometer.png', cv2.IMREAD_COLOR)
scale_gray = cv2.cvtColor(scale, cv2.COLOR_BGR2GRAY)
# adjust the second value of the next line to tune the detection
ret, thresh = cv2.threshold(scale_gray, 210, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# filter noisy detection
contours = [c for c in contours if cv2.contourArea(c) > 100]
# sort from by (y, x)
contours.sort(key=lambda c: (cv2.boundingRect(c)[1], cv2.boundingRect(c)[0]))
# work on the segment
cv2.rectangle(scale, cv2.boundingRect(contours[-1]), (0,255,0), 2)
x,y,w,h = cv2.boundingRect(contours[-1])
print(x,y,w,h) # x,y: (39 152) w,h: [304 21]

If you want to detect the value, you can use tesseract ocr.

Upvotes: 3

Related Questions