Skip to content

Commit

Permalink
Polish hough transformations (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
jw3126 authored and timholy committed Oct 7, 2018
1 parent 6a1be90 commit bc0c60d
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 79 deletions.
9 changes: 8 additions & 1 deletion docs/src/function_reference.md
Expand Up @@ -82,4 +82,11 @@ lbp_original
lbp_uniform
lbp_rotation_invariant
multi_block_lbp
```
```

# Misc

```@docs
hough_transform_standard
hough_circle_gradient
```
119 changes: 75 additions & 44 deletions src/houghtransform.jl
Expand Up @@ -2,7 +2,12 @@ using Images

"""
```
lines = hough_transform_standard(image, ρ, θ, threshold, linesMax)
lines = hough_transform_standard(
img_edges::AbstractMatrix;
stepsize=1,
angles=range(0,stop=pi,length=minimum(size(img))),
vote_threshold=minimum(size(img)) / stepsize -1,
max_linecount=typemax(Int))
```
Returns a vector of tuples corresponding to the tuples of (r,t)
Expand All @@ -15,35 +20,42 @@ where r and t are parameters for normal form of line:
The lines are generated by applying hough transform on the image.
Parameters:
- `image` = Image to be transformed (eltype should be `Bool`)
- `ρ` = Discrete step size for perpendicular length of line
- `θ` = List of angles for which the transform is computed
- `threshold` = Accumulator threshold for line detection
- `linesMax` = Maximum no of lines to return
- `img_edges` = Image to be transformed (eltype should be `Bool`)
- `stepsize` = Discrete step size for perpendicular length of line
- `angles` = List of angles for which the transform is computed
- `vote_threshold` = Accumulator threshold for line detection
- `max_linecount` = Maximum no of lines to return
# Example
```julia
julia> img = load("line.jpg");
julia> img_edges = canny(img, (Percentile(0.99), Percentile(0.97)), 1);
julia> lines = hough_transform_standard(img_edges, 1, linspace(0,π,30), 40, 5)
5-element Array{Tuple{Float64,Float64},1}:
(45.0,1.73329)
(1.0,1.73329)
(32.0,1.73329)
(209.0,0.649985)
(-9.0,2.49161)
julia> using ImageFeatures
julia> img = fill(false,5,5); img[3,:] .= true; img
5×5 Array{Bool,2}:
false false false false false
false false false false false
true true true true true
false false false false false
false false false false false
julia> hough_transform_standard(img)
1-element Array{Tuple{Float64,Float64},1}:
(3.0, 1.5707963267948966)
```
"""
function hough_transform_standard(
img::AbstractArray{T,2},
ρ::Real, θ::AbstractRange,
threshold::Integer, linesMax::Integer) where T<:Union{Bool,Gray{Bool}}
img_edges::AbstractMatrix{Bool};
stepsize=1,
angles=range(0,stop=pi,length=minimum(size(img_edges))),
vote_threshold=minimum(size(img_edges)) / stepsize -1,
max_linecount=typemax(Int))

stepsize > 0 || error("Discrete step size must be positive")
ρ = stepsize
θ = angles

