Capture geotagged image with Arducam 16mp autofoucus camera, Respberry pi 4 and Pixhawk

Hello, All. I’m new to DIY drones community
I built my first quadcopter using Pixhawk 2.4.8 and the Copter V4.2.1 firmware and I’m using a Raspberry Pi 4 as a companion computer.

My aspiration
I am willing to take geotaged photos with an Arducam camera connected to the RPi while completing a waypoint mission autonomously.

Following ae the hardware option available for me:

  1. Pixhawk 2.4.8
  2. Respberry pi 4
  3. Arducam 16mp autofocus

Objective:
Trigger Arducam automatically for taking geotagged photos while the drone is on a waypoint mission.

Is there any lead / solution that i can follow ?
Could some one please point me in right direction.
Thank you.

Hi Birendra,
It has been a while since applying these workflows, so there may be a error in my description of the process, but I think these are two approaches that may inform your efforts.
I think that you are wanting to create a mission using the survey grid tool. this will allow you to define the camera parameters to achieve the ground image you want. This tool will also allow for setting up the camera trigger for optimizing for 3D reconstruction (image sidelap and overlap. Camera triggers can be set as a high pin from one of the Autopilot output pins. You should, in theory, be able to trigger your Pi cam using this signal.
Alternatively you can set the camera trigger to a distance parameter and the signal will be sent at that interval or distance. I think this approach will work (if enabled) during a standard mission.
You can then use the geotagging tool in mission planner to correlate the cam messages to the images, creating a separate geotagged folder of the images.
Best of Luck,
Sean

2 Likes

Hi SeanHeadrick,

Thank you for the headsup, survey grid tool should work. Challage would be to automae it.
I will work on it and post the progress here.

Thank you

I was looking here for help with Lua scripting when I came upon your post. you might want to look into this as well for automating your process.https://ardupilot.org/copter/docs/common-lua-scripts.html

Thanks again, Every little information helps :slight_smile:

A few years ago I used an RPI+RPI camera to capture images as part of the TAP-J team’s entry in the Japan Innovation Challenge search and rescue competition. This included capturing the autopilot’s location and embedding it into JPEG images. The code we used is here (I think).

If you’re looking for a more robust solution there are some professional geotaggers on the AP wiki that work with sony cameras.

1 Like

After a lot of beating around the bush, I was able to capture the pictures and geotag them onboard.
Still had some issues in reading the gps data at the same time while triggering the camera and geotagging the images. I was also not sure about the accuracy. It would take a lot of effort to write fully optimised code for it.
Instead it would be easy and feasible to use Airpixel geotagger which has way more features than just geotagging, it is light weight, superfast etc, you can read about it in docs.

Hi Birendra

I’m working on pretty much exactly this but I’d like to trigger an array of arducams (ideally 5 in an oblique setup)

I was wondering if you could share your method and code on how you’ve done this?
I’m still a noob with this stuff but I’m slowly coming right haha.

Hi @Werner_Pretorius,

Assuming you have successful connection between Rpi and Pixhawk.
You can use below code as template, install python, exifread, dronekit.

import asyncio
import os
from datetime import datetime
import exifread
import dronekit
import time
import shutil

from picamera2 import Picamera2

# Define the target folder for geotagged images
TARGET_FOLDER = "/home/pi/Desktop/images/"


#1 Initialize PiCamera
picam2 = Picamera2()
#Create a new object, camera_config and use it to set the still image resolution (main) to 1920 x 1080. and a “lowres” image with a size of 640 x 480. This lowres image is used as the preview image when framing a shot.
camera_config = picam2.create_still_configuration(main={"size": (1920, 1080)}, lores={"size": (640, 480)}, display="lores")
#Load the configuration.
picam2.configure(camera_config)

#2 Connect to the drone and wait for GPS fix
print("Connecting to vehicle...")
vehicle = dronekit.connect('/dev/ttyAMA0', baud=57600)
print("Waiting for GPS fix...")
while not vehicle.gps_0.fix_type:
    pass
print("GPS fix obtained.")

# Define the capture_photo function
async def capture_photo():
    filename = datetime.now().strftime("%Y%m%d_%H%M%S.jpg")
    temp_file = "/run/shm/{}".format(filename) # Use the ramdisk for faster I/O
    picam2.start()
    #Pause the code for two seconds.
    time.sleep(1)
    #Capture an image and save it as test.jpg.
    picam2.capture_file(temp_file)
    return temp_file

# Define the get_gps_data function
async def get_gps_data():
    #return (vehicle.location.global_frame.lat,
     #       vehicle.location.global_frame.lon,
     #       vehicle.location.global_frame.alt)
     return(39.668756, -127.334674, 10)

# Define the geotag function
async def geotag(temp_file, gps_data):
    # gps_data should be a tuple containing latitude, longitude, and altitude
    latitude, longitude, altitude = gps_data
    
    # Construct the ExifTool command
    exiftool_cmd = ['exiftool', '-GPSLatitude={}'.format(latitude), '-GPSLongitude={}'.format(longitude), '-GPSAltitude={}'.format(altitude), temp_file]
    
    # Run the ExifTool command using subprocess
    try:
        subprocess.run(exiftool_cmd, check=True)
    except subprocess.CalledProcessError as e:
        print("Error geotagging photo: ", e)
        return False
    
    return True

# Define the main function
async def main():
    while True:
        # Wait for 5 seconds
        # Capture photo and get GPS data asynchronously
        temp_file_task = asyncio.create_task(capture_photo())
        gps_data_task = asyncio.create_task(get_gps_data())
        # Wait for both tasks to complete
        temp_file = await temp_file_task
        gps_data = await gps_data_task
        # Geotag the image asynchronously
        await geotag(temp_file, gps_data)

# Run the main function
asyncio.run(main())

In async def get_gps_data(): function you can use (I had used hard coded random value for testing)

gps_location = vehicle.location.global_relative
return(gps_location.lat, gps_location.lon, gps_location.alt))

