Compare commits
2 Commits
34eaf9348a
...
3eec262076
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eec262076 | ||
|
|
c758fd8d41 |
@@ -163,6 +163,21 @@ impl<T: Num, const D: usize> AxisAlignedBoundingBox<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_uniform(self, scalar: T) -> Self
|
||||
where
|
||||
T: core::ops::MulAssign,
|
||||
T: core::ops::DivAssign,
|
||||
T: core::ops::SubAssign,
|
||||
{
|
||||
let two = T::one() + T::one();
|
||||
let new_size = self.size * scalar;
|
||||
let new_point = self.point.coords - (new_size - self.size) / two;
|
||||
Self {
|
||||
point: Point::from(new_point),
|
||||
size: new_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_bbox(&self, other: &Self) -> bool
|
||||
where
|
||||
T: core::ops::AddAssign,
|
||||
@@ -270,15 +285,17 @@ impl<T: Num, const D: usize> AxisAlignedBoundingBox<T, D> {
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn as_<T2>(&self) -> Option<Aabb<T2, D>>
|
||||
// where
|
||||
// T2: Num + simba::scalar::SubsetOf<T>,
|
||||
// {
|
||||
// Some(Aabb {
|
||||
// point: Point::from(self.point.coords.as_()),
|
||||
// size: self.size.as_(),
|
||||
// })
|
||||
// }
|
||||
pub fn as_<T2>(&self) -> Aabb<T2, D>
|
||||
where
|
||||
T2: Num,
|
||||
T: num::cast::AsPrimitive<T2>,
|
||||
{
|
||||
Aabb {
|
||||
point: Point::from(self.point.coords.map(|x| x.as_())),
|
||||
size: self.size.map(|x| x.as_()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn measure(&self) -> T
|
||||
where
|
||||
T: core::ops::MulAssign,
|
||||
|
||||
145
src/gui/app.rs
145
src/gui/app.rs
@@ -10,6 +10,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::gui::bridge::FaceDetectionBridge;
|
||||
use ::image::{DynamicImage, ImageFormat, RgbImage};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
@@ -43,6 +44,7 @@ pub enum Message {
|
||||
ImageLoaded(Option<Arc<Vec<u8>>>),
|
||||
SecondImageLoaded(Option<Arc<Vec<u8>>>),
|
||||
ProcessedImageUpdated(Option<Vec<u8>>),
|
||||
FaceRoisLoaded(Vec<image::Handle>, Vec<image::Handle>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -76,6 +78,8 @@ pub enum ComparisonResult {
|
||||
Success {
|
||||
image1_faces: usize,
|
||||
image2_faces: usize,
|
||||
image1_face_rois: Vec<ndarray::Array3<u8>>,
|
||||
image2_face_rois: Vec<ndarray::Array3<u8>>,
|
||||
best_similarity: f32,
|
||||
processing_time: f64,
|
||||
},
|
||||
@@ -110,6 +114,10 @@ pub struct FaceDetectorApp {
|
||||
current_image_handle: Option<image::Handle>,
|
||||
processed_image_handle: Option<image::Handle>,
|
||||
second_image_handle: Option<image::Handle>,
|
||||
|
||||
// Face ROI handles for comparison display
|
||||
image1_face_roi_handles: Vec<image::Handle>,
|
||||
image2_face_roi_handles: Vec<image::Handle>,
|
||||
}
|
||||
|
||||
impl Default for FaceDetectorApp {
|
||||
@@ -130,6 +138,8 @@ impl Default for FaceDetectorApp {
|
||||
current_image_handle: None,
|
||||
processed_image_handle: None,
|
||||
second_image_handle: None,
|
||||
image1_face_roi_handles: Vec::new(),
|
||||
image2_face_roi_handles: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,6 +323,8 @@ impl FaceDetectorApp {
|
||||
self.detection_result = None;
|
||||
self.comparison_result = None;
|
||||
self.processed_image_handle = None;
|
||||
self.image1_face_roi_handles.clear();
|
||||
self.image2_face_roi_handles.clear();
|
||||
self.status_message = "Results cleared".to_string();
|
||||
Task::none()
|
||||
}
|
||||
@@ -356,6 +368,8 @@ impl FaceDetectorApp {
|
||||
ComparisonResult::Success {
|
||||
best_similarity,
|
||||
processing_time,
|
||||
image1_face_rois,
|
||||
image2_face_rois,
|
||||
..
|
||||
} => {
|
||||
let interpretation = if *best_similarity > 0.8 {
|
||||
@@ -372,6 +386,16 @@ impl FaceDetectorApp {
|
||||
"Comparison complete! Similarity: {:.3} - {} (Processing time: {:.2}s)",
|
||||
best_similarity, interpretation, processing_time
|
||||
);
|
||||
|
||||
// Convert face ROIs to image handles
|
||||
let image1_handles = convert_face_rois_to_handles(image1_face_rois.clone());
|
||||
let image2_handles = convert_face_rois_to_handles(image2_face_rois.clone());
|
||||
|
||||
self.comparison_result = Some(result);
|
||||
return Task::perform(
|
||||
async move { (image1_handles, image2_handles) },
|
||||
|(h1, h2)| Message::FaceRoisLoaded(h1, h2),
|
||||
);
|
||||
}
|
||||
ComparisonResult::Error(error) => {
|
||||
self.status_message = format!("Comparison failed: {}", error);
|
||||
@@ -382,6 +406,12 @@ impl FaceDetectorApp {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::FaceRoisLoaded(image1_handles, image2_handles) => {
|
||||
self.image1_face_roi_handles = image1_handles;
|
||||
self.image2_face_roi_handles = image2_handles;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ProgressUpdate(progress) => {
|
||||
self.progress = progress;
|
||||
Task::none()
|
||||
@@ -765,6 +795,8 @@ impl FaceDetectorApp {
|
||||
ComparisonResult::Success {
|
||||
image1_faces,
|
||||
image2_faces,
|
||||
image1_face_rois: _,
|
||||
image2_face_rois: _,
|
||||
best_similarity,
|
||||
processing_time,
|
||||
} => {
|
||||
@@ -790,7 +822,7 @@ impl FaceDetectorApp {
|
||||
)
|
||||
};
|
||||
|
||||
column![
|
||||
let mut result_column = column![
|
||||
text("Comparison Results").size(18),
|
||||
text(format!("First image faces: {}", image1_faces)),
|
||||
text(format!("Second image faces: {}", image2_faces)),
|
||||
@@ -800,7 +832,89 @@ impl FaceDetectorApp {
|
||||
}),
|
||||
text(format!("Processing time: {:.2}s", processing_time)),
|
||||
]
|
||||
.spacing(5);
|
||||
|
||||
// Add face ROI displays if available
|
||||
if !self.image1_face_roi_handles.is_empty()
|
||||
|| !self.image2_face_roi_handles.is_empty()
|
||||
{
|
||||
result_column = result_column.push(text("Detected Faces").size(16));
|
||||
|
||||
// Create face ROI rows
|
||||
let image1_faces_row = if !self.image1_face_roi_handles.is_empty() {
|
||||
let faces: Element<'_, Message> = self
|
||||
.image1_face_roi_handles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(row![].spacing(5), |row, (i, handle)| {
|
||||
row.push(
|
||||
column![
|
||||
text(format!("Face {}", i + 1)).size(12),
|
||||
container(
|
||||
image(handle.clone())
|
||||
.width(80)
|
||||
.height(80)
|
||||
.content_fit(iced::ContentFit::Cover)
|
||||
)
|
||||
.style(container::bordered_box)
|
||||
.padding(2),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_x(Alignment::Center),
|
||||
)
|
||||
})
|
||||
.into();
|
||||
|
||||
column![
|
||||
text("First Image Faces:").size(14),
|
||||
scrollable(faces).direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new()
|
||||
)),
|
||||
]
|
||||
.spacing(5)
|
||||
} else {
|
||||
column![text("First Image Faces: None detected").size(14)]
|
||||
};
|
||||
|
||||
let image2_faces_row = if !self.image2_face_roi_handles.is_empty() {
|
||||
let faces: Element<'_, Message> = self
|
||||
.image2_face_roi_handles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.fold(row![].spacing(5), |row, (i, handle)| {
|
||||
row.push(
|
||||
column![
|
||||
text(format!("Face {}", i + 1)).size(12),
|
||||
container(
|
||||
image(handle.clone())
|
||||
.width(80)
|
||||
.height(80)
|
||||
.content_fit(iced::ContentFit::Cover)
|
||||
)
|
||||
.style(container::bordered_box)
|
||||
.padding(2),
|
||||
]
|
||||
.spacing(2)
|
||||
.align_x(Alignment::Center),
|
||||
)
|
||||
})
|
||||
.into();
|
||||
|
||||
column![
|
||||
text("Second Image Faces:").size(14),
|
||||
scrollable(faces).direction(scrollable::Direction::Horizontal(
|
||||
scrollable::Scrollbar::new()
|
||||
)),
|
||||
]
|
||||
.spacing(5)
|
||||
} else {
|
||||
column![text("Second Image Faces: None detected").size(14)]
|
||||
};
|
||||
|
||||
result_column = result_column.push(image1_faces_row).push(image2_faces_row);
|
||||
}
|
||||
|
||||
result_column
|
||||
}
|
||||
ComparisonResult::Error(error) => column![
|
||||
text("Comparison Results").size(18),
|
||||
@@ -816,9 +930,11 @@ impl FaceDetectorApp {
|
||||
]
|
||||
};
|
||||
|
||||
scrollable(
|
||||
column![file_section, comparison_image_section, controls, results]
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.padding(20),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -881,6 +997,31 @@ impl std::fmt::Display for ExecutorType {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert face ROIs to image handles
|
||||
fn convert_face_rois_to_handles(face_rois: Vec<ndarray::Array3<u8>>) -> Vec<image::Handle> {
|
||||
face_rois
|
||||
.into_iter()
|
||||
.filter_map(|roi| {
|
||||
// Convert ndarray to image::RgbImage
|
||||
let (height, width, _) = roi.dim();
|
||||
let (raw_data, _offset) = roi.into_raw_vec_and_offset();
|
||||
|
||||
if let Some(img) = RgbImage::from_raw(width as u32, height as u32, raw_data) {
|
||||
// Convert to PNG bytes
|
||||
let mut buffer = Vec::new();
|
||||
let mut cursor = std::io::Cursor::new(&mut buffer);
|
||||
if DynamicImage::ImageRgb8(img)
|
||||
.write_to(&mut cursor, ImageFormat::Png)
|
||||
.is_ok()
|
||||
{
|
||||
return Some(image::Handle::from_bytes(buffer));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run() -> iced::Result {
|
||||
iced::application(
|
||||
"Face Detector",
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::errors;
|
||||
use crate::facedet::{FaceDetectionConfig, FaceDetector, retinaface};
|
||||
use crate::faceembed::{FaceNetEmbedder, facenet};
|
||||
use crate::faceembed::facenet;
|
||||
use crate::gui::app::{ComparisonResult, DetectionResult, ExecutorType};
|
||||
use bounding_box::Aabb2;
|
||||
use bounding_box::roi::MultiRoi as _;
|
||||
@@ -70,11 +70,19 @@ impl FaceDetectionBridge {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((image1_faces, image2_faces, best_similarity)) => {
|
||||
Ok((
|
||||
image1_faces,
|
||||
image2_faces,
|
||||
image1_face_rois,
|
||||
image2_face_rois,
|
||||
best_similarity,
|
||||
)) => {
|
||||
let processing_time = start_time.elapsed().as_secs_f64();
|
||||
ComparisonResult::Success {
|
||||
image1_faces,
|
||||
image2_faces,
|
||||
image1_face_rois,
|
||||
image2_face_rois,
|
||||
best_similarity,
|
||||
processing_time,
|
||||
}
|
||||
@@ -180,9 +188,13 @@ impl FaceDetectionBridge {
|
||||
threshold: f32,
|
||||
nms_threshold: f32,
|
||||
executor_type: ExecutorType,
|
||||
) -> Result<(usize, usize, f32), Box<dyn std::error::Error + Send + Sync>> {
|
||||
) -> Result<
|
||||
(usize, usize, Vec<Array3<u8>>, Vec<Array3<u8>>, f32),
|
||||
Box<dyn std::error::Error + Send + Sync>,
|
||||
> {
|
||||
// Create detector and embedder, detect faces and generate embeddings
|
||||
let (faces1, faces2, best_similarity) = match executor_type {
|
||||
let (image1_faces, image2_faces, image1_rois, image2_rois, best_similarity) =
|
||||
match executor_type {
|
||||
ExecutorType::MnnCpu | ExecutorType::MnnMetal | ExecutorType::MnnCoreML => {
|
||||
let forward_type = match executor_type {
|
||||
ExecutorType::MnnCpu => mnn::ForwardType::CPU,
|
||||
@@ -191,7 +203,8 @@ impl FaceDetectionBridge {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||
let mut detector =
|
||||
retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
|
||||
.map_err(|e| format!("Failed to create MNN detector: {}", e))?
|
||||
.with_forward_type(forward_type.clone())
|
||||
.build()
|
||||
@@ -220,13 +233,30 @@ impl FaceDetectionBridge {
|
||||
2,
|
||||
)?;
|
||||
|
||||
let image1_rois = img_1.rois;
|
||||
let image2_rois = img_2.rois;
|
||||
let image1_bbox_len = img_1.bbox.len();
|
||||
let image2_bbox_len = img_2.bbox.len();
|
||||
let best_similarity = compare_faces(&img_1.embeddings, &img_2.embeddings)?;
|
||||
(img_1, img_2, best_similarity)
|
||||
|
||||
(
|
||||
image1_bbox_len,
|
||||
image2_bbox_len,
|
||||
image1_rois,
|
||||
image2_rois,
|
||||
best_similarity,
|
||||
)
|
||||
}
|
||||
ExecutorType::OnnxCpu => unimplemented!(),
|
||||
};
|
||||
|
||||
Ok((faces1.bbox.len(), faces2.bbox.len(), best_similarity))
|
||||
Ok((
|
||||
image1_faces,
|
||||
image2_faces,
|
||||
image1_rois,
|
||||
image2_rois,
|
||||
best_similarity,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,19 +326,27 @@ where
|
||||
.change_context(errors::Error)
|
||||
.attach_printable("Failed to detect faces")?;
|
||||
|
||||
for bbox in &output.bbox {
|
||||
let bboxes = output
|
||||
.bbox
|
||||
.iter()
|
||||
.inspect(|bbox| tracing::info!("Raw bbox: {:?}", bbox))
|
||||
.map(|bbox| bbox.as_::<f32>().scale_uniform(1.30).as_::<usize>())
|
||||
.inspect(|bbox| tracing::info!("Padded bbox: {:?}", bbox))
|
||||
.collect_vec();
|
||||
for bbox in &bboxes {
|
||||
tracing::info!("Detected face: {:?}", bbox);
|
||||
use bounding_box::draw::*;
|
||||
array.draw(bbox, color::palette::css::GREEN_YELLOW.to_rgba8(), 1);
|
||||
}
|
||||
use itertools::Itertools;
|
||||
let face_rois = array
|
||||
.view()
|
||||
.multi_roi(&output.bbox)
|
||||
.multi_roi(&bboxes)
|
||||
.change_context(Error)?
|
||||
.into_iter()
|
||||
.map(|roi| {
|
||||
roi.as_standard_layout()
|
||||
.fast_resize(320, 320, &ResizeOptions::default())
|
||||
.fast_resize(224, 224, &ResizeOptions::default())
|
||||
.change_context(Error)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
@@ -322,7 +360,7 @@ where
|
||||
let og_size = chunk.len();
|
||||
if chunk.len() < chunk_size {
|
||||
tracing::warn!("Chunk size is less than 8, padding with zeros");
|
||||
let zeros = Array3::zeros((320, 320, 3));
|
||||
let zeros = Array3::zeros((224, 224, 3));
|
||||
let chunk: Vec<_> = chunk
|
||||
.iter()
|
||||
.map(|arr| arr.reborrow())
|
||||
@@ -358,7 +396,7 @@ where
|
||||
.collect::<Vec<Array1<f32>>>();
|
||||
|
||||
Ok(DetectionOutput {
|
||||
bbox: output.bbox,
|
||||
bbox: bboxes,
|
||||
rois: face_rois,
|
||||
embeddings,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user