├── LICENSE ├── OSD-recording.py └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Maksim Surguy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OSD-recording.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import picamera 4 | import time 5 | import datetime 6 | import numpy as np 7 | import string 8 | import random 9 | import os 10 | import math 11 | from gps import * 12 | import subprocess 13 | import threading 14 | 15 | from PIL import Image, ImageDraw, ImageFont 16 | 17 | # Video Resolution for recording 18 | VIDEO_HEIGHT = 720 19 | VIDEO_WIDTH = 1280 20 | 21 | baseDir='/home/pi/OSD/' # directory where the video will be recorded 22 | 23 | crosshairImagePath = '/home/pi/OSD/crosshair.png' 24 | gpsd = None # seting the global variable to track whether GPSD service is running 25 | 26 | os.system('clear') # clear the terminal from any other text 27 | 28 | # Create empty images to store text overlays 29 | textOverlayCanvas = Image.new("RGB", (704, 60)) 30 | textOverlayPixels = textOverlayCanvas.load() 31 | 32 | # Use Roboto font (must be downloaded first) 33 | font = ImageFont.truetype("/usr/share/fonts/truetype/roboto/Roboto-Regular.ttf", 20) 34 | 35 | initialLatitude = 0 36 | initialLongitude = 0 37 | initialStartupTime = "" 38 | timeActive = 0 39 | distanceTraveled = 0 40 | secondsRecorded = 0 41 | isCameraRecording = False 42 | 43 | def make_time(gps_datetime_str): 44 | """Makes datetime object from string object""" 45 | if not 'n/a' == gps_datetime_str: 46 | datetime_string = gps_datetime_str 47 | datetime_object = datetime.datetime.strptime(datetime_string, "%Y-%m-%dT%H:%M:%S") 48 | return datetime_object 49 | 50 | def elapsed_time_from(start_time, now_time): 51 | """calculate time delta from latched time and current time""" 52 | time_then = make_time(start_time) 53 | time_now = make_time(now_time) 54 | if time_then is None: 55 | return 56 | delta_t = time_now - time_then 57 | return delta_t 58 | 59 | def distance(origin, destination): 60 | lat1, lon1 = origin 61 | lat2, lon2 = destination 62 | radius = 6371 # km 63 | 64 | dlat = math.radians(lat2-lat1) 65 | dlon = math.radians(lon2-lon1) 66 | a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \ 67 | * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2) 68 | c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) 69 | d = radius * c 70 | 71 | return d 72 | 73 | class GpsPoller(threading.Thread): 74 | def __init__(self): 75 | threading.Thread.__init__(self) 76 | global gpsd #bring it in scope 77 | gpsd = gps(mode=WATCH_ENABLE) #starting the stream of info 78 | self.current_value = None 79 | self.running = True #setting the thread running to true 80 | 81 | def run(self): 82 | global gpsd 83 | while gpsp.running: 84 | gpsd.next() #this will continue to loop and grab EACH set of gpsd info to clear the buffer 85 | 86 | with picamera.PiCamera() as camera: 87 | camera.resolution = (VIDEO_WIDTH, VIDEO_HEIGHT) 88 | camera.framerate = 60 89 | camera.led = False 90 | camera.start_preview() 91 | 92 | time.sleep(3) 93 | 94 | camera.start_recording('video.h264') 95 | isCameraRecording = True 96 | 97 | gpsp = GpsPoller() # create the GPS Poller thread 98 | 99 | topOverlayImage = textOverlayCanvas.copy() 100 | bottomOverlayImage = textOverlayCanvas.copy() 101 | 102 | # Load the crosshair image 103 | crosshairImg = Image.open(crosshairImagePath) 104 | 105 | # Create an image padded to the required size with 106 | crosshairPad = Image.new('RGBA', (((crosshairImg.size[0] + 31) // 32) * 32, ((crosshairImg.size[1] + 15) // 16) * 16)) 107 | crosshairPad.paste(crosshairImg, (0, 0)) 108 | 109 | # Attach overlays 110 | topOverlay = camera.add_overlay(topOverlayImage.tobytes(), format='rgb', size=(704,60), layer=5, alpha=128, fullscreen=False, window=(0,20,704,60)) 111 | bottomOverlay = camera.add_overlay(bottomOverlayImage.tobytes(), format='rgb', size=(704,60), layer=4, alpha=128, fullscreen=False, window=(0,400,704,60)) 112 | crosshairOverlay = camera.add_overlay(crosshairPad.tobytes(), format='rgba', size=(400,400), layer=3, alpha=10, fullscreen=False, window=(0,0,704,512)) 113 | 114 | try: 115 | gpsp.start() # start receiving data from GPS sensor 116 | 117 | while True: 118 | if (gpsd.fix.latitude != "n/a" and initialLatitude == 0): 119 | initialLatitude = gpsd.fix.latitude 120 | 121 | if (gpsd.fix.longitude != "n/a" and initialLongitude == 0): 122 | initialLongitude = gpsd.fix.longitude 123 | 124 | if (gpsd.utc != "n/a" and initialStartupTime == "" and '-' in gpsd.utc): 125 | initialStartupTime = gpsd.utc.split('.')[0] 126 | 127 | if (gpsd.utc != "n/a" and '-' in initialStartupTime and '-' in gpsd.utc): 128 | timeActive = elapsed_time_from(initialStartupTime, gpsd.utc.split('.')[0]) 129 | 130 | if isCameraRecording == False: 131 | timeActive = "OFF" 132 | 133 | distanceTraveled = round(distance( (initialLatitude, initialLongitude), (gpsd.fix.latitude, gpsd.fix.longitude) ),2) 134 | 135 | topOverlayImage = textOverlayCanvas.copy() 136 | bottomOverlayImage = textOverlayCanvas.copy() 137 | 138 | topText = "Spd: {0:.2f} Climb:{1:.2f} Dir: {2} Sats: {3} Mode: {4}".format(gpsd.fix.speed, gpsd.fix.climb, gpsd.fix.track, len(gpsd.satellites), gpsd.fix.mode) 139 | drawTopOverlay = ImageDraw.Draw(topOverlayImage) 140 | drawTopOverlay.text((200, 15), topText, font=font, fill=(255, 0, 255)) 141 | topOverlay.update(topOverlayImage.tobytes()) 142 | 143 | bottomText = "Alt: {0}m Loc: {1:.5f}, {2:.5f} Home: {3}m Rec: {4}".format(gpsd.fix.altitude,gpsd.fix.latitude, gpsd.fix.longitude, distanceTraveled, timeActive) 144 | drawBottomOverlay = ImageDraw.Draw(bottomOverlayImage) 145 | drawBottomOverlay.text((50, 20), bottomText, font=font, fill=(255, 255, 255)) 146 | bottomOverlay.update(bottomOverlayImage.tobytes()) 147 | 148 | secondsRecorded = secondsRecorded + 1 149 | 150 | if isCameraRecording == True: 151 | camera.wait_recording(1) 152 | 153 | if secondsRecorded > 10: 154 | camera.stop_recording() 155 | isCameraRecording = False 156 | 157 | else: 158 | time.sleep(1) 159 | 160 | except KeyboardInterrupt: 161 | gpsp.running = False 162 | gpsp.join() 163 | camera.remove_overlay(topOverlay) 164 | camera.remove_overlay(bottomOverlay) 165 | camera.remove_overlay(crosshairOverlay) 166 | if isCameraRecording: 167 | camera.stop_recording() 168 | 169 | print "Cancelled" 170 | 171 | finally: 172 | gpsp.running = False 173 | gpsp.join() 174 | camera.remove_overlay(topOverlay) 175 | camera.remove_overlay(bottomOverlay) 176 | camera.remove_overlay(crosshairOverlay) 177 | if isCameraRecording: 178 | camera.stop_recording() 179 | 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # raspberry-pi-fpv-osd 2 | On Screen Display for Raspberry Pi using an RF transmitter 3 | --------------------------------------------------------------------------------