#function to compute local maximum lines with values > threshold and return a vector containing them
function findlocalmaxima!(validLines::AbstractVector{CartesianIndex{2}}, accumulator_matrix::Array{Int,2}, threshold::T) where T<:Integer
function findlocalmaxima!(validLines::AbstractVector{CartesianIndex{2}}, accumulator_matrix::Array{Int,2}, threshold)
for val in CartesianIndices(size(accumulator_matrix))
if accumulator_matrix[val] > threshold &&
accumulator_matrix[val] > accumulator_matrix[val[1],val[2] - 1] &&
Expand All @@ -55,9 +67,7 @@ function hough_transform_standard(
end
end

ρ > 0 || error("Discrete step size must be positive")

indsy, indsx = axes(img)
indsy, indsx = axes(img_edges)
ρinv = 1 / ρ
numangle = length(θ)
numrho = round(Int,(2(length(indsx) + length(indsy)) + 1)*ρinv)
Expand All @@ -69,8 +79,8 @@ function hough_transform_standard(

#Hough Transform implementation
constadd = round(Int,(numrho -1)/2)
for pix in CartesianIndices(size(img))
if img[pix]
for pix in CartesianIndices(size(img_edges))
if img_edges[pix]
for i in 1:numangle
dist = round(Int, pix[1] * sinθ[i] + pix[2] * cosθ[i])
dist += constadd
Expand All @@ -81,30 +91,29 @@ function hough_transform_standard(

#Finding local maximum lines
validLines = Vector{CartesianIndex{2}}(undef, 0)
findlocalmaxima!(validLines, accumulator_matrix, threshold)
findlocalmaxima!(validLines, accumulator_matrix, vote_threshold)

#Sorting by value in accumulator_matrix
@noinline sort_by_votes(validLines, accumulator_matrix) = sort!(validLines, lt = (a,b)-> accumulator_matrix[a]>accumulator_matrix[b])
sort_by_votes(validLines, accumulator_matrix)

linesMax = min(linesMax, length(validLines))
max_linecount = min(max_linecount, length(validLines))

lines = Vector{Tuple{Float64,Float64}}(undef, 0)

#Getting lines with Maximum value in accumulator_matrix && size(lines) < linesMax
for l in 1:linesMax
#Getting lines with Maximum value in accumulator_matrix && size(lines) < max_linecount
for l in 1:max_linecount
lrho = ((validLines[l][2]-1) - (numrho-1)*0.5)*ρ
langle = θ[validLines[l][1]-1]
push!(lines,(lrho,langle))
end

lines

end

"""
```
circle_centers, circle_radius = hough_circle_gradient(img_edges, img_phase, scale, min_dist, vote_thres, min_radius:max_radius)
circle_centers, circle_radius = hough_circle_gradient(img_edges, img_phase, radii; scale=1, min_dist=minimum(radii), vote_threshold)
```
Returns two vectors, corresponding to circle centers and radius.
Expand All @@ -114,28 +123,42 @@ centers perpendicular to the local gradient. In case of concentric circles, only
Parameters:
- `img_edges` = edges of the image
- `img_phase` = phase of the gradient image
- `radii` = circle radius range
- `scale` = relative accumulator resolution factor
- `min_dist` = minimum distance between detected circle centers
- `vote_thres` = accumulator threshold for circle detection
- `min_radius:max_radius` = circle radius range
- `vote_threshold` = accumulator threshold for circle detection
[`canny`](@ref) and [`phase`](@ref) can be used for obtaining img_edges and img_phase respectively.
# Example
```julia
img = load("circle.png")
julia> using Images, ImageFeatures, FileIO, ImageView
julia> img = load(download("http://docs.opencv.org/3.1.0/water_coins.jpg"));
julia> img = Gray.(img);
img_edges = canny(img, 1, 0.99, 0.97)
dx, dy=imgradients(img, KernelFactors.ando5)
img_phase = phase(dx, dy)
julia> img_edges = canny(img, (Percentile(99), Percentile(80)));
centers, radii=hough_circle_gradient(img_edges, img_phase, 1, 60, 60, 3:50)
julia> dx, dy=imgradients(img, KernelFactors.ando5);
julia> img_phase = phase(dx, dy);
julia> centers, radii = hough_circle_gradient(img_edges, img_phase, 20:30);
julia> img_demo = Float64.(img_edges); for c in centers img_demo[c] = 2; end
julia> imshow(img_demo)
```
"""
function hough_circle_gradient(
img_edges::AbstractArray{Bool,2}, img_phase::AbstractArray{T,2},
scale::Number, min_dist::Number,
vote_thres::Number, radii::AbstractVector{Int}) where T<:Number
img_edges::AbstractArray{Bool,2},
img_phase::AbstractArray{<:Number,2},
radii::AbstractRange{<:Integer};
scale::Number=1,
min_dist::Number=minimum(radii),
vote_threshold::Number=minimum(radii)*min(scale, length(radii)))

rows,cols=size(img_edges)

Expand Down Expand Up @@ -179,7 +202,7 @@ function hough_circle_gradient(
end

for i in findlocalmaxima(accumulator_matrix)
if accumulator_matrix[i]>vote_thres
if accumulator_matrix[i]>vote_threshold
push!(centers, i);
end
end
Expand Down Expand Up @@ -219,11 +242,19 @@ function hough_circle_gradient(
voters, radius = findmax(radius_accumulator)
radius=(radius-1)*scale;

if voters>vote_thres
if voters>vote_threshold
push!(circle_centers, center)
push!(circle_radius, radius)
end
end
return circle_centers, circle_radius
end

@deprecate hough_circle_gradient(
img_edges, img_phase,
scale, min_dist,
vote_threshold, radii) hough_circle_gradient(img_edges, img_phase, radii; scale=scale, min_dist=min_dist, vote_threshold=vote_threshold)

@deprecate hough_transform_standard(image, ρ, θ, threshold, linesMax) hough_transform_standard(
image, stepsize=ρ, angles=θ, vote_threshold=threshold, max_linecount=linesMax)

70 changes: 36 additions & 34 deletions test/houghtransform.jl
@@ -1,25 +1,36 @@
using ImageFeatures

function random_circle_example()
dims = rand(100:200,2)
img = zeros(dims...)
r = rand(10:20)
x,y = rand(20:80,2)
for i in axes(img)[1]
for j in axes(img)[2]
if (x-i)^2 + (y-j)^2 <= r^2
img[i,j] = 1
end
end
end
img, [x,y], r
end

@testset "Hough_Transform" begin
@testset "Hough Line Transform" begin

#For images containing a straight line parallel to axes
img = zeros(Bool,10,10)
for i in 1:size(img)[1]
for j in 1:size(img)[2]
img[i,j] = true
end
h = hough_transform_standard(img,1,range(0,step=π/2/99,length=100),9,2)
for i in 1:9
img = zeros(Bool,9,9)
img[i,:] .= true
h = hough_transform_standard(img)
@test length(h) == 1
@test h[1][1] == i
for j in 1:size(img)[2]
img[i,j] = false
end
line = first(h)
@test line == (i, pi/2)
end

#For images with diagonal line
img = Matrix(Diagonal([true, true ,true]))
h = hough_transform_standard(img,1,range(0,step=π/99,length=100),2,3)
h = hough_transform_standard(img, angles=range(0,stop=pi,length=100))
@test length(h) == 1
@test h[1][1] == 0

Expand All @@ -28,7 +39,8 @@ using ImageFeatures
for i in 1:10
img[2,i] = img[i,2] = img[7,i] = img[i,9] = true
end
h = hough_transform_standard(img,1,range(0,step=π/2/99,length=100),9,10)
# h = hough_transform_standard(img,1,
h = hough_transform_standard(img, angles=range(0,stop=π/2,length=100))
@test length(h) == 4
r = [h[i][1] for i in CartesianIndices(size(h))]
@test all(r .== [2,2,7,9])
Expand All @@ -38,28 +50,18 @@ using ImageFeatures
end

@testset "Hough Circle Gradient" begin

dist(a, b) = sqrt(sum(abs2, (a-b).I))

img=zeros(Int, 300, 300)
for i in CartesianIndices(size(img))
if dist(i, CartesianIndex(100, 100))<25 || dist(i, CartesianIndex(200, 200))<50
img[i]=1
else
img[i]=0
for _ in 1:10
img, c_truth, r_truth = random_circle_example()
img_edges = canny(img, (Percentile(80), Percentile(20)))
dx, dy=imgradients(img, KernelFactors.ando5)
img_phase = phase(dx, dy)
radii = 10:20
centers, rs = hough_circle_gradient(img_edges, img_phase, radii)
@test length(centers) == length(rs) == 1
c_hough = [Tuple(first(centers))...]
r_hough = first(rs)
@test r_hough r_truth atol=4
@test c_hough c_truth atol=4
end
end

img_edges = canny(img, (0.2, 0.1) ,1)
dx, dy=imgradients(img, KernelFactors.ando3)
img_phase = phase(dx, dy)

centers, radii=hough_circle_gradient(img_edges, img_phase, 1, 40, 40, 5:75)

@test dist(centers[1], CartesianIndex(200,200))<5
@test dist(centers[2], CartesianIndex(100,100))<5
@test abs(radii[1]-50)<5
@test abs(radii[2]-25)<5

end
end

0 comments on commit bc0c60d

Please sign in to comment.