You also you need to move geotagged image f(temp_file) to Target location of your choice…
You can add below code in async def geotag(temp_file, gps_data): function (inside try block)

shutil.move(temp_file, os.path.join(TARGET_FOLDER, filename))

For multiple camera you can use arducam multi camera adapter.
I hope this helps.

2 Likes

Also please read this in order to listen to the camera trigger signal from pixhawk and trigger the camera.from Rpi.

Hello, All. I’m quite new to the DIY drones community and programing. I want to build a drone with a Pixhawk orange cube with Copter 4.5.1 and a computer companion Raspberry Pi 4 with its own camera module V2.
Objective:
To trigger the camera on my command from Mission Planner and Geotag the picture with the data from the Pixhawk’s GPS. I have successfully made the serial connection between the Pixhawk and Raspberry Pi. I have employed a relay module that will act as a button to trigger the RPi camera from the GPIO pin with a signal from the autopilot.
I saw this solution and I tried to add lines of code to obtain the desired result.
The main question is if the code below would work? If not could anybody point me in the right direction? Thank you all in advance for your attention!

import RPi.GPIO as GPIO
from picamera2 import Picamera2
import asyncio
import os
from datetime import datetime
import exifread
import dronekit
import time
import shutil

image_path=“/home/pi/my_captures/” #folder for iamges

camera configuration and initialization
camera = Picamera2()
camera_config = camera.create_still_configuration(main={“size”: (1920, 1080)}, lores={“size”: (640, 480)}, display=“lores”)
camera.configure(camera_config)

gpio pins setup
GPIO.setmode(GPIO.BCM)

#Connect to the drone and wait for GPS fix
print(“Connecting to vehicle…”)
vehicle = dronekit.connect(‘/dev/ttyAMA0’, baud=57600)
print(“Waiting for GPS fix…”)
while not vehicle.gps_0.fix_type:
pass
print(“GPS fix obtained.”)

async def capture_photo(channel):
filename = datetime.now().strftime(“%Y%m%d_%H%M%S.jpg”)
temp_file = “/run/shm/{}”.format(filename) # use the ramdisk for faster I/O
camera.start_and_capture_file(temp_file)
return temp_file

async def get_gps_data():
return (vehicle.location.global_frame.lat,
vehicle.location.global_frame.lon,
vehicle.location.global_frame.alt)

async def geotag(temp_file, gps_data):
latitude, longitude, altitude = gps_data
exiftool_cmd = [‘exiftool’, ‘-GPSLatitude={}’.format(latitude), ‘-GPSLongitude={}’.format(longitude), ‘-GPSAltitude={}’.format(altitude), temp_file]

try:
    subprocess.run(exiftool_cmd, check=True)
except subprocess.CalledProcessError as e:
    print("Error geotagging photo: ", e)
    return False

return True

