From 6928b5e10566f1bbd158008fd98ae35e88a5173e Mon Sep 17 00:00:00 2001 From: "Dr. Zygmunt L. Szpak" Date: Wed, 16 Dec 2020 21:56:47 +1030 Subject: [PATCH] Resolves problem of incorrect handing of NaN values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Gaussian filtering step associated with the Canny edge detector used `KernelFactors.IIRGaussian((σ,σ))` which does not leave the NaN values untouched but returns what seem to be spurious values in the NaN region. Consequently, when one computes the gradient one obtains a gradient response in those regions, which in turns cause canny to detect edges there as well. I address this issue by swapping to a `KernelFactors.gaussian((σ,σ))` implementation. This should, in principle, yield a more accurate results. In practice, it means that the choice of `σ` had greater influence on the detected edges (the algorithm becomes more sensitive to the choice of that parameter). This meant that I had to rejigger some of the reference and test parameters in the test suite. --- Project.toml | 2 +- src/algorithms/canny.jl | 24 ++++-- src/algorithms/nonmaxima_suppression.jl | 2 +- .../subpixel_nonmaxima_suppression.jl | 2 +- test/algorithms/References/cameraman_edge.png | Bin 0 -> 17519 bytes test/algorithms/References/circle.png | Bin 225 -> 287 bytes test/algorithms/References/circle_edge.png | Bin 227 -> 303 bytes test/algorithms/References/circle_nms.png | Bin 2691 -> 359 bytes .../References/edges_from_image_with_nan.png | Bin 0 -> 180 bytes test/algorithms/canny.jl | 70 +++++++++++++----- 10 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 test/algorithms/References/cameraman_edge.png create mode 100644 test/algorithms/References/edges_from_image_with_nan.png diff --git a/Project.toml b/Project.toml index 69ed9a4..6142053 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ImageEdgeDetection" uuid = "2b14c160-480b-11ea-1b58-656063328ff7" authors = ["Dr. Zygmunt L. Szpak"] -version = "0.1.0" +version = "0.1.1" [deps] ColorVectorSpace = "c3611d14-8923-5661-9e6a-0046d554d3a4" diff --git a/src/algorithms/canny.jl b/src/algorithms/canny.jl index d27a0fe..cb15151 100644 --- a/src/algorithms/canny.jl +++ b/src/algorithms/canny.jl @@ -79,7 +79,7 @@ function (f::Canny)(out::GenericGrayImage, img::GenericGrayImage) # Smooth the image with a Gaussian filter of width σ, which specifies the # scale level of the edge detector. - kernel = KernelFactors.IIRGaussian((σ,σ)) + kernel = KernelFactors.gaussian((σ,σ)) imgf = imfilter(img, kernel, NA()) # Calculate the gradient vector at each position of the filtered image. @@ -89,8 +89,14 @@ function (f::Canny)(out::GenericGrayImage, img::GenericGrayImage) # Gradient magnitude mag = hypot.(g₁, g₂) - low_threshold = typeof(low) <: Percentile ? StatsBase.percentile(vec(mag), low.p) : low - high_threshold = typeof(high) <: Percentile ? StatsBase.percentile(vec(mag), high.p) : high + # In StatsBase quantiles are undefined in the presence of NaNs + # hence we need to keep only valid magnitudes before we can determine + # the percentiles. + valid_indices = map(x-> !isnan(x), mag) + valid_mag = view(mag, valid_indices) + + low_threshold = typeof(low) <: Percentile ? StatsBase.percentile(vec(valid_mag), low.p) : low + high_threshold = typeof(high) <: Percentile ? StatsBase.percentile(vec(valid_mag), high.p) : high thinning_algorithm = @set thinning_algorithm.threshold = low_threshold @@ -122,7 +128,7 @@ function (f::Canny)(out₁::GenericGrayImage, out₂::AbstractArray{<:StaticVect # Smooth the image with a Gaussian filter of width σ, which specifies the # scale level of the edge detector. - kernel = KernelFactors.IIRGaussian((σ,σ)) + kernel = KernelFactors.gaussian((σ,σ)) imgf = imfilter(img, kernel, NA()) # Calculate the gradient vector at each position of the filtered image. @@ -132,8 +138,14 @@ function (f::Canny)(out₁::GenericGrayImage, out₂::AbstractArray{<:StaticVect # Gradient magnitude mag = hypot.(g₁, g₂) - low_threshold = typeof(low) <: Percentile ? StatsBase.percentile(vec(mag), low.p) : low - high_threshold = typeof(high) <: Percentile ? StatsBase.percentile(vec(mag), high.p) : high + # In StatsBase quantiles are undefined in the presence of NaNs + # hence we need to keep only valid magnitudes before we can determine + # the percentiles. + valid_indices = map(x-> !isnan(x), mag) + valid_mag = view(mag, valid_indices) + + low_threshold = typeof(low) <: Percentile ? StatsBase.percentile(vec(valid_mag), low.p) : low + high_threshold = typeof(high) <: Percentile ? StatsBase.percentile(vec(valid_mag), high.p) : high thinning_algorithm = @set thinning_algorithm.threshold = low_threshold diff --git a/src/algorithms/nonmaxima_suppression.jl b/src/algorithms/nonmaxima_suppression.jl index 97d2500..c0272fb 100644 --- a/src/algorithms/nonmaxima_suppression.jl +++ b/src/algorithms/nonmaxima_suppression.jl @@ -80,7 +80,7 @@ function suppress_non_maxima!(out::AbstractArray, mag::AbstractArray, g₁::Abst d₁ = g₁[i] d₂ = g₂[i] mc = mag[i] - if mc < threshold || mc == 0 + if mc < threshold || mc == 0 || isnan(mc) out[r,c] = zero(eltype(mag)) else # Ensure the vector 𝐝 = [d₁, d₂] has unit norm. diff --git a/src/algorithms/subpixel_nonmaxima_suppression.jl b/src/algorithms/subpixel_nonmaxima_suppression.jl index 8556416..a7879f5 100644 --- a/src/algorithms/subpixel_nonmaxima_suppression.jl +++ b/src/algorithms/subpixel_nonmaxima_suppression.jl @@ -89,7 +89,7 @@ function suppress_subpixel_non_maxima!(out₁::AbstractArray, out₂::AbstractAr d₁ = g₁[i] d₂ = g₂[i] mc = mag[i] - if mc < threshold || mc == 0 + if mc < threshold || mc == 0 || isnan(mc) out₁[r,c] = zero(eltype(mag)) else # Ensure the vector 𝐝 = [d₁, d₂] has unit norm. diff --git a/test/algorithms/References/cameraman_edge.png b/test/algorithms/References/cameraman_edge.png new file mode 100644 index 0000000000000000000000000000000000000000..375a3655d3ea3b7255ef95b36890319d56687d4e GIT binary patch literal 17519 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 M)78&qol`;+0HoYvVgLXD delta 175 zcmbQw^pH`pGr-TCmrII^fq{Y7)59eQNE?ALBamP)bJ;30QBioJqfR|jlDE4H1LNH; z;Uz$cI8PVH5Q)pl2?|m-{x@Iv|KGepJ W+^TiYt3Xa*@O1TaS?9!*&;$UxD?`cv diff --git a/test/algorithms/References/circle_edge.png b/test/algorithms/References/circle_edge.png index d4e1f598051a7eee7272e5168140ec915b8f6466..eab0b1ad7767d7a9463445f2d8cec121e735403c 100644 GIT binary patch delta 251 zcmaFNxSmO|Gr-TCmrII^fq{Y7)59eQNE?GN2asTpKb8M&qM|StV{wqX6T`Z5GA}0@ z>esLJba4#v;GKHVG4F_j0P{wn1^??KtGjonzhO}FJ^#fA1>HHMp!ZDVh`^D9GEFn4 zbez?3lyMQ5eDjEJqG9k6X;+>!j(8=xG@!SyDVP-pD77i@JyQYMr1lR@|99qvYVue4ryUCgdC1e% K&t;ucLK6U($6{*$ delta 177 zcmZ3_^q5hxGr-TCmrII^fq{Y7)59eQNE?ALBamP)bJ;30QBioJqfR|jlDE4H1LNH; z;Uz$c1Wy;o5Q)pl2?|nI{<8=7@ba)bm`F1>Gj=>nOqjrMku5=yt&N3^EluDpLkQO* zX@wbrF7gdsj8A7cY+-6jW4y@pW%ERLtq>VwC14uB)pUVF>QPI1e;i;#KV~7Xu)Pol@ z4;Toz9Av*I74)R3DuBO9`;slkr^h!Y>9FUyJ?XTWsQ$@i-U;DKC4V4iWAM%pvm?<3 zMVx24BeovqStKr&BzU*y&Y?3$mdK_BcEm|^o=m(a<8wsgOy6OiBMVBuRcYHCd0n!&NJ0Lyd#?64{cT z7)U3&C0#na$0c1!t@TB#r}Y^hojZqk7R_#r5N(=#n*W*Wo|&hP=5)+GRgZ8g$ldq! X<@?Q_p2@Vk5AwUGtDnm{r-UW|+CqIl literal 2691 zcmeH|KTE?v7{;Gs1zYJ_K^&Z16-RYxsWcrbTKWMh4niOxh>O#qT?9cS5FGpr1y?_W ztCPD1=MG}PyC!$9p#T|Y3oH6%kKp4u~o2=OykD&4rgPv!`*mmvXOdI2>QV)~_kq~_ANMEuB# zGCLw=O(MCr1WeWvgk;URwU)9bRkX25)fkyVgn!Z`YE~5%PBJqZ4;d5XtSX~9rhfdZ zO#P~oz#n{LOxvb8HmmVLGqW-IU*|{wa)->UqP9As=!iu1G7HhiGwQz