From d611b9280280016ce096254a4ec9467c1d65c718 Mon Sep 17 00:00:00 2001 From: "Dr. Zygmunt L. Szpak" Date: Fri, 1 Dec 2017 23:15:04 +1030 Subject: [PATCH] Add sub-pixel corner detection capability Introduces the function imcorner_subpixel as well as several requisite support functions to facilitate the ability to detect corners to sub-pixel precision. The imcorner_subpixel uses the existing imcorner functionality to obtain initial corner response values and a binary corner indicator matrix. The corner response values and the indicator matrix are used in a univariate quadratic interpolation scheme to determine the corner coordinates to sub-pixel precision. The new imcorner_subpixel function returns a length-3 NTuple of type HomogeneousPoint, which represents the homogeneous coordinates of a planar point. --- REQUIRE | 1 + src/Images.jl | 52 ++++- src/corner.jl | 136 +++++++++++++ test/corner.jl | 515 ++++++++++++++++++++++++++++++++++--------------- 4 files changed, 544 insertions(+), 160 deletions(-) diff --git a/REQUIRE b/REQUIRE index 96991849..fd0aa561 100644 --- a/REQUIRE +++ b/REQUIRE @@ -13,6 +13,7 @@ ImageMetadata IndirectArrays MappedArrays SIUnits +StaticArrays Graphics 0.1 FileIO Compat 0.19 diff --git a/src/Images.jl b/src/Images.jl index 79adc3e2..eba9ec45 100644 --- a/src/Images.jl +++ b/src/Images.jl @@ -13,11 +13,13 @@ import Base: abs, atan2, clamp, convert, copy, copy!, ctranspose, delete!, strides, sum, write, zero export float32, float64 +export HomogeneousPoint using Base: depwarn using Base.Order: Ordering, ForwardOrdering, ReverseOrdering using Compat +using StaticArrays # "deprecated imports" are below @@ -64,6 +66,52 @@ Indicate that `x` should be interpreted as a [percentile](https://en.wikipedia.o """ struct Percentile{T} <: Real p::T end + +""" +HomogeneousPoint(x::NTuple{N, T}) + +In projective geometry [homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates) are the +natural coordinates for describing points and lines. + +For instance, the homogeneous coordinates for a planar point are a triplet of real numbers ``(u, v ,w)``, with ``w \\neq 0``. +This triple can be associated with a point ``P = (x,y)`` in Cartesian coordinates, where ``x = \\frac{u}{w}`` and ``y = \\frac{v}{w}`` +[(more details)](http://www.geom.uiuc.edu/docs/reference/CRC-formulas/node6.html#SECTION01140000000000000000). + +In particular, the `HomogeneousPoint((10.0,5.0,1.0))` is the standardised projective representation of the Cartesian +point `(10.0,5.0)`. +""" +struct HomogeneousPoint{T <: AbstractFloat,N} + coords::NTuple{N, T} +end + +# By overwriting Base.to_indices we can define how to index into an N-dimensional array +# given an (N+1)-dimensional [`HomogeneousPoint`](@ref) type. +# We do this by converting the homogeneous coordinates to Cartesian coordinates +# and rounding to nearest integer. +# +# For homogeneous coordinates of a planar point we return +# a tuple of permuted Cartesian coordinates, (y,x), since matrices +# are indexed according to row and then column. +# For homogeneous coordinates of other dimensions we do not permute +# the corresponding Cartesian coordinates. +Base.to_indices(A::AbstractArray, p::Tuple{<: HomogeneousPoint}) = homogeneous_point_to_indices(p[1]) + +function homogeneous_point_to_indices(p::HomogeneousPoint{T,3}) where T + if p.coords[end] == 1 + return round(Int, p.coords[2]), round(Int, p.coords[1]) + else + return round(Int, p.coords[2] / p.coords[end]), round(Int, p.coords[1] / p.coords[end]) + end +end + +function homogeneous_point_to_indices(p::HomogeneousPoint) + if p.coords[end] == 1 + return round.(Int, p.coords) + else + return round.(Int, p.coords ./ p.coords[end]) + end +end + include("labeledarrays.jl") include("algorithms.jl") include("exposure.jl") @@ -154,6 +202,8 @@ export # types forwarddiffx, forwarddiffy, imcorner, + imcorner_subpixel, + corner2subpixel, harris, shi_tomasi, kitchen_rosenfeld, @@ -244,7 +294,7 @@ Algorithms: - Exposure : `imhist`, `histeq`, `adjust_gamma`, `histmatch`, `imadjustintensity`, `imstretch`, `imcomplement`, `clahe`, `cliphist` - Gradients: `backdiffx`, `backdiffy`, `forwarddiffx`, `forwarddiffy`, `imgradients` - Edge detection: `imedge`, `imgradients`, `thin_edges`, `magnitude`, `phase`, `magnitudephase`, `orientation`, `canny` - - Corner detection: `imcorner`, `harris`, `shi_tomasi`, `kitchen_rosenfeld`, `meancovs`, `gammacovs`, `fastcorners` + - Corner detection: `imcorner`,`imcorner_subpixel`, `harris`, `shi_tomasi`, `kitchen_rosenfeld`, `meancovs`, `gammacovs`, `fastcorners` - Blob detection: `blob_LoG`, `findlocalmaxima`, `findlocalminima` - Morphological operations: `dilate`, `erode`, `closing`, `opening`, `tophat`, `bothat`, `morphogradient`, `morpholaplace`, `feature_transform`, `distance_transform`, `convexhull` - Connected components: `label_components`, `component_boxes`, `component_lengths`, `component_indices`, `component_subscripts`, `component_centroids` diff --git a/src/corner.jl b/src/corner.jl index f882c695..b58ac1fc 100644 --- a/src/corner.jl +++ b/src/corner.jl @@ -35,6 +35,142 @@ function imcorner(img::AbstractArray, thresholdp::Percentile; method::Function = map(i -> i > threshold, responses) end +""" +``` +corners = imcorner_subpixel(img; [method]) + -> Vector{HomogeneousPoint{Float64,3}} +corners = imcorner_subpixel(img, threshold, percentile; [method]) + -> Vector{HomogeneousPoint{Float64,3}} +``` + +Same as [`imcorner`](@ref), but estimates corners to sub-pixel precision. + +Sub-pixel precision is achieved by interpolating the corner response values using +the 4-connected neighbourhood of a maximum response value. +See [`corner2subpixel`](@ref) for more details of the interpolation scheme. + +""" +function imcorner_subpixel(img::AbstractArray; method::Function = harris, args...) + responses = method(img; args...) + corner_indicator = similar(img, Bool) + fill!(corner_indicator, false) + maxima = map(CartesianIndex, findlocalmaxima(responses)) + for m in maxima corner_indicator[m] = true end + corners = corner2subpixel(responses,corner_indicator) +end + +function imcorner_subpixel(img::AbstractArray, threshold; method::Function = harris, args...) + responses = method(img; args...) + corner_indicator = map(i -> i > threshold, responses) + corners = corner2subpixel(responses,corner_indicator) +end + +function imcorner_subpixel(img::AbstractArray, thresholdp::Percentile; method::Function = harris, args...) + responses = method(img; args...) + threshold = StatsBase.percentile(vec(responses), thresholdp.p) + corner_indicator = map(i -> i > threshold, responses) + corners = corner2subpixel(responses,corner_indicator) +end + + +""" +``` +corners = corner2subpixel(responses::AbstractMatrix,corner_indicator::AbstractMatrix{Bool}) + -> Vector{HomogeneousPoint{Float64,3}} +``` +Refines integer corner coordinates to sub-pixel precision. + +The function takes as input a matrix representing corner responses +and a boolean indicator matrix denoting the integer coordinates of a corner +in the image. The output is a vector of type [`HomogeneousPoint`](@ref) +storing the sub-pixel coordinates of the corners. + +The algorithm computes a correction factor which is added to the original +integer coordinates. In particular, a univariate quadratic polynomial is fit +separately to the ``x``-coordinates and ``y``-coordinates of a corner and its immediate +east/west, and north/south neighbours. The fit is achieved using a local +coordinate system for each corner, where the origin of the coordinate system is +a given corner, and its immediate neighbours are assigned coordinates of minus +one and plus one. + +The corner and its two neighbours form a system of three equations. For example, +let ``x_1 = -1``, ``x_2 = 0`` and ``x_3 = 1`` denote the local ``x`` coordinates +of the west, center and east pixels and let the vector ``\\mathbf{b} = [r_1, r_2, r_3]`` +denote the corresponding corner response values. With + +```math + \\mathbf{A} = + \\begin{bmatrix} + x_1^2 & x_1 & 1 \\\\ + x_2^2 & x_2 & 1 \\\\ + x_3^2 & x_3 & 1 \\\\ + \\end{bmatrix}, +``` +the coefficients of the quadratic polynomial can be found by solving the +system of equations ``\\mathbf{b} = \\mathbf{A}\\mathbf{x}``. +The result is given by ``x = \\mathbf{A}^{-1}\\mathbf{b}``. + +The vertex of the quadratic polynomial yields a sub-pixel estimate of the +true corner position. For example, for a univariate quadratic polynomial +``px^2 + qx + r``, the ``x``-coordinate of the vertex is ``\\frac{-q}{2p}``. +Hence, the refined sub-pixel coordinate is equal to: + ``c + \\frac{-q}{2p}``, where ``c`` is the integer coordinate. + +!!! note + Corners on the boundary of the image are not refined to sub-pixel precision. + +""" +function corner2subpixel(responses::AbstractMatrix, corner_indicator::AbstractMatrix{Bool}) + row_range, col_range = indices(corner_indicator) + row, col, _ = findnz(corner_indicator) + ncorners = length(row) + corners = fill(HomogeneousPoint((0.0,0.0,0.0)),ncorners) + invA = @SMatrix [0.5 -1.0 0.5; -0.5 0.0 0.5; 0.0 1.0 -0.0] + for k = 1:ncorners + # Corners on the perimeter of the image will not be interpolated. + if (row[k] == first(row_range) || row[k] == last(row_range) || + col[k] == first(col_range) || col[k] == last(col_range)) + y = convert(Float64,row[k]) + x = convert(Float64,col[k]) + corners[k] = HomogeneousPoint((x,y,1.0)) + else + center, north, south, east, west = + unsafe_neighbourhood_4(responses,row[k],col[k]) + # Solve for the coefficients of the quadratic equation. + a, b, c = invA* @SVector [west, center, east] + p, q, r = invA* @SVector [north, center, south] + # Solve for the first coordinate of the vertex. + u = -b/(2.0a) + v = -q/(2.0p) + corners[k] = HomogeneousPoint((col[k]+u,row[k]+v,1.0)) + end + end + return corners +end + +""" +``` +unsafe_neighbourhood_4(matrix::AbstractMatrix,r::Int,c::Int) +``` + +Returns the value of a matrix at given coordinates together with the values +of the north, south, east and west neighbours. + +This function does not perform bounds checking. It is up to the user to ensure +that the function is not called with indices that are on the boundary of the +matrix. + +""" +function unsafe_neighbourhood_4(matrix::AbstractMatrix,r::Int,c::Int) + center = matrix[r,c] + north = matrix[r-1,c] + south = matrix[r+1,c] + east = matrix[r,c+1] + west = matrix[r,c-1] + return center, north, south, east, west +end + + """ ``` harris_response = harris(img; [k], [border], [weights]) diff --git a/test/corner.jl b/test/corner.jl index 29ff4a62..c2b006f4 100644 --- a/test/corner.jl +++ b/test/corner.jl @@ -1,191 +1,388 @@ -using Base.Test, Images, Colors, FixedPointNumbers +using Base.Test, Images, Colors, FixedPointNumbers, OffsetArrays @testset "Corner" begin A = zeros(41,41) A[16:26,16:26] = 1 @testset "Corners API" begin - img = zeros(20, 20) - img[4:17, 4:17] = 1 - img[8:13, 8:13] = 0 + img = zeros(20, 20) + img[4:17, 4:17] = 1 + img[8:13, 8:13] = 0 - corners = imcorner(img, method = harris) + corners = imcorner(img, method = harris) expected_corners = falses(20, 20) ids = map(CartesianIndex{2}, [(4, 4), (4, 17), (17, 4), (17, 17), (8, 8), (8, 13), (13, 8), (13, 13)]) for id in ids expected_corners[id] = true end expected_harris = copy(expected_corners) - for id in ids @test corners[id] end - @test sum(corners .!= expected_corners) < 3 - corners = imcorner(img, method = shi_tomasi) - for id in ids @test corners[id] end - @test sum(corners .!= expected_corners) < 3 - - ids = map(CartesianIndex{2}, [(4, 4), (4, 17), (17, 4), (17, 17)]) - corners = imcorner(img, method = kitchen_rosenfeld) - for id in ids @test corners[id] end - ids = map(CartesianIndex{2}, [(8, 8), (8, 13), (13, 8), (13, 13)]) - for id in ids expected_corners[id] = 0 end - @test sum(corners .!= expected_corners) < 3 + for id in ids @test corners[id] end + @test sum(corners .!= expected_corners) < 3 + corners = imcorner(img, method = shi_tomasi) + for id in ids @test corners[id] end + @test sum(corners .!= expected_corners) < 3 + + ids = map(CartesianIndex{2}, [(4, 4), (4, 17), (17, 4), (17, 17)]) + corners = imcorner(img, method = kitchen_rosenfeld) + for id in ids @test corners[id] end + ids = map(CartesianIndex{2}, [(8, 8), (8, 13), (13, 8), (13, 13)]) + for id in ids expected_corners[id] = 0 end + @test sum(corners .!= expected_corners) < 3 + end + + @testset "Corners Sub-pixel API" begin + # Validate Base.to_indices implementation + # for a HomogeneousPoint which facilitates + # indexing into a multi-dimensional array. + + # Simulate a grid of voxels + V = fill(false,41,41,41) + V[16,16,20]= true + V[16,26,20]= true + V[26,16,20]= true + V[26,26,20]= true + + # Homogeneous coordinates that are in standard form + pt = HomogeneousPoint((16.3,16.4,20.3,1.0)) + @test V[pt] == true + pt = HomogeneousPoint((16.6,16.7,20.7,1.0)) + @test V[pt] == false + pt = HomogeneousPoint((26.3,16.4,20.2,1.0)) + @test V[pt] == true + pt = HomogeneousPoint((26.6,16.7,20.8,1.0)) + @test V[pt] == false + + # Homogeneous coordinates that are not in standard form + pt = HomogeneousPoint(5.*(16.3,16.4,20.3,1.0)) + @test V[pt] == true + pt = HomogeneousPoint(5.*(16.6,16.7,20.7,1.0)) + @test V[pt] == false + pt = HomogeneousPoint(5.*(26.3,16.4,20.2,1.0)) + @test V[pt] == true + pt = HomogeneousPoint(5.*(26.6,16.7,20.8,1.0)) + @test V[pt] == false + + # Simulate corner responses. + Ac = zeros(41,41) + Ac[16,15:17] = [0.00173804, 0.00607446, 0.00458574] + Ac[15:17,16] = [0.00173804, 0.00607446, 0.00458574] + Ac[16,25:27] = [0.00458574, 0.00607446, 0.00173804] + Ac[15:17,26] = [0.00173804, 0.00607446, 0.00458574] + Ac[25:27,16] = [0.00458574, 0.00607446, 0.00173804] + Ac[26,15:17] = [0.00173804, 0.00607446, 0.00458574] + Ac[26,25:27] = [0.00458574, 0.00607446, 0.00173804] + Ac[25:27,26] = [0.00458574, 0.00607446, 0.00173804] + + # Simulate corner detections. + I = fill(false,41,41) + I[16,16]= true + I[16,26]= true + I[26,16]= true + I[26,26]= true + + # Validate Base.to_indices implementation + # for a planar HomogeneousPoint which facilitates + # indexing into a matrix. + + # Homogeneous coordinates that are in standard form + pt = HomogeneousPoint((16.3,16.4,1.0)) + @test I[pt] == true + pt = HomogeneousPoint((16.6,16.7,1.0)) + @test I[pt] == false + pt = HomogeneousPoint((26.3,16.4,1.0)) + @test I[pt] == true + pt = HomogeneousPoint((26.6,16.7,1.0)) + @test I[pt] == false + + # Homogeneous coordinates that are not in standard form + pt = HomogeneousPoint(5.*(16.3,16.4,1.0)) + @test I[pt] == true + pt = HomogeneousPoint(5.*(16.6,16.7,1.0)) + @test I[pt] == false + pt = HomogeneousPoint(5.*(26.3,16.4,1.0)) + @test I[pt] == true + pt = HomogeneousPoint(5.*(26.6,16.7,1.0)) + @test I[pt] == false + + # Check sub-pixel refinement on Harris corner response. + pts = corner2subpixel(Ac, I) + @test pts[1].coords[1] ≈ 16.24443189348239 + @test pts[1].coords[2] ≈ 16.24443189348239 + @test pts[1].coords[3] ≈ 1 + @test pts[2].coords[1] ≈ 16.24443189348239 + @test pts[2].coords[2] ≈ 25.75556810651761 + @test pts[2].coords[3] ≈ 1 + @test pts[3].coords[1] ≈ 25.75556810651761 + @test pts[3].coords[2] ≈ 16.24443189348239 + @test pts[3].coords[3] ≈ 1 + @test pts[4].coords[1] ≈ 25.75556810651761 + @test pts[4].coords[2] ≈ 25.75556810651761 + @test pts[4].coords[3] ≈ 1 + + # Check imcorners_subpixel API. + img = zeros(20, 20) + img[4:17, 4:17] = 1 + img[8:13, 8:13] = 0 + + ids = map(HomogeneousPoint, + [(4.244432486132958, 4.244432486132958, 1.0), + (4.244432486132958, 16.755567513867042, 1.0), + (8.244432486132958, 8.244432486132958, 1.0), + (8.244432486132958, 12.755567513867042, 1.0), + (12.755567513867042, 8.244432486132958, 1.0), + (12.755567513867042, 12.755567513867042, 1.0), + (16.755567513867042, 4.244432486132958, 1.0), + (16.755567513867042, 16.755567513867042, 1.0)]) + + # Default. + corner_pts = imcorner_subpixel(img, method = harris) + @test length(corner_pts) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts[i].coords) + end + + # User specifies threshold. + corner_pts = imcorner_subpixel(img, 0.005, method = harris) + @test length(corner_pts) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts[i].coords) + end + + # User specifies percentile. + corner_pts = imcorner_subpixel(img, Percentile(98), method = harris) + @test length(corner_pts) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts[i].coords) + end + + # Check border cases for corner2subpixel. + responses = zeros(5,5) + corner_indicator = fill(false,5,5) + corner_indicator[1,3] = true + corner_indicator[3,1] = true + corner_indicator[3,5] = true + corner_indicator[5,3] = true + responses[1,3] = 0.5 + responses[1,2] = 0.2 + responses[5,3] = 0.5 + responses[5,2] = 0.2 + responses[3,1] = 0.5 + responses[2,1] = 0.2 + responses[3,5] = 0.5 + responses[2,5] = 0.2 + pts = corner2subpixel(responses, corner_indicator) + @test pts[1].coords[1] ≈ 1 + @test pts[1].coords[2] ≈ 3 + @test pts[1].coords[3] ≈ 1 + @test pts[2].coords[1] ≈ 3 + @test pts[2].coords[2] ≈ 1 + @test pts[2].coords[3] ≈ 1 + @test pts[3].coords[1] ≈ 3 + @test pts[3].coords[2] ≈ 5 + @test pts[3].coords[3] ≈ 1 + @test pts[4].coords[1] ≈ 5 + @test pts[4].coords[2] ≈ 3 + @test pts[4].coords[3] ≈ 1 + + # Test functionality using OffsetArray + img = zeros(20, 20) + img[4:17, 4:17] = 1 + img[8:13, 8:13] = 0 + + for s = -100:50:100 + for t = -100:50:100 + imgo = OffsetArray(img, (s, t)) + ids = map(HomogeneousPoint, + [(4.244432486132958 + t, 4.244432486132958 + s, 1.0), + (4.244432486132958 + t, 16.755567513867042 + s, 1.0), + (8.244432486132958 + t, 8.244432486132958 + s, 1.0), + (8.244432486132958 + t, 12.755567513867042 + s, 1.0), + (12.755567513867042 + t, 8.244432486132958 + s, 1.0), + (12.755567513867042 + t, 12.755567513867042 + s, 1.0), + (16.755567513867042 + t, 4.244432486132958 + s, 1.0), + (16.755567513867042 + t, 16.755567513867042 + s, 1.0)]) + + # Default. + corner_pts_offset = imcorner_subpixel(imgo, method = harris) + @test length(corner_pts_offset) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts_offset[i].coords) + end + + # User specifies threshold. + corner_pts_offset = imcorner_subpixel(imgo, 0.005, method = harris) + @test length(corner_pts_offset) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts_offset[i].coords) + end + + # User specifies percentile. + corner_pts_offset = imcorner_subpixel(imgo, Percentile(98), method = harris) + @test length(corner_pts_offset) == length(ids) + for i = 1:length(ids) + @test all(ids[i].coords .≈ corner_pts_offset[i].coords) + end + end + end end @testset "Harris" begin - Ac = imcorner(A, Percentile(99), method = harris) - # check corners - @test Ac[16,16] - @test Ac[16,26] - @test Ac[26,16] - @test Ac[26,26] - - @test !all(Ac[1:14, :]) - @test !all(Ac[:, 1:14]) - @test !all(Ac[28:end, :]) - @test !all(Ac[:, 28:end]) - @test !all(Ac[18:24, 18:24]) - - Ac = harris(A) - - @test Ac[16,16] ≈ maximum(Ac) - @test Ac[16,26] ≈ Ac[16,16] - @test Ac[26,16] ≈ Ac[16,16] - @test Ac[26,26] ≈ Ac[16,16] - - # check edge intensity - @test Ac[16,21] ≈ minimum(Ac) - @test Ac[21,16] ≈ Ac[16,21] - @test Ac[21,26] ≈ Ac[16,21] - @test Ac[26,21] ≈ Ac[16,21] - - # check background intensity - @test Ac[5,5] ≈ 0 - @test Ac[5,36] ≈ 0 - @test Ac[36,5] ≈ 0 - @test Ac[36,36] ≈ 0 + Ac = imcorner(A, Percentile(99), method = harris) + # check corners + @test Ac[16,16] + @test Ac[16,26] + @test Ac[26,16] + @test Ac[26,26] + + @test !all(Ac[1:14, :]) + @test !all(Ac[:, 1:14]) + @test !all(Ac[28:end, :]) + @test !all(Ac[:, 28:end]) + @test !all(Ac[18:24, 18:24]) + + Ac = harris(A) + + @test Ac[16,16] ≈ maximum(Ac) + @test Ac[16,26] ≈ Ac[16,16] + @test Ac[26,16] ≈ Ac[16,16] + @test Ac[26,26] ≈ Ac[16,16] + + # check edge intensity + @test Ac[16,21] ≈ minimum(Ac) + @test Ac[21,16] ≈ Ac[16,21] + @test Ac[21,26] ≈ Ac[16,21] + @test Ac[26,21] ≈ Ac[16,21] + + # check background intensity + @test Ac[5,5] ≈ 0 + @test Ac[5,36] ≈ 0 + @test Ac[36,5] ≈ 0 + @test Ac[36,36] ≈ 0 end @testset "Shi-Tomasi" begin - Ac = imcorner(A, Percentile(99), method = shi_tomasi) - # check corners - @test Ac[16,16] - @test Ac[16,26] - @test Ac[26,16] - @test Ac[26,26] - - @test !all(Ac[1:14, :]) - @test !all(Ac[:, 1:14]) - @test !all(Ac[28:end, :]) - @test !all(Ac[:, 28:end]) - @test !all(Ac[18:24, 18:24]) - - Ac = shi_tomasi(A) - # check corner intensity - @test Ac[16,16] ≈ maximum(Ac) - @test Ac[16,26] ≈ Ac[16,16] - @test Ac[26,16] ≈ Ac[16,16] - @test Ac[26,26] ≈ Ac[16,16] - - # check edge intensity - @test Ac[16,21] ≈ 0 - @test Ac[21,16] ≈ 0 - @test Ac[21,26] ≈ 0 - @test Ac[26,21] ≈ 0 - - # check background intensity - @test Ac[5,5] ≈ 0 - @test Ac[5,36] ≈ 0 - @test Ac[36,5] ≈ 0 - @test Ac[36,36] ≈ 0 + Ac = imcorner(A, Percentile(99), method = shi_tomasi) + # check corners + @test Ac[16,16] + @test Ac[16,26] + @test Ac[26,16] + @test Ac[26,26] + + @test !all(Ac[1:14, :]) + @test !all(Ac[:, 1:14]) + @test !all(Ac[28:end, :]) + @test !all(Ac[:, 28:end]) + @test !all(Ac[18:24, 18:24]) + + Ac = shi_tomasi(A) + # check corner intensity + @test Ac[16,16] ≈ maximum(Ac) + @test Ac[16,26] ≈ Ac[16,16] + @test Ac[26,16] ≈ Ac[16,16] + @test Ac[26,26] ≈ Ac[16,16] + + # check edge intensity + @test Ac[16,21] ≈ 0 + @test Ac[21,16] ≈ 0 + @test Ac[21,26] ≈ 0 + @test Ac[26,21] ≈ 0 + + # check background intensity + @test Ac[5,5] ≈ 0 + @test Ac[5,36] ≈ 0 + @test Ac[36,5] ≈ 0 + @test Ac[36,36] ≈ 0 end @testset "Kitchen-Rosenfeld" begin - A[10:30, 10:30] = 1 - A[15:25, 15:25] = 0 - Ac = imcorner(A, Percentile(99), method = kitchen_rosenfeld) - @test Ac[10, 10] - @test Ac[10, 30] - @test Ac[30, 10] - @test Ac[30, 30] - - @test !all(Ac[1:9, :]) - @test !all(Ac[:, 1:9]) - @test !all(Ac[31:end, :]) - @test !all(Ac[:, 31:end]) - @test !all(Ac[12:28, 12:28]) + A[10:30, 10:30] = 1 + A[15:25, 15:25] = 0 + Ac = imcorner(A, Percentile(99), method = kitchen_rosenfeld) + @test Ac[10, 10] + @test Ac[10, 30] + @test Ac[30, 10] + @test Ac[30, 30] + + @test !all(Ac[1:9, :]) + @test !all(Ac[:, 1:9]) + @test !all(Ac[31:end, :]) + @test !all(Ac[:, 31:end]) + @test !all(Ac[12:28, 12:28]) end @testset "Fast Corners" begin - img = reshape(1:1:49, 7, 7) - - for i in 1:8 - corners = fastcorners(img, i) - @test all(corners) - end - - corners = fastcorners(img, 9) - @test !all(corners[4, 1:end - 1]) - corners[4, 1:end - 1] = true - @test all(corners) - - corners = fastcorners(img, 10) - @test !all(corners[3:4, 1:end - 2]) - @test !corners[4, end - 1] - corners[3:4, 1:end - 2] = true - corners[4, end - 1] = true - @test all(corners) - - corners = fastcorners(img, 11) - @test !all(corners[2:5, 1:end - 3]) - @test !all(corners[3:5, end - 2]) - @test !corners[4, end - 1] - corners[2:5, 1:end - 3] = true - corners[3:5, 1:end - 2] = true - corners[4, end - 1] = true - @test all(corners) - - corners = fastcorners(img, 12) - @test !all(corners[1:6, 1:end - 3]) - @test !all(corners[3:5, end - 2]) - @test !corners[4, end - 1] - corners[1:6, 1:end - 3] = true - corners[3:5, 1:end - 2] = true - corners[4, end - 1] = true - @test all(corners) - - img = parent(Kernel.gaussian(1.4)) - img = vcat(img, img) - img = hcat(img, img) - corners = fastcorners(img, 12, 0.05) - - @test corners[5, 5] - @test corners[5, 14] - @test corners[14, 5] - @test corners[14, 14] - - @test all(corners[:, 1:3] .== false) - @test all(corners[1:3, :] .== false) - @test all(corners[:, 16:end] .== false) - @test all(corners[16:end, :] .== false) - @test all(corners[6:12, 6:12] .== false) - - img = 1 - img - - corners = fastcorners(img, 12, 0.05) - - @test corners[5, 5] - @test corners[5, 14] - @test corners[14, 5] - @test corners[14, 14] - - @test all(corners[:, 1:3] .== false) - @test all(corners[1:3, :] .== false) - @test all(corners[:, 16:end] .== false) - @test all(corners[16:end, :] .== false) - @test all(corners[6:12, 6:12] .== false) + img = reshape(1:1:49, 7, 7) + + for i in 1:8 + corners = fastcorners(img, i) + @test all(corners) + end + + corners = fastcorners(img, 9) + @test !all(corners[4, 1:end - 1]) + corners[4, 1:end - 1] = true + @test all(corners) + + corners = fastcorners(img, 10) + @test !all(corners[3:4, 1:end - 2]) + @test !corners[4, end - 1] + corners[3:4, 1:end - 2] = true + corners[4, end - 1] = true + @test all(corners) + + corners = fastcorners(img, 11) + @test !all(corners[2:5, 1:end - 3]) + @test !all(corners[3:5, end - 2]) + @test !corners[4, end - 1] + corners[2:5, 1:end - 3] = true + corners[3:5, 1:end - 2] = true + corners[4, end - 1] = true + @test all(corners) + + corners = fastcorners(img, 12) + @test !all(corners[1:6, 1:end - 3]) + @test !all(corners[3:5, end - 2]) + @test !corners[4, end - 1] + corners[1:6, 1:end - 3] = true + corners[3:5, 1:end - 2] = true + corners[4, end - 1] = true + @test all(corners) + + img = parent(Kernel.gaussian(1.4)) + img = vcat(img, img) + img = hcat(img, img) + corners = fastcorners(img, 12, 0.05) + + @test corners[5, 5] + @test corners[5, 14] + @test corners[14, 5] + @test corners[14, 14] + + @test all(corners[:, 1:3] .== false) + @test all(corners[1:3, :] .== false) + @test all(corners[:, 16:end] .== false) + @test all(corners[16:end, :] .== false) + @test all(corners[6:12, 6:12] .== false) + + img = 1 - img + + corners = fastcorners(img, 12, 0.05) + + @test corners[5, 5] + @test corners[5, 14] + @test corners[14, 5] + @test corners[14, 14] + + @test all(corners[:, 1:3] .== false) + @test all(corners[1:3, :] .== false) + @test all(corners[:, 16:end] .== false) + @test all(corners[16:end, :] .== false) + @test all(corners[6:12, 6:12] .== false) end end nothing +