Average distance between nearest neighbors of grain boundaries
Show older comments
I have an image which contains both grains and grain boundaries. I wanted to write a matlab code to determine the average distance from one grain boundary to its nearest grain boundary and to the second nearest neighbor and so forth.
8 Comments
Prince
on 4 Dec 2024
Image Analyst
on 5 Dec 2024
Since the grain boundaries are the lines, and the lines separating one pair of grains from a different pair or grains touch, the average distance between the two closest boundaries is either 0 or 1 pixel, depending on how you define distance. Did you perhaps mean the average distance between "grain centroids" rather than grain boundaries?
Do you mean the distance between grains (e.g. between centroids), or between boundaries? If the latter, define how we should consider the extent and uniqueness of a grain boundary and what it means for grain boundaries to be nearest if their distance is yet unknown. Are we just finding all distances and sorting them, or are we looking specifically at the grains which share a boundary? What about all those open spurs and incomplete grains at the edges of the image? Do open spurs just get discarded, or are they even supposed to be open?
Also, bear in mind that the average distance between the members of two groups of points is not the distance between the centroids of the two groups. The average distance between points expresses both the group distance and the size of the groups. In other words, for two coincident sets of points, the distance you would get would not be zero.
% two unequal-size groups with only roughly coincident centroids
a = randn(1000,2)*100;
b = randn(100,2)*100;
% average distance between pixels
D1 = mean(hypot(a(:,1)-b(:,1).',a(:,2)-b(:,2).'),'all')
% distance between centroids
D2 = sqrt(sum((mean(a,1) - mean(b,1)).^2,2))
So which do you want? If it's the latter, then why does the boundary matter? Why not just use the grain centroid?
Prince
on 5 Dec 2024
DGM
on 5 Dec 2024
I assume now you want some sort of pairwise pixel distance.
You still haven't answered how you're defining the boundaries or nearest neighbors, or what should be done with spurs or partial grains.
For example, are boundary pixels shared? If they need to be treated that way, the lack of uniqueness complicates everything. Consider the boundaries of two blobs:

Is there any comment on the applicability of the example I gave below?
Image Analyst
on 5 Dec 2024
His binary image of the boundary showed single lines separating the grains. So it's the right hand side image. So in the right image, the distance between the white part of the boundaries is zero. Do you want the average distance between ALL the coordinates, i.e. all the (purple+white) and the (green+white) coordinates? If so, label your grains with bwlabel then take a pair of grains and dilate each with imdilate to enlarge it one pixel layer then get the boundaries with bwboundaries. Then use pdist2 to get the distance of every perimeter point of one grain to every perimeter point of the other grain. Then take the mean of all those distances. Not sure what physical meaning this would have though (I'm not a metallurgist). Or maybe you want the average of the closest distances rather than all the distances.
Or maybe you want the Hausdorff distance. See http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/98/normand/main.html
DGM
on 5 Dec 2024
It would seem like the second image is what's being described, but:
- it's just more complicated (and slower) to calculate the second case (I did give a crude example below)
- the difference between the two is fairly small
- we don't know how accurately these lines actually correspond to the original image, so is that difference meaningful?
I don't know what the contextual meaning of the average pixel distance would be either.
Answers (2)
Jaimin
on 5 Dec 2024
0 votes
Hi @Prince
To calculate the average distance from one grain boundary to its nearest grain boundaries, you can follow these general steps in MATLAB:
- Preprocess the Image: Convert the image to a binary format where the grain boundaries are clearly distinguished from the grains.
- Identify Grain Boundaries: Use edge detection or image segmentation techniques to identify the grain boundaries.
- Extract Boundary Points: Get the coordinates of the boundary points.
- Calculate Distances: For each boundary point, calculate distances to all other boundary points and find the nearest, second nearest, etc.
- Compute Average Distances: Compute the average of these distances for the first, second, nth nearest neighbours.
For more information kindly refer following resources.
I hope this will be helpful.
Maybe a start, maybe not:
% the image as a logical mask
inpict = imread('unettestfile.png')>128;
% if we're not going to fix open edges
% then just get rid of them
lastpix = nnz(inpict);
currpix = lastpix - 1;
while lastpix ~= currpix
inpict = inpict & ~bwmorph(inpict,'endpoints');
lastpix = currpix;
currpix = nnz(inpict);
end
% convert to a mask describing complete grains
grainmask = imclearborder(~inpict,4);
% of course this won't show up well unless it's displayed at full-scale
imshow(grainmask)
% if boundaries are "shared" by neighboring grains,
% i don't see a simple way to do that without loops
% dilating and evaluating each blob independently.
% it's not clear that it's necessary to treat the boundaries that way.
% instead, just consider the boundary of each blob
% in this case, no boundary pixels are shared between blobs
grbound2 = bwboundaries(grainmask,4);
% find distance between interior boundary centroids
nblobs = size(grbound2,1);
Db = zeros(nblobs);
for kr = 1:nblobs
groupA = grbound2{kr};
for kc = kr+1:nblobs
groupB = grbound2{kc};
Db(kr,kc) = sqrt(sum((mean(groupA,1) - mean(groupB,1)).^2,2));
end
end
Db = Db + Db.'; % make symmetric for easier sorting
% find distance between the actual blob centroids
% in this case, we don't need grbound2
CC = bwconncomp(grainmask,4);
S = regionprops(CC,'PixelList');
Dgr = zeros(nblobs);
for kr = 1:nblobs
groupA = S(kr).PixelList;
for kc = kr+1:nblobs
groupB = S(kc).PixelList;
Dgr(kr,kc) = sqrt(sum((mean(groupA,1) - mean(groupB,1)).^2,2));
end
end
Dgr = Dgr + Dgr.'; % make symmetric for easier sorting
% the difference is small. does it matter?
immse(Db,Dgr)
% alternatively, we can calculate Dgr quite a bit more easily
% since we don't really need to deal with many lists of points
CC = bwconncomp(grainmask,4);
S = regionprops(CC,'Centroid');
c = vertcat(S.Centroid);
Dgr2 = pdist2(c,c); % same thing
% find average distance between individual boundary pixels
nblobs = size(grbound2,1);
Dpxpx = zeros(nblobs);
for kr = 1:nblobs
groupA = grbound2{kr};
for kc = kr+1:nblobs
groupB = grbound2{kc};
dr = groupA(:,1) - groupB(:,1).';
dc = groupA(:,2) - groupB(:,2).';
Dpxpx(kr,kc) = mean(hypot(dr,dc),'all');
end
end
Dpxpx = Dpxpx + Dpxpx.'; % make symmetric for easier sorting
Here are three different distance matrices:
- Dpxpx: the average distance between groups of boundary pixels
- Db: the distance between the centroids of boundary pixel groups
- Dgr/Dgr2: the distance between the grain centroids
These rows/columns of these matrices are ordered based on how the connected groups are extracted from the image. You will have to sort them according to whatever your goals are. For example:
[Dpxpx idx] = sort(Dpxpx,2);
... will sort the rows of Dpxpx and return the sorted neighbor indices. The first column of Dpxpx will be the self-distance (0), and the following columns will be the distance to the nearest neighbors. The first column of idx will be the blob/boundary index, and the following columns will be the indices of its nearest neighbors.
1 Comment
Assuming we're talking about the average distance between all members of two pixel sets, we can disregard the centroid-based examples. So that leaves us with two different definitions of what the boundaries are:
% the image as a logical mask
inpict = imread('unettestfile.png')>128;
% if we're not going to fix open edges
% then just get rid of them
lastpix = nnz(inpict);
currpix = lastpix - 1;
while lastpix ~= currpix
inpict = inpict & ~bwmorph(inpict,'endpoints');
lastpix = currpix;
currpix = nnz(inpict);
end
% convert to a mask describing complete grains
grainmask = imclearborder(~inpict,4);
% INNER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this is the same as in the prior example
% find the inner (unique) boundary pixels
grbound2 = bwboundaries(grainmask,4);
% find average distance between individual boundary pixels
nblobs = size(grbound2,1);
Dpxinner = zeros(nblobs);
for kr = 1:nblobs
groupA = grbound2{kr};
for kc = kr+1:nblobs
groupB = grbound2{kc};
dr = groupA(:,1) - groupB(:,1).';
dc = groupA(:,2) - groupB(:,2).';
Dpxinner(kr,kc) = mean(hypot(dr,dc),'all');
end
end
Dpxinner = Dpxinner + Dpxinner.'; % make symmetric for easier sorting
% show a sample
Dpxinner(1:10,1:10)
% OUTER %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% find the outer (potentially shared) boundary pixels
[L nblobs] = bwlabel(grainmask,4);
grbound1 = cell(nblobs,1);
for k = 1:nblobs
thisgrain = L == k;
thisgrain = imdilate(thisgrain,ones(3)) & inpict;
[r c] = find(thisgrain); % if order doesn't matter, this is faster
grbound1{k} = [r c];
end
% find average distance between individual boundary pixels
nblobs = size(grbound1,1);
Dpxouter = zeros(nblobs);
for kr = 1:nblobs
groupA = grbound1{kr};
for kc = kr+1:nblobs
groupB = grbound1{kc};
dr = groupA(:,1) - groupB(:,1).';
dc = groupA(:,2) - groupB(:,2).';
Dpxouter(kr,kc) = mean(hypot(dr,dc),'all');
end
end
Dpxouter = Dpxouter + Dpxouter.'; % make symmetric for easier sorting
% show a sample
Dpxouter(1:10,1:10)
Some of this might simplify, but we're not at that point yet.
Categories
Find more on Neighborhood and Block Processing in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!