vignettes/keypoint-alignment.Rmd
keypoint-alignment.Rmd
The R code underlying the functions in this vignette were borrowed with permission from Vinces Gaitan’s blog post: https://www.kaggle.com/vicensgaitan/image-registration-the-r-way/notebook
I have modified the code where necessary in order to make it more pipeline-friendly and function oriented.
imlinks <- system.file(package = "ShoeSampleData", "extdata/") %>%
list.files(pattern = "036285L", full.names = T) %>%
sort()
clean_shoe_img <- function(im) {
suppressMessages({
im_bbox <- im %>%
imsplit(axis = "c") %>%
(function(x) is.finite((x[[1]] + x[[2]]) / x[[3]])) %>%
as.cimg() %>%
(function(x) x == 1)
crop.bbox(im, im_bbox) %>%
grayscale() %>%
map_halfimg(fun = autocrop) %>%
crop.borders(nx = 5, ny = 5) %>%
autocrop() %>%
threshold() %>%
shrink(3) %>%
grow(3) %>%
autocrop() %>%
# img_rotate_refit() %>%
# magrittr::extract2("img") %>%
grayscale()
})
}
img_a <- load.image(imlinks[1]) %>% clean_shoe_img()
img_b <- load.image(imlinks[2]) %>% clean_shoe_img()
plot(imlist(img_a, img_b))
We need to pad image a so that it is the same size as image b:
dim(img_a)
# [1] 1179 3847 1 1
dim(img_b)
# [1] 1196 3860 1 1
pad_size <- dim(img_b) - dim(img_a)
img_a <- pad(img_a, nPix = pad_size[1], axes = "x", pos = 1, val = max(img_a)) %>%
pad(nPix = pad_size[2], axes = "y", pos = 1, val = max(img_a))
We can then overlay the two images to see how far apart they are:
The Harris corner detection algorithm has high values (light pixels) in areas where there are well-defined corners in the image.
The detector seems to be working fairly well, outlining major features of the shoe print.
Thresholding the image from the previous step at 99% produces the best 1% of keypoints:
The regions are labeled, and region centers are computed for each separately labeled region
keypoints <- hkp_threshold %>% label() %>% region_centers(bord = 30)
plot(img_a)
points(keypoints$mx, keypoints$my, col = "red")
The first two steps can be streamlined using the harris_keypoints()
function:
For each angle, we pull features from a 40x40 area around the keypoint. These features will be used to identify points of similarity across the two images.
hkp_a <- harris_keypoints(img_a, sigma = 6)
hkp_b <- harris_keypoints(img_b, sigma = 6)
angles_a <- img_a %>% oriented_gradients(sigma = 3)
get_kpf <- function(angles, hkp, im) {
kpa <- data_frame(angle = angles, v = list(hkp$centers)) %>%
tidyr::unnest(v) %>%
dplyr::rename(theta = angle, x = mx, y = my) %>%
mutate(idx = 1:n()) %>%
rowwise() %>%
tidyr::nest(-theta, -idx, .key = "v") %>%
select(-idx)
purrr::pmap(list(theta = kpa$theta, v = kpa$v), descriptor_orientation, im = grayscale(im)) %>%
do.call("rbind", .)
}
kpf_a <- get_kpf(angles_a, hkp_a, img_a)
# Warning in grayscale(im): Image appears to already be in grayscale mode
kpf_b <- get_kpf(angles_b, hkp_b, img_b)
# Warning in grayscale(im): Image appears to already be in grayscale mode
Match points are calculated using the K nearest neighbors algorithm, combined with some thresholding by distance.
RANSAC is then used to find points that have similar homography.
par(mfrow = c(1, 2))
plot(img_a)
hkp_a$centers %$% points(mx, my, col = "orange")
points(match_points$points_a[ransac_points$inliers, ], col = "purple", pch = 16)
plot(img_b)
hkp_b$centers %$% points(mx, my, col = "orange")
points(match_points$points_b[ransac_points$inliers, ], col = "purple", pch = 16)
The homography can be used to warp one image onto the other:
map_fcn <- map_affine_gen(ransac_points$homography)
img_a_warp <- imwarp(img_a, map_fcn, direction = "backward", boundary = "neumann")
plot(img_a_warp)
We can then overlay the two images:
blank_channel <- as.cimg(img_b > 0 & img_a_warp > 0)
overlaid_images <- imappend(imlist(img_a_warp, blank_channel, img_b), axis = "c")
plot(overlaid_images)
Areas that are in the first image only are shown in red; areas in the second image only are shown in blue. Areas in both images are shown in black.