You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
112 lines
3.3 KiB
112 lines
3.3 KiB
use std::os::unix::fs::MetadataExt;
|
|
use std::path::PathBuf;
|
|
use chrono::{NaiveDateTime, Utc};
|
|
use image::{GenericImageView, Pixel};
|
|
use log::{debug, error, info};
|
|
|
|
fn main() {
|
|
open_and_reduce_img();
|
|
}
|
|
|
|
|
|
const FOLDER_WITH_IMAGES : &str = "../2/";
|
|
|
|
const FOLDER_OUTPUT : &str = "birds";
|
|
|
|
const THUMB_W : usize = 12;
|
|
const THUMB_H : usize = 8;
|
|
|
|
const THRESHOLD_DEVIATION : u32 = 500;
|
|
|
|
fn open_and_reduce_img() {
|
|
env_logger::init_from_env("LOGGING");
|
|
|
|
info!("Bird finder starts");
|
|
|
|
let paths = std::fs::read_dir(FOLDER_WITH_IMAGES).unwrap();
|
|
|
|
let mut crops = vec![];
|
|
let mut thumbs = vec![];
|
|
let mut timestamps = vec![];
|
|
|
|
info!("Loading files");
|
|
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() {
|
|
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 thumb = crop.thumbnail(THUMB_W as u32, THUMB_H as u32);
|
|
|
|
crops.push(crop);
|
|
|
|
let mut thumb_ar = [0u8; THUMB_H * THUMB_W];
|
|
for y in 0..thumb.height() {
|
|
for x in 0..thumb.width() {
|
|
thumb_ar[(y * thumb.width() + x) as usize] = thumb.get_pixel(x, y).to_luma().0[0];
|
|
}
|
|
}
|
|
|
|
thumbs.push(thumb_ar);
|
|
}
|
|
|
|
info!("Computing digests & medians");
|
|
let mut transposed = vec![];
|
|
for thumb in thumbs.iter() {
|
|
for (b, item) in thumb.iter().enumerate() {
|
|
if transposed.len() <= b {
|
|
transposed.push(vec![]);
|
|
}
|
|
transposed[b].push(*item);
|
|
}
|
|
}
|
|
|
|
let mut medians = [0; THUMB_H * THUMB_W];
|
|
|
|
for (a, column) in transposed.iter().enumerate() {
|
|
medians[a] = column[column.len() / 2]; // approximately median ...
|
|
}
|
|
|
|
debug!("Medians: {:?}", medians);
|
|
|
|
info!("Finding deviations");
|
|
for (thumb_num, thumb) in thumbs.iter().enumerate() {
|
|
let deviation = thumb.iter().zip(medians.iter())
|
|
.map(|(a, b)| (*a as i16 - *b as i16).abs() as u32).fold(0u32, |a, b| a + b);
|
|
|
|
debug!("Thumb {thumb_num} summary diff from median is: {deviation}");
|
|
|
|
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 mut pb = PathBuf::from(path);
|
|
let mut cnt = 2;
|
|
while pb.exists() {
|
|
pb.set_file_name(format!("{}-{cnt}.jpg", datetime));
|
|
cnt += 1;
|
|
}
|
|
|
|
if let Err(e) = crops[thumb_num].save(pb) {
|
|
error!("Fail to save pic: {e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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();
|
|
}
|
|
|
|
fn parse_unix_ts(ts: i64) -> chrono::DateTime<Utc> {
|
|
chrono::DateTime::<Utc>::from_naive_utc_and_offset(NaiveDateTime::from_timestamp_opt(ts, 0)
|
|
.expect("Bad timestamp"), Utc)
|
|
}
|
|
|