Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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!

share|improve this question
1  
this is a good start –  gpap Aug 14 '13 at 16:47
    
Matlab has a function that I think is similar, called bwperim: mathworks.com/help/images/ref/bwperim.html –  Levi Aug 14 '13 at 16:49
add comment

4 Answers

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:

Mathematica graphics

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[9]), {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

share|improve this answer
    
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 –  belisarius Aug 14 '13 at 21:13
    
@Kuba I deleted my previous comment. That isn't the reason. Still thinking. –  belisarius Aug 14 '13 at 22:16
add comment

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
share|improve this answer
    
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 –  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 –  belisarius Aug 14 '13 at 22:32
show 1 more comment

How about using

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]}]

enter image description here

(*Convert to image*)
imgGray = ColorNegate@Binarize@(compGray // Image);
imgWhite = ColorNegate@Binarize@(compWhite // Image);
(*Find bounding circle information*)
circleInfo = ComponentMeasurements[imgGray,
   {"BoundingDiskCenter",
    "BoundingDiskRadius"}][[All, 2]]

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}]}]]

enter image description here

(*Calculate the total perimeter*)
ImageMeasurements[Thinning@Binarize@iCurves, "Total"]

The result

10358

share|improve this answer
1  
The exterior circle boundary should not be counted. Only black/white transitions –  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
add comment

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[1];
ArrayPlot[neighborhood, Mesh -> True]

enter image description here

You can use BoxMatrix[1] 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

share|improve this answer
    
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. –  nikie Aug 16 '13 at 15:11
    
It's now clearer to me. Thank you! –  s.s.o Aug 16 '13 at 15:14
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.