|
|
|
@ -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<Utc> { |
|
|
|
|