Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
674 changes: 674 additions & 0 deletions COPYING.txt

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# cuav

[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tridge/cuav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tridge/cuav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

The CanberraUAV Image Processing (cuav) tools are designed to quickly and
accurately locate objects of interest from a large set of photos. With the addition
of flight logs of geo-tagging, it can also give the location of the objects.

It can be run in realtime directly from a camera, or offine from a folder of images.

The tools can be run on both Linux and Windows platforms.

Cuav was created by `CanberraUAV <http://canberrauav.org.au/>`_ as part of the
`UAV Challenge <https://uavchallenge.org/>`_, in order to find a missing
bushwalker from photos taken by a Unmanned Aerial System (UAS, commonly known as a "drone")
as it flew over the search area at low altitude.

Documentation is available at http://canberrauav.github.io/cuav
5 changes: 5 additions & 0 deletions cuav/image/scanner.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ static PyObject *ScannerError;

#define MAX_REGIONS 4000

#ifdef __MINGW32__
#define __LITTLE_ENDIAN 1
#define __BYTE_ORDER 1
#endif

struct scan_params {
uint16_t min_region_area;
uint16_t max_region_area;
Expand Down
9 changes: 8 additions & 1 deletion cuav/lib/cuav_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def __init__(self, slipmap,
self.region_class = lxml.objectify.E.regions()

self.add_menus()

def add_menus(self):
'''add menus'''
menu = MPMenuTop([])
Expand Down Expand Up @@ -422,6 +422,7 @@ def change_page(self, page):
if last_page != self.page:
print("Page %u/%u" % (self.page, max_page))
self.redisplay_mosaic()
self.image_mosaic.set_title("Mosaic (Page %u of %u)" % (self.page+1, max(max_page+1, 1)))

def re_sort(self):
'''re sort the mosaic'''
Expand Down Expand Up @@ -694,6 +695,9 @@ def redisplay_mosaic(self):
if self.brightness != 1.0:
cv.ConvertScale(self.mosaic, self.mosaic, scale=self.brightness)
self.image_mosaic.set_image(self.mosaic, bgr=True)

max_page = (len(self.regions_sorted)-1) / self.display_regions
self.image_mosaic.set_title("Mosaic (Page %u of %u)" % (self.page+1, max(max_page+1, 1)))

def add_regions(self, regions, thumbs, filename, pos=None):
'''add some regions'''
Expand Down Expand Up @@ -732,6 +736,9 @@ def add_regions(self, regions, thumbs, filename, pos=None):
ridx = len(self.regions)
self.regions.append(MosaicRegion(ridx, r, filename, pos, thumbs[i], thumb, latlon=(lat,lon)))
self.regions_sorted.append(self.regions[-1])

max_page = (len(self.regions_sorted)-1) / self.display_regions
self.image_mosaic.set_title("Mosaic (Page %u of %u)" % (self.page+1, max(max_page+1, 1)))

frame_time = cuav_util.parse_frame_time(filename)
if not frame_time in self.ridx_by_frame_time:
Expand Down
188 changes: 106 additions & 82 deletions cuav/tools/geosearch.py

Large diffs are not rendered by default.

87 changes: 54 additions & 33 deletions cuav/tools/geotag.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
#!/usr/bin/python

import numpy, os, time, cv, sys, math, sys, glob
import pyexiv2, datetime
import pyexiv2, datetime, argparse

from cuav.lib import cuav_util, cuav_mosaic, mav_position, cuav_joe, cuav_region
from MAVProxy.modules.mavproxy_map import mp_slipmap
from MAVProxy.modules.lib import mp_image

from optparse import OptionParser
parser = OptionParser("geotag.py [options] <directory|files>")
parser.add_option("--mavlog", default=None, help="flight log for geo-referencing")
parser.add_option("--max-deltat", default=0.0, type='float', help="max deltat for interpolation")
parser.add_option("--max-attitude", default=45, type='float', help="max attitude geo-referencing")
parser.add_option("--lens", default=4.0, type='float', help="lens focal length")
parser.add_option("--roll-stabilised", default=False, action='store_true', help="roll is stabilised")
parser.add_option("--gps-lag", default=0.0, type='float', help="GPS lag in seconds")
parser.add_option("--destdir", default=None, help="destination directory")
parser.add_option("--inplace", default=False, action='store_true', help="in-place modify")
(opts, args) = parser.parse_args()

from gooey import Gooey, GooeyParser

@Gooey
def parse_args_gooey():
'''parse command line arguments'''
parser = GooeyParser(description="Geotag images from flight log")

parser.add_argument("files", default=None, help="Image folder", widget='DirChooser')
parser.add_argument("mavlog", default=None, help="flight log for geo-referencing", widget='FileChooser')
parser.add_argument("--max-deltat", default=0.0, type=float, help="max deltat for interpolation")
parser.add_argument("--max-attitude", default=45, type=float, help="max attitude geo-referencing")
parser.add_argument("--lens", default=4.0, type=float, help="lens focal length")
parser.add_argument("--roll-stabilised", default=False, action='store_true', help="Is camera roll stabilised?")
parser.add_argument("--gps-lag", default=0.0, type=float, help="GPS lag in seconds")
parser.add_argument("--destdir", default=None, help="destination directory", widget='DirChooser')
parser.add_argument("--inplace", default=False, action='store_true', help="modify images in-place?")
return parser.parse_args()

def parse_args():
'''parse command line arguments'''
parser = argparse.ArgumentParser("Geotag images from flight log")

parser.add_argument("files", default=None, help="Image directory or files")
parser.add_argument("mavlog", default=None, help="flight log for geo-referencing")
parser.add_argument("--max-deltat", default=0.0, type=float, help="max deltat for interpolation")
parser.add_argument("--max-attitude", default=45, type=float, help="max attitude geo-referencing")
parser.add_argument("--lens", default=4.0, type=float, help="lens focal length")
parser.add_argument("--roll-stabilised", default=False, action='store_true', help="Is camera roll stabilised?")
parser.add_argument("--gps-lag", default=0.0, type=float, help="GPS lag in seconds")
parser.add_argument("--destdir", default=None, help="destination directory")
parser.add_argument("--inplace", default=False, action='store_true', help="modify images in-place?")
return parser.parse_args()

def to_deg(value, loc):
if value < 0:
loc_value = loc[0]
Expand Down Expand Up @@ -82,35 +102,30 @@ def process(args):

count = 0
files = []
for a in args:
if os.path.isdir(a):
files.extend(glob.glob(os.path.join(a, '*.png')))
else:
files.append(a)
if os.path.isdir(args.files):
files.extend(glob.glob(os.path.join(args.files, '*.png')))
else:
files.append(args.files)
files.sort()
num_files = len(files)
print("num_files=%u" % num_files)

if opts.mavlog:
mpos = mav_position.MavInterpolator(gps_lag=opts.gps_lag)
mpos.set_logfile(opts.mavlog)
else:
print("You must provide a mavlink log file")
sys.exit(1)
mpos = mav_position.MavInterpolator(gps_lag=args.gps_lag)
mpos.set_logfile(args.mavlog)

frame_time = 0

if opts.destdir:
cuav_util.mkdir_p(opts.destdir)
if args.destdir:
cuav_util.mkdir_p(args.destdir)

for f in files:
frame_time = os.path.getmtime(f)
try:
if opts.roll_stabilised:
if args.roll_stabilised:
roll = 0
else:
roll = None
pos = mpos.position(frame_time, opts.max_deltat,roll=roll)
pos = mpos.position(frame_time, args.max_deltat,roll=roll)
except mav_position.MavInterpolatorException as e:
print e
pos = None
Expand All @@ -120,13 +135,13 @@ def process(args):
lat_deg = pos.lat
lng_deg = pos.lon

if opts.inplace:
if args.inplace:
newfile = f
else:
basefile = f.split('.')[0]
newfile = basefile + '.jpg'
if opts.destdir:
newfile = os.path.join(opts.destdir, os.path.basename(newfile))
if args.destdir:
newfile = os.path.join(args.destdir, os.path.basename(newfile))
cv.SaveImage(newfile, im_orig)
count += 1

Expand All @@ -135,5 +150,11 @@ def process(args):
set_gps_location(newfile, lat_deg, lng_deg, pos.altitude, pos.time)

# main program
if __name__ == '__main__':
if not len(sys.argv) > 1:
args = parse_args_gooey()
else:
args = parse_args()

process(args)

process(args)
65 changes: 34 additions & 31 deletions cuav/tools/pgm_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,63 @@
convert images from PGM to other formats
'''

import os, sys, glob, cv
import os, sys, glob, cv, argparse

from cuav.lib import cuav_util
from gooey import Gooey, GooeyParser

def parse_args():
@Gooey
def parse_args_gooey():
'''parse command line arguments'''
if 1 == len(sys.argv):
from MAVProxy.modules.lib.optparse_gui import OptionParser
file_type='file'
directory_type='directory'
else:
from optparse import OptionParser
file_type='str'
directory_type='str'
parser = GooeyParser(description="Convert pgm image to png or jpg")

parser.add_argument("directory", default=None,
help="directory containing PGM image files", widget='DirChooser')
parser.add_argument("--output-directory", default=None,
help="directory to use for converted files", widget='DirChooser')
parser.add_argument("--format", default='png', choices=['png', 'jpg'], help="type of file to convert to (png or jpg)")
return parser.parse_args()

parser = OptionParser("pgm_convert.py [options] <directory>")
parser.add_option("--directory", default=None, type=directory_type,
def parse_args():
'''parse command line arguments'''
parser = argparse.ArgumentParser("Convert pgm image to png or jpg")

parser.add_argument("directory", default=None,
help="directory containing PGM image files")
parser.add_option("--output-directory", default=None, type=directory_type,
parser.add_argument("--output-directory", default=None,
help="directory to use for converted files")
parser.add_option("--format", default='png', help="type of file to convert to (png or jpg)")
parser.add_argument("--format", default='png', choices=['png', 'jpg'], help="type of file to convert to (png or jpg)")
return parser.parse_args()

if __name__ == '__main__':
(opts, args) = parse_args()

def process(args):
'''process a set of files'''

files = []
for a in args:
if os.path.isdir(a):
files.extend(glob.glob(os.path.join(a, '*.pgm')))
if os.path.isdir(args.directory):
files.extend(glob.glob(os.path.join(args.directory, '*.pgm')))
else:
if args.directory.find('*') != -1:
files.extend(glob.glob(args.directory))
else:
if a.find('*') != -1:
files.extend(glob.glob(a))
else:
files.append(a)
files.append(args.directory)
files.sort()

for f in files:
im_orig = cuav_util.LoadImage(f)
if not opts.output_directory:
if not args.output_directory:
outdir = os.path.dirname(f)
else:
outdir = opts.output_directory
outdir = args.output_directory
basename = os.path.basename(f)[:-4]
new_name = os.path.join(outdir, basename + '.' + opts.format)
new_name = os.path.join(outdir, basename + '.' + args.format)
print("Creating %s" % new_name)
cv.SaveImage(new_name, im_orig)

if __name__ == '__main__':
# main program
if opts.directory is not None:
process([opts.directory])
if not len(sys.argv) > 1:
args = parse_args_gooey()
else:
process(args)
args = parse_args()

# main program
process(args)
3 changes: 2 additions & 1 deletion cuav/uav/uav.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from numpy import array, linalg, eye, zeros, dot, transpose
from numpy import sin, cos, pi
from matplotlib import pyplot

#def rotationMatrix(phi, theta, psi):
# out = zeros((3,3))
Expand Down Expand Up @@ -91,6 +90,8 @@ def __init__(self, fu=200, fv=200, cu=512, cv=480):


if __name__ == '__main__':
from matplotlib import pyplot

xfer = uavxfer()
xfer.setCameraParams(200.0, 200.0, 512, 480)
xfer.setCameraOrientation(0.0, 0.0, -pi/2)
Expand Down
48 changes: 48 additions & 0 deletions windows/createChangelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python
'''
Create a user readable changelog
Requires the gitpython package via pip install gitpython

Stephen Dade
November 2016
'''

from git import Repo
import os
import time

#Get the parent (..\) directory. ie the root cuav dir
path = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
repo = Repo(path)
assert not repo.bare

#get list of all commits
all_commits = list(repo.iter_commits('master'))

#open the changelog for writing
f = open("changelog.txt","w")

#go through all the commits
for comm in all_commits:
#if it's a version raise, add a special message
if "raise version" in comm.message:
commit_date = time.strftime("%d-%m-%Y", time.gmtime(comm.committed_date))
tree = comm.tree
#get setup.py and grab the version number from the file
blob = tree['setup.py']
data = blob.data_stream.read()
curversion = ""
for line in data.split('\n'):
if "version = " in line:
curversion = line[11:len(line)-1]
break

f.write("\n")
f.write("CUAV " + curversion + " (" + commit_date + ")\n")
else:
#just print the summary (1st line) of the commit message
comm.message.split('\n', 1)[0]
f.write("-" + comm.summary + "\n")

f.close()
print("Done")
Loading