GPIO.setup(16, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.add_event_detect(16, GPIO.FALLING, callback= capture_photo, bouncetime=1000)

main function

async def main():
while True:
temp_file_task = asyncio.create_task(capture_photo())
gps_data_task = asyncio.create_task(get_gps_data())
temp_file = await temp_file_task
gps_data = await gps_data_task
await geotag(temp_file, gps_data)

execute the whole code?

try:
while True:
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()

yes, this should work, but you’ll have to modify it according to your use case.
you can test it onrpi and if you face any issue, please post here.

Thank you for the replay. I will make the needed changes to the code and test it today. In the mean time I have a quick question about the exif data of the picture. How can I check on the Raspberry Pi if the GPS coord are saved in the correct way. So basically the question is how can I read the exif data after I finish the script?
I have see this solution ; pi@raspi /usr/freeware # time exiftool /snapshot.jpg

you just need to check captured image property, you should be able to see geolocation.
or you can use exiftool to read its data.

Hello and sorry for the late replay. I made the appropriate changes to the code and it works. Thank you for the solution and help. Now I have a follow up question to your code. Because I’m a beginner at programing , I’m a little confused with the temp_file created. I’m wondering if I could geotag the photos directly without the need of the temp_file being the intermediary, if so could you please provide an example.
Till now I’ve been working with the simple def function() and in your code I se you used asycn def function() . The question is ,if I modify your code and make all the function simple and then call them when I need them, will this ruin it?

Thank you in advance!

Hi,
I’m happy to see that solution worked for you.

  1. temp_file (image ) is stored in ram for faster read/write (exift data). you can store it in regular Sd card space but read/write operation will be slower (not ideal for geotagging ).
    To do so, just replace the file path (/runn/shm/) to your SD cad path where you want to store it.
    temp_file = "/run/shm/{}".format(filename) # Use the ramdisk for faster I/O

  2. Simple def function should be okay, but for time critical opration such as geotagging I have used async to capture the image and geotag at the same time. (capturing the photo and getting geolocation is done at the same time using async)

Thank you very much for all the help. You really helped me, thank you again!

Hi Luca,

I’ve used your code and adapted it for my own use on RPI5 and using Arducam IMX519

It triggers the image capture off GPIO 16 and GND as a relay “button”

Here it is, still WIP but it’s working with GPS tagged to metadata, main change is that I’m using GPIOzero instead of RPI.GPIO. I don’t think that works on Raspberry Pi 5 at this stage

from gpiozero import Button
from picamera2 import Picamera2
import asyncio
import os
from datetime import datetime
import exifread
import dronekit
import shutil
import subprocess

# Update image storage location
image_path = "/home/PEPPIE/Desktop/ObiCam Images/"

# Camera configuration and initialization
camera = Picamera2()
camera_config = camera.create_still_configuration(
    main={"size": (1920, 1080)}, lores={"size": (640, 480)}, display="lores"
)
camera.configure(camera_config)

# GPIO pin setup with GPIOZero
button = Button(16)  # Change 16 to your actual GPIO pin

# Connect to the drone and wait for GPS fix
print("Connecting to vehicle...")
vehicle = dronekit.connect("/dev/ttyAMA0", baud=57600)
print("Waiting for GPS fix...")
while not vehicle.gps_0.fix_type:
    pass
print("GPS fix obtained.")


async def capture_photo():
    filename = datetime.now().strftime("%Y%m%d_%H%M%S.jpg")
    temp_file = f"/run/shm/{filename}"  # Use the ramdisk for faster I/O
    camera.start_and_capture_file(temp_file)

    # Move the image from temporary location to final destination
    final_path = os.path.join(image_path, filename)
    shutil.move(temp_file, final_path)

    return final_path


async def get_gps_data():
    return (
        vehicle.location.global_frame.lat,
        vehicle.location.global_frame.lon,
        vehicle.location.global_frame.alt,
    )


async def geotag(temp_file, gps_data):
    latitude, longitude, altitude = gps_data
    exiftool_cmd = [
        "exiftool",
        "-GPSLatitude={}".format(latitude),
        "-GPSLongitude={}".format(longitude),
        "-GPSAltitude={}".format(altitude),
        temp_file,
    ]

    try:
        subprocess.run(exiftool_cmd, check=True)
    except subprocess.CalledProcessError as e:
        print("Error geotagging photo: ", e)
        return False

    return True


async def main():
    while True:
        # Wait for button press using GPIOZero
        button.wait_for_press()

        temp_file_task = asyncio.create_task(capture_photo())
        gps_data_task = asyncio.create_task(get_gps_data())
        temp_file = await temp_file_task
        gps_data = await gps_data_task
        await geotag(temp_file, gps_data)


# Execute the whole code
try:
    asyncio.run(main())
except KeyboardInterrupt:
    GPIOZero.cleanup()

1 Like