Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With imager package, how to get the correlation of an image (with a kernel)?

I have this pic:

enter image description here

I want to get the image named the "correlation" of this image with a discrete kernel, the matrix given below. I first try to use the correlate function:

kernel <- rbind(
  c( 0, 1, 1, 2, 2, 2, 1, 1, 0 )
  , c( 1,  2,  4, 5, 5, 5, 4, 2, 1 )
  , c( 1,  4,  5, 3, 0, 3, 5, 4, 1 )
  , c( 2,  5,  3, -12, -24, -12, 3, 5, 2 )
  , c( 2,  5,  0, -24, -40, -24, 0, 5, 2 )
  , c( 2,  5,  3, -12, -24, -12, 3, 5, 2 )
  , c( 1,  4,  5, 3, 0, 3, 5, 4, 1 )
  , c( 1,  2,  4, 5, 5, 5, 4, 2, 1 )
  , c( 0,  1,  1, 2, 2, 2, 1, 1, 0 )
)

library(imager)
im <- load.image("mypic.png")
img <- correlate(im, as.cimg(kernel))
plot(img)

This gives this gray image:

enter image description here

Now I try manually, not taking care of the borders:

RED <- red <- 255*as.matrix(R(im)[, , 1, 1])
GRE <- gre <- 255*as.matrix(G(im)[, , 1, 1])
BLU <- blu <- 255*as.matrix(B(im)[, , 1, 1])

for(r in 10:500) {
  for(c in 10:500){
    red[r, c] <- gre[r, c] <- blu[r, c] <- 0
    for(u in 1:9){
      for(v in 1:9) {
        red[r, c] <- red[r, c] + kernel[u, v] * RED[r+u-5, c+v-5]
        gre[r, c] <- gre[r, c] + kernel[u, v] * GRE[r+u-5, c+v-5]
        blu[r, c] <- blu[r, c] + kernel[u, v] * BLU[r+u-5, c+v-5]
      }
    }
  }
}

img <- as.cimg(abind::abind(red, gre, blu, along = 3L))
plot(img)

This gives a gray image as well.

The gray image is not the correct result. I can get the expected result with the magick package:

library(magick)
im <- image_read("mypic.png")
kern <- "9x9:
    0    1    1    2    2    2    1    1    0
    1    2    4    5    5    5    4    2    1
    1    4    6    3    0    3    6    4    1
    2    5    3  -12  -24  -12    3    5    2
    2    5    0  -24  -40  -24    0    5    2
    2    5    3  -12  -24  -12    3    5    2
    1    4    6    3    0    3    6    4    1
    1    2    4    5    5    5    4    2    1
    0    1    1    2    2    2    1    1    0"
image_morphology(im, kernel = kern, method = "Correlate")

enter image description here

I know it's the expected result because I get almost the same one with Haskell (except that the colors are inversed). So how to do with imager::correlate and manually?

like image 767
Stéphane Laurent Avatar asked Dec 06 '25 07:12

Stéphane Laurent


1 Answers

I think the correct information is there, but the contrast is poor. You can see it better if you set normalise = TRUE

library(imager)

load.image("Klein.png") |>
  correlate(as.cimg(kernel), normalise = TRUE) |> 
  plot()

enter image description here

The problem seems to be that the LoG can give both positive and negative values, and we would expect the pixels where no change is occuring to be 0. Since the plot method for these images normalizes values to between 0 and 1, any negative pixels will be dark and the positive pixels are bright, with 0 values being gray.

We get something much closer to the desired result if we set all negative pixels to 0, since this will make the areas with no changes black upon normalization (effectively setting 0 as the new floor)

arr <- correlate(im, as.cimg(kernel), norm = TRUE) |> as.array()
arr[arr < 0] <- 0
as.cimg(arr) |> plot()

enter image description here

You can bring out more detail by raising arr to a fractional power (since the plot method by default auto-normalizes to between 0 and 1)

as.cimg(arr^0.4) |> plot()

enter image description here

And of course if you want an inverted image, you can do

as.cimg(-(sqrt(arr))) |> plot()

enter image description here


Edit

The main drawback of implementing this entirely in R is speed. You need to get the correlation of kernel with the 9x9 pixel square surrounding each pixel of each channel. There are almost certainly quicker ways to implement this in R, but the naive approach would be something like:

correlate_image <- function(image, kernel) {
  
  correlate_channel <- function(mat, kernel) {
    
    height_seq <- seq(nrow(kernel)) - 1
    width_seq  <- seq(ncol(kernel)) - 1
    res        <- matrix(0, ncol = ncol(mat), nrow = nrow(mat))
    c_kernel   <- c(kernel)
    
    for(i in seq(nrow(mat) - nrow(kernel) - 1)) {
      for(j in seq(ncol(mat) - ncol(kernel) - 1)) {
        submat <- c(mat[i + height_seq, j + width_seq])
        if(sd(submat) != 0) res[i, j] <- cor(submat, c_kernel)
      }
    }
    res[res < 0] <- 0
    return(res/max(res))
  }
  
  arr <- as.array(image)
  arr[,,1,1]   <- correlate_channel(arr[,,1,1], kernel)
  arr[,,1,2]   <- correlate_channel(arr[,,1,2], kernel)
  arr[,,1,3]   <- correlate_channel(arr[,,1,3], kernel)
  arr[,,1,4]   <- 1 # Set alpha channel to 1 for full opacity
  as.cimg(arr)
}

Which allows:

im <- load.image("Klein.png")

my_img <- correlate_image(im, kernel)

plot(my_img)

enter image description here

But takes about 5 minutes to process the image!

The Haskell version seems to use the absolute value of the convolution of the image with the kernel rather than correlation, since if we change cor(submat, c_kernel) to sum(submat * c_kernel) we get very close to the Haskell image, providing we use absolute values and clamp between 0 and 1:

my_img <- correlate_image(im, kernel)
my_img2 <- abs(my_img)
my_img2[my_img2 > 1] <- 1
plot(my_img2)

enter image description here

like image 183
Allan Cameron Avatar answered Dec 09 '25 21:12

Allan Cameron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!