#!/usr/bin/env python # # __geotagging__ # Tag the images recorded during a flight with geo location extracted from # a PX4 log file. # # This file accepts *.jpg format images and reads position information # from a *.px4log file # # Example Syntax: # python geotagging.py --logfile=log001.px4log --input=images/ # --output=imagesWithTag/ # # Author: Andreas Bircher, Wingtra, http://wingtra.com, in 2016 # import glob import os import pyexiv2 import fractions from PIL import Image from PIL.ExifTags import TAGS import sys from shutil import copyfile from optparse import OptionParser from numpy import genfromtxt import shutil import csv class TriggerList( object ): def __init__( self ): self.CAMT_seq = [] self.CAMT_timestamp = [] self.GPOS_Lat = [] self.GPOS_Lon = [] self.GPOS_Alt = [] class ImageList( object ): def __init__( self ): self.jpg = [] self.raw = [] def to_degree(value, loc): if value < 0: loc_value = loc[0] elif value > 0: loc_value = loc[1] else: loc_value = "" absolute_value = abs(value) deg = int(absolute_value) t1 = (absolute_value-deg)*60 min = int(t1) sec = round((t1 - min)* 60, 5) return (deg, min, sec, loc_value) def SetGpsLocation(file_name, lat, lng): """ Adding GPS tag """ lat_deg = to_degree(lat, ["S", "N"]) lng_deg = to_degree(lng, ["W", "E"]) exiv_lat = (pyexiv2.Rational(lat_deg[0] * 60 + lat_deg[1], 60), pyexiv2.Rational(lat_deg[2] * 100, 6000), pyexiv2.Rational(0, 1)) exiv_lng = (pyexiv2.Rational(lng_deg[0] * 60 + lng_deg[1], 60), pyexiv2.Rational(lng_deg[2] * 100, 6000), pyexiv2.Rational(0, 1)) exiv_image = pyexiv2.ImageMetadata(file_name) exiv_image.read() exiv_image["Exif.GPSInfo.GPSLatitude"] = exiv_lat exiv_image["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3] exiv_image["Exif.GPSInfo.GPSLongitude"] = exiv_lng exiv_image["Exif.GPSInfo.GPSLongitudeRef"] = lng_deg[3] exiv_image["Exif.Image.GPSTag"] = 654 exiv_image["Exif.GPSInfo.GPSMapDatum"] = "WGS-84" exiv_image["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0' exiv_image.write(True) def LoadPX4log(px4_log_file): """ load px4 log file and extract trigger locations """ os.system('python sdlog2_dump.py ' + px4_log_file + ' -f log.csv') f = open('log.csv', 'rb') reader = csv.reader(f) headers = reader.next() line = {} for h in headers: line[h] = [] for row in reader: for h, v in zip(headers, row): line[h].append(v) trigger_list = TriggerList() for seq in range(0, len(line['CAMT_seq']) - 1): if line['CAMT_seq'][seq] != line['CAMT_seq'][seq + 1]: trigger_list.CAMT_seq.append(line['CAMT_seq'][seq + 1]) trigger_list.CAMT_timestamp.append(line['CAMT_timestamp'][seq + 1]) trigger_list.GPOS_Lat.append(line['GPOS_Lat'][seq + 1]) trigger_list.GPOS_Lon.append(line['GPOS_Lon'][seq + 1]) trigger_list.GPOS_Alt.append(line['GPOS_Alt'][seq + 1]) return trigger_list def LoadImageList(input_folder): """ load the image list """ image_list = ImageList() for jpg_image in glob.glob(input_folder + "/*.jpg"): image_list.jpg.append(jpg_image) for jpg_image in glob.glob(input_folder + "/*.JPG"): image_list.jpg.append(jpg_image) for raw_image in glob.glob(input_folder + "/*.RC"): image_list.raw.append(raw_image) if len(image_list.jpg) != len(image_list.raw) and len(image_list.jpg) * len(image_list.raw) != 0: print("Unequal number of jpg and raw images") if len(image_list.jpg) == 0 and len(image_list.raw) == 0: print("No images found") return image_list def FilterTrigger(trigger_list, image_list): """ filter triggers to allow exact matching with recorded images """ if len(image_list.jpg) != len(trigger_list.CAMT_seq) and len(image_list.raw) != len(trigger_list.CAMT_seq): # filter trigger list to match the number of pics print("No trigger filter implemented yet.") return trigger_list def TagImages(trigger_list, image_list, output_folder): """ load px4 log file and extract trigger locations """ print len(image_list.jpg) print len(trigger_list.GPOS_Lat) print len(trigger_list.GPOS_Lon) for image in range(len(image_list.jpg)): base_path, filename = os.path.split(image_list.jpg[image]) copyfile(image_list.jpg[image], output_folder + "/" + filename) SetGpsLocation(output_folder + "/" + filename, float(trigger_list.GPOS_Lat[image]), float(trigger_list.GPOS_Lon[image])) for image in range(len(image_list.raw)): base_path, filename = os.path.split(image_list.raw[image]) copyfile(image_list.raw[image], output_folder + "/" + filename) SetGpsLocation(output_folder + "/" + filename, float(trigger_list.GPOS_Lat[image]), float(trigger_list.GPOS_Lon[image])) def main(): """ Main method """ parser = OptionParser() parser.add_option("-l", "--logfile", dest = "LogFile", help = "PX4 log file containing recorded positions", metavar = "string") parser.add_option("-i", "--input", dest = "InputFolder", help = "Input folder containing untagged images", type = "string") parser.add_option("-o", "--output", dest = "OutputFolder", help = "Output folder to contain tagged images", type = "string") (options, args) = parser.parse_args() if not options.LogFile: print "please type python " \ "geotagging.py --logfile=[filename] --intput=[folder] [--output=[folder]]" elif not options.InputFolder: print "please type python " \ "geotagging.py --logfile=[filename] --intput=[folder] [--output=[folder]]s" else: trigger_list = LoadPX4log(options.LogFile) image_list = LoadImageList(options.InputFolder) if not options.OutputFolder: options.OutputFolder = "imagesWithTag" if not os.path.exists(options.OutputFolder): os.makedirs(options.OutputFolder) trigger_list = FilterTrigger(trigger_list, image_list) TagImages(trigger_list, image_list, options.OutputFolder) if __name__ == "__main__": main()