{"id":18929103,"url":"https://github.com/jwillikers/detectionator","last_synced_at":"2025-07-22T00:32:21.056Z","repository":{"id":224260684,"uuid":"762842037","full_name":"jwillikers/detectionator","owner":"jwillikers","description":"A Raspberry Pi camera for taking pictures using object detection.","archived":false,"fork":false,"pushed_at":"2025-03-18T13:25:27.000Z","size":20479,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-20T07:39:38.508Z","etag":null,"topics":["camera","computer-vision","gps","numpy","object-detection","opencv","python","raspberry-pi","tensorflow","tensorflow-lite"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jwillikers.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.adoc","code_of_conduct":"CODE_OF_CONDUCT.adoc","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-24T20:53:15.000Z","updated_at":"2025-03-18T13:25:26.000Z","dependencies_parsed_at":"2024-03-09T15:30:20.822Z","dependency_job_id":"0c1788c9-c91b-4ac3-858c-0f7f4f2c34c4","html_url":"https://github.com/jwillikers/detectionator","commit_stats":null,"previous_names":["jwillikers/detectionator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jwillikers/detectionator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwillikers%2Fdetectionator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwillikers%2Fdetectionator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwillikers%2Fdetectionator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwillikers%2Fdetectionator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jwillikers","download_url":"https://codeload.github.com/jwillikers/detectionator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwillikers%2Fdetectionator/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266404941,"owners_count":23923492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-21T11:47:31.412Z","response_time":64,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["camera","computer-vision","gps","numpy","object-detection","opencv","python","raspberry-pi","tensorflow","tensorflow-lite"],"created_at":"2024-11-08T11:30:06.848Z","updated_at":"2025-07-22T00:32:21.034Z","avatar_url":"https://github.com/jwillikers.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Detectionator\nJordan Williams \u003cjordan@jwillikers.com\u003e\n:experimental:\n:icons: font\n:keywords: camera detect detection gps object opencv photo pi picamera picamera2 python raspberry tensorflow\nifdef::env-github[]\n:tip-caption: :bulb:\n:note-caption: :information_source:\n:important-caption: :heavy_exclamation_mark:\n:caution-caption: :fire:\n:warning-caption: :warning:\nendif::[]\n:AutoUpload: https://github.com/jwillikers/autoupload[AutoUpload]\n:Exif: https://en.wikipedia.org/wiki/Exif[Exif]\n:Immich: https://immich.app/[Immich]\n:Immich-CLI: https://immich.app/docs/features/command-line-interface/[Immich CLI]\n:just: https://github.com/casey/just[just]\n:MinIO: https://min.io/[MinIO]\n:picamera2: https://github.com/raspberrypi/picamera2[picamera2]\n:pip-tools: https://github.com/jazzband/pip-tools[pip-tools]\n:pySerial: https://github.com/pyserial/pyserial[pySerial]\n:Rclone: https://rclone.org/[Rclone]\n:systemd: https://systemd.io/[systemd]\n\nimage:https://github.com/jwillikers/detectionator/actions/workflows/test.yaml/badge.svg[Tests, link=https://github.com/jwillikers/detectionator/actions/workflows/test.yaml]\nimage:https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit\u0026logoColor=white[pre-commit, link=https://github.com/pre-commit/pre-commit]\nimage:https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json[Ruff, link=https://github.com/astral-sh/ruff]\n\nA Raspberry Pi camera for taking pictures using object detection.\n\n== Overview\n\nThe Detectionator is a fairly minimal Raspberry Pi camera build for taking pictures of particular objects like animals or people.\nThe Detectionator incorporates {Exif} geolocation metadata in the photographs it takes.\nWith the help of my {AutoUpload} project, pictures are automatically offloaded from the local storage to cloud-based storage such as S3-compatible object storage with {Rclone} or to {Immich} with the {Immich-CLI}.\nThe photos are then removed from local storage.\nThis repository contains documentation, configuration files, and the Python program for the Detectionator.\nThe project is largely based off of the tensorflow example in the {picamera2} project and includes the same models.\n\n== Components\n\nThe Detectionator is based off of the Raspberry Pi 5 and the Camera Module 3.\nIt incorporates a GPS HAT for the Raspberry Pi to obtain the geolocation data to embed in the photographs.\nThe Raspberry Pi AI Kit is recommended, as it reduces the CPU usage dramatically on the Pi.\n\n[NOTE]\n====\nThe GPS HAT requires soldering on a GPIO header.\n====\n\n.Detectionator Components\n* https://www.raspberrypi.com/products/raspberry-pi-5-model-b/[Raspberry Pi 5 Model B] (4 GB RAM or better)\n* https://www.raspberrypi.com/products/camera-module-3/[Raspberry Pi Camera Module 3]\n* https://www.adafruit.com/product/1646[100mm Flex Cable for Raspberry Pi Camera]\n* https://www.arducam.com/product/white-camera-enclosure-case-pi-cameras/[Camera Enclosure Case for Raspberry Pi Camera Module 3/V1/V2 and Arducam 16MP/64MP Camera]\n* https://www.adafruit.com/product/2324[Adafruit Ultimate GPS HAT for Raspberry Pi]\n* A CR1220 Battery for the RTC.\n// todo Also add battery for the on-board RTC?\n* https://shop.pimoroni.com/products/nvme-base?variant=41219587178579[NVMe Base for Raspberry Pi 5].\n* A sufficiently large and performant M.2 NVMe SSD.\nI prefer the SK hynix Gold P31 for lower-power and heat situations.\n* Alternatively, a sufficiently large and performant microSD card can be used instead of NVMe storage.\nThe https://www.samsung.com/us/computing/memory-storage/memory-cards/pro-ultimate-adapter-microsdxc-128gb-mb-my128sa-am/[128GB Samsung Pro Ultimate] and https://www.samsung.com/us/computing/memory-storage/memory-cards/pro-endurance-adapter-microsdxc-128gb-mb-mj128ka-am/[128GB Samsung Pro Endurance] are two good options.\n* https://www.raspberrypi.com/products/27w-power-supply/[Raspberry Pi 27W USB-C Power Supply]\n* https://www.raspberrypi.com/products/active-cooler/[Raspberry Pi Active Cooler]\n* https://shop.pimoroni.com/products/extended-m2-5-standoffs-for-pi-hats?variant=41206707880019[Extended M2.5 Standoffs for Pi HATs (Raspberry Pi 5) - Pack of 4]\nThis is 4 17mm M2.5 Hex socket-socket standoffs and 8 6mm M2.5 screws.\n* https://shop.pimoroni.com/products/booster-header?variant=47414520906[Booster Header]\nThis header has a standard 8.5mm height with 5mm tall pins.\n* https://www.raspberrypi.com/products/ai-kit/[Raspberry Pi AI Kit] This is component is optional.\n\n[TIP]\n====\nThe Adafruit Ultimate GPS HAT's cutout for the camera flex cable doesn't line up with the connectors on the Raspberry Pi 5.\nUse a drill to widen the cutout towards the edge of the board to allow the flex cable enough room to fit.\n====\n\n== Install\n\n. Install the 64-bit full version of https://www.raspberrypi.com/software/[Raspberry Pi OS] to a microSD card.\nThis project has been tested with Raspberry Pi OS 5 based on Debian Bookworm.\n. Insert the microSD card into the Raspberry Pi.\n. Boot the Raspberry Pi.\n. Create the `detectionator` user account.\nThe detectionator service is intended to be run under a dedicated user account.\nSince the autoupload mechanism for Immich CLI relies on running containers, the instructions here will configure this user account to run containers.\n\n.. Create a `detectionator` system group.\n+\n[,sh]\n----\nsudo groupadd --gid 818 --system detectionator\n----\n\n.. Create a primary user account named `detectionator`.\n+\n--\n[,sh]\n----\nsudo useradd \\\n  --add-subids-for-system \\\n  --comment \"Primary account taking photographs with object-detection\" \\\n  --create-home \\\n  --gid detectionator \\\n  --groups render,systemd-journal,video \\\n  --shell /usr/sbin/nologin \\\n  --system \\\n  --uid 818 \\\n  detectionator\n----\n\n[TIP]\n====\nThe `--btrfs-subvolume-home` flag can be used to create the user's home directory on a separate Btrfs subvolume.\n====\n--\n\n.. Verify that the new `detectionator` user has entries in `/etc/subuid` and `/etc/subgid`.\nIf for some reason, there are no subuid and subgid ranges for the user, follow these steps.\nI don't know why this happens, but it does sometimes.\n+\n[NOTE]\n====\nThese commands use the fish shell because I can never remember how to math in Bash.footnote:[Or anything else in Bash for that matter.]\n====\n\n.. Calculate the first value for the next subuid allotment.\n+\n--\nIf `/etc/subuid` is empty, use 100,000 as the initial value.\n\n[,sh]\n----\nset new_subuid 100000\n----\n\nOtherwise, use the following function to calculate the next available subuid range.\n\n[,sh]\n----\nset new_subuid (math (tail -1 /etc/subuid | awk -F \":\" '{print $2}') + 65536)\n----\n--\n\n.. Calculate the first value for the next subuid allotment.\n+\n--\nIf `/etc/subgid` is empty, use 100,000 as the initial value.\n\n[,sh]\n----\nset new_subgid 100000\n----\n\nOtherwise, use the following function to calculate the next available subgid range.\n\n[,sh]\n----\nset new_subgid (math (tail -1 /etc/subgid | awk -F \":\" '{print $2}') + 65536)\n----\n--\n\n.. Configure the `core` user with the calculated subuid and subgid ranges.\n+\n[,sh]\n----\nsudo usermod \\\n  --add-subuids $new_subuid-(math $new_subuid + 65535) \\\n  --add-subgids $new_subgid-(math $new_subgid + 65535) \\\n  detectionator\n----\n\n.. Automatically start the core user's session.\n+\n[,sh]\n----\nsudo loginctl enable-linger detectionator\n----\n\n.. Open a shell as the `detectionator` user with the following command.\nI prefer the fish shell, so I use that here, but substitute Bash, ZSH, etc. per your preference.\n+\n[,sh]\n----\nsudo -H -u detectionator fish -c 'cd; fish'\n----\n\n.. Configure the `XDG_RUNTIME_DIR` environment variable for the user in order for sockets to be found correctly.\n+\n[,sh]\n----\nset -Ux XDG_RUNTIME_DIR /run/user/(id -u)\n----\n\n. Install {just} by following the instructions in the https://github.com/casey/just?tab=readme-ov-file#installation[installation section].\n. Follow the instructions to configure the storage service and autoupload {systemd} units in the {AutoUpload} README.\nThis should automatically upload photos in the `/home/detectionator/Pictures` directory.\nThe commands to enable the units should look similar to the following.\nUse the _user_ unit, for Immich since it is running a container.\n+\nImmich::\n+\n// todo Fix this command to actually work.\n[,sh]\n----\nsudo -H -u detectionator bash -c 'systemctl --user enable --now autoupload-immich@$(systemd-escape --path ~/Pictures).timer'\n----\n\nRclone::\n+\n[,sh]\n----\nsudo systemctl enable --now autoupload-rclone@$(systemd-escape --path /home/detectionator/Pictures).timer\n----\n\n. For security, be sure to disable password-based SSH authentication.\nAfter your public key has been added to the `~/.ssh/authorized_keys` file on the Pi Camera, this can be configured in the `/etc/ssh/sshd_config` file.\nYou can follow the instructions in my https://github.com/jwillikers/openssh-config[OpenSSH Config] repository to accomplish this and a few other optimizations.\n\n. Update the package lists.\n+\n[,sh]\n----\nsudo apt-get update\n----\n\n. Upgrade everything.\n+\n[,sh]\n----\nsudo apt-get --yes full-upgrade\n----\n\n. Make the `~/Projects` directory.\n+\n[,sh]\n----\nmkdir --parents ~/Projects\n----\n\n. Clone this project's repository to the `~/Projects` directory.\n+\n[,sh]\n----\ngit -C ~/Projects clone https://github.com/jwillikers/detectionator.git\n----\n\n. Change to the project's directory.\n+\n[,sh]\n----\ncd ~/Projects/detectionator\n----\n\n. Set up the environment with `just init`.\nThis will install dependencies and initialize the virtual environment.\nIt also installs a special udev rule for the Adafruit Ultimate GPS to give it a static device name.\n+\n[,sh]\n----\njust init\n----\n\n. Enable the serial port hardware and better PCIe speeds in `config.txt`.\n+\n./boot/firmware/config.txt\n[,ini]\n----\n[all]\ndtparam=pciex1_gen=3\ndtoverlay=pps-gpio,gpiopin=4\ndtparam=uart0=on\n\n# Allow USB devices to pull the maximum current possible.\nusb_max_current_enable=1\n\n# Recharge the RTC battery.\ndtparam=rtc_bbat_vchg=3000000\n\n# Reduce clockspeeds and temperature limit to reduce the likelihood of crashes.\n# Crashes seem particularly common when recording video.\n#arm_freq = 2000\n#core_freq = 900\n#v3d_freq = 900\n#temp_limit = 80\n\ndisable_camera_led = 1\n----\n\n// todo eeprom config\n// UART_BAUD=9600\n\n. Ensure that `console=tty1` is in `/boot/firmware/cmdline.txt` and not `console=ttyAMA0` or `console=serial0`.\n+\n./boot/firmware/cmdline.txt\n[source]\n----\nconsole=tty1 root=PARTUUID=c64d4099-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=US\n----\n\n. Configure the `pps-gpio` module to be loaded.\n+\n[,sh]\n----\necho 'pps-gpio' | sudo tee /etc/modules-load.d/pps-gpio.conf\n----\n\n. Configure gpsd to use the GPS HAT.\nThe serial port `ttyAMA0` is used and since the Raspberry Pi 5 has a built-in RTC, `pps1` is used instead of `pps0` here.\n+\n./etc/default/gpsd\n[,ini]\n----\n# Devices gpsd should collect to at boot time.\n# They need to be read/writeable, either by user gpsd or the group dialout.\nDEVICES=\"/dev/ttyAMA0 /dev/pps1\"\n\n# Other options you want to pass to gpsd\nGPSD_OPTIONS=\"--nowait\"\n\n# Automatically hot add/remove USB GPS devices via gpsdctl\nUSBAUTO=\"true\"\n----\n\n. Configure chrony to use the GPS HAT for time.\n+\n./etc/chrony/conf.d/gpsd.conf\n[source]\n----\n# set larger delay to allow the NMEA source to overlap with\n# the other sources and avoid the falseticker status\nrefclock SOCK /run/chrony.ttyAMA10.sock refid GPS precision 1e-1 offset 0.9999\nrefclock SOCK /run/chrony.pps1.sock refid PPS precision 1e-7\n----\n\n. Reboot for the new udev rules to take effect.\n+\n[,sh]\n----\nsudo systemctl reboot\n----\n\n. Use `just run` to run the `detectionator.py` Python script inside the virtual environment.\n+\n[,sh]\n----\njust run\n----\n\n. Install and activate the systemd service with `just install`.\n+\n[,sh]\n----\njust install\n----\n\n[TIP]\n====\nSend the application the `SIGUSR1` signal to capture sample photographs for both the high and low resolution modes.\n\n[,sh]\n----\nkill --signal SIGUSR1 $(pgrep python)\n----\n====\n\n. Check the status of the `detectionator.service` unit with the `systemctl status` command.\n+\n[,sh]\n----\nsudo systemctl status detectionator.service\n----\n\n. Check the logs of the `detectionator.service` unit with `journalctl`.\n+\n[,sh]\n----\nsudo journalctl -xeu detectionator.service\n----\n\n. Set up unattended upgrades to automatically update the system.\nI document how to do this in my blog post https://www.jwillikers.com/unattended-upgrades[Unattended Upgrades].\n\n== HDR\n\nThe Raspberry Pi Camera Module 3 supports HDR, but only at a lower resolution.\nHDR support has to toggled when `detectionator.py` isn't running.\n\n. Show the available V4L subdevices.\n+\n[,sh]\n----\nls /dev/v4l-subdev*\n/dev/v4l-subdev0  /dev/v4l-subdev1  /dev/v4l-subdev2  /dev/v4l-subdev3\n----\n\n. To enable HDR support for the Raspberry Pi Camera Module 3, use the following command on one of the V4L subdevices.\nIn my case, this ended up being `/dev/v4l-subdev2`.\n+\n[,sh]\n----\njust hdr /dev/v4l-subdev2\n----\n\n. To disable HDR support for the Raspberry Pi Camera Module 3, use this command with the corresponding V4L subdevice.\n+\n[,sh]\n----\njust hdr /dev/v4l-subdev2 disable\n----\n\n== Development\n\n. Run `just init-dev` to initialize the virtual environment for development.\nThis will install all of the necessary dependencies and the {pre-commit} hooks.\n+\n[,sh]\n----\njust init-dev\n----\n\n. Run the tests with https://docs.pytest.org/en/latest/[pytest] by running `just test`.\n+\n[,sh]\n----\njust test\n----\n\n. To update dependencies, run `just update`.\n+\n[,sh]\n----\njust update\n----\n\n. Use `just --list` to list other available tasks.\n+\n[,sh]\n----\njust --list\n----\n\n== Limitations\n\nThe Raspberry Pi Camera Module 3 uses a rolling shutter.\nRolling shutter can make object detection less accurate and produce graphical distortions for fast-moving objects.\nGlobal shutter would be ideal, but it's not as easy to find high resolution cameras using this technology for an embedded system.\nLower resolution cameras, such as the Raspberry Pi Global Shutter Camera exist, which could be used to improve the object detection of fast moving objects.\nI might try to use a dual-camera system in the future which could take advantage of the lower-resolution global shutter camera for detections while still capturing pictures with the a higher resolution rolling shutter camera.\n\n== todo\n\n- [] Add better configuration parameter checking, particularly around enum type options.\n- [] Add a configuration parameter to configure the focus range.\nThis could help speed up autofocus.\n- [] Add a configuration parameter to configure the rest time in-between runs of the detection algorithm when there was no detection.\n- [] Add a configuration parameter to control whether or not to refocus when capturing burst shots.\n- [x] Use https://gpsd.gitlab.io/gpsd/gpsd-time-service-howto.html[gpsd].\n- [] Create classes for different data types to better organize things.\n- [x] Cache the GPS data to reduce the time to capture pictures between detections?\nUse TTLCache from https://github.com/tkem/cachetools/[cachetools].\n- [x] Add support for a TOML config file with https://github.com/bw2/ConfigArgParse[ConfigArgParse].\n- [] Switch from picamera2 to gstreamer to work with more hardware.\n- [] mypy\n- [] async\n- [] Create a weatherproof enclosure for the camera.\n- [] Add a NixOS configuration and build SD card images.\n- [] Should I be processing the images in grayscale?\n\n== See Also\n\n* https://docs.circuitpython.org/projects/gps/en/latest/[Adafruit GPS Library Documentation]\n* https://learn.adafruit.com/adafruit-ultimate-gps-hat-for-raspberry-pi[Adafruit Ultimate GPS HAT for Raspberry Pi Documentation]\n* https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf[Exchangeable image file format for digital still cameras Exif Version 2.3]\n* https://pyserial.readthedocs.io/en/latest/index.html[pySerial Documentation]\n* https://www.raspberrypi.com/news/using-the-picamera2-library-with-tensorflow-lite/[Using the Picamera2 library with TensorFlow Lite]\n\n== Code of Conduct\n\nThe project's Code of Conduct is available in the link:CODE_OF_CONDUCT.adoc[Code of Conduct] file.\n\n== License\n\nThe models are from the {picamera2} project's TensorFlow example, and are likely subject to their own licenses.\nThis repository is licensed under the https://www.gnu.org/licenses/gpl-3.0.html[GPLv3], available in the link:LICENSE.adoc[license file].\n\n© 2024 Jordan Williams\n\n== Authors\n\nmailto:{email}[{author}]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwillikers%2Fdetectionator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjwillikers%2Fdetectionator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwillikers%2Fdetectionator/lists"}