Is there a more idiomatic way to do edge detection using ndarray?

54 views Asked by At

Suppose I have an image represented as an Array2<u32>, for example:

const SIZE: usize = 8;
let image = Array2::from_elem((SIZE, SIZE), 0_u32);

I want to obtain an Array2<bool> that has true in all locations where at least one pixel (in a 4-connected neighbourhood) is different from the current one. It's fine if this is 2 pixels smaller on each side.

Right now I'm doing it like this:

    let edges = Zip::from(image.slice(s![1..(INNER + 1), 1..(INNER + 1)]))
        .and(image.slice(s![0..(INNER + 0), 1..(INNER + 1)]))
        .and(image.slice(s![2..(INNER + 2), 1..(INNER + 1)]))
        .and(image.slice(s![1..(INNER + 1), 0..(INNER + 0)]))
        .and(image.slice(s![1..(INNER + 1), 2..(INNER + 2)]))
        .map_collect(|c, n, s, w, e| {
            [n, s, w, e].into_iter().any(|v| v != c)
        });

However, I have several other operations that act on neighbouring elements, so I would like to build an abstraction. I can probably write an extension trait for Zip that lets me write this:

    let edges = Zip::from(image.slice(s![1..(INNER + 1), 1..(INNER + 1)]))
        .and_neighbors(&image) // From the extension trait
        .map_collect(|c, n, s, w, e| {
            [n, s, w, e].into_iter().any(|v| v != c)
        });

Sadly, I still have to repeat the four arguments every time, and turn them back into an array inside the closure. Is there a better way?

It would be great to have an NdProducer that has an extra axis of length 4, that contains the four neighbour views as slices. I tried to write that, but it turns out that the NdProducer trait doesn't allow implementations outside of its crate.

I also tried ArrayView::from_shape_ptr to create a view like that, but we have to specify a fixed stride for each index, while the index of the "neighbours" axis doesn't have a fixed stride.

0

There are 0 answers