1use crate::error::{Result, VisionError};
12use crate::feature::KeyPoint;
13use image::DynamicImage;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum DescriptorMethod {
18 Brief,
20 Orb,
22 Hog,
24}
25
26#[derive(Debug, Clone)]
28pub struct UnifiedDescriptor {
29 pub keypoint: KeyPoint,
31 pub float_vector: Vec<f32>,
34 pub binary_vector: Option<Vec<u32>>,
36 pub method: DescriptorMethod,
38}
39
40#[derive(Debug, Clone)]
42pub struct DescriptorParams {
43 pub method: DescriptorMethod,
45 pub brief_size: usize,
47 pub patch_size: usize,
49 pub orb_num_features: usize,
51 pub hog_cell_size: usize,
53 pub hog_num_bins: usize,
55}
56
57impl Default for DescriptorParams {
58 fn default() -> Self {
59 Self {
60 method: DescriptorMethod::Brief,
61 brief_size: 256,
62 patch_size: 48,
63 orb_num_features: 500,
64 hog_cell_size: 8,
65 hog_num_bins: 9,
66 }
67 }
68}
69
70pub fn compute_descriptors(
103 img: &DynamicImage,
104 keypoints: &[KeyPoint],
105 params: &DescriptorParams,
106) -> Result<Vec<UnifiedDescriptor>> {
107 match params.method {
108 DescriptorMethod::Brief => compute_brief_unified(img, keypoints, params),
109 DescriptorMethod::Orb => compute_orb_unified(img, keypoints, params),
110 DescriptorMethod::Hog => compute_hog_unified(img, keypoints, params),
111 }
112}
113
114fn compute_brief_unified(
116 img: &DynamicImage,
117 keypoints: &[KeyPoint],
118 params: &DescriptorParams,
119) -> Result<Vec<UnifiedDescriptor>> {
120 let config = crate::feature::brief::BriefConfig {
121 descriptor_size: params.brief_size,
122 patch_size: params.patch_size,
123 use_smoothing: true,
124 smoothing_sigma: 2.0,
125 };
126
127 let brief_descs =
128 crate::feature::brief::compute_brief_descriptors(img, keypoints.to_vec(), &config)?;
129
130 let mut unified = Vec::with_capacity(brief_descs.len());
131 for desc in brief_descs {
132 let float_vector = binary_to_float(&desc.descriptor, params.brief_size);
133 unified.push(UnifiedDescriptor {
134 keypoint: desc.keypoint,
135 float_vector,
136 binary_vector: Some(desc.descriptor),
137 method: DescriptorMethod::Brief,
138 });
139 }
140
141 Ok(unified)
142}
143
144fn compute_orb_unified(
146 img: &DynamicImage,
147 _keypoints: &[KeyPoint],
148 params: &DescriptorParams,
149) -> Result<Vec<UnifiedDescriptor>> {
150 let config = crate::feature::orb::OrbConfig {
151 num_features: params.orb_num_features,
152 patch_size: params.patch_size.min(31),
153 ..crate::feature::orb::OrbConfig::default()
154 };
155
156 let orb_descs = crate::feature::orb::detect_and_compute_orb(img, &config)?;
158
159 let mut unified = Vec::with_capacity(orb_descs.len());
160 for desc in orb_descs {
161 let float_vector = binary_to_float(&desc.descriptor, 256);
162 unified.push(UnifiedDescriptor {
163 keypoint: desc.keypoint,
164 float_vector,
165 binary_vector: Some(desc.descriptor),
166 method: DescriptorMethod::Orb,
167 });
168 }
169
170 Ok(unified)
171}
172
173fn compute_hog_unified(
179 img: &DynamicImage,
180 keypoints: &[KeyPoint],
181 params: &DescriptorParams,
182) -> Result<Vec<UnifiedDescriptor>> {
183 let config = crate::feature::hog::HogConfig {
184 cell_size: params.hog_cell_size,
185 num_bins: params.hog_num_bins,
186 ..crate::feature::hog::HogConfig::default()
187 };
188
189 let hog_desc = crate::feature::hog::compute_hog(img, &config)?;
190
191 let cell_size = params.hog_cell_size;
193 let bins = params.hog_num_bins;
194 let cells_x = hog_desc.cells_x;
195
196 let mut unified = Vec::with_capacity(keypoints.len());
197
198 for kp in keypoints {
199 let cell_x = (kp.x as usize) / cell_size;
200 let cell_y = (kp.y as usize) / cell_size;
201
202 if cell_x >= hog_desc.cells_x || cell_y >= hog_desc.cells_y {
203 continue;
204 }
205
206 let mut float_vector = Vec::new();
208 for dy in -1i32..=1 {
209 for dx in -1i32..=1 {
210 let cy = cell_y as i32 + dy;
211 let cx = cell_x as i32 + dx;
212
213 if cy >= 0
214 && cy < hog_desc.cells_y as i32
215 && cx >= 0
216 && cx < hog_desc.cells_x as i32
217 {
218 let offset = (cy as usize * cells_x + cx as usize) * bins;
219 if offset + bins <= hog_desc.features.len() {
220 float_vector.extend_from_slice(&hog_desc.features[offset..offset + bins]);
221 }
222 } else {
223 float_vector.extend(std::iter::repeat_n(0.0f32, bins));
225 }
226 }
227 }
228
229 unified.push(UnifiedDescriptor {
230 keypoint: kp.clone(),
231 float_vector,
232 binary_vector: None,
233 method: DescriptorMethod::Hog,
234 });
235 }
236
237 Ok(unified)
238}
239
240fn binary_to_float(binary: &[u32], num_bits: usize) -> Vec<f32> {
242 let mut float_vec = Vec::with_capacity(num_bits);
243
244 for (word_idx, &word) in binary.iter().enumerate() {
245 for bit in 0..32 {
246 if word_idx * 32 + bit >= num_bits {
247 break;
248 }
249 let val = if word & (1 << bit) != 0 {
250 1.0f32
251 } else {
252 0.0f32
253 };
254 float_vec.push(val);
255 }
256 }
257
258 float_vec
259}
260
261pub fn match_unified_descriptors(
276 desc1: &[UnifiedDescriptor],
277 desc2: &[UnifiedDescriptor],
278 max_distance: f32,
279) -> Vec<(usize, usize, f32)> {
280 if desc1.is_empty() || desc2.is_empty() {
281 return Vec::new();
282 }
283
284 let use_binary = desc1[0].binary_vector.is_some() && desc2[0].binary_vector.is_some();
285 let mut matches = Vec::new();
286
287 for (i, d1) in desc1.iter().enumerate() {
288 let mut best_dist = f32::MAX;
289 let mut best_idx = 0;
290 let mut second_best = f32::MAX;
291
292 for (j, d2) in desc2.iter().enumerate() {
293 let dist = if use_binary {
294 match (d1.binary_vector.as_ref(), d2.binary_vector.as_ref()) {
295 (Some(b1), Some(b2)) => {
296 let hamming: u32 = b1
297 .iter()
298 .zip(b2.iter())
299 .map(|(&a, &b)| (a ^ b).count_ones())
300 .sum();
301 let max_bits = (b1.len() * 32) as f32;
302 hamming as f32 / max_bits
303 }
304 _ => euclidean_distance(&d1.float_vector, &d2.float_vector),
305 }
306 } else {
307 euclidean_distance(&d1.float_vector, &d2.float_vector)
308 };
309
310 if dist < best_dist {
311 second_best = best_dist;
312 best_dist = dist;
313 best_idx = j;
314 } else if dist < second_best {
315 second_best = dist;
316 }
317 }
318
319 if best_dist < max_distance && best_dist < second_best * 0.75 {
321 matches.push((i, best_idx, best_dist));
322 }
323 }
324
325 matches.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
326 matches
327}
328
329fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
331 let len = a.len().min(b.len());
332 if len == 0 {
333 return f32::MAX;
334 }
335
336 let sum_sq: f32 = a
337 .iter()
338 .take(len)
339 .zip(b.iter().take(len))
340 .map(|(x, y)| {
341 let d = x - y;
342 d * d
343 })
344 .sum();
345
346 sum_sq.sqrt() / (len as f32).sqrt()
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_binary_to_float() {
355 let binary = vec![0b00001111u32]; let float_vec = binary_to_float(&binary, 8);
357 assert_eq!(float_vec.len(), 8);
358 assert_eq!(float_vec[0], 1.0); assert_eq!(float_vec[3], 1.0); assert_eq!(float_vec[4], 0.0); assert_eq!(float_vec[7], 0.0); }
363
364 #[test]
365 fn test_euclidean_distance_identical() {
366 let a = vec![1.0, 2.0, 3.0];
367 let dist = euclidean_distance(&a, &a);
368 assert!(dist.abs() < 1e-6);
369 }
370
371 #[test]
372 fn test_euclidean_distance_different() {
373 let a = vec![0.0, 0.0, 0.0];
374 let b = vec![1.0, 0.0, 0.0];
375 let dist = euclidean_distance(&a, &b);
376 assert!((dist - 1.0 / 3.0f32.sqrt()).abs() < 0.01);
378 }
379
380 #[test]
381 fn test_match_unified_empty() {
382 let matches = match_unified_descriptors(&[], &[], 1.0);
383 assert!(matches.is_empty());
384 }
385
386 #[test]
387 fn test_match_unified_float_descriptors() {
388 let desc1 = vec![UnifiedDescriptor {
389 keypoint: KeyPoint {
390 x: 10.0,
391 y: 10.0,
392 scale: 1.0,
393 orientation: 0.0,
394 response: 1.0,
395 },
396 float_vector: vec![1.0, 0.0, 0.0, 0.0],
397 binary_vector: None,
398 method: DescriptorMethod::Hog,
399 }];
400
401 let desc2 = vec![
402 UnifiedDescriptor {
403 keypoint: KeyPoint {
404 x: 20.0,
405 y: 20.0,
406 scale: 1.0,
407 orientation: 0.0,
408 response: 1.0,
409 },
410 float_vector: vec![1.0, 0.0, 0.0, 0.0],
411 binary_vector: None,
412 method: DescriptorMethod::Hog,
413 },
414 UnifiedDescriptor {
415 keypoint: KeyPoint {
416 x: 30.0,
417 y: 30.0,
418 scale: 1.0,
419 orientation: 0.0,
420 response: 1.0,
421 },
422 float_vector: vec![0.0, 1.0, 0.0, 0.0],
423 binary_vector: None,
424 method: DescriptorMethod::Hog,
425 },
426 ];
427
428 let matches = match_unified_descriptors(&desc1, &desc2, 1.0);
430 assert!(matches.len() <= 1);
433 }
434
435 #[test]
436 fn test_descriptor_params_default() {
437 let params = DescriptorParams::default();
438 assert_eq!(params.method, DescriptorMethod::Brief);
439 assert_eq!(params.brief_size, 256);
440 assert!(params.patch_size > 0);
441 }
442
443 #[test]
444 fn test_compute_descriptors_brief_no_keypoints() {
445 let img = DynamicImage::new_luma8(64, 64);
446 let params = DescriptorParams {
447 method: DescriptorMethod::Brief,
448 ..DescriptorParams::default()
449 };
450
451 let descs =
452 compute_descriptors(&img, &[], ¶ms).expect("BRIEF with empty keypoints failed");
453 assert!(descs.is_empty());
454 }
455
456 #[test]
457 fn test_compute_descriptors_hog_empty_keypoints() {
458 let img = DynamicImage::new_luma8(64, 64);
459 let params = DescriptorParams {
460 method: DescriptorMethod::Hog,
461 ..DescriptorParams::default()
462 };
463
464 let descs =
465 compute_descriptors(&img, &[], ¶ms).expect("HOG with empty keypoints failed");
466 assert!(descs.is_empty());
467 }
468
469 #[test]
470 fn test_compute_hog_descriptor_has_values() {
471 let mut buf = image::GrayImage::new(64, 64);
473 for y in 0..64u32 {
474 for x in 0..64u32 {
475 buf.put_pixel(x, y, image::Luma([(x * 4) as u8]));
476 }
477 }
478 let img = DynamicImage::ImageLuma8(buf);
479
480 let keypoints = vec![KeyPoint {
481 x: 32.0,
482 y: 32.0,
483 scale: 1.0,
484 orientation: 0.0,
485 response: 1.0,
486 }];
487
488 let params = DescriptorParams {
489 method: DescriptorMethod::Hog,
490 hog_cell_size: 8,
491 hog_num_bins: 9,
492 ..DescriptorParams::default()
493 };
494
495 let descs = compute_descriptors(&img, &keypoints, ¶ms)
496 .expect("HOG descriptor computation failed");
497 assert_eq!(descs.len(), 1);
498 assert_eq!(descs[0].float_vector.len(), 81);
500 assert!(descs[0].binary_vector.is_none());
501 }
502}