Im not at a race track often, so I took Mrs O'Hanlon's fiesta for a spin around Stourbridge ring road, its got speed restrictions and its very busy but it does at least go round and round!
This has been a pretty intense project, probably the most complex I've done with the raspberry pi and has involved several steps:
- I needed a better way of syncing data gathered by the Pi with video taken on the camera, with my first OBD overlay it was 'VERY' difficult to get it lined up. To acheive this I made a custom version of Raspivid to allow me to read, in real time, what frame it was on
- I had to create a python class to control raspivid
- I had to set up a GPS receiver and create a python class to control it
The data movie is created by producing an animation of single images. While raspivid is running my program continually reads the current frame count, the GPS data and then using PIL (python imaging library) creates and image of the map and some GPS data (lat, long, speed) for that frame.
The map is made by converting GPS co-ordinates to XY positions using a Mercator projection and then each frame is drawn by creating lines between all the sets of XY positions captured since the video started.
Where there are gaps between the frames, I use symbolic links to reference the last frame that was created, so I end up with a data frame (image) for every frame in the car video, this is frame 4993:
I then used mencoder to join all the frames together, in exactly the same way you would for a time lapse video.
My rig was pretty basic, I ran a simple program at boot which started the GPS service, lit an led and waited for a button to be pressed, when the button was pressed it started my program, which ran until it timed out and then the program shut the pi down. Plenty of gaffer tape was also used to stick it to the dashboard!
Finally once I had 2 videos, one from the camera and one from the data, I used Microsoft Expression (its a free and pretty effective encoder for Windows) to overlay the data onto of the video.
You will find all my code at https://github.com/martinohanlon/vidGPSOverlay, although please don't expect it to just run, there are dependencies for my custom raspivid program as well as the GPS service, so you may need to give it a bit of love to get it going!
The code:
from PIL import Image, ImageFont, ImageDraw from RaspividController import * from GPSController import * import time import datetime DATAFRAME_WIDTH = 500 DATAFRAME_HEIGHT = 300 MAP_WIDTH = 300 MAP_HEIGHT = 300 MAX_SCALE = 1 FONT_PATH = "/usr/share/fonts/truetype/freefont/FreeMono.ttf" VIDEOTIME = 600000 def drawPoint(imagedraw,x,y,width,colour): imagedraw.ellipse((x-(width/2),y-(width/2),x+(width/2),y+(width/2)), colour) class DataDrawer(): def __init__(self, imagesFolder): #setup variables self.imagesFolder = imagesFolder self.imageSize = (300,300) self.minX = 99999999999 self.maxX = -99999999999 self.minY = 99999999999 self.maxY = -99999999999 self.lastFrameNo = 0 self.lastLat = 0 self.lastLon = 0 self.xyPositions = [] self.mapScale = 1 self.padX = 0 self.padY = 0 #load font self.font = ImageFont.truetype(FONT_PATH, 14) #create first frame self.newDataFrame(1, 0, 0 ,0) def newDataFrame(self, frameNo, speed, lat, lon): #check to make sure the frame has moved on since last time if frameNo > self.lastFrameNo: #create sumbolic links between last frame and this frame for missingFrameNo in range(self.lastFrameNo+1, frameNo): os.symlink(self.imagesFolder + "/" + "{0:06d}".format(self.lastFrameNo) + ".jpg", self.imagesFolder + "/" + "{0:06d}".format(missingFrameNo) + ".jpg") #create new image frame = Image.new("RGBA", (DATAFRAME_WIDTH, DATAFRAME_HEIGHT)) frameDraw = ImageDraw.Draw(frame) #data frameDraw.text((315,10)," Speed " + str(round(speed,2)),font=self.font) frameDraw.text((315,50)," Latitude " + str(lat),font=self.font) frameDraw.text((315,90),"Longitude " + str(lon),font=self.font) #map #only create map if we have a GPS fix if lat != 0 and lon != 0: #only add a new set of coords if the lat and lon have changed if self.lastLat != lat or self.lastLon != lon: #get x & y coords x,y = GpsUtils.latLongToXY(lat, lon) #print "x " + str(x) #print "y " + str(y) #add x,y to list self.xyPositions.append([x,y]) #update mins and maxs if x < self.minX: self.minX = x if x > self.maxX: self.maxX = x if y < self.minY: self.minY = y if y > self.maxY: self.maxY = y #persist lat and lon self.lastLat = lat self.lastLon = lon #calculate scale diffX = self.maxX - self.minX #print "diffX " + str(diffX) diffY = self.maxY - self.minY #print "diffY " + str(diffY) if diffX > diffY: if diffX != 0: self.mapScale = MAP_WIDTH / float(diffX) else: self.mapScale = 1 else: if diffY != 0: self.mapScale = MAP_HEIGHT / float(diffY) else: self.mapScale = 1 #print "mapScale " + str(self.mapScale) #set max scale if self.mapScale > MAX_SCALE: self.mapScale = MAX_SCALE #re-calculate padding self.padX = int((MAP_WIDTH - (diffX * self.mapScale)) / 2) self.padY = int((MAP_HEIGHT - (diffY * self.mapScale)) / 2) #draw lines for position in range(1, len(self.xyPositions)): #draw line between previous position and this one x1 = self.padX + abs((self.xyPositions[position-1][0] * self.mapScale) - (self.minX * self.mapScale)) y1 = self.padY + abs((self.xyPositions[position-1][1] * self.mapScale) - (self.maxY * self.mapScale)) x2 = self.padX + abs((self.xyPositions[position][0] * self.mapScale) - (self.minX * self.mapScale)) y2 = self.padY + abs((self.xyPositions[position][1] * self.mapScale) - (self.maxY * self.mapScale)) #print "coords - " + str(x1) + " " + str(y1) + " " + str(x2) + " " + str(y2) frameDraw.line((x1, y1, x2, y2), fill="white", width=3) #draw start and end point if len(self.xyPositions) > 1: # start drawPoint(frameDraw, self.padX + abs((self.xyPositions[0][0] * self.mapScale) - (self.minX * self.mapScale)),self.padY + abs((self.xyPositions[0][1] * self.mapScale) - (self.maxY * self.mapScale)), 10, "red") # end drawPoint(frameDraw, self.padX + abs((self.xyPositions[len(self.xyPositions)-1][0] * self.mapScale) - (self.minX * self.mapScale)), self.padY + abs((self.xyPositions[len(self.xyPositions)-1][1] * self.mapScale) - (self.maxY * self.mapScale)), 10, "green") #save image frame.save(self.imagesFolder + "/" + "{0:06d}".format(frameNo) + ".jpg", "JPEG") #update last frame self.lastFrameNo = frameNo
# main program if __name__ == "__main__": try: #create data folder localtime = datetime.datetime.now() foldername = "/home/pi/dev/cbb/vidGPSOverlay/data/" + str(localtime.year) + str(localtime.month) + str(localtime.day) + str(localtime.hour) + str(localtime.minute) + str(localtime.second) if not os.path.exists(foldername): os.makedirs(foldername) #start raspivid print "starting raspivid controller" vidcontrol = RaspiVidController(foldername+"/vid.h264", VIDEOTIME, False, ["-fps", "25", "-vf", "-hf"]) vidcontrol.start() #start gps controller print "starting gps contoller" gpscontrol = GpsController() gpscontrol.start() #wait for controllers to startup and gps fix time.sleep(3) #create data file datafile = open(foldername+"/data.csv", "w") #create datadrawer object datadrawer = DataDrawer(foldername) #loop while(vidcontrol.isAlive()): #get framecount framecount = vidcontrol.getFrameCount() #wait for a bit as the gps data is a little behind + it allows the processor to have a rest! time.sleep(0.1) #get gps data lat = gpscontrol.fix.latitude lon = gpscontrol.fix.longitude utc = gpscontrol.utc speed = gpscontrol.fix.speed speedMPH = speed * GpsUtils.MPS_TO_MPH #write data dataString = str(framecount) + "," + str(speedMPH) + "," + str(lat) + "," + str(lon) + "," + "\n" datafile.write(dataString) datadrawer.newDataFrame(int(framecount), speedMPH, lat, lon) except KeyboardInterrupt: print "Cancelled" except: print "Unexpected error:", sys.exc_info()[0], sys.exc_info()[1] raise finally: #shutdown raspivid vidcontrol.stopController() vidcontrol.join() print "stopped raspivid controllder" #shutdown gps controller gpscontrol.stopController() gpscontrol.join() print "stopped gps controllder" #close data file print "closing data file" datafile.close()
well done bro :)
ReplyDeleteI've been trying to do exactly this for the last 3 weekends. Looks perfect for what I want. Well done
ReplyDeleteHave you changed something also on the odb script? Because since your first post about it never worked for me...
ReplyDeleteNo, I dont think so. Do you get an error?
DeleteWhere did you buy the long cameracable?
ReplyDeleteebay! http://www.ebay.co.uk/itm/Raspberry-Pi-Camera-Ribbon-Cable-Multiple-Lengths-/261241155328?pt=UK_BOI_Electrical_Components_Supplies_ET&var=560210982527&hash=item3cd32faf00
DeleteThis comment has been removed by the author.
DeleteThx =) .. I just found another one on ebay: http://www.ebay.de/itm/121051772597?var=420115662547&ssPageName=STRK:MEWAX:IT&_trksid=p3984.m1423.l2649
DeleteNice - have you considered colouring the points of the track to indicate speed or altitude at that point of the trip?
ReplyDeleteI hadn't, but its a great idea, thank you
Deletecan we take GPS from mobile phone use for raspberry pi ?
ReplyDeleteNot sure. It might be possible reading the data using bluetooth, but you would probably need an app on the phone and need to write some code to interpret it on the pi.
DeletePlease add the realtime streaming on your Dash Cam or GPS Helmet.
ReplyDeleteIf so, Other people can see your sight. Please improve it.
Could you do recording video and streaming at the same time?
It sounds like a good enhancement, getting connectivity might be difficult though, race track rarely have wifi and even if they did it wouldnt stretch as far as the end of the track and they are often in rural areas with little 3g connectivity.
DeleteHave a look at picamera, its a python module for the raspberry pi camera, that can take pictures as you are recording / streaming video.
Thanks
ReplyDeleteIf you are looking at downsizing your car, chances are it is because you have been upsizing your car in the past. And that makes sense - you needed a bigger car to fit little Jessica's cello and little Johnny's cricket bag, and now they've moved out of home.Garanzia Auto
ReplyDelete