3

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

enter image description here

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?

h612
  • 544
  • 2
  • 11
  • 5
    Can you add a little detail, in code, to the question about what you have and what is the result you want to compute. – Dan Getz Nov 28 '22 at 07:53
  • 3
    Put the code you've tried so far; then, we can hopefully help you efficiently. Otherwise, it's not likely to get answers here. I recommend you to read this: [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) – Shayan Nov 28 '22 at 16:01
  • In the question: "two contours that look like rectangles (roughly)". Looking at the image, I find the description quite weak. It is also a duplicate of https://stackoverflow.com/questions/74259172/how-to-connect-image-regions. Finally, add *runnable code* for best responses. – Dan Getz Nov 30 '22 at 00:15
  • 1
    See the code below at distances = [(euclidean(p1, p2), p1, p2) for p1 in points5, p2 in points10]; d, idx = findmin(t -> t[1], distances) where points5 and points10 are your contour arrays (you may need to adjust the types to make euclidean() work). – Bill Nov 30 '22 at 21:42

2 Answers2

2

You are vague in your question (as of 27 November) about the packages you are using. If you are using Contour, here is a nonoptimized way to find the minimum distance between two contours. You would need to select the two contours using contour() for the different x, y, z sets of values in each of your different contours.

using Contour
using Distances


const x = -3:0.01:3
const y = -4:0.02:5
const z = [Float64((xi^2 + yi^2)) for xi in x, yi in y]

x5, y5 = contour(x, y, z, 5) |> lines |> first |> coordinates
points5 = [[x5[i], y5[i]] for i in eachindex(x5)]
x10, y10 = contour(x, y, z, 10) |> lines |> first |> coordinates
points10 = [[x10[i], y10[i]] for i in eachindex(x10)]

distances = [(euclidean(p1, p2), p1, p2) for p1 in points5, p2 in points10]
d, idx = findmin(t -> t[1], distances)

@show d, idx   #(d, idx) = (0.9261944913478685, CartesianIndex(918, 126))
@show distances[idx] # distances[idx] = (0.9261944913478685, [1.64, 1.52], [2.32, 2.1488372093023256])
@show points5[idx[1]] points10[idx[2]]

# points5[idx[1]] = [1.64, 1.52]
# points10[idx[2]] = [2.32, 2.1488372093023256]
Bill
  • 5,600
  • 15
  • 27
1

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: enter image description here

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)

enter image description here

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: enter image description here

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 CartesianIndexes 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)

enter image description here enter image description here

TL;DR

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)
Shayan
  • 5,165
  • 4
  • 16
  • 45
  • Thanks Shayan. I'm looking into it. I couldn't use imshow() on highlighted? – h612 Dec 12 '22 at 06:24
  • @h612, One workaround can be using VSCode. Run the code in the VSCode with [the **activated** plot pane](https://stackoverflow.com/a/74730921/11747148). Then you can see the result as an image and even save it. – Shayan Dec 12 '22 at 06:27
  • i dont have that option. I checked the display Plot settign and it is already on – h612 Dec 12 '22 at 06:32
  • @h612, For activation, use "Ctrl + Shift + p" and search for "Enable plot pane" and choose it. Then, rerun the code. – Shayan Dec 12 '22 at 07:12
  • I'm getting an issue, so I've updated the question with an attempt to fix it. – h612 Jan 24 '23 at 06:06