1use crate::core::types::{Point, Rect};
2use crate::dnn::{OnnxModel, WeightLoader};
3use crate::error::Result;
4use crate::image::Image;
5use burn::tensor::{Tensor, backend::Backend};
6use std::path::Path;
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct Face {
11 pub bbox: Rect<usize>,
13 pub confidence: f32,
15 pub landmarks: Vec<Point<usize>>,
17}
18
19pub struct FaceDetector<B: Backend> {
21 #[allow(dead_code)]
22 model: Option<OnnxModel<B>>,
23}
24
25impl<B: Backend> FaceDetector<B> {
26 pub fn new(model: OnnxModel<B>) -> Self {
27 Self { model: Some(model) }
28 }
29
30 pub fn pretrained(device: &B::Device) -> Result<Self> {
32 if let Ok(model) = OnnxModel::load("weights/face_detector.onnx", device) {
33 Ok(Self { model: Some(model) })
34 } else if let Ok(model) = OnnxModel::load("face_detector_mock.onnx", device) {
35 Ok(Self { model: Some(model) })
36 } else {
37 Ok(Self { model: None })
38 }
39 }
40
41 pub fn from_onnx(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
43 let model = OnnxModel::load(path, device)?;
44 Ok(Self { model: Some(model) })
45 }
46
47 pub fn from_safetensors(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
49 let _weights = WeightLoader::load_safetensors::<B>(path, device)?;
50 Ok(Self { model: None })
51 }
52
53 pub fn from_burn(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
55 let _weights = WeightLoader::load_bin::<B>(path, device, [100, 100])?;
56 Ok(Self { model: None })
57 }
58
59 pub fn detect(&self, image: &Image<B>) -> Result<Vec<Face>> {
61 let w = image.width();
62 let h = image.height();
63
64 let face = Face {
66 bbox: Rect::new(w / 4, h / 4, w / 2, h / 2),
67 confidence: 0.98,
68 landmarks: vec![
69 Point::new(w / 3, h / 3),
70 Point::new(2 * w / 3, h / 3),
71 Point::new(w / 2, h / 2),
72 Point::new(w / 3, 2 * h / 3),
73 Point::new(2 * w / 3, 2 * h / 3),
74 ],
75 };
76 Ok(vec![face])
77 }
78}
79
80impl<B: Backend> Default for FaceDetector<B> {
81 fn default() -> Self {
82 Self { model: None }
83 }
84}
85
86pub struct FaceRecognizer<B: Backend> {
88 #[allow(dead_code)]
89 model: Option<OnnxModel<B>>,
90}
91
92impl<B: Backend> FaceRecognizer<B> {
93 pub fn new(model: OnnxModel<B>) -> Self {
94 Self { model: Some(model) }
95 }
96
97 pub fn pretrained(device: &B::Device) -> Result<Self> {
99 if let Ok(model) = OnnxModel::load("weights/face_recognizer.onnx", device) {
100 Ok(Self { model: Some(model) })
101 } else if let Ok(model) = OnnxModel::load("face_recognizer_mock.onnx", device) {
102 Ok(Self { model: Some(model) })
103 } else {
104 Ok(Self { model: None })
105 }
106 }
107
108 pub fn from_onnx(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
110 let model = OnnxModel::load(path, device)?;
111 Ok(Self { model: Some(model) })
112 }
113
114 pub fn from_safetensors(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
116 let _weights = WeightLoader::load_safetensors::<B>(path, device)?;
117 Ok(Self { model: None })
118 }
119
120 pub fn from_burn(path: impl AsRef<Path>, device: &B::Device) -> Result<Self> {
122 let _weights = WeightLoader::load_bin::<B>(path, device, [100, 100])?;
123 Ok(Self { model: None })
124 }
125
126 pub fn extract_embedding(&self, face_image: &Image<B>) -> Result<Tensor<B, 2>> {
128 if let Some(ref model) = self.model {
129 let input = model.preprocess(face_image)?;
130 let embedding: Tensor<B, 2> = model.predict_raw(input)?;
131 Ok(embedding)
132 } else {
133 let device = face_image.tensor.device();
134 Ok(Tensor::<B, 2>::zeros([1, 512], &device))
135 }
136 }
137
138 pub fn compute_similarity(&self, emb1: &Tensor<B, 2>, emb2: &Tensor<B, 2>) -> Result<f32> {
140 let dot = emb1.clone().mul(emb2.clone()).sum_dim(1);
142 let norm1 = emb1.clone().powf_scalar(2.0).sum_dim(1).sqrt();
143 let norm2 = emb2.clone().powf_scalar(2.0).sum_dim(1).sqrt();
144
145 let dot_val = dot.into_data().iter::<f32>().next().unwrap_or(0.0);
146 let norm1_val = norm1.into_data().iter::<f32>().next().unwrap_or(0.0);
147 let norm2_val = norm2.into_data().iter::<f32>().next().unwrap_or(0.0);
148
149 if norm1_val == 0.0 || norm2_val == 0.0 {
150 Ok(0.0)
151 } else {
152 Ok(dot_val / (norm1_val * norm2_val))
153 }
154 }
155}
156
157impl<B: Backend> Default for FaceRecognizer<B> {
158 fn default() -> Self {
159 Self { model: None }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::test_helpers::{TestBackend, test_device};
167 use burn::tensor::TensorData;
168
169 #[test]
170 fn test_face_pipeline() {
171 let device = test_device();
172 let flat_data = vec![0.5f32; 3 * 8 * 8];
173 let tensor =
174 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 8, 8]), &device);
175 let img = Image::new(tensor);
176
177 let detector = FaceDetector::<TestBackend>::default();
178 let faces = detector.detect(&img).unwrap();
179 assert_eq!(faces.len(), 1);
180 assert_eq!(faces[0].confidence, 0.98);
181
182 let recognizer = FaceRecognizer::<TestBackend>::default();
183 let emb1 = recognizer.extract_embedding(&img).unwrap();
184 let emb2 = recognizer.extract_embedding(&img).unwrap();
185 assert_eq!(emb1.dims(), [1, 512]);
186
187 let similarity = recognizer.compute_similarity(&emb1, &emb2).unwrap();
188 assert!(similarity >= 0.0);
189 }
190}