Fill in an incomplete outline of an image
Turns out that MorphologicalComponents
will give the convex hull:
imageHull = Image@MorphologicalComponents[img, Method -> "ConvexHull"]
HighlightImage[imageHull, img]
I am still interested in a solution that doesn't require ConvexHull for the whole image, but just fills in the missing hole where there is a gap.
First, extract the points from the image and take the convex hull mesh:
ClearAll["Global`*"]
img = Import["https://i.stack.imgur.com/yC9ym.png"];
allpts = PixelValuePositions[img, 1];
chmesh = ConvexHullMesh@allpts;
Use MeshPrimitives
to extract the boundary lines. Extract their endpoints. Since the endpoints are not unique, take every other end point to obtain a set of unique points on the convex hull:
endpts = Flatten[MeshPrimitives[chmesh, 1] /. Line -> List, 2];
hullpts = Take[endpts, {1, -1, 2}];
Plot the results:
Graphics[{Black, PointSize[1/300], Point@allpts,
Red, Line[hullpts],
Opacity[1/8], Blue, FilledCurve@Line[hullpts] }]
EDIT:
What we are really after is an image of the filled curve that will overlay the original image, which has ImageDimensions
of {1024,1024}. We want to use ImagePadding
to position the filled image on the original image. The amount of padding is first estimated by looking at the minimum and maximum coordinates in allpts
, then adjusting by a small $\delta$. Instead of the original image let's work with its negative.
MinMax/@Transpose[allpts]
δ = 16;
filled = Image[Graphics[
{Opacity[1/8], Red, FilledCurve@Line[hullpts]},
ImagePadding -> {{137 - δ,
1024 - 895 - δ}, {114 - δ,
1024 - 917 - δ}}],
ImageSize -> ImageDimensions[reverse]];
reverse = ColorNegate[img];
Show[{reverse, filled}, ImageSize -> 200]
(* {{137, 895}, {114, 917}} *)
To verify that $\delta = 16$ is optimal, we can use ImageTake
to zoom in on the left edge, say,
β = 20;
Show[ImageTake[#, {512 - β, 512 + β}, {137 - β,
137 + β}] & /@ {reverse, filled}, ImageSize -> 200,
Frame -> True]
Here we have zoomed in on both the reverse image and the filled image at about 137 pixels from the left and 512 pixels from the top. Our view frame is $2\beta$ square. We could adjust $\delta$ a little to see how the filled image shifts relative to the reverse image. We can also zoom in to check the fit at other critical points of the image.
pvp = PixelValuePositions[img, 1];
Graphics[{LightBlue, EdgeForm[Blue], Polygon[pvp[[FindShortestTour[pvp][[2]]]]]}]
ImageAdd[img,
Graphics[{Red, EdgeForm[Blue], Polygon[pvp[[FindShortestTour[pvp][[2]]]]]},
PlotRange -> Thread[{0, ImageDimensions[img]}]]