Hi I'm using contour detection in Julia to detect contours. UPDATE: I need to find closest distance between two contours (made from broken lines).Is there function that finds the closest distance ? I'm not sure whats the best way of joining those two. The case I'm looking at is
so I've used the code given at link example link [https://juliaimages.org/v0.21/democards/examples/contours/contour_detection/] I've contours with n-element Vector{CartesianIndex} datatype. How do I join the line / two contours?
UPDATE: I've tried removing close contour points using the following
sorted_contours = sort!(contours, by=x -> x[1])
for i in eachindex(sorted_contours)
if check_adjacent(sorted_contours[i], sorted_contours) >= 1
sorted_contours[i] = CartesianIndex.(0, 0)
end
end
deleteat!(sorted_contours,findall(sorted_contours.==CartesianIndex.(0, 0))) but from this [![enter image description here][3]][3] it improved to this only [![enter image description here][3]][3]
I want to remove these points as these are incorrect contour points. Or how could I remove the spur which causes it?
It's the best approach to show your effort and explain where you have the problem, and you'll get the best answers to solve it. Since this topic interested me and made me curious, I found it worthwhile, although your question doesn't meet the requirements of a good question.
To better detect the contours, First, I perform thinning on the image to locate the contours easily. I use the following image as the input to see the results in a bigger size:
using Images, FileIO, ImageBinarization
img = load("img.png")
gimg = Gray.(img)
bin = binarize(gimg, UnimodalRosin()) .> 0.5
thinned = thinning(bin, algo=GuoAlgo())
The thinned
variable represents the following picture:
julia> Gray.(thinned)
Now, I define my functions to find the contours:
"""
check_adjacent(loc::CartesianIndex{2}, all_locs::Vector{CartesianIndex{2}})
Return the number of adjacent locations that are in `all_locs` to `loc`.
This function is used to determine whether a location is a corner (contour) or not.
# Arguments
- `loc::CartesianIndex{2}`: the location to check.
- `all_locs::Vector{CartesianIndex{2}}`: all the locations that aren't black.
# Returns
- `Int`: the number of adjacent locations that are in `all_locs` to `loc`.
"""
function check_adjacent(loc::CartesianIndex{2}, all_locs::Vector{CartesianIndex{2}})
conditions = [
loc - CartesianIndex(0,1) ∈ all_locs,
loc + CartesianIndex(0,1) ∈ all_locs,
loc - CartesianIndex(1,0) ∈ all_locs,
loc + CartesianIndex(1,0) ∈ all_locs,
loc - CartesianIndex(1,1) ∈ all_locs,
loc + CartesianIndex(1,1) ∈ all_locs,
loc - CartesianIndex(1,-1) ∈ all_locs,
loc + CartesianIndex(1,-1) ∈ all_locs
]
return sum(conditions)
end
"""
find_the_contour(img::BitMatrix)
Return the contours of the image `img`.
# Arguments
- `img::BitMatrix`: the image to get the contours of.
# Returns
- `Vector{CartesianIndex{2}}`: the contours of the image `img`.
"""
function find_the_contour(img::BitMatrix)
img_matrix = convert(Array{Float64}, img)
not_black = findall(!=(0.0), img_matrix)
contours = Vector{CartesianIndex{2}}()
for nb∈not_black
t = check_adjacent(nb, not_black)
t==1 && push!(contours, nb)
end
return contours
end
Now I use the find_the_contour
function:
julia> contours = find_the_contour(thinned)
4-element Vector{CartesianIndex{2}}:
CartesianIndex(161, 334)
CartesianIndex(256, 452)
CartesianIndex(69, 1213)
CartesianIndex(210, 1243)
How should I know this function locates the contours correctly? I define another function to highlight the contours:
"""
highlight_contours(img, contours)
Return the image `img` with the contours `contours` highlighted with red color.
# Arguments
- `img::BitMatrix`: the image to highlight the contours on.
- `contours::Vector{CartesianIndex{2}}`: the contours to highlight.
- `resize`::Bool=false: whether to resize the image to 512x512 or not.
- `scale`::Union{Int64, Float64}=1: the scale to resize the image to.
# Returns
- `img_matrix`: the image `img` with the contours `contours` highlighted with red color.
"""
function highlight_contours(img, contours; resize::Bool=false, scale::Union{Int64, Float64}=1)
img_matrix = convert(Array{RGB, 2}, img)
for c∈contours
img_matrix[c] = RGB(1,0,0)
end
if resize
img_matrix = typeof(scale)==Int64 ? imresize(img_matrix, size(img).*scale) : imresize(img_matrix, ratio=scale)
end
return img_matrix
end
Then I pass the thinned
Matrix and contours
to it:
julia> highlited = highlight_contours(thinned, contours)
If you zoom in a little bit, you find the contours with red color:
Now that I made sure the contours are located successfully, I go for calculating the distance between contours. I used the Euclidean distance between the CartesianIndex
es of each contour! You can adjust it to match your taste:
"""
distance_between_contours(V::Vector{T}, v::T) where T<:Tuple{Int64, Int64}
Return the euclidean distance between the contour `v` and the contours in `V`.
# Arguments
- `V::Vector{T}`: the contours to check the distance to.
- `v::T`: the contour to check the distance from.
# Returns
- `Float64`: the euclidean distance between the contour `v` and the contours in `V`.
"""
function distance_between_contours(V::Vector{T}, v::T) where T<:Tuple{Int64, Int64}
return broadcast((x, y)->sqrt(sum((x.-y).^2)), V, Ref(v))
end
"""
distance_between_contours(all_contours::Vector{CartesianIndex{2}})
Return the euclidean distance between each pair of contours in `all_contours`.
# Arguments
- `all_contours::Vector{CartesianIndex{2}}`: the contours to calculate the distance between.
# Returns
- `Matrix{Float64}`: the euclidean distance between each pair of contours in `all_contours`.
"""
function distance_between_contours(all_contours::Vector{CartesianIndex{2}})
n = length(all_contours)
distances = Matrix{Float64}(undef, n, n)
for j in eachindex(all_contours)
distances[:, j] .= distance_between_contours(
Tuple.(all_contours),
Tuple(all_contours[j])
)
end
return distances
end
Now I pass it the contours
and calculate the pairwise Euclidean distance between each:
julia> distance_between_contours(contours)
4×4 Matrix{Float64}:
0.0 151.489 883.801 910.32
151.489 0.0 783.639 792.336
883.801 783.639 0.0 144.156
910.32 792.336 144.156 0.0
In this matrix, for example, the 151.489
value shows the distance between the first contour and the second. Remember that the first and second contours are the followings:
CartesianIndex(161, 334)
CartesianIndex(256, 452)
I followed the same procedure for your image, and this the followings are the distances and the contours:
julia> distance_between_contours(contours)
6×6 Matrix{Float64}:
0.0 27.7308 88.6397 168.6 166.676 170.188
27.7308 0.0 60.9262 140.872 139.284 142.622
88.6397 60.9262 0.0 80.1311 79.2465 82.0061
168.6 140.872 80.1311 0.0 26.2488 19.2354
166.676 139.284 79.2465 26.2488 0.0 8.06226
170.188 142.622 82.0061 19.2354 8.06226 0.0
highlited = highlight_contours(thinned, contours, resize=true, scale=2)
Code:
using Images, FileIO, ImageBinarization
function check_adjacent(loc::CartesianIndex{2}, all_locs::Vector{CartesianIndex{2}})
conditions = [
loc - CartesianIndex(0,1) ∈ all_locs,
loc + CartesianIndex(0,1) ∈ all_locs,
loc - CartesianIndex(1,0) ∈ all_locs,
loc + CartesianIndex(1,0) ∈ all_locs,
loc - CartesianIndex(1,1) ∈ all_locs,
loc + CartesianIndex(1,1) ∈ all_locs,
loc - CartesianIndex(1,-1) ∈ all_locs,
loc + CartesianIndex(1,-1) ∈ all_locs
]
return sum(conditions)
end
function find_the_contour(img::BitMatrix)
img_matrix = convert(Array{Float64}, img)
not_black = findall(!=(0.0), img_matrix)
contours = Vector{CartesianIndex{2}}()
for nb∈not_black
t = check_adjacent(nb, not_black)
t==1 && push!(contours, nb)
end
return contours
end
function distance_between_contours(V::Vector{T}, v::T) where T<:Tuple{Int64, Int64}
return broadcast((x, y)->sqrt(sum((x.-y).^2)), V, Ref(v))
end
function distance_between_contours(all_contours::Vector{CartesianIndex{2}})
n = length(all_contours)
distances = Matrix{Float64}(undef, n, n)
for j in eachindex(all_contours)
distances[:, j] .= distance_between_contours(
Tuple.(all_contours),
Tuple(all_contours[j])
)
end
return distances
end
img = load("img.png")
gimg = Gray.(img)
bin = binarize(gimg, UnimodalRosin()) .> 0.5
thinned = thinning(bin, algo=GuoAlgo())
contours = find_the_contour(thinned)
distance_between_contours(contours)