In this post we will show you how to geotag and capture the “attitude” of photos taken with the Raspberry Pi camera and record these values within the photo itself using EXIF metadata
We used a modified (hacked?) cap to take the images in this post. The cap took photos, geo-tagged and recorded attitude as we walked around Sydney Harbour.
Components used were;
- Raspberry Pi Zero W
- BerryGPS-IMU
- Raspberry Camera V2
- A cap
The BerryGPS-IMU was used to capture the GPS coordinates as well as “attitude”. No external antenna was needed as the BerryGPS-IMU includes an internal antenna.
The “attitude” would include values such as pitch, roll, direction. Some of this data you can see annotate in the image below.
Other programs can use some of this data to plot the image on a map and even show the direction of the camera at the time the image was taken. A good example of this is seen in GeoSetter
The Cap
The cap has the BerryGPS-IMU sitting on top of the visor, with the Raspberry Pi sitting under the viso. Some holes where made in the visor to allow connectivity between the BerryGPS-IMU and Raspberry Pi.
We also created a basic camera mount out of 3mm laser cut acrylic. M2.5 Nylon screws were used to hold everything in place.
Setup Raspberry Pi
- Configure your Raspberry Pi to work with a GPS module on the BerryGPS-IMU. Use this guide.
- Configure your Raspberry Pi so it can use the IMU on the BerryGPS-IMU, this guide can be followed.
- Confirm you Raspberry Pi camera module is working.
Install exiftool and imagemagick
exiftool is used to update the metadata within the image.
The convert utility that comes with imagemagick will be used to annotate the photos.
pi@raspberrypi ~ $ sudo apt-get install exiftool imagemagick -yConfigure exiftool
The current specification of EXIF metadata doesn’t include a field for roll or pitch, we can however add these using custom fields. You will need to create a exiftool config file in the home directory, where we will define the custom tags for GPSPitch and GPSRoll.
pi@raspberrypi ~ $ nano ~/.ExifTool_configCopy the text in below
# exiftool config file %Image::ExifTool::UserDefined = ( # GPS 'Image::ExifTool::GPS::Main' => { 0xd000 => { Name => 'GPSPitch', Writable => 'rational64s', }, 0xd001 => { Name => 'GPSRoll', Writable => 'rational64s', }, }, );
- Test exiftool
You can now test exiftool by adding some exif metadata to an image and then view this data.
Adding;
pi@raspberrypi ~ $ exiftool -GPSRoll=123.45 photo.jpgViewing
pi@raspberrypi ~ $ exiftool -GPSRoll* photo.jpg
GPS Roll : 123.45This is what it looks like when the image is geotagged and attitude added.
pi@raspberrypi ~ $ exiftool -GPS* image_20170624_023731.jpg
GPS Version ID : 2.3.0.0 GPS Latitude Ref : South GPS Longitude Ref : East GPS Altitude : 12.2 m GPS Time Stamp : 02:37:37 GPS Img Direction Ref : Magnetic North GPS Img Direction : 45.58 GPS Date Stamp : 2017:06:24 GPS Pitch : 11.34 GPS Roll : 26.66 GPS Date/Time : 2017:06:24 02:37:37Z GPS Latitude : 33 deg 51' 17.81" S GPS Longitude : 151 deg 12' 36.73" E GPS Position : 33 deg 51' 17.81" S, 151 deg 12' 36.73" E
Connect GPS fix pin to GPIO on the Raspberry Pi
We will use a bash script which will start when the Raspberry Pi boots and it will be configured to take a photo every 120 seconds.
Once the photo has been taken, it will be updated with attitude (roll,pitch and direction) and if we have GPS fix, it will also add the GPS coordinates.
To detect that the GPS has a fix, you will need to connect the fix pin on the BerryGPS-IMU(shown below) to a GPIO pin on the Raspberry Pi. We will use GPIO 21 (physical pin 40) in this guide.
Taking photos automatically and updating metadata
As the Raspberry Pi, BerryGPS-IMU and camera will all be sitting on our head (with the cap), we will need to use a bash script to take the photos automatically. We will set it to take one very 2 minutes.
The script will also detect if the BerryIMU has a GPS fix and update the image with GPS data.
Download the code used to read pitch, roll and the direction from the IMU on the BerryGPS-IMU.
pi@raspberrypi ~ $ wget ozzmaker.com/downloads/berryIMU.py
pi@raspberrypi ~ $ wget ozzmaker.com/downloads/LSM9DS0.pyTest the python code you just downloaded.
pi@raspberrypi ~ $ python berryIMU.py
{“Roll”:”8.97″,”Pitch”:”-2.39″, “tiltCompensatedHeading”:”334.57″}
{“Roll”:”12.64″,”Pitch”:”-2.99″, “tiltCompensatedHeading”:”335.06″}
{“Roll”:”14.24″,”Pitch”:”-3.01″, “tiltCompensatedHeading”:”335.05″}
{“Roll”:”14.86″,”Pitch”:”-3.50″, “tiltCompensatedHeading”:”334.32″}
{“Roll”:”14.87″,”Pitch”:”-3.43″, “tiltCompensatedHeading”:”334.95″}
{“Roll”:”14.89″,”Pitch”:”-3.84″, “tiltCompensatedHeading”:”334.51″}
{“Roll”:”14.87″,”Pitch”:”-3.63″, “tiltCompensatedHeading”:”335.25″}
{“Roll”:”14.82″,”Pitch”:”-3.68″, “tiltCompensatedHeading”:”334.76″}
{“Roll”:”15.20″,”Pitch”:”-3.71″, “tiltCompensatedHeading”:”334.26″}
{“Roll”:”15.10″,”Pitch”:”-3.52″, “tiltCompensatedHeading”:”334.93″}This python code reads the angles from the IMU ten times and prints them out a JSON readable format. The last line contains the values we be adding to the metadata of the photo.
Download the main bash script which will be doing all the work and set it to executable.
pi@raspberrypi ~ $ wget ozzmaker.com/downloads/takephoto.sh
pi@raspberrypi ~ $ chmod +x takephoto.shBelow is a copy of the bash script;
#!/bin/bash #Setup GPIO21 as input. GPIO21 will be used to detect if the GPS has a fix. echo "21" > /sys/class/gpio/export echo "in" > /sys/class/gpio/gpio21/direction while true; do START_TIME=$SECONDS #Check to see if GPS has a FIX. GPS fix pin will go high every 1 sec if there is a fix. FIX="NO" # Reset fix condition #Stay in this 'while' loop if there is no fix and less than two secands have passed. while [ $FIX == "NO" ] && [ $(($SECONDS - $START_TIME)) -lt 2 ]; do fixcheck=$(cat /sys/class/gpio/gpio21/value) #GPIO21 is connected to the fix indication pin on BerryGPS-IMU if [ "$fixcheck" == "0" ]; then FIX="NO" else FIX="YES" fi done #If there is a fix, grab all the relevant GPS data if [ $FIX == "YES" ]; then tpv=$(gpspipe -w -n 10 | grep -m 1 TPV) latitude=$(echo $tpv | python -c 'import sys, json; print json.load(sys.stdin)["lat"]') longitude=$(echo $tpv | python -c 'import sys, json; print json.load(sys.stdin)["lon"]') altitude=$(echo $tpv | python -c 'import sys, json; print json.load(sys.stdin)["alt"]') speed=$(echo $tpv | python -c 'import sys, json; print json.load(sys.stdin)["speed"]') time=$(echo $tpv | python -c 'import sys, json; print json.load(sys.stdin)["time"]') #Convert latitude and longitude to 5 decimal places latitude=$(printf "%0.5f\n" $latitude) longitude=$(printf "%0.5f\n" $longitude) fi #Get angles and heading from the IMU attitude=$(sudo python /home/pi/berryIMU.py | tail -1) roll=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["Roll"]') pitch=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["Pitch"]') heading=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["tiltCompensatedHeading"]') #Create file name with current time FILE="/home/pi/image_$(date +%Y%m%d_%H%M%S).jpg" #Remove old file sudo rm -f /home/pi/image.jpg #Take photo sudo raspistill -o /home/pi/image.jpg -ex auto -w 1280 -h 720 --nopreview --rotation 180 #Add GeoTags and attitude to bottom of image and save with new file name if [ $FIX == "YES" ];then cmd="convert /home/pi/image.jpg -gravity SouthEast -stroke '#000C' -pointsize 24 -strokewidth 2 -annotate 0 'Lat: $latitude $ #If you dont want to annotate the photos with the metadata, comment out the above line and use the line below. #cmd="convert /home/pi/image.jpg $FILE" eval $cmd #Update the photo with all the relevant exif metadata exiftool -fast -fast2 -overwrite_original_in_place '-gpstimestamp<${DateTimeOriginal}+1:00' '-gpsdatestamp<${DateTimeOrigina$ else #As there isnt a GPS fix, only add roll,pitch and direction. cmd="convert /home/pi/image.jpg -gravity SouthEast -stroke '#000C' -pointsize 24 -strokewidth 2 -annotate 0 'Pitch: $pitch R$ eval $cmd #Update EXIF information exiftool -fast -fast2 -overwrite_original_in_place -GPSroll="$roll" -GPSpitch="$pitch" -GPSImgDirectionRef="m" -GPSImgDirect$ fi #Delay loop, set to how often you want a photo to be taken while [ $(($SECONDS - $START_TIME)) -lt 120 ]; do sleep 1 done done
Detailed description of the important sections in the script:
- Lines 11 to 21
In this while loop we test to see if there is a GPS fix. The fix pin on the BerryGPS-IMU is connected to GPIO21 on the Raspberry Pi. If there is a GPS fix, this pin will go high once a second. The loop will continue to check for 2 seconds, if the pin doesn’t go high within 2 seconds, the $FIX = NO and we exit the loop - Lines 23 to 35
If there is a fix, we need to grab all the relevant GPS data. On line 25, gpspipe is used to get the last ten NMEA sentences from the GPS, we then grep this to get one line which matches TPV. This will have all the coordinates needed outputted in JSON format, which is stored in $tpv. Here is an example{"class":"TPV","tag":"GLL","device":"/dev/serial0","mode":3,"time":"2017-07-17T07:26:33.000Z","ept":0.005,"lat":-33.779853333,"lon":151.111763333,"alt":72.000,"epx":12.505,"epy":16.161,"epv":20.700,"speed":0.154,"climb":0.000,"eps":32.32,"epc":41.40}
We then use the python command line to parse the JSON data in $tpv and separate the GPS data into individual variables.
Line 33 and 34 will limit the latitude longitude values to 5 decimal places. - Lines 37 to 41
This is where we read the attitude values from the IMU. The output of BerryIMU.py is in JSON format, so we use python to separate all the values stored in $attitude. - Lines 56 to 60.
This is where the photo is annotated with GPS and attitude data. The convert utility from imagemagick is used to perform this - Line 63
exiftool is used to update the metadata on the photo. - Lines 77 to 79
This loop is used to create a delay. Currently the delay is set to 120 seconds and the loop will only exit once 120 seconds have past since the start of the main loop.
- Lines 11 to 21
Automatically run the script when the Raspberry Pi boots.
To do this, we need to edit rc.local
pi@raspberrypi ~ $ sudo nano /etc/rc.localJust before ‘exit 0’, add the two lines below;
su pi -c '/home/pi/takePhoto.sh >> /tmp/takePhoto.log 2>&1 &' su pi -c 'gpspipe -r > /home/pi/`date +"%Y%m%d%h%m%s"`.nmea'
The first line will run the script which will take the photos. it will also log any output to a log file.
The second like will record the NMEA sentences from the GPS, which can be used later to plot out the route.
Viewing your Photo
Geosetter (Windows only)is a great free tool which can be used to view geotagged photos.
Save your photos and your route (.NMEA ) files into the same folder and open Geosetter and selected ‘Images’ ‘Open Folder’
You can also use the convert utility to straighten the horizon on an image by using the pitch value.
Here is the original;
We can use exiftool to look at the metadata to see what the roll is.
GPS Roll : 12.09
The roll is 12.09. Now we can use convert to rotate the image by this amount to straighten the horizon.
How is the rpi powered?
a very small lipo which only provides enough power for 1 hour. I tucked it up under the hat
Could this be adapted to record video with the BerryGPS-IMU data overlayed onto the recording? I’d like to attach to a rocket!
Hi Mark,
Is the link to “Takephoto.sh” broken? And are a few of the lines of the script truncated in the copy posted above? Any chance of getting a copy of the complete script? Thanks!
Hi Chad.
I have fixed the link… sorry about that.
give it another go.
Thanks!!!
Hi Mark,
Thanks again for fixing that so quickly, that helped me figure out what I was doing wrong. A few more tweaks and my system is working. I don’t have an IMU, just the Berry-GPS, so if there is no fix, I am not writing any updated exif data, and if there is a fix then I am just writing the GPS position info.
Thanks!
This is really cool.. I’m going on a cruise soon, and i’ve got a plan to mount a RPi 0w by magnet, outside of the cabin, and try to get a full weeks worth of timelapse of my trip, and I was trying to figure out a way to encode the Lat/Lon into each picture, which led me to your site, which had all that and more! I’ve ordered the IMU and it should be here tomorrow.. If I can get it all working quickly enough and it works, I’ll come back and post a video of the timelapse! Thanks for the tremendous head start on my project!!
Did I speak too soon? In trying to prepare from this post, I get an error on the line:
sudo apt-get install exiftool imagemagick-y
It responds:
Note, selecting ‘libimage-exiftool-perl’ instead of ‘exiftool’
E: Unable to locate package imagemagick-y
So – It seems to have installed some kind of exiftool that is perl related, but can’t find the imagemagick-y… Running exiftool doesn’t work, and isn’t found anywhere in the filesystem.
Looking around for exiftool, it looks like the tool has been retired. Do you know a valid replacement / update to your procedure, or where to get a version of each of these that will work? Thanks!!
There was a typo in the instructions. There should have a been a space just before ‘-y’
sudo apt-get install exiftool imagemagick -y
give it another go.
Awesome! That worked! Thanks.. I wouldn’t have expected that to stop the exiftool from loading, but putting it in that way worked!
Now to go finish this thing up! I got the GPS IMU.. I haven’t taken it outside yet.. My other GPS got a fix sitting here on my desk, so I guess this one isn’t quite as sensitive, but that SHOULD be OK since it will be outside when used for real. Thanks a lot for the quick reply!
I am *ALMOST* there.. all that you’ve helped with has worked..
One thing – I didn’t want to add a jumper, and thought I’d just ask the GPS if it had a fix instead, so I added a module I found called pynmea2 to easily search the GGA sentence for the direct indication of a fix. I put this in your same 2 second loop, and it’s working great:
fixcheck=$( gpspipe -r -n 10 | grep -m 1 GGA | python -c ‘import sys,pynmea2;input=raw_input() ; msg= pynmea2.parse(input) ; print msg.gps_qual’)
There’s probably an easier way, but at 2:00am, this worked!
The ONE remaining problem I have is that I’m getting this:
Traceback (most recent call last):
File “”, line 1, in
KeyError: ‘Roll’
Traceback (most recent call last):
File “”, line 1, in
KeyError: ‘Pitch’
It isnt finding the pitch and roll, and I have to admit I dont understand what your call to Python is doing there.. If you have any suggestions, I’d LOVE to hear it, otherwise this is going to be good enough and it’s going in the case, without pitch and roll!!
Thanks so much for all your help!!
it looks like it is failing here;
attitude=$(sudo python /home/pi/berryIMU.py | tail -1)
roll=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["Roll"]')
pitch=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["Pitch"]')
heading=$(echo $attitude | python -c 'import sys, json; print json.load(sys.stdin)["tiltCompensatedHeading"]')
The first line grabs pitch,roll & heading from berryIMU and stores it in the variable
$attitude
. it is stored in JSON format.The next three lines parse
$attitude
by using python with JSON to get the correct fields.What do you get when you run this command?
python /home/pi/berryIMU.py | tail -1
I’m with you there, and based on that, I was expecting to see somthing that says “Pitch” and “Roll” in that string, but I get:
pi@CruiseTimelapse:~ $ python /home/pi/berryIMU.py | tail -1
{“CFangleX”:”179.78″,”CFangleY”:”179.12″, “tiltCompensatedHeading”:”351.28″}
pi@CruiseTimelapse:~ $
SO.. It seems to be talking to the IMU, and it seems to know the compensated heading, but what the heck is a “CFangle”? 🙂
“complementary filtered X angle”
I see the problem.
edit /home/pi/berryIMU.py
change this;
print ("{\"CFangleX\":\"%.2f\",\"CFangleY\":\"%.2f\"," % (CFangleX,CFangleY)),
to
print ("{\"Roll\":\"%.2f\",\"Pitch\":\"%.2f\"," % (CFangleX,CFangleY)),
BTW what devices are you using?
That looks like it has worked.. At least the errors stopped! I’ll go check out the data embedded in a photo now..
I’m using the same GPS/IMU as you referenced, and it’s on a Pi ZeroW. Here’s a link to a onedrive:
https://1drv.ms/f/s!ApthK9KHvqixhLg5BDJ3mIVDvDiigg
I put my current version of your code that I have hacked up, plus a picture of my test setup, which is to say it’s sitting in the sun on my deck. I have it powered with a USB battery pack which is being charged during the day by a solar panel I bought for $15 or so on ebay.. So far it’s been running for 2 days and the battery pack has more power than when I started, so I think I should be good for the week. It’s Summer Solstice time so the sun in Alaska should be even better than here in Maryland right now, at least for raw hours of daylight.
Thanks for all your help!!! Near the end of the month, I’ll check back and let you know how it went!!
Good news and good luck!