feat(ndcv-bridge): add ndcv-bridge for ndarray and opencv interaction
This commit is contained in:
@@ -3,6 +3,8 @@ use std::path::PathBuf;
|
||||
use crate::facedet::{FaceDetectionConfig, FaceDetector, retinaface};
|
||||
use crate::faceembed::facenet;
|
||||
use crate::gui::app::{ComparisonResult, DetectionResult, ExecutorType};
|
||||
use bounding_box::Aabb2;
|
||||
use error_stack::ResultExt;
|
||||
use ndarray_image::ImageToNdarray;
|
||||
|
||||
const RETINAFACE_MODEL_MNN: &[u8] = include_bytes!("../../models/retinaface.mnn");
|
||||
@@ -227,42 +229,6 @@ impl FaceDetectionBridge {
|
||||
// Extract face crops and generate embeddings
|
||||
let mut best_similarity = 0.0f32;
|
||||
|
||||
for bbox1 in &faces1.bbox {
|
||||
let crop1 = Self::crop_face_from_image(&img1, bbox1)?;
|
||||
let crop1_array = ndarray::Array::from_shape_vec(
|
||||
(1, crop1.height() as usize, crop1.width() as usize, 3),
|
||||
crop1
|
||||
.pixels()
|
||||
.flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
.collect(),
|
||||
)?;
|
||||
|
||||
let embedding1 = embedder
|
||||
.run_models(crop1_array.view())
|
||||
.map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
for bbox2 in &faces2.bbox {
|
||||
let crop2 = Self::crop_face_from_image(&img2, bbox2)?;
|
||||
let crop2_array = ndarray::Array::from_shape_vec(
|
||||
(1, crop2.height() as usize, crop2.width() as usize, 3),
|
||||
crop2
|
||||
.pixels()
|
||||
.flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
.collect(),
|
||||
)?;
|
||||
|
||||
let embedding2 = embedder
|
||||
.run_models(crop2_array.view())
|
||||
.map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
let similarity = Self::cosine_similarity(
|
||||
embedding1.row(0).as_slice().unwrap(),
|
||||
embedding2.row(0).as_slice().unwrap(),
|
||||
);
|
||||
best_similarity = best_similarity.max(similarity);
|
||||
}
|
||||
}
|
||||
|
||||
(faces1, faces2, best_similarity)
|
||||
}
|
||||
ExecutorType::OnnxCpu => {
|
||||
@@ -287,40 +253,14 @@ impl FaceDetectionBridge {
|
||||
// Extract face crops and generate embeddings
|
||||
let mut best_similarity = 0.0f32;
|
||||
|
||||
for bbox1 in &faces1.bbox {
|
||||
let crop1 = Self::crop_face_from_image(&img1, bbox1)?;
|
||||
let crop1_array = ndarray::Array::from_shape_vec(
|
||||
(1, crop1.height() as usize, crop1.width() as usize, 3),
|
||||
crop1
|
||||
.pixels()
|
||||
.flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
.collect(),
|
||||
)?;
|
||||
|
||||
let embedding1 = embedder
|
||||
.run_models(crop1_array.view())
|
||||
.map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
for bbox2 in &faces2.bbox {
|
||||
let crop2 = Self::crop_face_from_image(&img2, bbox2)?;
|
||||
let crop2_array = ndarray::Array::from_shape_vec(
|
||||
(1, crop2.height() as usize, crop2.width() as usize, 3),
|
||||
crop2
|
||||
.pixels()
|
||||
.flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
.collect(),
|
||||
)?;
|
||||
|
||||
let embedding2 = embedder
|
||||
.run_models(crop2_array.view())
|
||||
.map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
let similarity = Self::cosine_similarity(
|
||||
embedding1.row(0).as_slice().unwrap(),
|
||||
embedding2.row(0).as_slice().unwrap(),
|
||||
);
|
||||
best_similarity = best_similarity.max(similarity);
|
||||
}
|
||||
if faces1.bbox.is_empty() || faces2.bbox.is_empty() {
|
||||
return Ok((faces1.bbox.len(), faces2.bbox.len(), 0.0));
|
||||
}
|
||||
if faces1.bbox.len() != faces2.bbox.len() {
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Face count mismatch between images",
|
||||
)));
|
||||
}
|
||||
|
||||
(faces1, faces2, best_similarity)
|
||||
@@ -329,39 +269,55 @@ impl FaceDetectionBridge {
|
||||
|
||||
Ok((faces1.bbox.len(), faces2.bbox.len(), best_similarity))
|
||||
}
|
||||
|
||||
fn crop_face_from_image(
|
||||
img: &image::RgbImage,
|
||||
bbox: &bounding_box::Aabb2<usize>,
|
||||
) -> Result<image::RgbImage, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let min_point = bbox.min_vertex();
|
||||
let size = bbox.size();
|
||||
let x = min_point.x as u32;
|
||||
let y = min_point.y as u32;
|
||||
let width = size.x as u32;
|
||||
let height = size.y as u32;
|
||||
|
||||
// Ensure crop bounds are within image
|
||||
let img_width = img.width();
|
||||
let img_height = img.height();
|
||||
|
||||
let crop_x = x.min(img_width.saturating_sub(1));
|
||||
let crop_y = y.min(img_height.saturating_sub(1));
|
||||
let crop_width = width.min(img_width - crop_x);
|
||||
let crop_height = height.min(img_height - crop_y);
|
||||
|
||||
Ok(image::imageops::crop_imm(img, crop_x, crop_y, crop_width, crop_height).to_image())
|
||||
}
|
||||
|
||||
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
|
||||
let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
|
||||
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
|
||||
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
|
||||
|
||||
if norm_a == 0.0 || norm_b == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
dot_product / (norm_a * norm_b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for bbox1 in &faces1.bbox {
|
||||
// let crop1 = Self::crop_face_from_image(&img1, bbox1)?;
|
||||
// let crop1_array = ndarray::Array::from_shape_vec(
|
||||
// (1, crop1.height() as usize, crop1.width() as usize, 3),
|
||||
// crop1
|
||||
// .pixels()
|
||||
// .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
// .collect(),
|
||||
// )?;
|
||||
|
||||
// let embedding1 = embedder
|
||||
// .run_models(crop1_array.view())
|
||||
// .map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
// for bbox2 in &faces2.bbox {
|
||||
// let crop2 = Self::crop_face_from_image(&img2, bbox2)?;
|
||||
// let crop2_array = ndarray::Array::from_shape_vec(
|
||||
// (1, crop2.height() as usize, crop2.width() as usize, 3),
|
||||
// crop2
|
||||
// .pixels()
|
||||
// .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
|
||||
// .collect(),
|
||||
// )?;
|
||||
|
||||
// let embedding2 = embedder
|
||||
// .run_models(crop2_array.view())
|
||||
// .map_err(|e| format!("Embedding generation failed: {}", e))?;
|
||||
|
||||
// let similarity = Self::cosine_similarity(
|
||||
// embedding1.row(0).as_slice().unwrap(),
|
||||
// embedding2.row(0).as_slice().unwrap(),
|
||||
// );
|
||||
// best_similarity = best_similarity.max(similarity);
|
||||
// }
|
||||
// }
|
||||
|
||||
use crate::errors::Error;
|
||||
pub fn compare_faces(
|
||||
image1: &[Aabb2<usize>],
|
||||
image2: &[Aabb2<usize>],
|
||||
) -> Result<f32, error_stack::Report<crate::errors::Error>> {
|
||||
use error_stack::Report;
|
||||
|
||||
if image1.is_empty() || image2.is_empty() {
|
||||
Err(Report::new(crate::errors::Error))
|
||||
.change_context(Report::new(crate::errors::Error))
|
||||
.attach_printable("One or both images have no detected faces")
|
||||
}
|
||||
Ok(0.0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user