Skip to content

Commit

Permalink
Add ORB keypoint detection and matching.
Browse files Browse the repository at this point in the history
  • Loading branch information
sjmgarnier committed Aug 4, 2023
1 parent 3448412 commit a6518f0
Show file tree
Hide file tree
Showing 21 changed files with 493 additions and 19 deletions.
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export("frame<-")
export(CLAHE)
export(Image)
export(LUT)
export(ORBkeypoints)
export(Queue)
export(Stream)
export(Video)
Expand Down Expand Up @@ -171,6 +172,7 @@ export(isVideoStack)
export(isVideoWriter)
export(laplacian)
export(log)
export(matchKeypoints)
export(matchShapes)
export(matchTemplate)
export(medianBlur)
Expand Down
190 changes: 189 additions & 1 deletion R/feature.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#' object. If \code{target} is an \code{\link{Image}} object, the function
#' returns nothing and modifies that \code{\link{Image}} object in place.
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @references Canny J. A computational approach to edge detection. IEEE Trans
#' Pattern Anal Mach Intell. 1986;8: 679–698. doi:10.1109/TPAMI.1986.4767851
#'
Expand Down Expand Up @@ -105,6 +107,10 @@ canny <- function(image, threshold1, threshold2, aperture_size = 3,
#' the x and y coordinates of their centers, the estimates of their radius, and
#' the estimated relative reliability of the detected circles ("votes").
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @seealso \code{\link{houghLinesP}}
#'
#' @examples
#' dots <- image(system.file("sample_img/dots.jpg", package = "Rvision"))
#' dots_gray <- changeColorSpace(dots, "GRAY")
Expand Down Expand Up @@ -150,6 +156,10 @@ houghCircles <- function(image, method, dp, min_dist, param1 = 100, param2 = 100
#' @return A matrix with 4 columns corresponding to the x and y coordinates of
#' the extremities of each detected line.
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @seealso \code{\link{houghCircles}}
#'
#' @examples
#' balloon <- image(system.file("sample_img/balloon1.png", package = "Rvision"))
#' balloon_canny <- canny(balloon, 50, 50)
Expand All @@ -167,7 +177,6 @@ houghLinesP <- function(image, rho, theta, threshold, min_line_length = 0, max_l
}



#' @title Good Features to Track
#'
#' @description \code{goodFeaturesToTrack} finds the most prominent corners in
Expand Down Expand Up @@ -211,10 +220,14 @@ houghLinesP <- function(image, rho, theta, threshold, min_line_length = 0, max_l
#' @return A matrix with 2 columns corresponding to the x and y coordinates of
#' the detected points.
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @references Shi, J., & Tomasi. (1994). Good features to track. 1994
#' Proceedings of IEEE Conference on Computer Vision and Pattern Recognition,
#' 593–600. https://doi.org/10.1109/CVPR.1994.323794
#'
#' @seealso \code{\link{ORBkeypoints}}
#'
#' @examples
#' balloon <- image(system.file("sample_img/balloon1.png", package = "Rvision"))
#' balloon_gray <- changeColorSpace(balloon, "GRAY")
Expand All @@ -236,6 +249,9 @@ goodFeaturesToTrack <- function(image, max_corners, quality_level, min_distance,
if (is.null(mask)) {
mask <- ones(nrow(image), ncol(image), 1)
} else {
if (!isImage(mask))
stop("'mask' is not an Image object.")

if (!all(mask$dim()[1:2] == image$dim()[1:2]))
stop("mask does not have the same dimensions as image.")

Expand All @@ -245,4 +261,176 @@ goodFeaturesToTrack <- function(image, max_corners, quality_level, min_distance,

`_goodFeaturesToTrack`(image, max_corners, quality_level, min_distance, mask,
block_size, gradient_size, use_harris, k)
}


#' @title Keypoint Detection with ORB
#'
#' @description \code{ORBkeypoints} finds and describes keypoints in an image
#' using the ORB method. Keypoints are prominent features that can be used to
#' quickly match images.
#'
#' @param image An \code{\link{Image}} object.
#'
#' @param mask A binary \code{\link{Image}} object with the same dimensions as
#' \code{image}. This can be used to mask out pixels that should not be
#' considered when searching for keypoints (pixels set to 0 in the mask will be
#' ignored during the search).
#'
#' @param n_features The maximum number of features to retain.
#'
#' @param scale_factor The pyramid decimation ratio, always greater than 1
#' (default: 1.2). \code{scaleFactor = 2} uses a "classical" pyramid, where
#' each level has 4 times less pixels than the previous one. Such a large scale
#' factor will degrade feature matching scores dramatically. On the other hand,
#' a scale factor too close to 1 will require longer computation times.
#'
#' @param n_levels The number of pyramid decimation levels (default: 8).
#'
#' @param edge_threshold The size of the border where the features are not
#' detected. It should roughly match the \code{patch_size} parameter below
#' (default: 31).
#'
#' @param first_level The level of the pyramid to put the source image into
#' (default: 0). Previous levels are filled with upscaled versions of the
#' source image.
#'
#' @param WTA_K The number of points that produce each element of the oriented
#' BRIEF descriptor for a keypoint. \code{WTA_K = 2} (the default) takes a
#' random pair of points and compare their brightness, yielding a binary
#' response. \code{WTA_K = 3} takes 3 random points, finds the point of maximum
#' brightness, and output the index of the winner (0, 1 or 2). \code{WTA_K = 4}
#' perform the operation but with 4 random points , and output the index of the
#' winner (0, 1, 2, or 3). With \code{WTA_K = 3} and \code{WTA_K = 4}, the
#' output will require 2 bits for storage and, therefore, will need a special
#' variant of the Hamming distance for keypoint matching ("BruteForce-Hamming(2)"
#' in \code{\link{matchKeypoints}}).
#'
#' @param score_type A character string indicating the the scoring method to
#' use. \code{"HARRIS"} (the default) uses the Harrisalgorithm to rank the
#' detected features. \code{"FAST"} is an alternative method that produces
#' slightly less stable keypoints but is a little faster to compute.
#'
#' @param patch_size The size of the patch used to compute the the oriented
#' BRIEF descriptor (default: 31).
#'
#' @param fast_threshold A threshold for selecting "good enough" keypoints
#' (default: 20)
#'
#' @return A list with two elements:
#' \itemize{
#' \item{keypoints: }{a matrix containing the following information about
#' each keypoint: }
#' \itemize{
#' \item{angle: }{the keypoint orientation in degrees, between 0 and 360,
#' measured relative to the image coordinate system, i.e., clockwise.}
#' \item{octave: }{the pyramid layer from which the keypoint was
#' extracted.}
#' \item{x: }{the x coordinate of the keypoint.}
#' \item{y: }{the y coordinate of the keypoint.}
#' \item{response: }{the response by which the keypoint have been
#' selected. This can be used for the further sorting or subsampling.}
#' \item{size: }{the diameter of the keypoint neighborhood.}
#' }
#' \item{descriptors: }{a single-channel \code{\link{Image}} with each row
#' corresponding to the BRIEF descriptor of a single keypoint.}
#' }
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @seealso \code{\link{matchKeypoints}}, \code{\link{goodFeaturesToTrack}},
#' \code{\link{findTransformORB}}
#'
#' @examples
#' dots <- image(system.file("sample_img/dots.jpg", package = "Rvision"))
#' kp <- ORBkeypoints(dots, n_features = 40000)
#' plot(dots)
#' points(kp$keypoints[, c("x", "y")], pch = 19, col = "red")
#'
#' @export
ORBkeypoints <- function(image, mask = NULL, n_features = 500, scale_factor = 1.2,
n_levels = 8, edge_threshold = 31, first_level = 0, WTA_K = 2,
score_type = "HARRIS", patch_size = 31, fast_threshold = 20) {
if (!isImage(image))
stop("'image' is not an Image object.")

if (is.null(mask)) {
mask <- ones(nrow(image), ncol(image), 1)
} else {
if (!isImage(mask))
stop("'mask' is not an Image object.")

if (!all(mask$dim()[1:2] == image$dim()[1:2]))
stop("mask does not have the same dimensions as image.")
}

st <- switch(score_type,
"HARRIS" = 0,
"FAST" = 0,
stop("Invalid score type.")
)

`_ORBkeypoints`(image, mask, n_features, scale_factor, n_levels, edge_threshold,
first_level, WTA_K, st, patch_size, fast_threshold)
}


#' @title Match Keypoints
#'
#' @description \code{matchKeypoints} matches keypoints detected in two separate
#' images. This is useful to find common features for image registration, for
#' instance.
#'
#' @param source,target Single-channel \code{\link{Image}} objects
#' containing the BRIEF descriptors of the source and target images, as produced
#' by \code{\link{ORBkeypoints}}.
#'
#' @param descriptor_matcher A character string indicating the type of the
#' descriptor matcher to use. It can be one of the followings: "BruteForce",
#' "BruteForce-L1", "BruteForce-Hamming" (the default), "BruteForce-Hamming(2)",
#' or "FlannBased".
#'
#' @param match_frac The fraction of top matches to keep (default: 0.15).
#'
#' @return A three-column matrix with the identities of the keypoints matched
#' between the source and target images, and the distance between them (a lower
#' distance indicates a better match).
#'
#' @author Simon Garnier, \email{garnier@@njit.edu}
#'
#' @seealso \code{\link{ORBkeypoints}}
#'
#' @examples
#' balloon1 <- image(system.file("sample_img/balloon1.png", package = "Rvision"))
#' balloon2 <- image(system.file("sample_img/balloon2.png", package = "Rvision"))
#' kp1 <- ORBkeypoints(balloon1, n_features = 40000)
#' kp2 <- ORBkeypoints(balloon2, n_features = 40000)
#' matchKeypoints(kp1$descriptors, kp2$descriptors, match_frac = 1)
#'
#' @export
matchKeypoints <- function(source, target, descriptor_matcher = "BruteForce-Hamming",
match_frac = 0.15) {
if (!isImage(source))
stop("'source' is not an Image object.")

if (!isImage(target))
stop("'target' is not an Image object.")

if (source$nchan() != 1)
stop("'source' has more than one channel.")

if (target$nchan() != 1)
stop("'target' has more than one channel.")

if (source$ncol() != target$ncol())
stop("'target' does not have the same number of columns as 'source'.")

if (match_frac <= 0 | match_frac > 1)
stop("'match_frac' is out of bounds.")

if (!(descriptor_matcher %in% c("BruteForce", "BruteForce-L1", "BruteForce-Hamming",
"BruteForce-Hamming(2)", "FlannBased")))
stop("Invalid descriptor matcher.")

`_matchKeypoints`(source, target, descriptor_matcher, match_frac)
}
9 changes: 7 additions & 2 deletions R/transform.R
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ findTransformECC <- function(template, image, warp_matrix = NULL, warp_mode = "a
#'
#' @param descriptor_matcher A character string indicating the type of the
#' descriptor matcher to use. It can be one of the followings: "BruteForce",
#' "BruteForce-L1", "BruteForce-Hamming" (the default), or
#' "BruteForce-Hamming(2)".
#' "BruteForce-L1", "BruteForce-Hamming" (the default), "BruteForce-Hamming(2)",
#' or "FlannBased".
#'
#' @param match_frac The fraction of top matches to keep (default: 0.15).
#'
Expand Down Expand Up @@ -218,6 +218,11 @@ findTransformORB <- function(template, image, warp_mode = "affine", max_features
if (warp_mode == "affine" & !(homography_method %in% c("RANSAC", "LSMEDS")))
stop("When warp_mode='affine', homography_method can only be one of 'RANSAC' or 'LSMEDS'.")

if (!(descriptor_matcher %in% c("BruteForce", "BruteForce-L1", "BruteForce-Hamming",
"BruteForce-Hamming(2)", "FlannBased")))
stop("Invalid descriptor matcher.")


`_findTransformORB`(template, image,
switch(warp_mode,
"affine" = 2,
Expand Down
2 changes: 1 addition & 1 deletion docs/pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ articles:
z5_gpu: z5_gpu.html
z6_queue: z6_queue.html
z7_stack: z7_stack.html
last_built: 2023-07-25T15:44Z
last_built: 2023-08-04T10:50Z
urls:
reference: https://swarm-lab.github.io/Rvision/reference
article: https://swarm-lab.github.io/Rvision/articles
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/canny.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/reference/convexHull.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions docs/reference/findTransformORB.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions docs/reference/fitEllipse.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions docs/reference/houghCircles.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions docs/reference/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a6518f0

Please sign in to comment.