Finding the vanishing point in images

Simple method to find vanishing points in images with Canny, Hough

python
computer-vision
programming
Author

Jaime Ruiz Serra

Published

October 5, 2021

Find vanishing point in image (1 point perspective)

%matplotlib inline
import cv2
import numpy as np
import urllib
import matplotlib.pyplot as plt

Get sample image

First download a sample image.

def get_image_from_url(url):
    """https://stackoverflow.com/a/3969809"""

    with urllib.request.urlopen(url) as u:
        s = u.read()
    arr = np.asarray(bytearray(s), dtype=np.uint8)
    img = cv2.imdecode(arr, -1) # 'Load it as it is'
    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

    return img

img = get_image_from_url('https://static.toiimg.com/photo/69928969/cobble.jpg')
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7fb5ef127670>

Edge detection

We are interested in using the most prominent edges in the image to compute the Hough lines. To find the edges, we use Canny edge detection.

gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 200, 700)

plt.imshow(edges, cmap='gray')
<matplotlib.image.AxesImage at 0x7fb5ef468e80>

Hough lines from edges

We can use the edges obtained above to compute Hough lines.

lines = cv2.HoughLines(edges, 1, np.pi/180, 100)
img_lines = img.copy()

# adapted from https://stackoverflow.com/a/60515853
for line in lines:
    rho,theta = line[0]
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    x1 = int(x0 + 10000*(-b))
    y1 = int(y0 + 10000*(a))
    x2 = int(x0 - 10000*(-b))
    y2 = int(y0 - 10000*(a))
    cv2.line(img_lines, (x1,y1),(x2,y2),(0,255,0),2)
    
plt.imshow(img_lines)
<matplotlib.image.AxesImage at 0x7fb5da4183a0>

lines = cv2.HoughLines(edges, 
                       0.85, 
                       np.pi/180, 
                       100, 
                       # min_theta=np.pi/36, 
                       # max_theta=np.pi-np.pi/36
                      )

img_lines = img.copy()

segments = []
for line in lines:
    rho, theta = line[0]
    # skip near-vertical lines
    if abs(theta-np.pi/90) < np.pi/9:
        continue
    
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    
    x1 = int(x0 + 10000*(-b))
    y1 = int(y0 + 10000*(a))
    
    x2 = int(x0 - 10000*(-b))
    y2 = int(y0 - 10000*(a))
    
    segments.append((np.array((x1, y1)), 
                     np.array((x2, y2))))
    
    cv2.line(img_lines, (x1,y1),(x2,y2),(0,255,0),2)
    
plt.imshow(img_lines)
<matplotlib.image.AxesImage at 0x7fb5786558b0>

def seg_intersect(s1, s2):
    """https://stackoverflow.com/a/3252222"""
    da = s1[0] - s1[1]
    db = s2[0] - s2[1]
    dp = s1[0] - s2[0]
    dap = perp(da)
    denom = np.dot( dap, db)
    num = np.dot( dap, dp )
    return (num / denom.astype(float))*db + s2[0]

def perp(a):
    b = np.empty_like(a)
    b[0] = -a[1]
    b[1] = a[0]
    return b

intersections = np.empty((len(segments), len(segments), 2))
intersections[:] = np.nan
for i, s1 in enumerate(segments):
    for j, s2 in enumerate(segments[i:], start=i):
        if i != j:
            intersections[i,j] = seg_intersect(s1, s2)

intersections = intersections[~np.isnan(intersections)]
intersections = intersections.reshape((int(len(intersections)/2), 2)).astype(np.int16)
intersections
array([[484, 332],
       [522, 314],
       [858, 157],
       [550, 301],
       [513, 318],
       [523, 314],
       [525, 313],
       [477, 335],
       [525, 307],
       [776, 156],
       [170, 520],
       [509, 317],
       [517, 312],
       [524, 308],
       [477, 336],
       [587, 153],
       [520, 318],
       [519, 321],
       [522, 314],
       [524, 309],
       [479, 422],
       [800, 156],
       [ 58, 144],
       [103, 144],
       [493, 151],
       [474, 151],
       [517, 320],
       [526, 315],
       [525, 315],
       [477, 343],
       [926, 477],
       [527, 323],
       [476, 304],
       [525, 315],
       [476, 295],
       [472,  40]], dtype=int16)
img_points = img.copy()

# show all intersection points
for p in intersections:
    cv2.circle(img_points, (p[0], p[1]), 20, (255,0,0), 5)
    
# show mean intersection point
vp_hat = np.mean(intersections, axis=0).astype(np.int16)
cv2.circle(img_points, (vp_hat[0], vp_hat[1]), 20, (0,255,0), 5)

plt.imshow(img_points)

print(f"Estimated vanishing point coordinates:", vp_hat)
Estimated vanishing point coordinates: [513 285]