Search code examples
pythonnumpymatrixlinehough-transform

Get points of a line that touch the edge of the image


I need help to get the real "start" and "end" points where a line is drawn in the image matrix. In the example below, a detected line is shown using y0 and y1.

import numpy as np

from skimage.transform import hough_line, hough_line_peaks
from skimage.feature import canny
from skimage import data

import matplotlib.pyplot as plt
from matplotlib import cm


# Constructing test image
image = np.zeros((200, 200))
idx = np.arange(0, 200)
image[idx, idx//2] = 255

imgOriginal = image.copy()

# Classic straight-line Hough transform
# Set a precision of 0.5 degree.
tested_angles = np.linspace(-np.pi / 2, np.pi / 2, 360)
h, theta, d = hough_line(image, theta=tested_angles)

# Generating figure 1
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
ax = axes.ravel()

ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')
ax[0].set_axis_off()
ax[1].imshow(np.log(1 + h),
             extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]), d[-1], d[0]],
             cmap=cm.gray, aspect=1/1.5)
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')

ax[2].imshow(image, cmap=cm.gray)
origin = np.array((0, image.shape[1]))
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
    y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
    print('y0 = {} y1 = {}'.format(y0, y1))
    ax[2].plot(origin, (y0, y1), '-r')
ax[2].set_xlim(origin)
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_axis_off()
ax[2].set_title('Detected lines')

plt.tight_layout()
plt.show()

This code results in:

https://i.ibb.co/YtD5104/1.png

What I want is to get the following points in the real image matrix:

https://i.ibb.co/5B4kGQC/2.png

Which is likely to be (0,0) and (199, 100)

In summary, I want to transform y0 and y1 into real points in my numpy matrix.


Solution

  • Your problem is to find the point of intersection between two lines essentially. One is your given line and the other is the line defined by the edge of the image.

    That can be done as explained here. I am borrowing the code in the first answer. Define these functions -

    def line_intersection(line1, line2):
    
        xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
        ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
    
        def det(a, b):
            return a[0] * b[1] - a[1] * b[0]
    
        div = det(xdiff, ydiff)
        if div == 0:
           raise Exception('lines do not intersect')
    
        d = (det(*line1), det(*line2))
        x = det(d, xdiff) / div
        y = det(d, ydiff) / div
        return x, y
    

    Change your loop as follows -

    for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
    
        y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
        print('y0 = {} y1 = {}'.format(y0, y1))
        ax[2].plot(origin, (y0, y1), '-r')
    
        l1 = ((origin[0], y0), (origin[1], y1))
        l2 = ((0, 200), (200, 200))
        print(line_intersection(l1, l2))
    

    This is obviously assuming that the line of interest always intersects with the lower edge of the image. If the line intersects with the right edge, l2 will have to be modified accordingly. In practice, I would suggest finding the intersect with the two edges and picking the "closest" intersect.

    This also assumes that the the line of interest always starts from the top left corner of the image (as you have defined your problem) . If that's not the case, you would need to do this for all four edges of the image and pick the first two intersects.