Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenCV Detect Very Small Lines

Here is an example image of what I’m working with:

enter image description here

On every image there is a measurement strip. The measurement strip may vary in scale and angle. I’ve identified some intersection with the measurement strip and now need to determine what number it corresponds to (e.g 256, 192, 128 ...). So I need to identify ranges of pixels and map each of them to a number. To identify these ranges, it seems the only way is to detect the the small lines next to each number and join them into a larger line.

enter image description here

My plan was to to isolate these small measurement lines and then use HoughTransform to connect lines between them, however I’m finding it very difficult to isolate these small lines. I’ve tried Canny edge detection but the small measurement lines are always detected as part of the vertical edge. I've tried many different thresholds and upscaling with no success.

img = cv2.imread('example.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC)
blur_gray = cv2.GaussianBlur(resized,(5, 5),0)
edges = cv2.Canny(blur_gray, 100, 200)

Upscaled x2 vs Upscaled x10

enter image description here enter image description here

Is this even the correct approach or is there an alternative method I could use to extract these measurement lines?

like image 671
Tom Avatar asked May 11 '18 02:05

Tom


1 Answers

I would take the following approach:

  1. Detect long straight lines using cv2.HoughLinesP() after Canny edge detection
  2. Remove wrongly detected lines, keeping only 2 that correspond to the long edge of the strip.
  3. Make sure both lines are positioned nicely along the black line on the strip.
  4. Grab the region of interest by sampling the image between both lines.
  5. Analyze the mean intensity along the width of the strip. Use find_peaks with the distance argument to detect the white areas in between the markings/text.

The following code works for you example.analyze measurement strip

import cv2
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import find_peaks
from skimage.draw import line

NOF_MARKERS = 30

# Show input image
img = cv2.imread("mPIXY.jpg")
img_orig = img.copy()
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
fig, axs = plt.subplots(2, 2)

# Detect long lines in the image
img_edg = cv2.Canny(img, 50, 120)
img_edg = cv2.morphologyEx(img_edg, cv2.MORPH_CLOSE, (5, 5), iterations=1)
img_edg = cv2.cvtColor(img_edg, cv2.COLOR_GRAY2RGB)
axs[0, 0].set_title("Canny edges + morphology closed")
axs[0, 0].imshow(img_edg)
lines = cv2.HoughLinesP(
    img_edg[:, :, 0].copy(),
    rho=1,
    theta=np.pi / 360,
    threshold=70,
    minLineLength=300,
    maxLineGap=15,
)
lines = lines.squeeze()
for x1, y1, x2, y2 in lines:
    cv2.line(img_edg, (x1, y1), (x2, y2), (255, 0, 0))
axs[0, 0].imshow(img_edg, aspect="auto")


def optimize_line_alignment(img_gray, line_end_points):
    # Shift endpoints to find optimal alignment with black line in the  origial image
    opt_line_mean = 255
    x1, y1, x2, y2 = line_end_points
    for dx1 in range(-3, 4):
        for dy1 in range(-3, 4):
            for dx2 in range(-3, 4):
                for dy2 in range(-3, 4):
                    line_discrete = np.asarray(
                        list(zip(*line(*(x1 + dx1, y1 + dy1), *(x2 + dx2, y2 + dy2))))
                    )
                    line_pixel_values = img_gray[
                        line_discrete[:, 1], line_discrete[:, 0]
                    ]
                    line_mean = np.mean(line_pixel_values)
                    if line_mean < opt_line_mean:
                        opt_line_end_points = np.array(
                            [x1 + dx1, y1 + dy1, x2 + dx2, y2 + dy2]
                        )
                        opt_line_discrete = line_discrete
                        opt_line_mean = line_mean
    return opt_line_end_points, opt_line_discrete


# Optimize alignment for the 2 outermost lines
dx = np.mean(abs(lines[:, 2] - lines[:, 0]))
dy = np.mean(abs(lines[:, 3] - lines[:, 1]))
if dy > dx:
    lines = lines[np.argsort(lines[:, 0]), :]
else:
    lines = lines[np.argsort(lines[:, 1]), :]
line1, line1_discrete = optimize_line_alignment(img_gray, lines[0, :])
line2, line2_discrete = optimize_line_alignment(img_gray, lines[-1, :])
cv2.line(img, (line1[0], line1[1]), (line1[2], line1[3]), (255, 0, 0))
cv2.line(img, (line2[0], line2[1]), (line2[2], line2[3]), (255, 0, 0))
axs[0, 1].set_title("Edges of the strip")
axs[0, 1].imshow(img, aspect="auto")

# Take region of interest from image
dx = round(0.5 * (line2[0] - line1[0]) + 0.5 * (line2[2] - line1[2]))
dy = round(0.5 * (line2[1] - line1[1]) + 0.5 * (line2[3] - line1[3]))
strip_width = len(list(zip(*line(*(0, 0), *(dx, dy)))))
img_roi = np.zeros((strip_width, line1_discrete.shape[0]), dtype=np.uint8)
for idx, (x, y) in enumerate(line1_discrete):
    perpendicular_line_discrete = np.asarray(
        list(zip(*line(*(x, y), *(x + dx, y + dy))))
    )
    img_roi[:, idx] = img_gray[
        perpendicular_line_discrete[:, 1], perpendicular_line_discrete[:, 0]
    ]

axs[1, 0].set_title("Strip analysis")
axs[1, 0].imshow(img_roi, cmap="gray")
extra_ax = axs[1, 0].twinx()
roi_mean = np.mean(img_roi, axis=0)
extra_ax.plot(roi_mean, label="mean")
extra_ax.plot(np.min(roi_mean, axis=0), label="min")
plt.legend()

# Locate the markers within region of interest
black_bar = np.argmin(roi_mean)
length = np.max([img_roi.shape[1] - black_bar, black_bar])
if black_bar < img_roi.shape[1] / 2:
    roi_mean = np.append(roi_mean, 0)
    peaks, _ = find_peaks(roi_mean[black_bar:], distance=length / NOF_MARKERS * 0.75)
    peaks = peaks + black_bar
else:
    roi_mean = np.insert(roi_mean, 0, 0)
    peaks, _ = find_peaks(roi_mean[:black_bar], distance=length / NOF_MARKERS * 0.75)
    peaks = peaks - 1
extra_ax.vlines(
    peaks,
    extra_ax.get_ylim()[0],
    extra_ax.get_ylim()[1],
    colors="green",
    linestyles="dotted",
)
axs[1, 1].set_title("Midpoints between markings")
axs[1, 1].imshow(img_orig, aspect="auto")
axs[1, 1].plot(line1_discrete[peaks, 0], line1_discrete[peaks, 1], "r+")
fig.show()
like image 129
Bart van Otterdijk Avatar answered Nov 03 '22 00:11

Bart van Otterdijk