Group Abstract Group Abstract

Message Boards Message Boards

1
|
11.4K Views
|
4 Replies
|
5 Total Likes
View groups...
Share
Share this post:

How to get the perimeter of an image structure correctly?

Dear all,

I would need the perimeter of structures in images very precisely. So I started by doing some simple test:

enter image description here

Here is the above code:

img = ColorNegate@ColorConvert[Image@Graphics[Disk[]], "Grayscale"]
ImageDimensions[img]
ComponentMeasurements[img, #] & /@ {"Length", "Width"}
diameter = (1 /. ComponentMeasurements[img, "PolygonalLength"])/Pi

While "Length" and "Width" do make sense with reference to ImageDimensions the calculated diameter (by using the perimeter) is just wrong! Or am I making a stupid mistake?

Many thanks and best regards -- Henrik

POSTED BY: Henrik Schachner
4 Replies
POSTED BY: Matthew Sottile

Hi Matthew,

thank you for looking into this problem! I found it interesting that "ConvexPerimeterLength" is giving a much better value. But as you pointed out this does not work for general objects.

I was rather hoping that maybe I was misunderstanding the term "PolygonalLength" or some other stupid thing. But as it seems ComponentMeasurements[_, "PolygonalLength"] is giving a wrong value. But what is worse: One looses the faith in those functions. Am I supposed to make plausibility tests before I use them?

Meanwhile I found a workaround:

ArcLength[RegionBoundary[ImageMesh[img]]]/Pi
(*  Out:    344.696  *)

Best regards -- Henrik

POSTED BY: Henrik Schachner

Don't loose faith, report bugs and read the documentation carefully! =)

"PolygonalLength" is described as the

total length of the polygon formed by the centers of the perimeter elements

and indeed if you get the perimeter pixel positions and compute the length you get exactly the same value (I am disabling the antialiasing to make things easier)

img = ColorConvert[Rasterize[
   Graphics[{White, Style[Disk[], Antialiasing -> False]}, 
    Background -> Black]],
  "Grayscale"];
perimeterPixels = ComponentMeasurements[img, "PerimeterPositions"][[1, 2, 1]]; 
ArcLength[Line[Append[perimeterPixels, First[perimeterPixels]]]]
(* ==> 1140.03 *)

ComponentMeasurements[img, "PolygonalLength"][[1, 2]]
(* ==> 1140.03 *)

The issue here is that you are approximating a circle with a series of lines that is strictly longer. You can see it well by comparing the different approximation methods in ImageMesh:

ArcLength@RegionBoundary@ImageMesh[img, Method -> #] & /@ {"Exact", 
  "LinearSeparable", "MarchingSquares", "DualMarchingSquares"}
(* ==> {1376., 1085.78, 1142.86, 1112.12} *)

The first value is exactly the length of the outer contour

ArcLength[ComponentMeasurements[img, "Contours"][[1, 2, 1]]]
(* ==> 1376 *)

The solution to your problem really depends on what precise means in your context. If you want to approximate the underline ideal circle one way could be to average the pixel center positions

ArcLength[
 Line@MovingAverage[Append[perimeterPixels, First[perimeterPixels]], 
   2]]
(* ==> 1095.44 *)

Iterating on the procedure you get a convergence of sort:

dual[list_] := MovingAverage[Append[list, First[list]], 2]
ArcLength@*Line /@ NestList[dual, perimeterPixels, 50]/Pi // ListPlot

perimeter_length

Hi Giulio,

many thanks for your detailed clarification! In this case I definitely prefer to see that the mistake is on my side - otherwise it would have been too stupid! I do read the documentation carefully, but I envisioned that a "polygon formed by the centers of the perimeter elements" would be something like a smooth curve or at least some approximation - in contrast to "PerimeterLength" as "total length of outer pixel sides". And on my machine here (v. 11.1.1) there is no ComponentMeasurements[_, "PerimeterPositions"], which would have helped.

Again many thanks and best regards -- Henrik

POSTED BY: Henrik Schachner
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard