imageproc/
integral_image.rs

1//! Functions for computing [integral images](https://en.wikipedia.org/wiki/Summed_area_table)
2//! and running sums of rows and columns.
3
4use crate::definitions::Image;
5use crate::map::{ChannelMap, WithChannel};
6use image::{GenericImageView, GrayImage, Luma, Pixel, Primitive, Rgb, Rgba};
7use std::ops::AddAssign;
8
9/// Computes the 2d running sum of an image. Channels are summed independently.
10///
11/// An integral image I has width and height one greater than its source image F,
12/// and is defined by I(x, y) = sum of F(x', y') for x' < x, y' < y, i.e. each pixel
13/// in the integral image contains the sum of the pixel intensities of all input pixels
14/// that are strictly above it and strictly to its left. In particular, the left column
15/// and top row of an integral image are all 0, and the value of the bottom right pixel of
16/// an integral image is equal to the sum of all pixels in the source image.
17///
18/// Integral images have the helpful property of allowing us to
19/// compute the sum of pixel intensities in a rectangular region of an image
20/// in constant time. Specifically, given a rectangle [l, r] * [t, b] in F,
21/// the sum of the pixels in this rectangle is
22/// I(r + 1, b + 1) - I(r + 1, t) - I(l, b + 1) + I(l, t).
23///
24/// # Examples
25/// ```
26/// # extern crate image;
27/// # #[macro_use]
28/// # extern crate imageproc;
29/// # fn main() {
30/// use imageproc::integral_image::{integral_image, sum_image_pixels};
31///
32/// let image = gray_image!(
33///     1, 2, 3;
34///     4, 5, 6);
35///
36/// let integral = gray_image!(type: u32,
37///     0,  0,  0,  0;
38///     0,  1,  3,  6;
39///     0,  5, 12, 21);
40///
41/// assert_pixels_eq!(integral_image::<_, u32>(&image), integral);
42///
43/// // Compute the sum of all pixels in the right two columns
44/// assert_eq!(sum_image_pixels(&integral, 1, 0, 2, 1)[0], 2 + 3 + 5 + 6);
45///
46/// // Compute the sum of all pixels in the top row
47/// assert_eq!(sum_image_pixels(&integral, 0, 0, 2, 0)[0], 1 + 2 + 3);
48/// # }
49/// ```
50pub fn integral_image<P, T>(image: &Image<P>) -> Image<ChannelMap<P, T>>
51where
52    P: Pixel<Subpixel = u8> + WithChannel<T>,
53    T: From<u8> + Primitive + AddAssign,
54{
55    integral_image_impl(image, false)
56}
57
58/// Computes the 2d running sum of the squares of the intensities in an image. Channels are summed
59/// independently.
60///
61/// See the [`integral_image`](fn.integral_image.html) documentation for more information on integral images.
62///
63/// # Examples
64/// ```
65/// # extern crate image;
66/// # #[macro_use]
67/// # extern crate imageproc;
68/// # fn main() {
69/// use imageproc::integral_image::{integral_squared_image, sum_image_pixels};
70///
71/// let image = gray_image!(
72///     1, 2, 3;
73///     4, 5, 6);
74///
75/// let integral = gray_image!(type: u32,
76///     0,  0,  0,  0;
77///     0,  1,  5, 14;
78///     0, 17, 46, 91);
79///
80/// assert_pixels_eq!(integral_squared_image::<_, u32>(&image), integral);
81///
82/// // Compute the sum of the squares of all pixels in the right two columns
83/// assert_eq!(sum_image_pixels(&integral, 1, 0, 2, 1)[0], 4 + 9 + 25 + 36);
84///
85/// // Compute the sum of the squares of all pixels in the top row
86/// assert_eq!(sum_image_pixels(&integral, 0, 0, 2, 0)[0], 1 + 4 + 9);
87/// # }
88/// ```
89pub fn integral_squared_image<P, T>(image: &Image<P>) -> Image<ChannelMap<P, T>>
90where
91    P: Pixel<Subpixel = u8> + WithChannel<T>,
92    T: From<u8> + Primitive + AddAssign,
93{
94    integral_image_impl(image, true)
95}
96
97/// Implementation of `integral_image` and `integral_squared_image`.
98fn integral_image_impl<P, T>(image: &Image<P>, square: bool) -> Image<ChannelMap<P, T>>
99where
100    P: Pixel<Subpixel = u8> + WithChannel<T>,
101    T: From<u8> + Primitive + AddAssign,
102{
103    // TODO: Make faster, add a new IntegralImage type
104    // TODO: to make it harder to make off-by-one errors when computing sums of regions.
105    let (in_width, in_height) = image.dimensions();
106    let out_width = in_width + 1;
107    let out_height = in_height + 1;
108
109    let mut out = Image::<ChannelMap<P, T>>::new(out_width, out_height);
110
111    if in_width == 0 || in_height == 0 {
112        return out;
113    }
114
115    for y in 0..in_height {
116        let mut sum = vec![T::zero(); P::CHANNEL_COUNT as usize];
117        for x in 0..in_width {
118            // JUSTIFICATION
119            //  Benefit
120            //      Using checked indexing here makes bench_integral_image_rgb take 1.05x as long
121            //      (The results are noisy, but this seems to be reproducible. I've not checked the generated assembly.)
122            //  Correctness
123            //      x and y are within bounds by definition of in_width and in_height
124            let input = unsafe { image.unsafe_get_pixel(x, y) };
125            for (s, c) in sum.iter_mut().zip(input.channels()) {
126                let pix: T = (*c).into();
127                *s += if square { pix * pix } else { pix };
128            }
129
130            // JUSTIFICATION
131            //  Benefit
132            //      Using checked indexing here makes bench_integral_image_rgb take 1.05x as long
133            //      (The results are noisy, but this seems to be reproducible. I've not checked the generated assembly.)
134            //  Correctness
135            //      0 <= x < in_width, 0 <= y < in_height and out has width in_width + 1 and height in_height + 1
136            let above = unsafe { out.unsafe_get_pixel(x + 1, y) };
137            // For some reason there's no unsafe_get_pixel_mut, so to update the existing
138            // pixel here we need to use the method with bounds checking
139            let current = out.get_pixel_mut(x + 1, y + 1);
140            // Using zip here makes this slower.
141            for c in 0..P::CHANNEL_COUNT {
142                current.channels_mut()[c as usize] = above.channels()[c as usize] + sum[c as usize];
143            }
144        }
145    }
146
147    out
148}
149
150/// Hack to get around lack of const generics. See comment on `sum_image_pixels`.
151pub trait ArrayData {
152    /// The type of the data for this array.
153    /// e.g. `[T; 1]` for `Luma`, `[T; 3]` for `Rgb`.
154    type DataType;
155
156    /// Get the data from this pixel as a constant length array.
157    fn data(&self) -> Self::DataType;
158
159    /// Add the elements of two data arrays elementwise.
160    fn add(lhs: Self::DataType, other: Self::DataType) -> Self::DataType;
161
162    /// Subtract the elements of two data arrays elementwise.
163    fn sub(lhs: Self::DataType, other: Self::DataType) -> Self::DataType;
164}
165
166impl<T: Primitive> ArrayData for Luma<T> {
167    type DataType = [T; 1];
168
169    fn data(&self) -> Self::DataType {
170        [self.channels()[0]]
171    }
172
173    fn add(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
174        [lhs[0] + rhs[0]]
175    }
176
177    fn sub(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
178        [lhs[0] - rhs[0]]
179    }
180}
181
182impl<T> ArrayData for Rgb<T>
183where
184    Rgb<T>: Pixel<Subpixel = T>,
185    T: Primitive,
186{
187    type DataType = [T; 3];
188
189    fn data(&self) -> Self::DataType {
190        [self.channels()[0], self.channels()[1], self.channels()[2]]
191    }
192
193    fn add(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
194        [lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2]]
195    }
196
197    fn sub(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
198        [lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2]]
199    }
200}
201
202impl<T> ArrayData for Rgba<T>
203where
204    Rgba<T>: Pixel<Subpixel = T>,
205    T: Primitive,
206{
207    type DataType = [T; 4];
208
209    fn data(&self) -> Self::DataType {
210        [
211            self.channels()[0],
212            self.channels()[1],
213            self.channels()[2],
214            self.channels()[3],
215        ]
216    }
217
218    fn add(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
219        [
220            lhs[0] + rhs[0],
221            lhs[1] + rhs[1],
222            lhs[2] + rhs[2],
223            lhs[3] + rhs[3],
224        ]
225    }
226
227    fn sub(lhs: Self::DataType, rhs: Self::DataType) -> Self::DataType {
228        [
229            lhs[0] - rhs[0],
230            lhs[1] - rhs[1],
231            lhs[2] - rhs[2],
232            lhs[3] - rhs[3],
233        ]
234    }
235}
236
237/// Sums the pixels in positions [left, right] * [top, bottom] in F, where `integral_image` is the
238/// integral image of F.
239///
240/// The of `ArrayData` here is due to lack of const generics. This library contains
241/// implementations of `ArrayData` for `Luma`, `Rgb` and `Rgba` for any element type `T` that
242/// implements `Primitive`. In that case, this function returns `[T; 1]` for an image
243/// whose pixels are of type `Luma`, `[T; 3]` for `Rgb` pixels and `[T; 4]` for `Rgba` pixels.
244///
245/// See the [`integral_image`](fn.integral_image.html) documentation for examples.
246pub fn sum_image_pixels<P>(
247    integral_image: &Image<P>,
248    left: u32,
249    top: u32,
250    right: u32,
251    bottom: u32,
252) -> P::DataType
253where
254    P: Pixel + ArrayData + Copy,
255{
256    // TODO: better type-safety. It's too easy to pass the original image in here by mistake.
257    // TODO: it's also hard to see what the four u32s mean at the call site - use a Rect instead.
258    let (a, b, c, d) = (
259        integral_image.get_pixel(right + 1, bottom + 1).data(),
260        integral_image.get_pixel(left, top).data(),
261        integral_image.get_pixel(right + 1, top).data(),
262        integral_image.get_pixel(left, bottom + 1).data(),
263    );
264    P::sub(P::sub(P::add(a, b), c), d)
265}
266
267/// Computes the variance of [left, right] * [top, bottom] in F, where `integral_image` is the
268/// integral image of F and `integral_squared_image` is the integral image of the squares of the
269/// pixels in F.
270///
271/// See the [`integral_image`](fn.integral_image.html) documentation for more information on integral images.
272///
273///# Examples
274/// ```
275/// # extern crate image;
276/// # #[macro_use]
277/// # extern crate imageproc;
278/// # fn main() {
279/// use std::f64;
280/// use imageproc::integral_image::{integral_image, integral_squared_image, variance};
281///
282/// let image = gray_image!(
283///     1, 2, 3;
284///     4, 5, 6);
285///
286/// let integral = integral_image(&image);
287/// let integral_squared = integral_squared_image(&image);
288///
289/// // Compute the variance of the pixels in the right two columns
290/// let mean: f64 = (2.0 + 3.0 + 5.0 + 6.0) / 4.0;
291/// let var = ((2.0 - mean).powi(2)
292///     + (3.0 - mean).powi(2)
293///     + (5.0 - mean).powi(2)
294///     + (6.0 - mean).powi(2)) / 4.0;
295///
296/// assert_eq!(variance(&integral, &integral_squared, 1, 0, 2, 1), var);
297/// # }
298/// ```
299pub fn variance(
300    integral_image: &Image<Luma<u32>>,
301    integral_squared_image: &Image<Luma<u32>>,
302    left: u32,
303    top: u32,
304    right: u32,
305    bottom: u32,
306) -> f64 {
307    // TODO: same improvements as for sum_image_pixels, plus check that the given rect is valid.
308    let n = (right - left + 1) as f64 * (bottom - top + 1) as f64;
309    let sum_sq = sum_image_pixels(integral_squared_image, left, top, right, bottom)[0];
310    let sum = sum_image_pixels(integral_image, left, top, right, bottom)[0];
311    (sum_sq as f64 - (sum as f64).powi(2) / n) / n
312}
313
314/// Computes the running sum of one row of image, padded
315/// at the beginning and end. The padding is by continuity.
316/// Takes a reference to buffer so that this can be reused
317/// for all rows in an image.
318///
319/// # Panics
320/// - If `buffer.len() < 2 * padding + image.width()`.
321/// - If `row >= image.height()`.
322/// - If `image.width() == 0`.
323///
324/// # Examples
325/// ```
326/// # extern crate image;
327/// # #[macro_use]
328/// # extern crate imageproc;
329/// # fn main() {
330/// use imageproc::integral_image::row_running_sum;
331///
332/// let image = gray_image!(
333///     1, 2, 3;
334///     4, 5, 6);
335///
336/// // Buffer has length two greater than image width, hence padding of 1
337/// let mut buffer = [0; 5];
338/// row_running_sum(&image, 0, &mut buffer, 1);
339///
340/// // The image is padded by continuity on either side
341/// assert_eq!(buffer, [1, 2, 4, 7, 10]);
342/// # }
343/// ```
344pub fn row_running_sum(image: &GrayImage, row: u32, buffer: &mut [u32], padding: u32) {
345    // TODO: faster, more formats
346    let (width, height) = image.dimensions();
347    let (width, padding) = (width as usize, padding as usize);
348    assert!(
349        buffer.len() >= width + 2 * padding,
350        "Buffer length {} is less than {} + 2 * {}",
351        buffer.len(),
352        width,
353        padding
354    );
355    assert!(row < height, "row out of bounds: {} >= {}", row, height);
356    assert!(width > 0, "image is empty");
357
358    let row_data = &(**image)[width * row as usize..][..width];
359    let first = row_data[0] as u32;
360    let last = row_data[width - 1] as u32;
361
362    let mut sum = 0;
363
364    for b in &mut buffer[..padding] {
365        sum += first;
366        *b = sum;
367    }
368    for (b, p) in buffer[padding..].iter_mut().zip(row_data) {
369        sum += *p as u32;
370        *b = sum;
371    }
372    for b in &mut buffer[padding + width..] {
373        sum += last;
374        *b = sum;
375    }
376}
377
378/// Computes the running sum of one column of image, padded
379/// at the top and bottom. The padding is by continuity.
380/// Takes a reference to buffer so that this can be reused
381/// for all columns in an image.
382///
383/// # Panics
384/// - If `buffer.len() < 2 * padding + image.height()`.
385/// - If `column >= image.width()`.
386/// - If `image.height() == 0`.
387///
388/// # Examples
389/// ```
390/// # extern crate image;
391/// # #[macro_use]
392/// # extern crate imageproc;
393/// # fn main() {
394/// use imageproc::integral_image::column_running_sum;
395///
396/// let image = gray_image!(
397///     1, 4;
398///     2, 5;
399///     3, 6);
400///
401/// // Buffer has length two greater than image height, hence padding of 1
402/// let mut buffer = [0; 5];
403/// column_running_sum(&image, 0, &mut buffer, 1);
404///
405/// // The image is padded by continuity on top and bottom
406/// assert_eq!(buffer, [1, 2, 4, 7, 10]);
407/// # }
408/// ```
409pub fn column_running_sum(image: &GrayImage, column: u32, buffer: &mut [u32], padding: u32) {
410    // TODO: faster, more formats
411    let (width, height) = image.dimensions();
412    assert!(
413        // assertion 1
414        buffer.len() >= height as usize + 2 * padding as usize,
415        "Buffer length {} is less than {} + 2 * {}",
416        buffer.len(),
417        height,
418        padding
419    );
420    assert!(
421        // assertion 2
422        column < width,
423        "column out of bounds: {} >= {}",
424        column,
425        width
426    );
427    assert!(
428        // assertion 3
429        height > 0,
430        "image is empty"
431    );
432
433    let first = image.get_pixel(column, 0)[0] as u32;
434    let last = image.get_pixel(column, height - 1)[0] as u32;
435
436    let mut sum = 0;
437
438    for b in &mut buffer[..padding as usize] {
439        sum += first;
440        *b = sum;
441    }
442    // JUSTIFICATION:
443    //  Benefit
444    //      Using checked indexing here makes this function take 1.8x longer, as measured by bench_column_running_sum
445    //  Correctness
446    //      column is in bounds due to assertion 2.
447    //      height + padding - 1 < buffer.len() due to assertions 1 and 3.
448    unsafe {
449        for y in 0..height {
450            sum += image.unsafe_get_pixel(column, y)[0] as u32;
451            *buffer.get_unchecked_mut(y as usize + padding as usize) = sum;
452        }
453    }
454    for b in &mut buffer[padding as usize + height as usize..] {
455        sum += last;
456        *b = sum;
457    }
458}
459
460#[cfg(test)]
461mod tests {
462    use super::*;
463    use crate::definitions::Image;
464    use crate::property_testing::GrayTestImage;
465    use crate::utils::{gray_bench_image, pixel_diff_summary, rgb_bench_image};
466    use ::test;
467    use image::{GenericImage, ImageBuffer, Luma};
468    use quickcheck::{quickcheck, TestResult};
469
470    #[test]
471    fn test_integral_image_gray() {
472        let image = gray_image!(
473            1, 2, 3;
474            4, 5, 6);
475
476        let expected = gray_image!(type: u32,
477            0,  0,  0,  0;
478            0,  1,  3,  6;
479            0,  5, 12, 21);
480
481        assert_pixels_eq!(integral_image::<_, u32>(&image), expected);
482    }
483
484    #[test]
485    fn test_integral_image_rgb() {
486        let image = rgb_image!(
487            [1, 11, 21], [2, 12, 22], [3, 13, 23];
488            [4, 14, 24], [5, 15, 25], [6, 16, 26]);
489
490        let expected = rgb_image!(type: u32,
491            [0, 0, 0],  [0,  0,  0], [ 0,  0,  0], [ 0,  0,   0];
492            [0, 0, 0],  [1, 11, 21], [ 3, 23, 43], [ 6, 36,  66];
493            [0, 0, 0],  [5, 25, 45], [12, 52, 92], [21, 81, 141]);
494
495        assert_pixels_eq!(integral_image::<_, u32>(&image), expected);
496    }
497
498    #[test]
499    fn test_sum_image_pixels() {
500        let image = gray_image!(
501            1, 2;
502            3, 4);
503
504        let integral = integral_image::<_, u32>(&image);
505
506        // Top left
507        assert_eq!(sum_image_pixels(&integral, 0, 0, 0, 0)[0], 1);
508        // Top row
509        assert_eq!(sum_image_pixels(&integral, 0, 0, 1, 0)[0], 3);
510        // Left column
511        assert_eq!(sum_image_pixels(&integral, 0, 0, 0, 1)[0], 4);
512        // Whole image
513        assert_eq!(sum_image_pixels(&integral, 0, 0, 1, 1)[0], 10);
514        // Top right
515        assert_eq!(sum_image_pixels(&integral, 1, 0, 1, 0)[0], 2);
516        // Right column
517        assert_eq!(sum_image_pixels(&integral, 1, 0, 1, 1)[0], 6);
518        // Bottom left
519        assert_eq!(sum_image_pixels(&integral, 0, 1, 0, 1)[0], 3);
520        // Bottom row
521        assert_eq!(sum_image_pixels(&integral, 0, 1, 1, 1)[0], 7);
522        // Bottom right
523        assert_eq!(sum_image_pixels(&integral, 1, 1, 1, 1)[0], 4);
524    }
525
526    #[test]
527    fn test_sum_image_pixels_rgb() {
528        let image = rgb_image!(
529            [1,  2,  3], [ 4,  5,  6];
530            [7,  8,  9], [10, 11, 12]);
531
532        let integral = integral_image::<_, u32>(&image);
533
534        // Top left
535        assert_eq!(sum_image_pixels(&integral, 0, 0, 0, 0), [1, 2, 3]);
536        // Top row
537        assert_eq!(sum_image_pixels(&integral, 0, 0, 1, 0), [5, 7, 9]);
538        // Left column
539        assert_eq!(sum_image_pixels(&integral, 0, 0, 0, 1), [8, 10, 12]);
540        // Whole image
541        assert_eq!(sum_image_pixels(&integral, 0, 0, 1, 1), [22, 26, 30]);
542        // Top right
543        assert_eq!(sum_image_pixels(&integral, 1, 0, 1, 0), [4, 5, 6]);
544        // Right column
545        assert_eq!(sum_image_pixels(&integral, 1, 0, 1, 1), [14, 16, 18]);
546        // Bottom left
547        assert_eq!(sum_image_pixels(&integral, 0, 1, 0, 1), [7, 8, 9]);
548        // Bottom row
549        assert_eq!(sum_image_pixels(&integral, 0, 1, 1, 1), [17, 19, 21]);
550        // Bottom right
551        assert_eq!(sum_image_pixels(&integral, 1, 1, 1, 1), [10, 11, 12]);
552    }
553
554    #[bench]
555    fn bench_integral_image_gray(b: &mut test::Bencher) {
556        let image = gray_bench_image(500, 500);
557        b.iter(|| {
558            let integral = integral_image::<_, u32>(&image);
559            test::black_box(integral);
560        });
561    }
562
563    #[bench]
564    fn bench_integral_image_rgb(b: &mut test::Bencher) {
565        let image = rgb_bench_image(500, 500);
566        b.iter(|| {
567            let integral = integral_image::<_, u32>(&image);
568            test::black_box(integral);
569        });
570    }
571
572    /// Simple implementation of integral_image to validate faster versions against.
573    fn integral_image_ref<I>(image: &I) -> Image<Luma<u32>>
574    where
575        I: GenericImage<Pixel = Luma<u8>>,
576    {
577        let (in_width, in_height) = image.dimensions();
578        let (out_width, out_height) = (in_width + 1, in_height + 1);
579        let mut out = ImageBuffer::from_pixel(out_width, out_height, Luma([0u32]));
580
581        for y in 1..out_height {
582            for x in 0..out_width {
583                let mut sum = 0u32;
584
585                for iy in 0..y {
586                    for ix in 0..x {
587                        sum += image.get_pixel(ix, iy)[0] as u32;
588                    }
589                }
590
591                out.put_pixel(x, y, Luma([sum]));
592            }
593        }
594
595        out
596    }
597
598    #[test]
599    fn test_integral_image_matches_reference_implementation() {
600        fn prop(image: GrayTestImage) -> TestResult {
601            let expected = integral_image_ref(&image.0);
602            let actual = integral_image(&image.0);
603            match pixel_diff_summary(&actual, &expected) {
604                None => TestResult::passed(),
605                Some(err) => TestResult::error(err),
606            }
607        }
608        quickcheck(prop as fn(GrayTestImage) -> TestResult);
609    }
610
611    #[bench]
612    fn bench_row_running_sum(b: &mut test::Bencher) {
613        let image = gray_bench_image(1000, 1);
614        let mut buffer = [0; 1010];
615        b.iter(|| {
616            row_running_sum(&image, 0, &mut buffer, 5);
617        });
618    }
619
620    #[bench]
621    fn bench_column_running_sum(b: &mut test::Bencher) {
622        let image = gray_bench_image(100, 1000);
623        let mut buffer = [0; 1010];
624        b.iter(|| {
625            column_running_sum(&image, 0, &mut buffer, 5);
626        });
627    }
628}