diff --git a/NAMESPACE b/NAMESPACE index 0f1122b5..acdf0966 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -69,6 +69,7 @@ export("frame<-") export(CLAHE) export(Image) export(LUT) +export(ORBkeypoints) export(Queue) export(Stream) export(Video) @@ -171,6 +172,7 @@ export(isVideoStack) export(isVideoWriter) export(laplacian) export(log) +export(matchKeypoints) export(matchShapes) export(matchTemplate) export(medianBlur) diff --git a/R/feature.R b/R/feature.R index b0746553..701f3238 100644 --- a/R/feature.R +++ b/R/feature.R @@ -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 #' @@ -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") @@ -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) @@ -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 @@ -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") @@ -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.") @@ -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) } \ No newline at end of file diff --git a/R/transform.R b/R/transform.R index 0ded75b8..28eccef2 100644 --- a/R/transform.R +++ b/R/transform.R @@ -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). #' @@ -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, diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 65b76e74..f6e72bfb 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -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 diff --git a/docs/reference/canny.html b/docs/reference/canny.html index c562f866..d118044c 100644 --- a/docs/reference/canny.html +++ b/docs/reference/canny.html @@ -125,6 +125,10 @@
Simon Garnier, garnier@njit.edu
+fitEllipse(rnorm(100), rnorm(100))
#> $angle
-#> [1] 97.38929
+#> [1] 175.1583
#>
#> $height
-#> [1] 4.164331
+#> [1] 3.805757
#>
#> $width
-#> [1] 3.369515
+#> [1] 2.914315
#>
#> $center
-#> [1] -0.14290006 0.07829523
+#> [1] -0.02007224 -0.06776221
#>
Simon Garnier, garnier@njit.edu
+Queue
matchShapes()
minAreaRect(rnorm(100), rnorm(100))
#> $angle
-#> [1] 78.76929
+#> [1] 1.715836
#>
#> $height
-#> [1] 4.217333
+#> [1] 5.917526
#>
#> $width
-#> [1] 5.279967
+#> [1] 4.508763
#>
#> $center
-#> [1] 0.3618168 0.7449882
+#> [1] -0.2853765 0.1885252
#>