Kaitlyn Strickfaden 2022-05-03
The edger
package provides a simple method for extracting the outlines
of an object of interest in one image and overlaying the same outlines
onto new images. This vignette is a step-by-step description of the
methodology. For a detailed description of the functions available in
the package right now, refer to the edger
use
vignette.
It’s really important that the object you want to extract contrasts with the background. If the image is overexposed or underexposed, or if the object is a similar color to the background, the method described below won’t work quite as well.
This code uses the package imager
, which is one of few image
processing packages in R. This particular package has some handy
built-in functions that we will be using to save a little time and
computing power.
Let’s take a look at an image using imager
’s “load.image” function.
I’ll also convert it into a data frame and give each pixel a unique
index to make it easier to manipulate later.
im1 <- load.image("../images/image01.jpg")
im1_df <- as.data.frame(im1)
im1_df$id <- rep(1:(dim(im1)[1] * dim(im1)[2]), times = 3)
plot(im1)
A color image is the combined result of three layers of pixels, one layer each for red, green, and blue shades, which are plotted below for reference. R “plots” an image by assigning a combination of red, green, and blue values to each pixel coordinate, with indexing starting in the upper left-hand corner. This image is 1040 pixels wide and 790 pixels high.
red <- im1
red[,,,c(2:3)] <- c(0,0)
green <- im1
green[,,,c(1,3)] <- c(0,0)
blue <- im1
blue[,,,1:2] <- c(0,0)
par(mfrow = c(1,3), mar = c(.1,.1,.1,.1))
plot(red, axes = F)
plot(green, axes = F)
plot(blue, axes = F)
Color images can be converted to grayscale. There are many ways to
convert a color image to grayscale, but imager
converts the luminance
(brightness) of a pixel into a shade of gray. Let’s see what that looks
like for our image:
plot(grayscale(im1), axes = F)
Now we have a black and white version of our image. This is the image from which we will extract edges.
The imgradient
function in imager
compares the value of one pixel to
the values of the pixels immediately around it. Greater differences
between neighboring pixels will give a pixel a larger gradient value,
while weaker differences will give a smaller gradient value. Images have
x gradients and y gradients, which, separately, look like this:
par(mar = c(1,.1,1,.1))
im1_xy <- imgradient(grayscale(im1), "xy")
plot(im1_xy, layout = "row", axes = F)
Next, we’ll find the distance between the values in the x gradient plot
and the values in the y gradient plot at each pixel coordinate. When a
pixel is an edge, there is a greater distance between the x and y
gradient values. Normalizing these distances lets us give greater weight
to places in the image where there are edges. imager
has a function
called enorm
which does these calculations for us. When we find the
distances between the pixels in the x and y gradient plots, the result
is a single image which looks like this:
im1_gr <- enorm(imgradient(grayscale(im1),"xy"))
im1_bw <- as.data.frame(im1_gr)
im1_bw$id <- 1:length(im1_bw$x)
plot(im1_gr, axes = F)
This is the image upon which this methodology stands. Love it, respect it, cherish it.
We’ll also save it as a data frame, because we’ll need it later.
We don’t necessarily want to find every edge in an image; in my case,
I only want the edges of the measuring stake. Fortunately, imager
has
functions that allow you to output the coordinates of particular points,
lines, or rectangles that you draw on an image. This lets us limit our
search for edges to just a region we care about.
The package will allow you to either draw in a region of interest using the “grabRect” function or define a region of interest in th call to the functions. I define my region of interest…
#im_c <- grabRect(im1, output = "coord") # draw a box around the object of interest
im_c <- c(316, 47, 389, 713)
plot(im1, axes = F)
rect(im_c[1], im_c[2], im_c[3], im_c[4], border = "cyan1")
And then filter out the pixels outside of this box so R isn’t dealing with so much data.
roi <- filter(im1_bw,
im1_bw$x >= im_c[1] & im1_bw$x <= im_c[3] &
im1_bw$y >= im_c[2] & im1_bw$y <= im_c[4])
edger_single
and edger_multi
both allow you to set the number of
regions so you can define several regions of interest if you need.
edger_multi
even lets you define regions in multiple images!
This next code chunk will find the coordinates of the edges in the edge
image and recolor them in the original image. You must specify some edge
value as the minimum threshold to keep. By trial and error, I found that
20 was a good th
for this image.
## Find edges in region of interest
roi_cv <- roi[roi$value >= 20/200,]
## Find edge pixels in full image
m1 <- im1_df$id[match(roi_cv$id, im1_df$id)]
## Recolor edge pixels in full image
rgbcolor <- col2rgb("cyan1")/255
im1_df$value[im1_df$id %in% m1] <- rep(rgbcolor, each = length(m1))
What’s the result?
im1_new <- as.cimg(im1_df, dim = dim(im1))
plot(im1_new, axes = F)
Cool! Now we have the original image with the edges of the measuring stake highlighted. We also know the coordinates of those pixels. Of course, this in itself isn’t all that useful; we already know where the measuring stake is in this image. But by extracting edges from this image and then saving the coordinates, we can now recolor those same pixels in new images.
So what happens if the image you have doesn’t have ideal lighting conditions?
When the image is over- or under-exposed, you’ll probably have a much harder time finding a threshold value that gives you good results.
If we instead use the edges from the first image, then we get much better results:
This package, or at least this methodology, will likely be useful for many camera applications which require some reference object. For instance, someone who was interested in being able to measure how far away animals are from cameras might be able to overlay a grid onto images. Or, someone who was interested in measuring the animals themselves might be able to use a similar method to mine.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.