Measure perimeter of black edge in Mathematica

I have a pattern like the one below. How could I measure the total length of the perimeter of the black/white edge? This would give me a measure of spatial frequency. Any tips would be great!

Thanks! Edit

You should be aware that there are 2 basic ways of counting perimeter pixels: 4-ways neighbors or 8-ways neighbors. The difference is about whether counting or not the yellow pixels in this figure: If you want to count them, then almost manually:

i = Import["http://i.stack.imgur.com/7jt0I.gif"];
mask = Complement[Partition[#, 3] & /@ ({0, 0, 0, 0, 1, 0, 0, 0, 0} -
RotateLeft[{1, 0, 0, 0, 0, 0, 0, 0, 0}, #] & /@ Range), {Array[0 &, {3, 3}]}];
ip = ImagePad[i, 10, RGBColor[0.4980 {1, 1, 1}]];
is = Binarize@
ImageSubtract[ HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .1],
ImageSubtract[HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .1],
HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .8]]]

ImageMeasurements[is, "Total"] 14715

If you don't want to count the diagonal neighbors, then we change the mask to consider only four neighbors per pixel:

i = Import["http://i.stack.imgur.com/7jt0I.gif"];
mask = {{{0, -1, 0}, {0, 1,  0}, {0, 0, 0}}, {{0, 0, 0}, {-1, 1, 0}, {0, 0, 0}},
{{0,  0, 0}, {0, 1, -1}, {0, 0, 0}}, {{0, 0, 0}, { 0, 1, 0}, {0,-1, 0}}};

ip = ImagePad[i, 10, RGBColor[0.4980 {1, 1, 1}]];
is = Binarize@
ImageSubtract[ HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .1],
ImageSubtract[HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .1],
HitMissTransform[ColorConvert[ip, "Grayscale"], mask, .8]]];

ImageMeasurements[is, "Total"]

10411

• I think you should explain your code a little bit :) also, do you have an idea why there is quite huge difference between yours 14.7k and mine 13k? – Kuba Aug 14 '13 at 20:16
• if you worry about that difference we need to get into a discussion over what the exact answer should be. Do you want the length of the smooth curves or the length of the pixelated edges? – george2079 Aug 14 '13 at 21:01
• @george2079 It's more than that. Take a look at the last edit – Dr. belisarius Aug 14 '13 at 21:13
• @Kuba I deleted my previous comment. That isn't the reason. Still thinking. – Dr. belisarius Aug 14 '13 at 22:16

img=Import["http://i.stack.imgur.com/7jt0I.gif"];
comp=ClusteringComponents[img];
ComponentMeasurements[comp, "PerimeterLength"]

(*{1 -> 6392, 2 -> 16402, 3 -> 16364}*) (*Here not only black/white transitions but all included not OP asked for!*)

Edit 1

Correction based on the comments below!

