79587470

Date: 2025-04-22 21:37:37
Score: 0.5
Natty:
Report link

As pointed out in the comments, the Canny edge detector does not necessarily produce closed contours. A different approach is to binarize the image, e.g., using a global threshold or one of the methods from ImageBinarization.jl. Potrace from GeoStats.jl can then trace the contours and retrieve polygons. It might be a hassle to get the raw coordinates, but GeoStats.jl can be convenient if you want to perform further operations on the polygons. You can also consider GeometryOpts.jl for further simplification of the geometry.

using Images, TestImages
using GeoStats, CairoMakie

img = testimage("blobs")
img_gray = Gray.(img)
img_mask = img_gray .> 0.5
# img_mask = binarize(img_gray, Otsu())
# closing!(img_mask, r=2)

data = georef((mask=img_mask,))
shape = data |> Potrace(:mask)

blobs = shape.geometry[findfirst(.!shape.mask)]

fig = Figure(size=(800, 400))
image(fig[1, 1], img, axis=(aspect=1, title="Original"))
image(fig[1, 2], img_mask, axis=(aspect=1, title="Mask"))
viz(fig[1, 3], blobs, color=:red, axis=(aspect=1, title="Geometry"))
display(fig)

first_blob = blobs.geoms[1]
area(first_blob)

Three images showing the original image, the thresholded binarized image, and the resulting polygons.Another option is to use the Julia OpenCV bindings, although, I had some issues getting OpenCV to precompile.

using ImageCore, TestImages
using CairoMakie
using Makie.GeometryBasics
import OpenCV as cv

img = testimage("blobs")

img_raw = collect(rawview(channelview(img)))
img_gray = cv.cvtColor(img_raw, cv.COLOR_RGB2GRAY)

_, mask = cv.threshold(img_gray, 127.0, 255.0, cv.THRESH_BINARY_INV)
contours, _ = cv.findContours(mask, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

f = Figure()
image(f[1, 1], img, axis=(aspect=1, title="Original"))
Axis(f[1, 2], aspect=1, title="Geometry")
for cont in contours
    coords = [Point(p[1], p[2]) for p in eachcol(reshape(cont, 2, :))]
    poly!(coords)
end
f

The original image (left) and traced polygons (right).

Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Martin Larsson