Fill in an incomplete outline of an image

Turns out that MorphologicalComponents will give the convex hull:

imageHull = Image@MorphologicalComponents[img, Method -> "ConvexHull"]

enter image description here

HighlightImage[imageHull, img]

enter image description here

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] }]

enter image description here

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}}   *)

enter image description here

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]

enter image description here

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]]]]]}]

enter image description here

ImageAdd[img, 
 Graphics[{Red, EdgeForm[Blue], Polygon[pvp[[FindShortestTour[pvp][[2]]]]]}, 
  PlotRange -> Thread[{0, ImageDimensions[img]}]]

enter image description here