use image::{GenericImageView, GrayImage, Luma};
use rand_distr::{Distribution, Normal};
use crate::{corners::Corner, definitions::Image, integral_image::integral_image, point::Point};
use super::{
BinaryDescriptor,
constants::{BRIEF_PATCH_DIAMETER, BRIEF_PATCH_RADIUS},
};
#[derive(Clone, PartialEq)]
pub struct BriefDescriptor {
pub bits: Vec<u128>,
pub corner: Corner,
}
impl BinaryDescriptor for BriefDescriptor {
fn get_size(&self) -> u32 {
(self.bits.len() * 128) as u32
}
fn hamming_distance(&self, other: &Self) -> u32 {
assert_eq!(self.get_size(), other.get_size());
self.bits
.iter()
.zip(other.bits.iter())
.fold(0, |acc, x| acc + (x.0 ^ x.1).count_ones())
}
fn get_bit_subset(&self, bits: &[u32]) -> u128 {
assert!(
bits.len() <= 128,
"Can't extract more than 128 bits (found {})",
bits.len()
);
let mut subset = 0;
for b in bits {
subset <<= 1;
subset += (self.bits[(b / 128) as usize] >> (b % 128)) % 2;
}
subset
}
fn position(&self) -> Point<u32> {
self.corner.into()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TestPair {
pub p0: Point<u32>,
pub p1: Point<u32>,
}
fn local_pixel_average(integral_image: &Image<Luma<u32>>, x: u32, y: u32, radius: u32) -> u8 {
if radius == 0 {
return 0;
}
let y_min = y.saturating_sub(radius);
let x_min = x.saturating_sub(radius);
let y_max = u32::min(y + radius + 1, integral_image.height() - 1);
let x_max = u32::min(x + radius + 1, integral_image.width() - 1);
let pixel_area = (y_max - y_min) * (x_max - x_min);
if pixel_area == 0 {
return 0;
}
let (bottom_right, top_left, top_right, bottom_left) = unsafe {
(
integral_image.unsafe_get_pixel(x_max, y_max)[0],
integral_image.unsafe_get_pixel(x_min, y_min)[0],
integral_image.unsafe_get_pixel(x_max, y_min)[0],
integral_image.unsafe_get_pixel(x_min, y_max)[0],
)
};
let total_intensity = bottom_right + top_left - top_right - bottom_left;
(total_intensity / pixel_area) as u8
}
pub(crate) fn brief_impl(
integral_image: &Image<Luma<u32>>,
keypoints: &[Point<u32>],
test_pairs: &[TestPair],
length: usize,
) -> Result<Vec<BriefDescriptor>, String> {
if !length.is_multiple_of(128) {
return Err(format!(
"BRIEF descriptor length must be a multiple of 128 bits (found {})",
length
));
}
if length != test_pairs.len() {
return Err(format!(
"BRIEF descriptor length must be equal to the number of test pairs ({} != {})",
length,
test_pairs.len()
));
}
let mut descriptors: Vec<BriefDescriptor> = Vec::with_capacity(keypoints.len());
let (width, height) = (integral_image.width(), integral_image.height());
for keypoint in keypoints {
if keypoint.x <= BRIEF_PATCH_RADIUS
|| keypoint.x + BRIEF_PATCH_RADIUS >= width
|| keypoint.y <= BRIEF_PATCH_RADIUS
|| keypoint.y + BRIEF_PATCH_RADIUS >= height
{
return Err(format!(
"Found keypoint within {} px of image edge: ({}, {})",
BRIEF_PATCH_RADIUS + 1,
keypoint.x,
keypoint.y
));
}
let patch_top_left = Point {
x: keypoint.x - BRIEF_PATCH_RADIUS,
y: keypoint.y - BRIEF_PATCH_RADIUS,
};
let mut descriptor = BriefDescriptor {
bits: Vec::with_capacity(length / 128),
corner: Corner {
x: keypoint.x,
y: keypoint.y,
score: 0.,
},
};
let mut descriptor_chunk = 0u128;
for (idx, test_pair) in test_pairs.iter().enumerate() {
if idx != 0 && idx % 128 == 0 {
descriptor.bits.push(descriptor_chunk);
descriptor_chunk = 0;
}
let p0 = Point {
x: test_pair.p0.x + patch_top_left.x + 1,
y: test_pair.p0.y + patch_top_left.y + 1,
};
let p1 = Point {
x: test_pair.p1.x + patch_top_left.x + 1,
y: test_pair.p1.y + patch_top_left.y + 1,
};
descriptor_chunk += (local_pixel_average(integral_image, p0.x, p0.y, 2)
< local_pixel_average(integral_image, p1.x, p1.y, 2))
as u128;
descriptor_chunk <<= 1;
}
descriptor.bits.push(descriptor_chunk);
descriptors.push(descriptor);
}
Ok(descriptors)
}
pub fn brief(
image: &GrayImage,
keypoints: &[Point<u32>],
length: usize,
override_test_pairs: Option<&Vec<TestPair>>,
) -> Result<(Vec<BriefDescriptor>, Vec<TestPair>), String> {
let test_pairs = if let Some(t) = override_test_pairs {
t.clone()
} else {
let test_pair_distribution = Normal::new(BRIEF_PATCH_RADIUS as f32 + 1.0, 6.6).unwrap();
let mut rng = rand::rng();
let mut test_pairs: Vec<TestPair> = Vec::with_capacity(length);
while test_pairs.len() < length {
let (x0, y0, x1, y1) = (
test_pair_distribution.sample(&mut rng) as u32,
test_pair_distribution.sample(&mut rng) as u32,
test_pair_distribution.sample(&mut rng) as u32,
test_pair_distribution.sample(&mut rng) as u32,
);
if x0 < BRIEF_PATCH_DIAMETER
&& y0 < BRIEF_PATCH_DIAMETER
&& x1 < BRIEF_PATCH_DIAMETER
&& y1 < BRIEF_PATCH_DIAMETER
{
test_pairs.push(TestPair {
p0: Point::new(x0, y0),
p1: Point::new(x1, y1),
});
}
}
test_pairs.clone()
};
let integral_image = integral_image(image);
let descriptors = brief_impl(&integral_image, keypoints, &test_pairs, length)?;
Ok((descriptors, test_pairs))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_hamming_distance() {
let d1 = BriefDescriptor {
bits: vec![
0xe41749023c71b74df05b57165519a180,
0x9c06c422620e05d01105618cb3a2dcf1,
],
corner: Corner::new(0, 0, 0.),
};
let d2 = BriefDescriptor {
bits: vec![
0x8af22bb0596edc267c6f72cf425ebe1a,
0xc1f6291520e474e8fa114e15420413d1,
],
corner: Corner::new(0, 0, 0.),
};
assert_eq!(d1.hamming_distance(&d2), 134);
}
#[test]
fn test_get_bit_subset() {
let d = BriefDescriptor {
bits: vec![
0xdbe3de5bd950adf3d730034f9e4a55f7,
0xf275f00f6243892a18ffefd0499996ee,
],
corner: Corner::new(0, 0, 0.),
};
let bits = vec![
226, 38, 212, 210, 60, 205, 68, 184, 47, 105, 152, 169, 11, 39, 76, 217, 183, 113, 189,
251, 37, 181, 62, 28, 148, 92, 251, 77, 222, 148, 56, 142,
];
assert_eq!(d.get_bit_subset(&bits), 0b11001010011100011100011111011110);
}
#[test]
fn test_local_pixel_average() {
let image = gray_image!(
186, 106, 86, 22, 191, 10, 204, 217;
37, 188, 82, 28, 99, 110, 166, 202;
36, 97, 176, 54, 141, 42, 44, 40;
248, 163, 218, 204, 117, 121, 151, 135;
138, 100, 77, 115, 93, 246, 204, 163;
123, 1, 104, 97, 67, 208, 0, 116;
5, 237, 254, 171, 172, 165, 50, 39;
92, 31, 238, 88, 44, 67, 140, 255
);
let integral_image: Image<Luma<u32>> = integral_image(&image);
assert_eq!(local_pixel_average(&integral_image, 3, 3, 2), 117);
}
}
#[cfg(not(miri))]
#[cfg(test)]
mod benches {
use super::*;
use crate::utils::gray_bench_image;
use rand::Rng;
use test::{Bencher, black_box};
#[bench]
#[ignore]
fn bench_brief_random_test_pairs_1000_keypoints(b: &mut Bencher) {
let image = gray_bench_image(640, 480);
let mut rng = rand::rng();
let keypoints = (0..1000)
.map(|_| {
Point::new(
rng.random_range(24..image.width() - 24),
rng.random_range(24..image.height() - 24),
)
})
.collect::<Vec<Point<u32>>>();
b.iter(|| {
black_box(brief(&image, &keypoints, 256, None)).unwrap();
})
}
#[bench]
#[ignore]
fn bench_brief_fixed_test_pairs_1000_keypoints(b: &mut Bencher) {
let image = gray_bench_image(640, 480);
let mut rng = rand::rng();
let keypoints = (0..1000)
.map(|_| {
Point::new(
rng.random_range(24..image.width() - 24),
rng.random_range(24..image.height() - 24),
)
})
.collect::<Vec<Point<u32>>>();
let (_, test_pairs) = brief(&image, &keypoints, 256, None).unwrap();
b.iter(|| {
black_box(brief(&image, &keypoints, 256, Some(&test_pairs))).unwrap();
})
}
}