(*Towards the solution*)
(*Find and filter gray and black components*)
{compGray, compWhite} = {comp /. { 2 -> 0, 3 -> 0},
comp /. { 1 -> 0, 3 -> 0}};
GraphicsRow [{Colorize[compGray] , Colorize[compWhite]}] (*Convert to image*)
imgGray = ColorNegate@Binarize@(compGray // Image);
imgWhite = ColorNegate@Binarize@(compWhite // Image);
(*Find bounding circle information*)
circleInfo = ComponentMeasurements[imgGray,
{"BoundingDiskCenter",

circle Center and radius

{{{414.499, 413.501}, 413.999}}

-

(*Detect edges of white components*)
edges = Closing[EdgeDetect[imgWhite], 2];
(*Subtract the bounding circle from curves of white component*)
iCurves = Rasterize[Show[{edges,
Graphics[{Black, Thickness[0.003],
Circle @@ # & /@ circleInfo}]}]] (*Calculate the total perimeter*)
ImageMeasurements[Thinning@Binarize@iCurves, "Total"]

The result

10358

• The exterior circle boundary should not be counted. Only black/white transitions – Dr. belisarius Aug 14 '13 at 20:29
• Arrgh! I see now... I'll try to correct it. – s.s.o Aug 14 '13 at 21:16

I'm not experienced in image processing so it may be naive but who knows :)

pro = EdgeDetect@pic
pro// ImageData // Flatten // Count[#, 1] & 15683

Which is counts of white pixels, it can be first rough estimate of perimeter I think. Well that's just a shot :)

Since I'm a beginner I do not know how to hide outer circle which should not add to the counts. But:

pro // ImageData // Dimensions
{828, 828}

So I can calculate its perimeter :D and subtract from our result:

15683 - 828 Pi // N
13081.8
• This is pretty good, thanks! I found that using MorphologicalPerimeter, the outer edge is not shown (although number of white pixels increase quite a bit) – Levi Aug 14 '13 at 17:19
• @Levi I'm glad you like it :) If you find more precise method you can show it here for future visitors. – Kuba Aug 14 '13 at 17:28
• With Mma 9.0 pro = EdgeDetect@Import@"http://i.stack.imgur.com/7jt0I.gif"; -828 Pi + (pro // ImageData // Flatten // Count[#, 1] &) // N gives me 14339 ... very near my own answer – Dr. belisarius Aug 14 '13 at 22:24
• @belisarius I've copied this code and it gives me exactly the same value as in my answer. mma 9.0 win 7... – Kuba Aug 14 '13 at 22:26
• @Kuba Your machine is bewitched, for certain – Dr. belisarius Aug 14 '13 at 22:32

Here's another simple solution: As @belsarius pointed out, there's a big difference between 4- and 8-neighborhood. I'll use 4-neighborhood:

neighborhood = DiamondMatrix;
ArrayPlot[neighborhood, Mesh -> True] You can use BoxMatrix for 8-neighborhood.

Now I'm going to use the fact that if I dilate the original image with this neighborhood (mask), then every border pixel is changed to white (because that's the brightest color in the neighborhood):

img = ColorConvert[Import["http://i.stack.imgur.com/7jt0I.gif"], "Grayscale"];
dilated = Dilation[img, neighborhood];

So I'm looking for the pixels that are black in the original image and white in the dilated image:

eps = 10^-5;
borderPixel =
Function[{pixel, dilated},
If[pixel < eps && dilated > 1 - eps, 1, 0], {Listable}];
border = borderPixel[ImageData[img], ImageData[dilated]];

Then Total[border, 2] gives the number of border pixels:

10422

I'm not sure why this number is different from @belisarius' result.

EDIT:

Ah, I think I've found the difference: I'm counting black pixels that have at least one white pixel in their neighborhood. And find 10422 of those. If I count white pixels that have at least one black pixel in the neighborhood, I get 10411 pixels:

eroded = Erosion[img, neighborhood];
eps = 10^-5;
borderPixel =
Function[{pixel, eroded},
If[pixel > 1 - eps && eroded < eps, 1, 0], {Listable}];
border = borderPixel[ImageData[img], ImageData[eroded ]];

I don't know which one Levi is looking for

• Are the white pixels extended 1 px outwards of the fictive circle? – s.s.o Aug 16 '13 at 13:56
• @s.s.o: Yes, the white pixels are extended over the borders. But borderPixels checks if a pixels is white in the dilated image and black in the original. So I don't have to worry about pixels that aren't black in the original image. – Niki Estner Aug 16 '13 at 15:11
• It's now clearer to me. Thank you! – s.s.o Aug 16 '13 at 15:14
• Why so big diffrence to me? – yode Mar 13 '16 at 20:24
• @yode: I think it's because you use Dilation[..., 1], which uses BoxMatrix as structuring element - meaning, you're using an 8-neighborhood, where I use a 4-neighborhood – Niki Estner Mar 14 '16 at 20:49
mm = Import["http://i.stack.imgur.com/7jt0I.gif"];
mult = ImageSubtract[MorphologicalPerimeter[Binarize[mm, {0, 0.0001}]],
Dilation[MorphologicalPerimeter[Binarize[mm, {0.4, 0.5}]], 1]] ImageMeasurements[mult, "Total"]

14714.