Detect lines with Hough transform

I have an image with 3 lines in it:
tf2

For each of the three lines, I’d like to find the coordinates of their ends (i.e. where in the image are the start and end of each line). I thought that the best tool for the job would be ImageFeatures.hough_transform_standard .

But I seem to always get an empty tuple back. Here’s everything you need to run it yourself:

import Pkg
Pkg.add(["ImageFeatures", "FileIO", "ImageIO"])
using ImageFeatures, FileIO
file = download("https://vision-group-temporary.s3.eu-central-1.amazonaws.com/tf.png")
tf = Bool.(FileIO.load(file))
hough_transform_standard(tf)

I’m puzzled because the image is fairly simple: no noise and the lines are straight and continuous. So I’m not sure why this won’t work…

Any ideas?

1 Like

It looks like it has some parameters, did you try playing with those?

:laughing:

The defaults seemed to be so reasonable I didn’t think it would matter:

    stepsize=1,
    angles=range(0,stop=pi,length=minimum(size(img))),
    vote_threshold=minimum(size(img)) / stepsize -1,
    max_linecount=typemax(Int))

Adding more angles to 1920 is a bit excessive I thought. I probably misunderstood the others though…

OK, after playing around with it more I found out that vote_threshold has little effect if any, and stepsize makes the biggest difference. Here’s a run with some results:

julia> hough_transform_standard(tf, max_linecount = 3, stepsize = 2)
3-element Array{Tuple{Float64,Float64},1}:
 (1059.0, 0.6204604563369107)
 (1053.0, 0.6286459504838356)
 (1059.0, 0.6253717528250656)

Although I’m not entirely sure if that’s what I want… I’ll try to correlate that to the line’s endpoints.

I’d be surprised if vote_threshold has no effect. Usually, if a parameter has the word threshold in it, try setting it to something tiny (like 0) and see what happens.

1 Like

From the doc string, it seems like vote_threshold is

julia> minimum(size(tf)) / stepsize -1
1919.0

if the stepsize is 1. So I assumed it can be large too.

In any case, I tried with 0 to 1:

julia> for i in 0:0.1:1
       @show hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i)
       end
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]

and for larger magnitudes of vote_threshold:

julia> for i in range(1, 1919, length = 10)
       @show hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i)
       end
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = [(1059.0, 0.6204604563369107), (1053.0, 0.6286459504838356), (1059.0, 0.6253717528250656)]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = Tuple{Float64,Float64}[]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = Tuple{Float64,Float64}[]
hough_transform_standard(tf, max_linecount = 3, stepsize = 2, vote_threshold = i) = Tuple{Float64,Float64}[]

I don’t even know what vote_threshold does exactly.

A docstring PR would be gratefully accepted.

2 Likes

Oooh, thank you, I’ll see what I can gleam from that and gladly PR an improvement to the docstring!

1 Like

AFAIU, this default value for the threshold means that for lines to be detected, they have to be at least as long as the smallest image dimension (i.e. lines have to cross the entire image, and mere segments are not detected). When stepsize is 1, the value of the threshold should roughly indicate the minimal length (in pixels) that a straight segment should have for it to be detected. More precisely, it should correspond to the minimal number of pixels that have to be aligned for a line to be detected (so that in a noisy image, or if line segments are “wavy” and only a fraction of the pixels are aligned, the line is still detected).

These interpretations come from vague recollections I have from of a previous life, when I implemented a Hough transform in OCaml for a CS class… So please take it with a grain of salt!

3 Likes

If you are still having problems detecting lines, ping me here or on Slack. I spent a good portion of my postdoctoral career on this problem and dealing with the situation where the lines intersect.

2 Likes

Thanks a lot for all the help! I got a lot further:

import Pkg
Pkg.add(["ImageFeatures", "FileIO", "ImageIO", "ImageDraw", "ImageView", "ColorVectorSpace", "ImageCore", "Clustering", "ImageMorphology"])

using ImageFeatures, FileIO, ImageDraw, ImageView, ColorVectorSpace, ImageCore, Clustering, ImageMorphology
file = download("https://vision-group-temporary.s3.eu-central-1.amazonaws.com/tf.png")
tf = Bool.(FileIO.load(file))
t = thinning(tf)

rθ = hough_transform_standard(t, stepsize = 3, vote_threshold = 500, max_linecount = 100, angles = range(0, pi, length = 10000))

R = kmeans(Matrix(hcat(vcat.(rθ...)...)'),3)
M = R.centers 
m = Tuple.(eachcol(M))

img = RGB.(tf);
for (r, θ) in m
    draw!(img, LineNormal(r, θ), RGB{N0f8}(1, 0, 0))
end
imshow(img)

@ffevotte, this terminology is super helpful, and it’s what Matlab used as well:
stepsize: “line thickness”
vote_threshold: “line length”

@mkitti, wow, thanks! checkout the issue I opened concerning improving this whole process.

1 Like