Created
April 6, 2018 20:57
-
-
Save crobinson42/0999b9d268f26dedb1cc64c7c5c9344f to your computer and use it in GitHub Desktop.
Track cars speed!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from picamera.array import PiRGBArray | |
from picamera import PiCamera | |
import time | |
import math | |
import datetime | |
import cv2 | |
# place a prompt on the displayed image | |
def prompt_on_image(txt): | |
global image | |
cv2.putText(image, txt, (10, 35), | |
cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 255), 1) | |
# calculate speed from pixels and time | |
def get_speed(pixels, ftperpixel, secs): | |
if secs > 0.0: | |
return ((pixels * ftperpixel)/ secs) * 0.681818 | |
else: | |
return 0.0 | |
# calculate elapsed seconds | |
def secs_diff(endTime, begTime): | |
diff = (endTime - begTime).total_seconds() | |
return diff | |
# record speed in .csv format | |
def record_speed(res): | |
global csvfileout | |
f = open(csvfileout, 'a') | |
f.write(res+"\n") | |
f.close | |
# mouse callback function for drawing capture area | |
def draw_rectangle(event,x,y,flags,param): | |
global ix,iy,fx,fy,drawing,setup_complete,image, org_image, prompt | |
if event == cv2.EVENT_LBUTTONDOWN: | |
drawing = True | |
ix,iy = x,y | |
elif event == cv2.EVENT_MOUSEMOVE: | |
if drawing == True: | |
image = org_image.copy() | |
prompt_on_image(prompt) | |
cv2.rectangle(image,(ix,iy),(x,y),(0,255,0),2) | |
elif event == cv2.EVENT_LBUTTONUP: | |
drawing = False | |
fx,fy = x,y | |
image = org_image.copy() | |
prompt_on_image(prompt) | |
cv2.rectangle(image,(ix,iy),(fx,fy),(0,255,0),2) | |
# define some constants | |
DISTANCE = 76 #<---- enter your distance-to-road value here | |
MIN_SPEED = 0 #<---- enter the minimum speed for saving images | |
SAVE_CSV = False #<---- record the results in .csv format in carspeed_(date).csv | |
THRESHOLD = 15 | |
MIN_AREA = 175 | |
BLURSIZE = (15,15) | |
IMAGEWIDTH = 640 | |
IMAGEHEIGHT = 480 | |
RESOLUTION = [IMAGEWIDTH,IMAGEHEIGHT] | |
FOV = 53.5 #<---- Field of view | |
FPS = 30 | |
SHOW_BOUNDS = True | |
SHOW_IMAGE = True | |
# the following enumerated values are used to make the program more readable | |
WAITING = 0 | |
TRACKING = 1 | |
SAVING = 2 | |
UNKNOWN = 0 | |
LEFT_TO_RIGHT = 1 | |
RIGHT_TO_LEFT = 2 | |
# calculate the the width of the image at the distance specified | |
frame_width_ft = 2*(math.tan(math.radians(FOV*0.5))*DISTANCE) | |
ftperpixel = frame_width_ft / float(IMAGEWIDTH) | |
print("Image width in feet {} at {} from camera".format("%.0f" % frame_width_ft,"%.0f" % DISTANCE)) | |
# state maintains the state of the speed computation process | |
# if starts as WAITING | |
# the first motion detected sets it to TRACKING | |
# if it is tracking and no motion is found or the x value moves | |
# out of bounds, state is set to SAVING and the speed of the object | |
# is calculated | |
# initial_x holds the x value when motion was first detected | |
# last_x holds the last x value before tracking was was halted | |
# depending upon the direction of travel, the front of the | |
# vehicle is either at x, or at x+w | |
# (tracking_end_time - tracking_start_time) is the elapsed time | |
# from these the speed is calculated and displayed | |
state = WAITING | |
direction = UNKNOWN | |
initial_x = 0 | |
last_x = 0 | |
#-- other values used in program | |
base_image = None | |
abs_chg = 0 | |
mph = 0 | |
secs = 0.0 | |
ix,iy = -1,-1 | |
fx,fy = -1,-1 | |
drawing = False | |
setup_complete = False | |
tracking = False | |
text_on_image = 'No cars' | |
prompt = '' | |
# initialize the camera. Adjust vflip and hflip to reflect your camera's orientation | |
camera = PiCamera() | |
camera.resolution = RESOLUTION | |
camera.framerate = FPS | |
camera.vflip = True | |
camera.hflip = True | |
rawCapture = PiRGBArray(camera, size=camera.resolution) | |
# allow the camera to warm up | |
time.sleep(0.9) | |
# create an image window and place it in the upper left corner of the screen | |
cv2.namedWindow("Speed Camera") | |
cv2.moveWindow("Speed Camera", 10, 40) | |
# call the draw_rectangle routines when the mouse is used | |
cv2.setMouseCallback('Speed Camera',draw_rectangle) | |
# grab a reference image to use for drawing the monitored area's boundry | |
camera.capture(rawCapture, format="bgr", use_video_port=True) | |
image = rawCapture.array | |
rawCapture.truncate(0) | |
org_image = image.copy() | |
if SAVE_CSV: | |
csvfileout = "carspeed_{}.cvs".format(datetime.datetime.now().strftime("%Y%m%d_%H%M")) | |
record_speed('Date,Day,Time,Speed,Image') | |
else: | |
csvfileout = '' | |
prompt = "Define the monitored area - press 'c' to continue" | |
prompt_on_image(prompt) | |
# wait while the user draws the monitored area's boundry | |
while not setup_complete: | |
cv2.imshow("Speed Camera",image) | |
#wait for for c to be pressed | |
key = cv2.waitKey(1) & 0xFF | |
# if the `c` key is pressed, break from the loop | |
if key == ord("c"): | |
break | |
# the monitored area is defined, time to move on | |
prompt = "Press 'q' to quit" | |
# since the monitored area's bounding box could be drawn starting | |
# from any corner, normalize the coordinates | |
if fx > ix: | |
upper_left_x = ix | |
lower_right_x = fx | |
else: | |
upper_left_x = fx | |
lower_right_x = ix | |
if fy > iy: | |
upper_left_y = iy | |
lower_right_y = fy | |
else: | |
upper_left_y = fy | |
lower_right_y = iy | |
monitored_width = lower_right_x - upper_left_x | |
monitored_height = lower_right_y - upper_left_y | |
print("Monitored area:") | |
print(" upper_left_x {}".format(upper_left_x)) | |
print(" upper_left_y {}".format(upper_left_y)) | |
print(" lower_right_x {}".format(lower_right_x)) | |
print(" lower_right_y {}".format(lower_right_y)) | |
print(" monitored_width {}".format(monitored_width)) | |
print(" monitored_height {}".format(monitored_height)) | |
print(" monitored_area {}".format(monitored_width * monitored_height)) | |
# capture frames from the camera (using capture_continuous. | |
# This keeps the picamera in capture mode - it doesn't need | |
# to prep for each frame's capture. | |
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): | |
#initialize the timestamp | |
timestamp = datetime.datetime.now() | |
# grab the raw NumPy array representing the image | |
image = frame.array | |
# crop area defined by [y1:y2,x1:x2] | |
gray = image[upper_left_y:lower_right_y,upper_left_x:lower_right_x] | |
# convert the fram to grayscale, and blur it | |
gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) | |
gray = cv2.GaussianBlur(gray, BLURSIZE, 0) | |
# if the base image has not been defined, initialize it | |
if base_image is None: | |
base_image = gray.copy().astype("float") | |
lastTime = timestamp | |
rawCapture.truncate(0) | |
cv2.imshow("Speed Camera", image) | |
# compute the absolute difference between the current image and | |
# base image and then turn eveything lighter gray than THRESHOLD into | |
# white | |
frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(base_image)) | |
thresh = cv2.threshold(frameDelta, THRESHOLD, 255, cv2.THRESH_BINARY)[1] | |
# dilate the thresholded image to fill in any holes, then find contours | |
# on thresholded image | |
thresh = cv2.dilate(thresh, None, iterations=2) | |
(_, cnts, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) | |
# look for motion | |
motion_found = False | |
biggest_area = 0 | |
# examine the contours, looking for the largest one | |
for c in cnts: | |
(x1, y1, w1, h1) = cv2.boundingRect(c) | |
# get an approximate area of the contour | |
found_area = w1*h1 | |
# find the largest bounding rectangle | |
if (found_area > MIN_AREA) and (found_area > biggest_area): | |
biggest_area = found_area | |
motion_found = True | |
x = x1 | |
y = y1 | |
h = h1 | |
w = w1 | |
if motion_found: | |
if state == WAITING: | |
# intialize tracking | |
state = TRACKING | |
initial_x = x | |
last_x = x | |
initial_time = timestamp | |
last_mph = 0 | |
text_on_image = 'Tracking' | |
print(text_on_image) | |
print("x-chg Secs MPH x-pos width") | |
else: | |
# compute the lapsed time | |
secs = secs_diff(timestamp,initial_time) | |
if secs >= 15: | |
state = WAITING | |
direction = UNKNOWN | |
text_on_image = 'No Car Detected' | |
motion_found = False | |
biggest_area = 0 | |
rawCapture.truncate(0) | |
base_image = None | |
print('Resetting') | |
continue | |
if state == TRACKING: | |
if x >= last_x: | |
direction = LEFT_TO_RIGHT | |
abs_chg = x + w - initial_x | |
else: | |
direction = RIGHT_TO_LEFT | |
abs_chg = initial_x - x | |
mph = get_speed(abs_chg,ftperpixel,secs) | |
print("{0:4d} {1:7.2f} {2:7.0f} {3:4d} {4:4d}".format(abs_chg,secs,mph,x,w)) | |
real_y = upper_left_y + y | |
real_x = upper_left_x + x | |
# is front of object outside the monitired boundary? Then write date, time and speed on image | |
# and save it | |
if ((x <= 2) and (direction == RIGHT_TO_LEFT)) \ | |
or ((x+w >= monitored_width - 2) \ | |
and (direction == LEFT_TO_RIGHT)): | |
if (last_mph > MIN_SPEED): # save the image | |
# timestamp the image | |
cv2.putText(image, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"), | |
(10, image.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 1) | |
# write the speed: first get the size of the text | |
size, base = cv2.getTextSize( "%.0f mph" % last_mph, cv2.FONT_HERSHEY_SIMPLEX, 2, 3) | |
# then center it horizontally on the image | |
cntr_x = int((IMAGEWIDTH - size[0]) / 2) | |
cv2.putText(image, "%.0f mph" % last_mph, | |
(cntr_x , int(IMAGEHEIGHT * 0.2)), cv2.FONT_HERSHEY_SIMPLEX, 2.00, (0, 255, 0), 3) | |
# and save the image to disk | |
imageFilename = "car_at_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".jpg" | |
# use the following image file name if you want to be able to sort the images by speed | |
#imageFilename = "car_at_%02.0f" % last_mph + "_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".jpg" | |
cv2.imwrite(imageFilename,image) | |
if SAVE_CSV: | |
cap_time = datetime.datetime.now() | |
record_speed(cap_time.strftime("%Y.%m.%d")+','+cap_time.strftime('%A')+','+\ | |
cap_time.strftime('%H%M')+','+("%.0f" % last_mph) + ','+imageFilename) | |
state = SAVING | |
# if the object hasn't reached the end of the monitored area, just remember the speed | |
# and its last position | |
last_mph = mph | |
last_x = x | |
else: | |
if state != WAITING: | |
state = WAITING | |
direction = UNKNOWN | |
text_on_image = 'No Car Detected' | |
print(text_on_image) | |
# only update image and wait for a keypress when waiting for a car | |
# This is required since waitkey slows processing. | |
if (state == WAITING): | |
# draw the text and timestamp on the frame | |
cv2.putText(image, datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"), | |
(10, image.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 1) | |
cv2.putText(image, "Road Status: {}".format(text_on_image), (10, 20), | |
cv2.FONT_HERSHEY_SIMPLEX,0.35, (0, 0, 255), 1) | |
if SHOW_BOUNDS: | |
#define the monitored area right and left boundary | |
cv2.line(image,(upper_left_x,upper_left_y),(upper_left_x,lower_right_y),(0, 255, 0)) | |
cv2.line(image,(lower_right_x,upper_left_y),(lower_right_x,lower_right_y),(0, 255, 0)) | |
# show the frame and check for a keypress | |
if SHOW_IMAGE: | |
prompt_on_image(prompt) | |
cv2.imshow("Speed Camera", image) | |
# Adjust the base_image as lighting changes through the day | |
if state == WAITING: | |
last_x = 0 | |
cv2.accumulateWeighted(gray, base_image, 0.25) | |
state=WAITING; | |
key = cv2.waitKey(1) & 0xFF | |
# if the `q` key is pressed, break from the loop and terminate processing | |
if key == ord("q"): | |
break | |
# clear the stream in preparation for the next frame | |
rawCapture.truncate(0) | |
# cleanup the camera and close any open windows | |
cv2.destroyAllWindows() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment