Create mask from bwtraceboundary in Matlab

4.3k views Asked by At

I'm trying to create a mask (or similar result) in order to erase pieces of a binary image that are not attached to the object surrounded by the boundary. I saw this thread (http://www.mathworks.com/matlabcentral/answers/120579-converting-boundary-to-mask) to do this from bwboundaries, but I'm having trouble making suitable changes to it. My goal is to use this code to isolate the part of this topography map that is connected, and get rid of the extra pieces. I need to retain the structure inside of the bounded area, as I was then going to use bwboundaries to create additional boundary lines of the main object's "interior" structure.

enter image description here

The following is my code to first create the single boundary line by searching for the bottom left pixel of the black area to begin the trace. It just looks for the first column of the image that isn't completely white and selects the last black pixel. The second section was then to create the inner boundary lines. Note that I am attempting this two step process, but if there is a way to do it with only one I'd like to hear that solution as well. Ultimately I just want boundaries for the main, large black area and the holes inside of it, while getting rid of the extra pieces hanging around.

 figName='Images/BookTrace_1';

BW = imread([figName,'.png']);
    BW=im2bw(BW);
    imshow(BW,[]);

    for j=1:size(BW,2)
        if sum(BW(:,j))~=sum(BW(:,1))
            corner=BW(:,j);
            c=j-1;
            break
        end
    end
    r=find(corner==0);
    r=r(end);

    outline = bwtraceboundary(BW,[r c],'W',8,Inf,'counterclockwise');
    hold on;
    plot(outline(:,2),outline(:,1),'g','LineWidth',2);


[B,L] = bwboundaries(BW);
hold on
for k = 1:length(B)
    boundary = B{k};
    plot(boundary(:,2), boundary(:,1), 'g', 'LineWidth', 2)
end

Any suggestions or tips are greatly appreciated. If there are questions, please let me know and I'll update the post. Thank you!

EDIT: For clarification, my end goal is as in the below image. I need to trace all of the outer and inner boundaries attached to the main object, while eliminating any spare small pieces that are not attached to it. enter image description here

1

There are 1 answers

13
rayryeng On BEST ANSWER

It's very simple. I actually wouldn't use the code above and use the image processing toolbox instead. There's a built-in function to remove any white pixels that touch the border of the image. Use the imclearborder function.

The function will return a new binary image where any pixels that were touching the borders of the image will be removed. Given your code, it's very simply:

out = imclearborder(BW);

Using the above image as an example, I'm going to threshold it so that the green lines are removed... or rather merged with the other white pixels, and I'll call the above function:

BW = imread('https://i.stack.imgur.com/jhLOw.png'); %// Read from StackOverflow
BW = im2bw(BW); %// Convert to binary
out = imclearborder(BW); %// Remove pixels along border
imshow(out); %// Show image

We get:

enter image description here


If you want the opposite effect, where you want to retain the boundaries and remove everything else inside, simply create a new image by copying the original one and use the output from the above to null these pixel locations.

out2 = BW; %// Make copy
out2(out) = 0; %// Set pixels not belonging to boundary to 0
imshow(out2); %// Show image

We thus get:

enter image description here

Edit

Given the above desired output, I believe I know what you want now. You wish to fill in the holes for each group of pixels and trace along the boundary of the desired result. The fact that we have this split up into two categories is going to be useful. For those objects that are in the interior, use the imfill function and specify the holes option to fill in any of the black holes so that they're white. For the objects that exterior, this will need a bit of work. What I would do is invert the image so that pixels that are black become white and vice-versa, then use the bwareaopen function to clear away any pixels whose area is below a certain amount. This will remove those small isolated black regions that are along the border of the exterior regions. Once you're done, re-invert the image. The effect of this is that the small holes will be eliminated. I chose a threshold of 500 pixels for the area... seems to work well.

Therefore, using the above variables as reference, do this:

%// Fill holes for both regions separately
out_fill = imfill(out, 'holes');
out2_fill = ~bwareaopen(~out2, 500);

%// Merge together
final_out = out_fill | out2_fill; 

This is what we get:

enter image description here

If you want a nice green border like in your example to illustrate this point, you can do this:

perim = bwperim(final_out);
red = final_out;
green = final_out;
blue = final_out;
red(perim) = 0;
blue(perim) = 0;
out_colour = 255*uint8(cat(3, red, green, blue));
imshow(out_colour);

The above code finds the perimeter of the objects, then we create a new image where the red and blue channels along the perimeter are set to 0, while setting the green channel to 255.

We get this:

enter image description here

You can ignore the green pixel border that surrounds the image. That's just a side effect with the way I'm finding the perimeter along the objects in the image. In fact, the image you supplied to me had a white pixel border that surrounds the whole region, so I'm not sure if that's intended or if that's part of the whole grand scheme of things.


To consolidate into a working example so that you can copy and paste into MATLAB, here's all of the code in one code block:

%// Pre-processing
BW = imread('https://i.stack.imgur.com/jhLOw.png'); %// Read from StackOverflow
BW = im2bw(BW); %// Convert to binary

out = imclearborder(BW); %// Remove pixels along border

%// Obtain pixels that are along border
out2 = BW; %// Make copy
out2(out) = 0; %// Set pixels not belonging to boundary to 0

%// Fill holes for both regions separately
out_fill = imfill(out, 'holes');
out2_fill = ~bwareaopen(~out2, 500);

%// Merge together
final_out = out_fill | out2_fill; 

%// Show final output
figure;
imshow(final_out);

%// Bonus - Show perimeter of output in green
perim = bwperim(final_out);
red = final_out;
green = final_out;
blue = final_out;
red(perim) = 0;
blue(perim) = 0;
out_colour = 255*uint8(cat(3, red, green, blue));
figure;
imshow(out_colour);