Uploading Street-Level Imagery to Multiple Sources

Posted by FTA on 29 June 2018 in English. Last updated on 25 February 2019.

Recently I picked up a GoPro HERO5 Black camera for use in street-level imagery. It’s been a really great platform that has displayed fairly steady GPS acquisition and tracking. (Though I may not necessarily be the person driving when I am capturing footage.) I’m also big fan of contributing to as many projects in the OpenStreetMap ecosystem as possible.

To that end, I’ve found a workflow that has worked fairly well for taking photos from my GoPro and resulting in photo contributions to OpenStreetCam and Mapillary. Here’s how (note this is a compilation of not only trial-and-error but also advice scattered throughout the internet):

Capture street-level imagery with the camera

As mentioned, I use a GoPro HERO5 Black because it has GPS, and I capture in time lapse photo mode with a linear frame and 1 second timing (I have found that 0.5 second timing causes some issues with upload backends). Unfortunately glare from the dash is a major issue unless you’ve situated the camera dead on the windshield, so I use some fabric I picked up at my local Walmart to cut down on the reflectivity of the dash.

Extract photos

I usually plug my card from the GoPro into an SD card adapter that I use on my MacBook. Once downloaded, I then scp the files to a Linux box. You could most likely also complete the following steps on another platform that has a Python interpreter, but I haven’t tested it out.

Separate and filter photos

Depending on how you capture the photos, you may need to perform some manual separation of sequences. If you’re the kind of person who leaves the camera on the whole trip, you’re going to initially have a directory (or many directories if you have over 999 photos) of many photos. Furthermore, you’re going to have many photos of you idling in the same position, so that will require filtering. I like to stop my camera with the remote switch right as I’m rolling up to a stop light; I turn the camera back on just as I’m about to accelerate from the stop. The GoPro increments one of the first few numbers (depending on what photo count you’re on) every time you start a new sequence, so this allows me to easily find breaking points to split up segments as I see fit. Note that Mapillary prefers sequence counts in the low hundreds.

Interpolate compass

The GoPro is great in that it has a built in GPS for photos, but there is no information supplied about the bearing. The easiest way to add that to the photos with a fairly good degree of accuracy is the script at which will require you to clone that full git repository and install dependencies; note I am using an older commit because that’s what I’m familiar with. With that installed, you run this step as python /data2/losangeles/seq01/. Repeat this for each sequence you have constructed.

(optional) Crop photos

The linear frame on the GoPro leaves a lot of sky at the top of the image and, even if you have some fabric in your dash, it may reflect other things in your car behind the dash such as your passenger seat, driver seat, or radio selection. Furthermore, we’re mostly only interested in what’s going on at street-level, so cropping the extra is useful. To crop the photos, I use Imagemagick: for f in 'ls /data2/losangeles/seq01/*.JPG'; do mogrify -gravity South -crop 4000x2200+0+0 $f; done (the ' is actually a grave accent on the ls command part–and by grave accent I mean the one on the same key as the tilde for American English keyboards). The parameters work well for my GoPro photos but will depend on the resolution of the camera you’re using. Repeat that for each sequence you have constructed. Here’s an example how, even with cropping, there’s still some reflection from past the dash:

Upload to OpenStreetCam

Now you ideally have sequences of photos that are GPS tagged by the GoPro, bearing set by the script above, and possibly filtered/cropped. The first place you can upload is OpenStreetCam. I cloned the repository at, installed dependencies and authenticated as instructed in their README, then navigated to the upload_photos_by_exif subdirectory. Once there, you can call python -p /data2/losangeles/seq01/ for a given sequence of photos. This will upload the photos and create two log files in their directory. Repeat this command for each sequence of photos.

Upload to Mapillary

The next place you can upload to is Mapillary. Again, I have been using an older version of their tools library, so your mileage may vary here. But with those scripts downloaded from before, I navigate to the python directory again and I edit the file. Depending on the commit, this line may not match exactly, but with the above-quoted commit, line 40 should have the variable MOVE_FILES defined as True. I set this to False so that the photos are not split up into success and failure directories while uploading. With that one-and-done edit, I now run upload script as python --auto_done /data2/losangeles/seq01/ and iterate over all the sequence directories.

Location: Linda Vista, Pasadena, California, 91109, United States


Comment from Marcos Dione on 29 June 2018 at 10:32

for f in ‘ls /data2/losangeles/seq01/*.JPG’; do mogrify -gravity South -crop 4000x2200+0+0 $f; done

You don’t need to run ls to get a list of files, just give the glob itself:

for f in /data2/losangeles/seq01/*.JPG; do mogrify -gravity South -crop 4000x2200+0+0 “$f”; done

If you have spaces or other stuff in the paths, escape them accordingly. And never forget to double-quote variable expansions, so spaces don’t break the parameter parsing.

Comment from alexkemp on 29 June 2018 at 11:19

(the “`” is actually a grave accent on the ls command part … the one on the same key as the tilde for American English keyboards)

The name of that character is “backtick” (inline execution operator in Linux, and also used within Markdown to produce inline code). On UK keyboards it is found immediately under the Esc key.

Having said all that, as @Marcos Dione so expertly explains above, you do not need it in the context of a for-do block, since the for() will expand the block for you.

Log in to leave a comment