Connecting the dots in a binary image

36 views (last 30 days)
Mia Grahn
Mia Grahn on 7 Mar 2022
Commented: DGM on 7 Mar 2022
I have an image (attached) and I want to connect the dots into a circle (to later be filled in and the white coordinates will be collected). For context, the current coordinates shown are those making up the verticies of an annotation I did around an image I hope to eventually classify, and I annotated it at the pixel level. I annotated a complete circle, but I think the software I did it on had a different process of deciding which coordinates to save and resulting in my image not being fully connected. In order to fully fill in my image eventually, I want to make sure I have a fully connected border first, and to air on the side of caution, I want to close the border using pixels closest to the inside (or centroid of the shape) in cases where 2 edge lines could be connected by a point closer to the center or further (I always want the closer one). For example, if you look at the bottom of the image I have attached, there seems to be one black square disconnecting two long white boarder lines. I want to add a white square bridging those two lines, but I dont want it to fill in the line and make it straight, I want the white pixel I am adding to be up a line and inbetween the two line edges, touching each edgeline diagonally at the corners (or be on the y=2 line, not the y=1 line if you'd rather think of it in terms of the bottom left corner being (1,1)).
I have tried using imclose() and unfortunately it is too generous and estimates points outside the boundaries I have annotated to complete the shape. If anyone has any smart ways of going about this they can share, please let me know, I would really appreciate it! Like I said, I am working with this shape at the coordinate level, so if you know of a way to compute the points at that level, that would be totally find as well. I have attached a photo of just the image I am trying to change and rougly how I would ideally like it to be connected if this would help to see.
  3 Comments
Mia Grahn
Mia Grahn on 7 Mar 2022
@Image Analyst Thank you for your response! First of all, I want to warn you that I am a beginner coder, so I apologize for not knowing exaclty how to send my segmentation code (or if I even did that...). In this reply, I am attaching a screen shot that contains a figure of the orignal image (called 'original'), an image called "box" (that is basically the original image fed into the 'filledges.m' code (which I thought would do what I was asking for originally after reading stuff on the links you suggested but it ended up actually removing some points I had in the orignial binary image (but also sort of did what I needed in the upper left section of the original image)) and then fed into the 'edgelink.m' function (overall, I think this did the best job but strangely, it still missed some areas?), and then 'boxfl', which is just the original image passed through 'filledges.m' and not ''edgelink.m".
%load the annotation coordinates for the first annotation
ann=test.features(1).geometry.coordinates;
x=ann(1,:,1);x=permute(x,[2 1]); %extract the x values into an array
y=ann(1,:,2);y=permute(y,[2 1]); %extract the y values into an array
xy=[x y]; %make an x,y array
%now try to create a bounding box around the annotation of interest:
minx=min(x);maxx=max(x);
miny=min(y);maxy=max(y);
lenx=maxx-minx;leny=maxy-miny;
box=zeros(lenx+2,leny+2); %bc matlab doesnt do matrix 0 indexes, must add one to everything (added 2 for visualization purposes)
annolen=length(x(:,1));
xbox=[];%do I possibly have these switched?
ybox=[];
for c=1:annolen
xnew=x(c)-minx +1;
ynew=y(c)-miny +1;
box(xnew,ynew)=1;
xbox=[xbox xnew];
ybox=[ybox ynew];
end
original=box; %to visualize a the end
box2=zeros(lenx+1,leny+1); %just using to compare the lengths in the variable editor
xbox2=[];
ybox2=[];
boxfl=filledgegaps(box,4); %filling the gaps I believe (found that 4 seems to add the most to xbox and ybox at the end (which I thought may be correlated to getting a full circle) but it can be changed)
boxfl=double(boxfl); %this just shaves off the edges, not sure why (however, was suggested to feed this into edgelink) the
[yfl,xfl]=find(boxfl); %save to see the array length
connbox=edgelink(boxfl); %doesnt work as well as above
connidx=numel(connbox); %may overestimate edges
for i=1:connidx
now=connbox{1,i};
nownum=size(now,1); %number of rows in the cell
for k=1:nownum
xnow=now(k,1); %x coordinate
ynow=now(k,2); %y coordinate
box2(xnow,ynow)=1;
xbox2=[xbox2 xnow]; %just comparing the lengths
ybox2=[ybox2 ynow];
if box(xnow,ynow)==1
continue
else
box(xnow,ynow)=1;
xbox=[xbox xnow]; %original length updated
ybox=[ybox ynow];
end
end
end
subplot 131
imshow(box);
title('box');
subplot 132
imshow(boxfl);
title('boxfl');
subplot 133;
imshow(original);
title('original');
^this is my code as is (again, I apologize if there are obvious shortcuts I could be taking, so please point them out if you think there are!).
In this post I have also just attached the variable I saved for the original x and y if that helps (in case you wanted to run it with those and delete my original first lines where I load them in from a different source).
I guess the main issue I am dealing with now is that 'edgelink.m' works great in some areas, but just completely misses other areas of my image and if you know of any way to combat this issue, I would greatly appreciate it! Thanks again for pointing me in the direction of 'edgelink.m' in the first place, it has helped me a ton already.
Mia Grahn
Mia Grahn on 7 Mar 2022
@Image Analyst also to add on to the above in case I did not clarify, My goal is to make a closed circle out of my original image so there is one perimiter that can I can then easily fill in.

Sign in to comment.

Accepted Answer

DGM
DGM on 7 Mar 2022
Edited: DGM on 7 Mar 2022
It may be overkill for such a small image, but my answer to a previous question works well for largely convex closed paths like this.
Either method works, but for simplicity, i'm just going to use the one based on interp1(). For an image this small, there really isn't an advantage to a spline fit.
% get the data
load xvariable.mat
load yvariable.mat
x = x-min(x)+1;
y = y-min(y)+1;
% construct an image out of it
A = zeros(max(y),max(x));
A(sub2ind(size(A),y,x)) = 1;
A = padarray(A,[1 1],0,'both');
imshow(A)
s = size(A);
cn = round(s(1:2)/2); % [ycenter xcenter]
% find locations of outline pixels
[idxy idxx] = find(A); % in rect coord
[idxth idxr] = cart2pol(idxx-cn(2),idxy-cn(1)); % in polar coord
% sort pixel locations by angular position wrt image center
[idxth sortmap] = sort(idxth,'ascend');
idxr = idxr(sortmap);
% use any 1-D interpolation or fitting method
newth = linspace(-pi,pi,1000).';
newr = interp1(idxth,idxr,newth,'linear');
nanmk = ~isnan(newr);
newr = newr(nanmk);
newth = newth(nanmk);
% construct output image
[newx newy] = pol2cart(newth,newr);
B = false(s);
B(sub2ind(s,round(newy+cn(1)),round(newx+cn(2)))) = true;
clf
imshow(double(cat(3,A,A,B))) % show which pixels are new
Note that this method of interpolation really tries to force the result to be convex, hence the spurious fill pixels placed along the exterior where there are no gaps. Either way, this isn't the end result you wanted anyway. We can use this closed path to find the pixels where the fill contacts the new pixels and try to fill the gaps at a smaller radius:
% get only the new pixels
newpx = B & ~A;
% get only the region inside the closed path
fillpx = imfill(B,'holes') & ~B;
% create a map to determine which fill pixels contact new pixels
contactmap = double(fillpx);
contactmap(newpx) = 2;
% process the map to find the contact pixels
st = [0 1 0; 1 1 1; 0 1 0];
fillcontactsnew = imdilate(contactmap,st)>1 & fillpx;
imshow(double(cat(3,A,A,fillcontactsnew)))
These can of course be combined to form a single image:
outpict = A | fillcontactsnew;
imshow(outpict)
You can use a similar technique to get rid of all the exterior spurs on the original path.
% create a map to determine which original pixels contact fill pixels
contactmap = double(A);
contactmap(fillpx) = 2;
Acontactsfill = imdilate(contactmap,st)>1 & A;
outpict = fillcontactsnew | Acontactsfill;
imshow(outpict)
  2 Comments
Mia Grahn
Mia Grahn on 7 Mar 2022
@DGM This looks amazing--and is exaclty what I needed! Thank you so much for sharing this with me, I really appreicate it. Also great to see that it would work well for larger complex structures too--since all my annoations will not be circularly shaped going forward. Thanks again!
DGM
DGM on 7 Mar 2022
Weeeeell...
As I mentioned, the initial part of the example used to get the intermediate closed path works by polar interpolation. It doesn't behave well for really complicated shapes.
That said, if you can at least close the path by other means (e.g. Image Analyst's edge-linking), then you can apply the methods used in the second part of the example to find new closure pixels on the interior.

Sign in to comment.

More Answers (1)

yanqi liu
yanqi liu on 7 Mar 2022
img = imread('https://ww2.mathworks.cn/matlabcentral/answers/uploaded_files/916929/Screen%20Shot%202022-03-06%20at%208.51.29%20PM.png');
img = imcrop(img,[78 74 202 224]);
bw = im2bw(img);
bw2 = ~bw;
bw2 = imerode(bw2, strel('disk', 10));
bw2 = bwareafilt(bw2,1);
bw2 = imadd(bw2,bw);
bw3 = imclose(bw2, strel('disk', 8));
bw4 = imadd(bw, imdilate(bwperim(bw3), strel('disk',3)));
figure; imshow(bw2);
figure; imshow(bw3);
figure; imshow(bw4);
  1 Comment
Mia Grahn
Mia Grahn on 7 Mar 2022
@yanqi liu Thank you for your answer! I think your method may have increases the pixel counts more than I would like it to unfortunatley

Sign in to comment.

Products


Release

R2021a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!