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}