A "camera trap" is just a camera that automatically captures images of animals. While camera traps are a wonderful, non-invasive way to photograph wildlife, they tend to capture hundreds or thousands of images that don't contain any animals. Often the effects of wind will be misconstrued as animals moving in front of the lens.
Manually filtering through so many images can be a total waste of time!
Microsoft's MegaDetector is an AI model that identifies animals, people, and vehicles in camera trap images. It's perfect for reviewing camera trap images and finding the ones that are probably images of animals.
Eastern Collared Lizard (Crotaphytus collaris) a.k.a. "mountain boomer" captured by my camera trap, and found by MegaDetector
My camera trap is just a Raspberry Pi 3B+ with an inexpensive camera, powered by an Anker power pack. It's disguised as a mis-delivered package. The app just snaps a picture every minute. Because the camera is mounted upside-down, it automatically corrects the images with this transform:
transform=Transform(hflip=True, vflip=True)
I previously tried triggering the camera with motion detection hardware. I used an "HC-SR501 PIR Infrared Sensor". This sensor resulted in way too many false positives and false negatives. It had a problem with direct sunlight and electrical interference. Also, because it's triggered by body heat, it didn't work detect the beautiful reptiles that prowl around Southwestern Colorado.
Some Raspberry Pi-based camera traps do the image analysis and classification in the device itself. While that is frankly amazing, I prefer to conserve the camera trap's battery. After my camera trap has amassed a day's worth of photos, I download them to a flash drive, and use my PC to find the animal photos with MegaDetector.
Camera Trap Program for Raspberry Pi 3B+:
import os
import logging
import logging.config
import time
from datetime import datetime
from picamera2 import Picamera2
from libcamera import Transform
logger = None
photo_quality = 90
photo_width = 1920
photo_height = 1080
# Take a photo every photo_interval seconds
photo_interval = 60 * 1
def setup_logging():
global logger
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler('log.log'), # Write logs to a file
logging.StreamHandler() # Print logs to the console
]
)
logger = logging.getLogger(__name__)
def take_photo():
def get_photo_filename():
now = datetime.now()
return f'{now.year:04}{now.month:02}{now.day:02}_{now.hour:02}{now.minute:02}{now.second:02}.jpg'
folder = "photos"
photo_file_path = f'{folder}{os.path.sep}{get_photo_filename()}'
logger.info(f'taking photograph "{photo_file_path}"')
with Picamera2() as cam:
try:
# The transform compensates for the camera being upside-down.
config = cam.create_still_configuration({"size": (photo_width, photo_height)},
transform=Transform(hflip=True, vflip=True))
cam.configure(config)
cam.options['quality'] = photo_quality
cam.start()
time.sleep(2) # Give the sensor time to adjust to light levels
cam.capture_file(photo_file_path)
cam.stop()
logger.info(f'Photo captured. size: {os.stat(photo_file_path).st_size:,} bytes')
except Exception as ex:
logger.info(f'Exception: {ex}')
def main():
global logger
setup_logging()
logger.info(f'photo_interval: {photo_interval}')
while True:
try:
take_photo()
time.sleep(photo_interval)
except Exception as ex:
logger.exception('Exception', ex)
if __name__ == '__main__':
main()
Here's the script I use to go through a set of photos to find the interesting ones:
Note: MegaDetector does not currently run on the latest version of Python. I use Python 3.12.
Note: I only use MegaDetector to determine if there may be animals in a photo. I do not use it to further classify the animals. I have not found the classification to be very accurate, at least for the denizens of my back yard.
Photo Filtering Program for PC:
import os
import shutil
import time
from pathlib import Path
from PytorchWildlife.models import detection as pw_detection
import logging
import logging.config
from PIL import Image
logger = None
# https://github.com/microsoft/MegaDetector
# Requires python version <= 3.12.
# Models
#
# MDV6-yolov9-c, MDV6-yolov9-e, MDV6-yolov10-c, MDV6-yolov10-e, MDV6-rtdetr-c
model_version = 'MDV6-yolov10-e'
detection_threshold = 0.70
def setup_logging():
global logger
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler('log.log'), # Write logs to a file
logging.StreamHandler() # Print logs to the console
]
)
logger = logging.getLogger(__name__)
def classify_images(photos_path, output_path):
def animal_detected(labels):
def is_animal(label):
values = label.split(' ')
return values[0] == 'animal' and float(values[1]) >= detection_threshold
return any(is_animal(label) for label in labels)
start_time = time.perf_counter()
model = pw_detection.MegaDetectorV6(None, 'cpu', True, model_version)
photos_processed = 0
for photo_path in [f for f in photos_path.rglob("*") if f.is_file()]:
detection_result = model.single_image_detection(os.path.abspath(photo_path))
if animal_detected(detection_result["labels"]):
logger.info(f'ANIMAL: "{photo_path}": {detection_result}\n"{photo_path}"')
dest_image_file_path = Path(os.path.join(Path(output_path), Path(os.path.basename(photo_path))))
text_file_path = Path(os.path.join(Path(output_path), Path(os.path.basename(photo_path)))).with_suffix('.txt')
with open(text_file_path, "w", encoding="utf-8") as text_file:
text_file.write(f'{detection_result}')
shutil.copy(photo_path, dest_image_file_path)
photos_processed += 1
logger.info(f'Processed {photos_processed} Elapsed Time: {(time.perf_counter() - start_time):,.2f} s')
def invert_images(folder_path):
print('invert_images')
for file_path in [f for f in folder_path.rglob("*") if f.is_file()]:
print(f'{file_path}')
img = Image.open(file_path)
flipped = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
flipped.save(file_path)
def main():
setup_logging()
logger.info(f'detection_threshold: {detection_threshold:.2f}\n')
root_folder = Path('C:/Temp/animals/2026-08-08')
dest_folder = Path(root_folder, 'output')
logger.info(f'dest_folder: "{dest_folder}"')
try:
shutil.rmtree(dest_folder, True)
Path(dest_folder).mkdir(False, True)
classify_images(Path(root_folder, 'captured_photos'), dest_folder)
except Exception as ex:
logger.exception(f'Fatal Error', ex)
if __name__ == '__main__':
main()
Raspberry Pi Camera Trap
| Title | Date |
| Camera Trap and Animal Detection Program | August 19, 2026 |
| Python Tip: Fix Incorrect Orientation of Digital Photos | August 8, 2026 |
| EBT Weather is now available for Windows and Linux | May 30, 2026 |
| Node.js + Express: How to Block Requests by User-Agent Headers | January 7, 2026 |
| Vault 3 is Now Available for Windows on ARM Machines! | December 13, 2025 |
| Vault 3: How to Include Outline Text in Exported Photos | October 26, 2025 |
| .NET Public-Key (Asymmetric) Cryptography Demo | July 20, 2025 |