diff --git a/Cargo.lock b/Cargo.lock index bb76989..f365f06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,12 +74,30 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bird-detector" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap", + "env_logger", + "image", + "log", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -318,17 +336,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "krmdet" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "env_logger", - "image", - "log", -] - [[package]] name = "libc" version = "0.2.150" diff --git a/Cargo.toml b/Cargo.toml index 6949982..101fc93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "krmdet" +name = "bird-detector" version = "0.1.0" edition = "2021" @@ -12,3 +12,4 @@ clap = "4.4.7" log = "0.4.20" env_logger = "0.10.0" chrono = "0.4.31" +anyhow = "1.0.75" diff --git a/birds/.gitignore b/birds/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/birds/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/main.rs b/src/main.rs index c9ef6de..96f6a4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,68 @@ use std::os::unix::fs::MetadataExt; use std::path::PathBuf; +use std::time::Duration; +use anyhow::Context; + use chrono::{NaiveDateTime, Utc}; use image::{GenericImageView, Pixel}; use log::{debug, error, info}; -fn main() { - open_and_reduce_img(); -} +/// Folder the camera images are uploaded to +const FOLDER_WITH_NEW_IMAGES: &str = "/dev/shm/camera"; + +/// File created in the uploads directory when the upload finishes. +const COMMIT_FILENAME : &str = "commit"; +/// Folder where the cropped bird pics are saved +const FOLDER_WITH_BIRD_IMAGES: &str = "/backup/krmitko"; -const FOLDER_WITH_IMAGES : &str = "../2/"; +/// Image cropping config: left top X +const CROP_X : u32 = 1872; -const FOLDER_OUTPUT : &str = "birds"; +/// Image cropping config: left top Y +const CROP_Y : u32 = 1329; -const THUMB_W : usize = 12; -const THUMB_H : usize = 8; +/// Image cropping config: crop width +const CROP_W : u32 = 1000; -const THRESHOLD_DEVIATION : u32 = 500; +/// Image cropping config: crop height +const CROP_H : u32 = 750; -fn open_and_reduce_img() { - env_logger::init_from_env("LOGGING"); +/// Thumbnail width, used for bird detection (together with height creates the kernel size) +/// This is a compromise between sensitivity and susceptibility to false positives. +const THUMB_W: usize = 12; - info!("Bird finder starts"); +/// Thumbnail height +const THUMB_H: usize = 8; + +/// Threshold for bird detection. +/// Bird is detected if the sum of deviations from a median value in a picture exceeds this threshold. +const THRESHOLD_DEVIATION: u32 = 500; + +fn main() { + env_logger::init(); - let paths = std::fs::read_dir(FOLDER_WITH_IMAGES).unwrap(); + let commit_file = PathBuf::from(FOLDER_WITH_NEW_IMAGES).join(COMMIT_FILENAME); + + loop { + if !commit_file.exists() { + debug!("No commit file, wait."); + std::thread::sleep(Duration::from_secs(10)); + continue; + } + + if let Err(e) = process_camera_pics() { + error!("Error processing pics: {e}"); + } + + let _ = std::fs::remove_dir_all(FOLDER_WITH_NEW_IMAGES); + } +} + +fn process_camera_pics() -> anyhow::Result<()> { + info!("Processing camera pics"); + + let paths = std::fs::read_dir(FOLDER_WITH_NEW_IMAGES).context("Read folder with images")?; let mut crops = vec![]; let mut thumbs = vec![]; @@ -33,16 +72,20 @@ fn open_and_reduce_img() { for dir_entry in paths { let Ok(dir_entry) = dir_entry else { continue; }; let Ok(meta) = dir_entry.metadata() else { continue; }; - if !meta.is_file() { + if !meta.is_file() || !dir_entry.file_name().to_string_lossy().ends_with(".jpg") { continue; } - timestamps.push(meta.ctime()); - debug!("{}", dir_entry.path().display()); - let img = image::open(dir_entry.path()).unwrap(); - let crop = img.crop_imm(1872, 1329, 1000, 750); + let Ok(img) = image::open(dir_entry.path()) else { + continue; + }; + + // File loaded fine, assume it's OK + timestamps.push(meta.ctime()); + + let crop = img.crop_imm(CROP_X, CROP_Y, CROP_W, CROP_H); let thumb = crop.thumbnail(THUMB_W as u32, THUMB_H as u32); crops.push(crop); @@ -86,12 +129,15 @@ fn open_and_reduce_img() { if deviation > THRESHOLD_DEVIATION { info!("LIKELY BIRD!!!! in picture #{thumb_num}"); - let datetime = parse_unix_ts(timestamps[thumb_num]).format("%Y-%m-%d_%H-%M-%S"); - let path = format!("{}/{}.jpg", FOLDER_OUTPUT, datetime); + let datetime_str = parse_unix_ts(timestamps[thumb_num]).format("%Y-%m-%d_%H-%M-%S"); + let path = format!("{}/{}.jpg", FOLDER_WITH_BIRD_IMAGES, datetime_str); let mut pb = PathBuf::from(path); + + // Ensure we do not overwrite a file if there are multiple in the same second! + // This adds -2, -3 etc. to the file name let mut cnt = 2; while pb.exists() { - pb.set_file_name(format!("{}-{cnt}.jpg", datetime)); + pb.set_file_name(format!("{}-{cnt}.jpg", datetime_str)); cnt += 1; } @@ -101,9 +147,7 @@ fn open_and_reduce_img() { } } - - // crop.save(format!("{}/{}", FOLDER_OUTPUT, dir_entry.file_name().to_string_lossy())).unwrap(); - // thumb.save(format!("{}/th-{}", FOLDER_OUTPUT, dir_entry.file_name().to_string_lossy())).unwrap(); + Ok(()) } fn parse_unix_ts(ts: i64) -> chrono::DateTime {