1use crate::definitions::{HasBlack, HasWhite, Image};
6use image::{GenericImage, GenericImageView, ImageBuffer, Luma};
7use itertools::Itertools;
8use std::marker::PhantomData;
9use std::ops::Range;
10
11#[derive(Copy, Clone, PartialEq, Eq, Debug)]
15pub struct HaarFeature {
16 sign: Sign,
17 feature_type: HaarFeatureType,
18 block_size: Size<Pixels>,
19 left: u8,
20 top: u8,
21}
22
23#[derive(Copy, Clone, PartialEq, Eq, Debug)]
26enum Sign {
27 Positive,
29 Negative,
31}
32
33#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
37pub enum HaarFeatureType {
38 TwoRegionHorizontal,
45 ThreeRegionHorizontal,
52 TwoRegionVertical,
61 ThreeRegionVertical,
72 FourRegion,
82}
83
84impl HaarFeatureType {
85 fn shape(&self) -> Size<Blocks> {
87 match *self {
88 HaarFeatureType::TwoRegionHorizontal => Size::new(2, 1),
89 HaarFeatureType::ThreeRegionHorizontal => Size::new(3, 1),
90 HaarFeatureType::TwoRegionVertical => Size::new(1, 2),
91 HaarFeatureType::ThreeRegionVertical => Size::new(1, 3),
92 HaarFeatureType::FourRegion => Size::new(2, 2),
93 }
94 }
95}
96
97impl HaarFeature {
98 pub fn evaluate(&self, integral: &Image<Luma<u32>>) -> i32 {
100 let size = feature_size(self.feature_type, self.block_size);
108 assert!(integral.width() > size.width as u32 + self.left as u32);
109 assert!(integral.height() > size.height as u32 + self.top as u32);
110
111 let a = self.block_boundary(0, 0);
121 let b = self.block_boundary(1, 0);
122 let e = self.block_boundary(0, 1);
123 let f = self.block_boundary(1, 1);
124
125 #[rustfmt::skip]
126 let sum = match self.feature_type {
127 HaarFeatureType::TwoRegionHorizontal => {
128 let c = self.block_boundary(2, 0);
129 let g = self.block_boundary(2, 1);
130
131 unsafe {
132 read(integral, a)
133 - 2 * read(integral, b)
134 + read(integral, c)
135 - read(integral, e)
136 + 2 * read(integral, f)
137 - read(integral, g)
138 }
139 }
140
141 HaarFeatureType::ThreeRegionHorizontal => {
142 let c = self.block_boundary(2, 0);
143 let g = self.block_boundary(2, 1);
144 let d = self.block_boundary(3, 0);
145 let h = self.block_boundary(3, 1);
146
147 unsafe {
148 read(integral, a)
149 - 2 * read(integral, b)
150 + 2 * read(integral, c)
151 - read(integral, d)
152 - read(integral, e)
153 + 2 * read(integral, f)
154 - 2 * read(integral, g)
155 + read(integral, h)
156 }
157 }
158
159 HaarFeatureType::TwoRegionVertical => {
160 let i = self.block_boundary(0, 2);
161 let j = self.block_boundary(1, 2);
162
163 unsafe {
164 read(integral, a)
165 - read(integral, b)
166 - 2 * read(integral, e)
167 + 2 * read(integral, f)
168 + read(integral, i)
169 - read(integral, j)
170 }
171 }
172
173 HaarFeatureType::ThreeRegionVertical => {
174 let i = self.block_boundary(0, 2);
175 let j = self.block_boundary(1, 2);
176 let m = self.block_boundary(0, 3);
177 let n = self.block_boundary(1, 3);
178
179 unsafe {
180 read(integral, a)
181 - read(integral, b)
182 - 2 * read(integral, e)
183 + 2 * read(integral, f)
184 + 2 * read(integral, i)
185 - 2 * read(integral, j)
186 - read(integral, m)
187 + read(integral, n)
188 }
189 }
190
191 HaarFeatureType::FourRegion => {
192 let c = self.block_boundary(2, 0);
193 let g = self.block_boundary(2, 1);
194 let i = self.block_boundary(0, 2);
195 let j = self.block_boundary(1, 2);
196 let k = self.block_boundary(2, 2);
197
198 unsafe {
199 read(integral, a)
200 - 2 * read(integral, b)
201 + read(integral, c)
202 - 2 * read(integral, e)
203 + 4 * read(integral, f)
204 - 2 * read(integral, g)
205 + read(integral, i)
206 - 2 * read(integral, j)
207 + read(integral, k)
208 }
209 }
210 };
211
212 let mul = if self.sign == Sign::Positive {
213 1i32
214 } else {
215 -1i32
216 };
217 sum * mul
218 }
219
220 fn block_boundary(&self, x: u8, y: u8) -> (u8, u8) {
221 (
222 self.left + x * self.block_width(),
223 self.top + y * self.block_height(),
224 )
225 }
226
227 fn blocks_wide(&self) -> u8 {
229 self.feature_type.shape().width
230 }
231
232 fn blocks_high(&self) -> u8 {
234 self.feature_type.shape().height
235 }
236
237 fn block_width(&self) -> u8 {
239 self.block_size.width
240 }
241
242 fn block_height(&self) -> u8 {
244 self.block_size.height
245 }
246}
247
248unsafe fn read(integral: &Image<Luma<u32>>, location: (u8, u8)) -> i32 {
249 integral.unsafe_get_pixel(location.0 as u32, location.1 as u32)[0] as i32
250}
251
252fn feature_size(feature_type: HaarFeatureType, block_size: Size<Pixels>) -> Size<Pixels> {
254 let shape = feature_type.shape();
255 Size::new(
256 shape.width * block_size.width,
257 shape.height * block_size.height,
258 )
259}
260
261pub fn enumerate_haar_features(frame_width: u8, frame_height: u8) -> Vec<HaarFeature> {
263 let frame_size = Size::new(frame_width, frame_height);
264
265 let feature_types = vec![
266 HaarFeatureType::TwoRegionHorizontal,
267 HaarFeatureType::ThreeRegionHorizontal,
268 HaarFeatureType::TwoRegionVertical,
269 HaarFeatureType::ThreeRegionVertical,
270 HaarFeatureType::FourRegion,
271 ];
272
273 feature_types
274 .into_iter()
275 .flat_map(|feature_type| haar_features_of_type(feature_type, frame_size))
276 .collect()
277}
278
279fn haar_features_of_type(
280 feature_type: HaarFeatureType,
281 frame_size: Size<Pixels>,
282) -> Vec<HaarFeature> {
283 let mut features = Vec::new();
284
285 for block_size in block_sizes(feature_type.shape(), frame_size) {
286 for (left, top) in feature_positions(feature_size(feature_type, block_size), frame_size) {
287 for &sign in [Sign::Positive, Sign::Negative].iter() {
288 features.push(HaarFeature {
289 sign,
290 feature_type,
291 block_size,
292 left,
293 top,
294 });
295 }
296 }
297 }
298
299 features
300}
301
302#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
304struct Pixels(u8);
305
306#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
308struct Blocks(u8);
309
310#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
312struct Size<T> {
313 width: u8,
314 height: u8,
315 units: PhantomData<T>,
316}
317
318impl<T> Size<T> {
319 fn new(width: u8, height: u8) -> Size<T> {
320 Size {
321 width,
322 height,
323 units: PhantomData,
324 }
325 }
326}
327
328fn block_sizes(feature_shape: Size<Blocks>, frame_size: Size<Pixels>) -> Vec<Size<Pixels>> {
330 (1..frame_size.width / feature_shape.width + 1)
331 .cartesian_product(1..frame_size.height / feature_shape.height + 1)
332 .map(|(w, h)| Size::new(w, h))
333 .collect()
334}
335
336fn start_positions(inner: u8, outer: u8) -> Range<u8> {
339 let upper = if inner > outer { 0 } else { outer - inner + 1 };
340 0..upper
341}
342
343fn feature_positions(feature_size: Size<Pixels>, frame_size: Size<Pixels>) -> Vec<(u8, u8)> {
345 start_positions(feature_size.width, frame_size.width)
346 .cartesian_product(start_positions(feature_size.height, frame_size.height))
347 .collect()
348}
349
350pub fn number_of_haar_features(width: u32, height: u32) -> u32 {
362 let num_positive_features =
363 num_features(width, 2) * num_features(height, 1)
365 + num_features(width, 3) * num_features(height, 1)
367 + num_features(width, 1) * num_features(height, 2)
369 + num_features(width, 1) * num_features(height, 3)
371 + num_features(width, 2) * num_features(height, 2);
373
374 num_positive_features * 2
375}
376
377fn num_features(image_side: u32, num_blocks: u32) -> u32 {
378 let m = image_side / num_blocks;
379 m * (image_side + 1) - num_blocks * ((m * (m + 1)) / 2)
380}
381
382pub fn draw_haar_feature<I>(image: &I, feature: HaarFeature) -> Image<I::Pixel>
385where
386 I: GenericImage,
387 I::Pixel: HasBlack + HasWhite,
388{
389 let mut out = ImageBuffer::new(image.width(), image.height());
390 out.copy_from(image, 0, 0).unwrap();
391 draw_haar_feature_mut(&mut out, feature);
392 out
393}
394
395pub fn draw_haar_feature_mut<I>(image: &mut I, feature: HaarFeature)
398where
399 I: GenericImage,
400 I::Pixel: HasBlack + HasWhite,
401{
402 let parity_shift = if feature.sign == Sign::Positive { 0 } else { 1 };
403
404 for w in 0..feature.blocks_wide() {
405 for h in 0..feature.blocks_high() {
406 let parity = (w + h + parity_shift) % 2;
407 let color = if parity == 0 {
408 I::Pixel::white()
409 } else {
410 I::Pixel::black()
411 };
412 for x in 0..feature.block_width() {
413 for y in 0..feature.block_height() {
414 let px = feature.left + w * feature.block_width() + x;
415 let py = feature.top + h * feature.block_height() + y;
416 image.put_pixel(px as u32, py as u32, color);
417 }
418 }
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use crate::integral_image::{integral_image, sum_image_pixels};
427 use crate::utils::gray_bench_image;
428 use ::test;
429
430 #[test]
431 fn test_block_sizes() {
432 assert_eq!(
433 block_sizes(
434 HaarFeatureType::TwoRegionHorizontal.shape(),
435 Size::new(1, 1)
436 ),
437 vec![]
438 );
439
440 assert_eq!(
441 block_sizes(
442 HaarFeatureType::TwoRegionHorizontal.shape(),
443 Size::new(2, 1)
444 ),
445 vec![Size::new(1, 1)]
446 );
447
448 assert_eq!(
449 block_sizes(
450 HaarFeatureType::TwoRegionHorizontal.shape(),
451 Size::new(5, 1)
452 ),
453 vec![Size::new(1, 1), Size::new(2, 1)]
454 );
455
456 assert_eq!(
457 block_sizes(HaarFeatureType::TwoRegionVertical.shape(), Size::new(1, 2)),
458 vec![Size::new(1, 1)]
459 );
460 }
461
462 #[test]
463 fn test_feature_positions() {
464 assert_eq!(feature_positions(Size::new(2, 3), Size::new(2, 2)), vec![]);
465
466 assert_eq!(
467 feature_positions(Size::new(2, 2), Size::new(2, 2)),
468 vec![(0, 0)]
469 );
470
471 assert_eq!(
472 feature_positions(Size::new(2, 2), Size::new(3, 2)),
473 vec![(0, 0), (1, 0)]
474 );
475
476 assert_eq!(
477 feature_positions(Size::new(2, 2), Size::new(3, 3)),
478 vec![(0, 0), (0, 1), (1, 0), (1, 1)]
479 );
480 }
481
482 #[test]
483 fn test_number_of_haar_features() {
484 for h in 0..6 {
485 for w in 0..6 {
486 let features = enumerate_haar_features(w, h);
487 let actual = features.len() as u32;
488 let expected = number_of_haar_features(w as u32, h as u32);
489 assert_eq!(actual, expected, "w = {}, h = {}", w, h);
490 }
491 }
492 }
493
494 #[test]
495 #[should_panic]
496 fn test_haar_invalid_image_size_top_left() {
497 let image = gray_image!(type: u32, 0, 0; 0, 1);
498 let feature = HaarFeature {
499 sign: Sign::Positive,
500 feature_type: HaarFeatureType::TwoRegionHorizontal,
501 block_size: Size::new(1, 1),
502 left: 0,
503 top: 0,
504 };
505 let _ = feature.evaluate(&image);
508 }
509
510 #[test]
511 fn test_haar_valid_image_size_top_left() {
512 let image = gray_image!(type: u32, 0, 0, 0; 0, 1, 1);
513 let feature = HaarFeature {
514 sign: Sign::Positive,
515 feature_type: HaarFeatureType::TwoRegionHorizontal,
516 block_size: Size::new(1, 1),
517 left: 0,
518 top: 0,
519 };
520 let x = feature.evaluate(&image);
521 assert_eq!(x, 1);
522 }
523
524 #[test]
525 #[should_panic]
526 fn test_haar_invalid_image_size_with_offset_feature() {
527 let image = gray_image!(type: u32, 0, 0, 0; 0, 1, 1);
528 let feature = HaarFeature {
529 sign: Sign::Positive,
530 feature_type: HaarFeatureType::TwoRegionHorizontal,
531 block_size: Size::new(1, 1),
532 left: 1,
533 top: 0,
534 };
535 let _ = feature.evaluate(&image);
538 }
539
540 #[test]
541 fn test_haar_valid_image_size_with_offset_feature() {
542 let image = gray_image!(type: u32, 0, 0, 0, 0; 0, 1, 1, 1);
543 let feature = HaarFeature {
544 sign: Sign::Positive,
545 feature_type: HaarFeatureType::TwoRegionHorizontal,
546 block_size: Size::new(1, 1),
547 left: 1,
548 top: 0,
549 };
550 let x = feature.evaluate(&image);
551 assert_eq!(x, 0);
552 }
553
554 #[test]
555 fn test_two_region_horizontal() {
556 let image = gray_image!(
557 1u8, 2u8, 3u8, 4u8, 5u8;
558 6u8, 7u8, 8u8,9u8, 0u8;9u8, 8u8, 7u8,6u8, 5u8;4u8, 3u8, 2u8,1u8, 0u8;6u8, 5u8, 4u8, 2u8, 1u8 );
564
565 let integral = integral_image(&image);
566 let feature = HaarFeature {
567 sign: Sign::Positive,
568 feature_type: HaarFeatureType::TwoRegionHorizontal,
569 block_size: Size::new(2, 3),
570 left: 1,
571 top: 1,
572 };
573 assert_eq!(feature.evaluate(&integral), 14i32);
574 }
575
576 #[test]
577 fn test_three_region_vertical() {
578 let image = gray_image!(
579 1u8, 2u8,3u8, 4u8, 5u8;
581 6u8, 7u8,8u8, 9u8, 0u8;
583 9u8, 8u8,7u8, 6u8, 5u8;
585 4u8, 3u8, 2u8, 1u8, 0u8;
587 6u8, 5u8, 4u8, 2u8, 1u8);
588
589 let integral = integral_image(&image);
590 let feature = HaarFeature {
591 sign: Sign::Negative,
592 feature_type: HaarFeatureType::ThreeRegionVertical,
593 block_size: Size::new(2, 1),
594 left: 0,
595 top: 0,
596 };
597 assert_eq!(feature.evaluate(&integral), -7i32);
598 }
599
600 #[test]
601 fn test_four_region() {
602 let image = gray_image!(
603 1u8,2u8, 3u8,4u8, 5u8;6u8,7u8, 8u8,9u8, 0u8;9u8,8u8, 7u8,6u8, 5u8;4u8,3u8, 2u8,1u8, 0u8;6u8, 5u8, 4u8, 2u8, 1u8);
611
612 let integral = integral_image(&image);
613 let feature = HaarFeature {
614 sign: Sign::Positive,
615 feature_type: HaarFeatureType::FourRegion,
616 block_size: Size::new(2, 2),
617 left: 1,
618 top: 0,
619 };
620
621 assert_eq!(feature.evaluate(&integral), -6i32);
622 }
623
624 fn reference_evaluate(feature: HaarFeature, integral: &Image<Luma<u32>>) -> i32 {
626 let parity_shift = if feature.sign == Sign::Positive { 0 } else { 1 };
627
628 let mut sum = 0i32;
629
630 for w in 0..feature.blocks_wide() {
631 let left = feature.left + feature.block_width() * w;
632 let right = left + feature.block_width() - 1;
633
634 for h in 0..feature.blocks_high() {
635 let top = feature.top + feature.block_height() * h;
636 let bottom = top + feature.block_height() - 1;
637 let parity = (w + h + parity_shift) & 1;
638 let multiplier = 1 - 2 * (parity as i32);
639
640 let block_sum = sum_image_pixels(
641 integral,
642 left as u32,
643 top as u32,
644 right as u32,
645 bottom as u32,
646 )[0] as i32;
647 sum += multiplier * block_sum;
648 }
649 }
650
651 sum
652 }
653
654 #[test]
655 fn test_haar_evaluate_against_reference_implementation() {
656 for w in 0..6 {
657 for h in 0..6 {
658 let features = enumerate_haar_features(w, h);
659 let image = gray_bench_image(w as u32, h as u32);
660 let integral = integral_image(&image);
661
662 for feature in features {
663 let actual = feature.evaluate(&integral);
664 let expected = reference_evaluate(feature, &integral);
665 assert_eq!(actual, expected, "w = {}, h = {}", w, h);
666 }
667 }
668 }
669 }
670
671 #[test]
672 fn test_draw_haar_feature_two_region_horizontal() {
673 let image = gray_image!(
674 1u8, 2u8, 3u8, 4u8, 5u8;
675 6u8, 7u8, 8u8,9u8, 0u8;9u8, 8u8, 7u8,6u8, 5u8;4u8, 3u8, 2u8,1u8, 0u8;6u8, 5u8, 4u8, 2u8, 1u8);
681
682 let feature = HaarFeature {
683 sign: Sign::Positive,
684 feature_type: HaarFeatureType::TwoRegionHorizontal,
685 block_size: Size::new(2, 3),
686 left: 1,
687 top: 1,
688 };
689 let actual = draw_haar_feature(&image, feature);
690
691 let expected = gray_image!(
692 1u8, 2u8, 3u8, 4u8, 5u8;
693 6u8, 255u8, 255u8,0u8, 0u8;9u8, 255u8, 255u8,0u8, 0u8;4u8, 255u8, 255u8,0u8, 0u8;6u8, 5u8, 4u8, 2u8, 1u8);
699
700 assert_pixels_eq!(actual, expected);
701 }
702
703 #[test]
704 fn test_draw_haar_feature_four_region() {
705 let image = gray_image!(
706 1u8,2u8, 3u8,4u8, 5u8;6u8,7u8, 8u8,9u8, 0u8;9u8,8u8, 7u8,6u8, 5u8;4u8,3u8, 2u8,1u8, 0u8;6u8, 5u8, 4u8, 2u8, 1u8);
714
715 let feature = HaarFeature {
716 sign: Sign::Positive,
717 feature_type: HaarFeatureType::FourRegion,
718 block_size: Size::new(2, 2),
719 left: 1,
720 top: 0,
721 };
722
723 let actual = draw_haar_feature(&image, feature);
724
725 let expected = gray_image!(
726 1u8,255u8, 255u8,0u8, 0u8; 6u8,255u8, 255u8,0u8, 0u8; 9u8,0u8, 0u8, 255u8, 255u8;4u8,0u8, 0u8, 255u8, 255u8;6u8, 5u8, 4u8, 2u8, 1u8);
734
735 assert_pixels_eq!(actual, expected);
736 }
737
738 #[bench]
739 fn bench_evaluate_all_features_10x10(b: &mut test::Bencher) {
740 let features = enumerate_haar_features(10, 10);
742 let image = gray_bench_image(10, 10);
743 let integral = integral_image(&image);
744
745 b.iter(|| {
746 for feature in &features {
747 let x = feature.evaluate(&integral);
748 test::black_box(x);
749 }
750 });
751 }
752}