I have an image that is all black with a shape's edges outlined in white. I also have the center coordinates for the feature being outlined. Is there a function I can use to fill in that area?
1 Answer
Yes, it is pretty simple
img = Binarize@Import["https://i.stack.imgur.com/pFsL7.png"];
FillingTransform[img]
Further details
What about the center coordinates? Could the outline be anywhere inside the black image? – Peter Mortensen
If you use FillingTransform
, the object can be located anywhere inside the image, but if you understand the basics, it is easy to write a simple solution yourself. First, what you have is a black and white image where white pixels represent the contour you want to fill. If this contour is closed, all you have to do is to find a point inside the object and fill all black pixels until you meet the white boundary.
For reasonably convex objects, the Centroid
is a good place to start as a pixel inside the object. Note that FillingTransform
does it differently and works in more general ways:
An extended minimum is a connected set of pixels surrounded by pixels that all have a greater value than the pixels in the set.
FillingTransform[image]
fills all extended minima by lifting their values to the lowest value found among the surrounding pixels.
For your simple situation, we can use a flood-fill starting from an inside pixel that stops when it reaches an already filled pixel. Let's first find the centroid of your fish object:
ComponentMeasurements[img, "Centroid"]
(* {1 -> {668.156, 391.217}, 2 -> {846., 473.}} *)
The second object is a small white dot inside your fish. We ignore that. The algorithm works as follow: It uses a start position. If the start position is a black pixel, it fills the pixel with white and looks then on all neighbors. What happens is that all pixels we want to fill are pushed on a stack, and then the stack is processed until it is empty.
Therefore, here a crude version a simple stack:
Module[{$s},
stack[elm_] := ($s = {elm});
push[elm_] := ($s = {elm, $s});
top[] := If[empty[], $Failed, First[$s]];
pop[] := If[empty[], $Failed,
With[{top = top[]},
$s = If[Length[$s] > 1, Last[$s], {}];
top
]
];
empty[] := TrueQ[Length[$s] < 1]
]
If you don't understand why I am using a Module
variable $s
here, please read this post from Leonid.
The flood-fill itself is now simple. If you want details on the algorithm, you can read through the queue implementation on Wiki. This one is similar. The only things to mention are:
- I'm checking if the initial point lies indeed inside the image
- I'm padding the image with a one-pixel layer of white. With this, the algorithm will always terminate at the image boundary. When returning the result, I'm removing this padding again.
- I need to reverse the image matrix because image coordinates and matrix coordinates have a different y-direction.
Here is the implementation
floodFill[img_Image, init : {_Integer, _Integer}] :=
Module[{result = img, nx, ny, data, x, y},
If[Not[1 <= init[[1]] <= nx && 1 <= init[[2]] <= ny],
Return[result]
];
stack[init + {1, 1}];
data = ArrayPad[Reverse@ImageData[img, "Bit"], 1, 1];
While[Not[empty[]],
{x, y} = pop[];
If[data[[y, x]] == 0,
data[[y, x]] = 1;
push[{x, y + 1}];
push[{x, y - 1}];
push[{x + 1, y}];
push[{x - 1, y}];
]
];
Image[Reverse@ArrayPad[data, -1]]
]
And now you can call floodFill[img, {668, 391}]
to get your result.
-
$\begingroup$ What about the center coordinates? Could the outline be anywhere inside the black image? $\endgroup$ Mar 11, 2017 at 9:08
-
$\begingroup$ @PeterMortensen I have given more details in my answer. Please see the edits. $\endgroup$– halirutan ♦Mar 11, 2017 at 14:02