Skip to content

Commit

Permalink
Make perspective calculation interactive
Browse files Browse the repository at this point in the history
This script was hard-coded, but now it prompts the user for all
of the values, so it can be used for any camera and image file.
  • Loading branch information
evenator committed Aug 2, 2017
1 parent 69ee32d commit 74a518b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 89 deletions.
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,28 @@ Top-down Perspective Transform
The third step of the pipeline is a perspective transformation from the camera's
point of view to a top-down perspective. This is accomplished with a
`GroundProjector` object. `GroundProjector` is a processor class that I defined
in `lanelines/processors.py` that encapsulates methods for transforming between the camera
perspective and the top-down perspective.
in `lanelines/processors.py` that encapsulates methods for transforming between
the camera perspective and the top-down perspective.

The `GroundProjector` needs a perspective transformation matrix, which I
calculated in the script in `bin/perspective_calculation`. It has four points
that I manually picked in `straight_lines1.jpg` with known scale. I was
able to determine the scale by the length and separation of the dashed lane
lines on the right, which are a known distance apart. The script calculates the
output points necessary for a top-down image that shows the lane plus 2 meters
on either side, stretching from the hood of the car to 3 meters past the
furthest lane marker chosen, with a top-down image resolution of 200 pixels per
meter, for a resolution of 0.5 cm per pixel. `bin/perspective_calculation`
calculates the perspective transform matrix from the four point correspondences
using OpenCV's `getPerspectiveTransform()` method. Above, you can see the
calculated in the script in `bin/calculate_perspective`. It prompts the user for
four points that form a rectangle on the ground with known dimensions (lane lines
are useful for this.) It then prompts the user for the dimensions of the rectangle
and the desired size and scale of the transformed top-down image.
`bin/calculate_perspective` calculates the perspective transform matrix from the
four point correspondences using OpenCV's `getPerspectiveTransform()` method.
`bin/calculate_perspective` constructs a `GroundProjector` object and saves it
in a Python pickle file, which is loaded by the pipeline and used for processing.

When I created the top-down image for my Udacity project, I picked four points in
`straight_lines1.jpg` with known scale. I was able to determine the scale by the
length and separation of the dashed lane lines on the right, which are a known
distance apart. I used the script to calculate a top-down image that showed the
lane plus 2 meters on either side, stretching from the hood of the car to 3 meters
past the furthest lane marker chosen, with a top-down image resolution of 200
pixels per meter, for a resolution of 0.5 cm per pixel. Above, you can see the
rectangle defined by these four points before (left) and after (right)
transformation. `bin/perspective_calculation` constructs a `GroundProjector`
object and saves with a Python pickle to `projector.p`, which is loaded by the
pipeline and used for processing.
transformation.

Here, you can see the same binary image from the previous step in the
pipeline transformed into the top-down perspective:
Expand Down
129 changes: 129 additions & 0 deletions bin/calculate_perspective
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#! /usr/bin/env python

import cv2
from lanelines.processors import GroundProjector
from lanelines.visualization import comparison_plot
import matplotlib.pyplot as plt
import numpy as np
import os
import pickle
import textwrap

'''
Script to generate the perspective projector and save it to a Python pickle.
'''

def prompt_coordinates(prompt):
while True:
string_coords = input(prompt)
split_string_coords = string_coords.split(',')
if len(split_string_coords) != 2:
print("Try again!")
continue
try:
coords = [float(x.strip()) for x in split_string_coords]
except (TypeError, ValueError):
print("Try again!")
continue
return coords

def prompt_float(prompt):
while True:
string_val = input(prompt)
try:
val = float(string_val.strip())
except (TypeError, ValueError):
print("Try again!")
continue
return val

def prompt_y_n(question):
answer = input(question).lower().strip()
while True:
if answer in ['y', 'yes']:
return True
elif answer in ['n', 'no']:
return False
answer = input("Please answer [y]es or [n]o")

def main():
print("\n",
textwrap.fill("In an image of straight road, select the pixel coordinates of "\
"4 points on lane markers. The points should form a rectangle "\
"on the ground."),"\n")

far_left = prompt_coordinates("Enter the pixel coordinates of the "\
"far left marker as two comma-separated numbers: ")

near_left = prompt_coordinates("Enter the pixel coordinates of the "\
"near left marker as two comma-separated numbers: ")

near_right = prompt_coordinates("Enter the pixel coordinates of the "\
"near right marker as two comma-separated numbers: ")

far_right = prompt_coordinates("Enter the pixel coordinates of the "\
"far right marker as two comma-separated numbers: ")
img_pts = np.float32([far_left, near_left, near_right, far_right])

offset = prompt_float("How many meters should the top-down image show on either side of the lane? ")

top_offset = prompt_float("How many meters should the top-down image extend past the far lane markers? ")

bottom_offset = prompt_float("How many meters should the top-down image extend before the close lane markers? ")

lane_width = prompt_float("In meters, how wide is the lane? ")

line_length = prompt_float("In meters, how long is it between the near and far lane markers? ")

px_m = prompt_float("In pixels per meter, what should the resolution of the top-down image be? ")

# Calculate the points' positions on the ground in meters
obj_pts = [(offset, top_offset), # Far left
(offset, top_offset + line_length), # Near Left
(offset+lane_width, top_offset + line_length), # Near right
(offset+lane_width, top_offset)] # Far right

# Convert from meters to pixels on the ground
obj_pts = np.float32(obj_pts) * px_m

# Calculate the perspective matrix
P = cv2.getPerspectiveTransform(img_pts, obj_pts)

# Calculate the shape of the output image
output_shape = (int(px_m * (offset * 2 + lane_width)),
int(px_m * (top_offset + bottom_offset + line_length)))

# Create a projector object
projector = GroundProjector(P, px_m, output_shape)

# Print results
print("Projection matrix:")
print(P)
print("Pixels per meter: {}".format(px_m))
print("Output shape (pixels): {}".format(output_shape))

# Draw results
if prompt_y_n("Would you like to preview the results? (y/n)"):
filename = input("Enter the path to the image file: ")
img = cv2.imread(filename)

transformed_pts = projector.transformPoint(img_pts).astype(np.int32)
img_pts = img_pts.astype(np.int32)
warped_img = projector.transformImage(img)

cv2.polylines(img, [img_pts], isClosed=True, color=(255, 0, 0), thickness=3)
cv2.polylines(warped_img, transformed_pts, isClosed=True, color=(255, 0, 0), thickness=int(px_m/10))
comparison_plot(img, warped_img, 'Original', 'Warped', filename)
plt.show()

# Save results
if prompt_y_n("Save the results?"):
out_path = input("Path to save the results (*.p)")
if not out_path[-2:] == ".p":
out_path = out_path + ".p"
print("Saving the projector to " + out_path)
with open(out_path, 'wb') as f:
pickle.dump(projector, f)

if __name__ == "__main__":
main()
73 changes: 0 additions & 73 deletions bin/perspective_calculation

This file was deleted.

Binary file modified projector.p
Binary file not shown.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
packages=['lanelines'],
scripts=['bin/calibrate_camera',
'bin/frame_extractor',
'bin/perspective_calculation',
'bin/calculate_perspective',
'bin/find_lanelines'],
install_requires=['matplotlib',
'moviepy',
Expand Down

0 comments on commit 74a518b

Please sign in to comment.