I have an outline which I have extracted from an image, which may well not entirely closed, for instance:

enter image description here

I now want to fill the inside of this, as another image, using the convex hull if it is not closed.

For instance:

img = Import["https://i.stack.imgur.com/yC9ym.png"];
ConvexHullMesh[PixelValuePositions[img, 1]]

Convex hull mesh

is the right shape, but is a mesh rather than an image and now in a different coordinate system.

FillingTransform doesn't seem to work, presumably because the outline is not complete. ComponentMeasurements[img, "ConvexVertices"] gives me the points that make up the convex hull, but I can't manage to fill in the middle in an easy (and ideally fast way).

Rasterizing the ConvexHullMesh has been suggested in the comments, but that doesn't appear to work for me, as the ConvexHullMesh zooms into the image.

HighlightImage[Rasterize[ConvexHullMesh[PixelValuePositions[img, 1]], 
    RasterSize -> ImageDimensions[img]], img]

Rasterized image with original

(* "11.3.0 for Mac OS X x86 (64-bit) (March 7, 2018)" *)
  • $\begingroup$ If ConvexHullMesh works as desired, what about Rasterize[ConvexHullMesh[PixelValuePositions[img, 1]], RasterSize -> ImageDimensions[img]]? $\endgroup$ – Theo Tiger Jan 10 at 10:16
  • $\begingroup$ @TheoTiger, because it is not in the same coordinate system, see HighlightImage[ ColorNegate@ Binarize[Rasterize[ConvexHullMesh[PixelValuePositions[img, 1]], RasterSize -> ImageDimensions[img]]], img] - the rasterized image is now zoomed in. $\endgroup$ – KraZug Jan 10 at 10:22
  • $\begingroup$ Unless I am missing something, they are in the same coordinate system for me. They overlap nicely. I'm on Mma 11.3 btw. Can you add an image of your HighlightImage to the question? $\endgroup$ – Theo Tiger Jan 10 at 10:32
  • $\begingroup$ @Theo, strange, as they definitely don't for me, on 11.3 too. $\endgroup$ – KraZug Jan 10 at 10:34
  • $\begingroup$ The ConvextHullMesh is just slightly larger, which seems logical since it needs to contain all the points. $\endgroup$ – Theo Tiger Jan 10 at 10:36
pvp = PixelValuePositions[img, 1];
Graphics[{LightBlue, EdgeForm[Blue], Polygon[pvp[[FindShortestTour[pvp][[2]]]]]}]

enter image description here

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

enter image description here

  • $\begingroup$ Can you make it be the same dimensions as the original image? So things like ImageAdd[img, Graphics[{Red, Polygon[pvp[[FindShortestTour[pvp][[2]]]]]}]] work properly. $\endgroup$ – KraZug Jan 10 at 14:46
  • $\begingroup$ @KraZug, please see the updated version. $\endgroup$ – kglr Jan 10 at 15:15
  • $\begingroup$ Thank you, I think that does exactly what I need. $\endgroup$ – KraZug Jan 12 at 6:45

First, extract the points from the image and take the convex hull mesh:

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


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.

δ = 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.


As stated in the comments, ConvexHullMesh works as desired for me on Windows.

Windows 7

(* "11.3.0 for Microsoft Windows (64-bit) (March 7, 2018)" *)

img = Import["https://i.stack.imgur.com/yC9ym.png"];
meshraster = Rasterize[ConvexHullMesh[PixelValuePositions[img, 1]], RasterSize -> ImageDimensions[img]];
HighlightImage[meshraster, img]

ConvexHull on Windows 7

Mac OS

I just ran it on Mac OS 10.13. I have no words.

(* "11.3.0 for Mac OS X x86 (64-bit) (March 7, 2018)" *)

ConvexHull on Mac OS

  • $\begingroup$ Thanks. That is rather more wider than the original line than I can accept though, even if I understood what was going on between Windows and Mac. $\endgroup$ – KraZug Jan 10 at 10:45
  • $\begingroup$ See my edit above - I can reproduce this strange behaviour on Mac OS. I have no idea what is happening there. I think this could be reported to Wolfram Support as bug. $\endgroup$ – Theo Tiger Jan 10 at 10:55
  • $\begingroup$ Maybe it has something to do with the device pixel scaling ("DPI settings")? I am on a 1200p non-Retina display, however. $\endgroup$ – Theo Tiger Jan 10 at 10:58
  • $\begingroup$ Hah, something like that presumably. Your image for the same code on a mac is zoomed out, while mine is zoomed in! $\endgroup$ – KraZug Jan 10 at 11:03
  • $\begingroup$ The help says: "Images generated by Rasterize can vary slightly from one computer system to another, mainly as a result of different fonts and anti-aliasing procedures." - slightly! Reported as a bug. $\endgroup$ – KraZug Jan 10 at 11:06

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.

  • $\begingroup$ "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." You mean you don't want the convex hull, just the missing line segment? Try getting the convex hull anyway and then computing the boundary. $\endgroup$ – kajacx Jan 10 at 13:00
  • $\begingroup$ @kajacx, I want an image that contains the filled inside of the line. Where the line is intact but non-convex, the ConvexHull will expand out of it. Where there are breaks, I'd like the shortest straight line to be taken. $\endgroup$ – KraZug Jan 10 at 13:04

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.