imageproc/
haar.rs

1//! Functions for creating and evaluating [Haar-like features].
2//!
3//! [Haar-like features]: https://en.wikipedia.org/wiki/Haar-like_features
4
5use crate::definitions::{HasBlack, HasWhite, Image};
6use image::{GenericImage, GenericImageView, ImageBuffer, Luma};
7use itertools::Itertools;
8use std::marker::PhantomData;
9use std::ops::Range;
10
11/// A [Haar-like feature].
12///
13/// [Haar-like feature]: https://en.wikipedia.org/wiki/Haar-like_features
14#[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/// Whether the top left region in a Haar-like feature is counted
24/// with positive or negative sign.
25#[derive(Copy, Clone, PartialEq, Eq, Debug)]
26enum Sign {
27    /// Top left region is counted with a positive sign.
28    Positive,
29    /// Top left region is counted with a negative sign.
30    Negative,
31}
32
33/// The type of a Haar-like feature determines the number of regions it contains and their orientation.
34/// The diagrams in the comments for each variant use the symbols (*, &) to represent either
35/// (+, -) or (-, +), depending on which `Sign` the feature type is used with.
36#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
37pub enum HaarFeatureType {
38    /// Two horizontally-adjacent regions of equal width.
39    /// <pre>
40    ///      -----------
41    ///     |  *  |  &  |
42    ///      -----------
43    /// </pre>
44    TwoRegionHorizontal,
45    /// Three horizontally-adjacent regions of equal width.
46    /// <pre>
47    ///      -----------------
48    ///     |  *  |  &  |  *  |
49    ///      -----------------
50    /// </pre>
51    ThreeRegionHorizontal,
52    /// Two vertically-adjacent regions of equal height.
53    /// <pre>
54    ///      -----
55    ///     |  *  |
56    ///      -----
57    ///     |  &  |
58    ///      -----
59    /// </pre>
60    TwoRegionVertical,
61    /// Three vertically-adjacent regions of equal height.
62    /// <pre>
63    ///      -----
64    ///     |  *  |
65    ///      -----
66    ///     |  &  |
67    ///      -----
68    ///     |  *  |
69    ///      -----
70    /// </pre>
71    ThreeRegionVertical,
72    /// Four regions arranged in a two-by-two grid. The two columns
73    /// have equal width and the two rows have equal height.
74    /// <pre>
75    ///      -----------
76    ///     |  *  |  &  |
77    ///      -----------
78    ///     |  &  |  *  |
79    ///      -----------
80    /// </pre>
81    FourRegion,
82}
83
84impl HaarFeatureType {
85    // The width and height of Haar-like feature, in blocks.
86    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    /// Evaluates the Haar-like feature on an integral image.
99    pub fn evaluate(&self, integral: &Image<Luma<u32>>) -> i32 {
100        // This check increases the run time of bench_evaluate_all_features_10x10
101        // by approximately 16%. Without it this function is unsafe as insufficiently
102        // large input images result in out of bounds accesses.
103        //
104        // We could alternatively create a new API where an image and a set of filters
105        // are validated to be compatible up front, or just mark the function
106        // as unsafe and document the requirement on image size.
107        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        // The corners of each block are lettered. Not all letters are evaluated for each feature type.
112        // A   B   C   D
113        //
114        // E   F   G   H
115        //
116        // I   J   K   L
117        //
118        // M   N   O
119
120        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    /// Width of this feature in blocks.
228    fn blocks_wide(&self) -> u8 {
229        self.feature_type.shape().width
230    }
231
232    /// Height of this feature in blocks.
233    fn blocks_high(&self) -> u8 {
234        self.feature_type.shape().height
235    }
236
237    /// Width of each block in pixels.
238    fn block_width(&self) -> u8 {
239        self.block_size.width
240    }
241
242    /// Height of each block in pixels.
243    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
252// The total width and height of a feature with the given type and block size.
253fn 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
261/// Returns a vector of all valid Haar-like features for an image with given width and height.
262pub 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// Indicates that a size size is measured in pixels, e.g. the width of an individual block within a Haar-like feature.
303#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
304struct Pixels(u8);
305
306// Indicates that a size is measured in blocks, e.g. the width of a Haar-like feature in blocks.
307#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
308struct Blocks(u8);
309
310// A Size, measured either in pixels (T = Pixels) or in blocks (T = Blocks)
311#[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
328// Returns the valid block sizes for a feature of shape `feature_shape` in a frame of size `frame_size`.
329fn 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
336// Returns the start positions for an interval of length `inner` for which the
337// interval is wholly contained within an interval of length `outer`.
338fn start_positions(inner: u8, outer: u8) -> Range<u8> {
339    let upper = if inner > outer { 0 } else { outer - inner + 1 };
340    0..upper
341}
342
343// Returns all valid (left, top) coordinates for a feature of the given total size
344fn 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
350/// Returns the number of distinct Haar-like features for an image of the given dimensions.
351///
352/// Includes positive and negative, two and three region, vertical and horizontal features,
353/// as well as positive and negative four region features.
354///
355/// Consider a `k`-region horizontal feature in an image of height `1` and width `w`. The largest valid block size
356/// for such a feature is `M = floor(w / k)`, and for a block size `s` there are `(w + 1) - 2 * s`
357/// valid locations for the leftmost column of this feature.
358/// Summing over `s` gives `M * (w + 1) - k * [(M * (M + 1)) / 2]`.
359///
360/// An equivalent argument applies vertically.
361pub fn number_of_haar_features(width: u32, height: u32) -> u32 {
362    let num_positive_features =
363        // Two-region horizontal
364        num_features(width, 2) * num_features(height, 1)
365        // Three-region horizontal
366        + num_features(width, 3) * num_features(height, 1)
367        // Two-region vertical
368        + num_features(width, 1) * num_features(height, 2)
369        // Three-region vertical
370        + num_features(width, 1) * num_features(height, 3)
371        // Four-region
372        + 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
382/// Draws the given Haar-like feature on an image, drawing pixels
383/// with a positive sign white and those with a negative sign black.
384pub 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
395/// Draws the given Haar-like feature on an image in place, drawing pixels
396/// with a positive sign white and those with a negative sign black.
397pub 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        // For a haar feature of width 2 the input image needs to have width
506        // at least 2, and so its integral image needs to have width at least 3.
507        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        // The feature's left offset would result in attempting to
536        // read outside the image boundaries
537        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                 /***+++++++++*****---------***/
559            6u8, /**/7u8, 8u8,/**/ 9u8, 0u8;/**/
560            9u8, /**/8u8, 7u8,/**/ 6u8, 5u8;/**/
561            4u8, /**/3u8, 2u8,/**/ 1u8, 0u8;/**/
562                 /***+++++++++*****---------***/
563            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        /*****************/
580        /*-*/1u8, 2u8,/*-*/ 3u8, 4u8, 5u8;
581        /*****************/
582        /*+*/6u8, 7u8,/*+*/ 8u8, 9u8, 0u8;
583        /*****************/
584        /*-*/9u8, 8u8,/*-*/ 7u8, 6u8, 5u8;
585        /*****************/
586             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            /*****************************/
604        1u8,/**/2u8, 3u8,/**/ 4u8, 5u8;/**/
605        6u8,/**/7u8, 8u8,/**/ 9u8, 0u8;/**/
606            /*****************************/
607        9u8,/**/8u8, 7u8,/**/ 6u8, 5u8;/**/
608        4u8,/**/3u8, 2u8,/**/ 1u8, 0u8;/**/
609            /*****************************/
610        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    // Reference implementation of Haar-like feature evaluation, to validate faster implementations against.
625    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                 /***+++++++++*****---------***/
676            6u8, /**/7u8, 8u8,/**/ 9u8, 0u8;/**/
677            9u8, /**/8u8, 7u8,/**/ 6u8, 5u8;/**/
678            4u8, /**/3u8, 2u8,/**/ 1u8, 0u8;/**/
679                 /***+++++++++*****---------***/
680            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                 /***+++++++++++++*****---------***/
694            6u8, /**/255u8, 255u8,/**/ 0u8, 0u8;/**/
695            9u8, /**/255u8, 255u8,/**/ 0u8, 0u8;/**/
696            4u8, /**/255u8, 255u8,/**/ 0u8, 0u8;/**/
697                 /***+++++++++++++*****---------***/
698            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            /*****************************/
707        1u8,/**/2u8, 3u8,/**/ 4u8, 5u8;/**/
708        6u8,/**/7u8, 8u8,/**/ 9u8, 0u8;/**/
709            /*****************************/
710        9u8,/**/8u8, 7u8,/**/ 6u8, 5u8;/**/
711        4u8,/**/3u8, 2u8,/**/ 1u8, 0u8;/**/
712            /*****************************/
713        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            /*************************************/
727        1u8,/**/255u8, 255u8,/**/ 0u8,   0u8;  /**/
728        6u8,/**/255u8, 255u8,/**/ 0u8,   0u8;  /**/
729            /*************************************/
730        9u8,/**/0u8,   0u8,  /**/ 255u8, 255u8;/**/
731        4u8,/**/0u8,   0u8,  /**/ 255u8, 255u8;/**/
732            /*************************************/
733        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        // 10050 features in total
741        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}