Boundary identification
My high-level strategy would be to use WatershedSegmentation
on a gradient image (see this answer for a description of what this function does), starting from a marker image that looks like this:
So WatershedSegmentation
can grow the highlighted "seeds" until they touch at the highest gradient border.
So first step: Get the marker image. It doesn't have to follow the grain contours perfectly, so I can use a large blurring filter to smooth out all the high-frequency details:
GaussianFilter[img, 100]
The grains are easily recognizable in this image using any segmentation technique you like, I'll just use Binarize
:
binary = Binarize[gaussian];
To get the markers as above, I take the perimeter of that binary image and make it "wider" using Erosion
:
markers =
Erosion[ColorNegate[MorphologicalPerimeter[binary]],
DiskMatrix[50]];
That's it, now we can pass the whole thing to WatershedSegmentation:
(seg = WatershedComponents[
ColorConvert[ImageAdd[GradientFilter[img, 5]], "Grayscale"],
markers]) // Colorize
(You might want to play with the GradientFilter
filter size to improve the result)
You can get the perimeter using
perimeter = Binarize[ColorNegate[Image[seg]]];
HighlightImage[img, perimeter]
To visualize the result, I'll show it filled:
HighlightImage[img, FillingTransform@perimeter]
One approach is to smooth the image (here I've used the curvature flow filter, which is a nonlinear edge-preserving smoothing filter) and then binarize before doing the edge detection.
img = Import["http://i.stack.imgur.com/TIiTZ.jpg"]
curve = CurvatureFlowFilter[img, 40];
EdgeDetect[FillingTransform[DeleteSmallComponents[Binarize[curve]]]]
Dilating makes it look a bit smoother, but you may (or may not) want to do that, depending on what you are doing with the images.
Dilation[EdgeDetect[FillingTransform[DeleteSmallComponents[Binarize[curve]]]], 1]