imageproc/gradients.rs
1//! Functions for computing gradients of image intensities.
2
3use crate::definitions::{HasBlack, Image};
4use crate::filter::filter3x3;
5use crate::map::{ChannelMap, WithChannel};
6use image::{GenericImage, GenericImageView, GrayImage, Luma, Pixel};
7use itertools::multizip;
8
9/// Sobel filter for detecting vertical gradients.
10///
11/// Used by the [`vertical_sobel`](fn.vertical_sobel.html) function.
12#[rustfmt::skip]
13pub static VERTICAL_SOBEL: [i32; 9] = [
14 -1, -2, -1,
15 0, 0, 0,
16 1, 2, 1];
17
18/// Sobel filter for detecting horizontal gradients.
19///
20/// Used by the [`horizontal_sobel`](fn.horizontal_sobel.html) function.
21#[rustfmt::skip]
22pub static HORIZONTAL_SOBEL: [i32; 9] = [
23 -1, 0, 1,
24 -2, 0, 2,
25 -1, 0, 1];
26
27/// Scharr filter for detecting vertical gradients.
28///
29/// Used by the [`vertical_scharr`](fn.vertical_scharr.html) function.
30#[rustfmt::skip]
31pub static VERTICAL_SCHARR: [i32; 9] = [
32 -3, -10, -3,
33 0, 0, 0,
34 3, 10, 3];
35
36/// Scharr filter for detecting horizontal gradients.
37///
38/// Used by the [`horizontal_scharr`](fn.horizontal_scharr.html) function.
39#[rustfmt::skip]
40pub static HORIZONTAL_SCHARR: [i32; 9] = [
41 -3, 0, 3,
42 -10, 0, 10,
43 -3, 0, 3];
44
45/// Prewitt filter for detecting vertical gradients.
46///
47/// Used by the [`vertical_prewitt`](fn.vertical_prewitt.html) function.
48#[rustfmt::skip]
49pub static VERTICAL_PREWITT: [i32; 9] = [
50 -1, -1, -1,
51 0, 0, 0,
52 1, 1, 1];
53
54/// Prewitt filter for detecting horizontal gradients.
55///
56/// Used by the [`horizontal_prewitt`](fn.horizontal_prewitt.html) function.
57#[rustfmt::skip]
58pub static HORIZONTAL_PREWITT: [i32; 9] = [
59 -1, 0, 1,
60 -1, 0, 1,
61 -1, 0, 1];
62
63/// Convolves an image with the [`HORIZONTAL_SOBEL`](static.HORIZONTAL_SOBEL.html)
64/// kernel to detect horizontal gradients.
65pub fn horizontal_sobel(image: &GrayImage) -> Image<Luma<i16>> {
66 filter3x3(image, &HORIZONTAL_SOBEL)
67}
68
69/// Convolves an image with the [`VERTICAL_SOBEL`](static.VERTICAL_SOBEL.html)
70/// kernel to detect vertical gradients.
71pub fn vertical_sobel(image: &GrayImage) -> Image<Luma<i16>> {
72 filter3x3(image, &VERTICAL_SOBEL)
73}
74
75/// Convolves an image with the [`HORIZONTAL_SCHARR`](static.HORIZONTAL_SCHARR.html)
76/// kernel to detect horizontal gradients.
77pub fn horizontal_scharr(image: &GrayImage) -> Image<Luma<i16>> {
78 filter3x3(image, &HORIZONTAL_SCHARR)
79}
80
81/// Convolves an image with the [`VERTICAL_SCHARR`](static.VERTICAL_SCHARR.html)
82/// kernel to detect vertical gradients.
83pub fn vertical_scharr(image: &GrayImage) -> Image<Luma<i16>> {
84 filter3x3(image, &VERTICAL_SCHARR)
85}
86
87/// Convolves an image with the [`HORIZONTAL_PREWITT`](static.HORIZONTAL_PREWITT.html)
88/// kernel to detect horizontal gradients.
89pub fn horizontal_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
90 filter3x3(image, &HORIZONTAL_PREWITT)
91}
92
93/// Convolves an image with the [`VERTICAL_PREWITT`](static.VERTICAL_PREWITT.html)
94/// kernel to detect vertical gradients.
95pub fn vertical_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
96 filter3x3(image, &VERTICAL_PREWITT)
97}
98
99/// Returns the magnitudes of gradients in an image using Sobel filters.
100pub fn sobel_gradients(image: &GrayImage) -> Image<Luma<u16>> {
101 gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, |p| p)
102}
103
104/// Computes per-channel gradients using Sobel filters and calls `f`
105/// to compute each output pixel.
106///
107/// # Examples
108/// ```
109/// # extern crate image;
110/// # #[macro_use]
111/// # extern crate imageproc;
112/// # fn main() {
113/// use imageproc::gradients::{sobel_gradient_map};
114/// use image::Luma;
115/// use std::cmp;
116///
117/// // A shallow horizontal gradient in the red
118/// // channel, a steeper vertical gradient in the
119/// // blue channel, constant in the green channel.
120/// let input = rgb_image!(
121/// [0, 5, 0], [1, 5, 0], [2, 5, 0];
122/// [0, 5, 2], [1, 5, 2], [2, 5, 2];
123/// [0, 5, 4], [1, 5, 4], [2, 5, 4]
124/// );
125///
126/// // Computing independent per-channel gradients.
127/// let channel_gradient = rgb_image!(type: u16,
128/// [ 4, 0, 8], [ 8, 0, 8], [ 4, 0, 8];
129/// [ 4, 0, 16], [ 8, 0, 16], [ 4, 0, 16];
130/// [ 4, 0, 8], [ 8, 0, 8], [ 4, 0, 8]
131/// );
132///
133/// assert_pixels_eq!(
134/// sobel_gradient_map(&input, |p| p),
135/// channel_gradient
136/// );
137///
138/// // Defining the gradient of an RGB image to be the
139/// // mean of its per-channel gradients.
140/// let mean_gradient = gray_image!(type: u16,
141/// 4, 5, 4;
142/// 6, 8, 6;
143/// 4, 5, 4
144/// );
145///
146/// assert_pixels_eq!(
147/// sobel_gradient_map(&input, |p| {
148/// let mean = (p[0] + p[1] + p[2]) / 3;
149/// Luma([mean])
150/// }),
151/// mean_gradient
152/// );
153///
154/// // Defining the gradient of an RGB image to be the pixelwise
155/// // maximum of its per-channel gradients.
156/// let max_gradient = gray_image!(type: u16,
157/// 8, 8, 8;
158/// 16, 16, 16;
159/// 8, 8, 8
160/// );
161///
162/// assert_pixels_eq!(
163/// sobel_gradient_map(&input, |p| {
164/// let max = cmp::max(cmp::max(p[0], p[1]), p[2]);
165/// Luma([max])
166/// }),
167/// max_gradient
168/// );
169/// # }
170pub fn sobel_gradient_map<P, F, Q>(image: &Image<P>, f: F) -> Image<Q>
171where
172 P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
173 Q: Pixel,
174 ChannelMap<P, u16>: HasBlack,
175 F: Fn(ChannelMap<P, u16>) -> Q,
176{
177 gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, f)
178}
179
180/// Returns the magnitudes of gradients in an image using Prewitt filters.
181pub fn prewitt_gradients(image: &GrayImage) -> Image<Luma<u16>> {
182 gradients(image, &HORIZONTAL_PREWITT, &VERTICAL_PREWITT, |p| p)
183}
184
185// TODO: Returns directions as well as magnitudes.
186// TODO: Support filtering without allocating a fresh image - filtering functions could
187// TODO: take some kind of pixel-sink. This would allow us to compute gradient magnitudes
188// TODO: and directions without allocating intermediates for vertical and horizontal gradients.
189fn gradients<P, F, Q>(
190 image: &Image<P>,
191 horizontal_kernel: &[i32; 9],
192 vertical_kernel: &[i32; 9],
193 f: F,
194) -> Image<Q>
195where
196 P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
197 Q: Pixel,
198 ChannelMap<P, u16>: HasBlack,
199 F: Fn(ChannelMap<P, u16>) -> Q,
200{
201 let horizontal: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, horizontal_kernel);
202 let vertical: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, vertical_kernel);
203
204 let (width, height) = image.dimensions();
205 let mut out = Image::<Q>::new(width, height);
206
207 // This would be more concise using itertools::multizip over image pixels, but that increased runtime by around 20%
208 for y in 0..height {
209 for x in 0..width {
210 // JUSTIFICATION
211 // Benefit
212 // Using checked indexing here makes this sobel_gradients 1.1x slower,
213 // as measured by bench_sobel_gradients
214 // Correctness
215 // x and y are in bounds for image by construction,
216 // vertical and horizontal are the result of calling filter3x3 on image,
217 // and filter3x3 returns an image of the same size as its input
218 let (h, v) = unsafe {
219 (
220 horizontal.unsafe_get_pixel(x, y),
221 vertical.unsafe_get_pixel(x, y),
222 )
223 };
224 let mut p = ChannelMap::<P, u16>::black();
225
226 for (h, v, p) in multizip((h.channels(), v.channels(), p.channels_mut())) {
227 *p = gradient_magnitude(*h as f32, *v as f32);
228 }
229
230 // JUSTIFICATION
231 // Benefit
232 // Using checked indexing here makes this sobel_gradients 1.1x slower,
233 // as measured by bench_sobel_gradients
234 // Correctness
235 // x and y are in bounds for image by construction,
236 // and out has the same dimensions
237 unsafe {
238 out.unsafe_put_pixel(x, y, f(p));
239 }
240 }
241 }
242
243 out
244}
245
246#[inline]
247fn gradient_magnitude(dx: f32, dy: f32) -> u16 {
248 (dx.powi(2) + dy.powi(2)).sqrt() as u16
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::utils::gray_bench_image;
255 use image::{ImageBuffer, Luma};
256 use test::{black_box, Bencher};
257
258 #[rustfmt::skip::macros(gray_image)]
259 #[test]
260 fn test_gradients_constant_image() {
261 let image = ImageBuffer::from_pixel(5, 5, Luma([15u8]));
262 let expected = ImageBuffer::from_pixel(5, 5, Luma([0i16]));
263 assert_pixels_eq!(horizontal_sobel(&image), expected);
264 assert_pixels_eq!(vertical_sobel(&image), expected);
265 assert_pixels_eq!(horizontal_scharr(&image), expected);
266 assert_pixels_eq!(vertical_scharr(&image), expected);
267 assert_pixels_eq!(horizontal_prewitt(&image), expected);
268 assert_pixels_eq!(vertical_prewitt(&image), expected);
269 }
270
271 #[test]
272 fn test_horizontal_sobel_gradient_image() {
273 let image = gray_image!(
274 3, 2, 1;
275 6, 5, 4;
276 9, 8, 7);
277
278 let expected = gray_image!(type: i16,
279 -4, -8, -4;
280 -4, -8, -4;
281 -4, -8, -4);
282
283 let filtered = horizontal_sobel(&image);
284 assert_pixels_eq!(filtered, expected);
285 }
286
287 #[test]
288 fn test_vertical_sobel_gradient_image() {
289 let image = gray_image!(
290 3, 6, 9;
291 2, 5, 8;
292 1, 4, 7);
293
294 let expected = gray_image!(type: i16,
295 -4, -4, -4;
296 -8, -8, -8;
297 -4, -4, -4);
298
299 let filtered = vertical_sobel(&image);
300 assert_pixels_eq!(filtered, expected);
301 }
302
303 #[test]
304 fn test_horizontal_scharr_gradient_image() {
305 let image = gray_image!(
306 3, 2, 1;
307 6, 5, 4;
308 9, 8, 7);
309
310 let expected = gray_image!(type: i16,
311 -16, -32, -16;
312 -16, -32, -16;
313 -16, -32, -16);
314
315 let filtered = horizontal_scharr(&image);
316 assert_pixels_eq!(filtered, expected);
317 }
318
319 #[test]
320 fn test_vertical_scharr_gradient_image() {
321 let image = gray_image!(
322 3, 6, 9;
323 2, 5, 8;
324 1, 4, 7);
325
326 let expected = gray_image!(type: i16,
327 -16, -16, -16;
328 -32, -32, -32;
329 -16, -16, -16);
330
331 let filtered = vertical_scharr(&image);
332 assert_pixels_eq!(filtered, expected);
333 }
334
335 #[test]
336 fn test_horizontal_prewitt_gradient_image() {
337 let image = gray_image!(
338 3, 2, 1;
339 6, 5, 4;
340 9, 8, 7);
341
342 let expected = gray_image!(type: i16,
343 -3, -6, -3;
344 -3, -6, -3;
345 -3, -6, -3);
346
347 let filtered = horizontal_prewitt(&image);
348 assert_pixels_eq!(filtered, expected);
349 }
350
351 #[test]
352 fn test_vertical_prewitt_gradient_image() {
353 let image = gray_image!(
354 3, 6, 9;
355 2, 5, 8;
356 1, 4, 7);
357
358 let expected = gray_image!(type: i16,
359 -3, -3, -3;
360 -6, -6, -6;
361 -3, -3, -3);
362
363 let filtered = vertical_prewitt(&image);
364 assert_pixels_eq!(filtered, expected);
365 }
366
367 #[bench]
368 fn bench_sobel_gradients(b: &mut Bencher) {
369 let image = gray_bench_image(500, 500);
370 b.iter(|| {
371 let gradients = sobel_gradients(&image);
372 black_box(gradients);
373 });
374 }
375}