42

Any suggestions on how to determine a Voronoi diagram for sites other than points, as e.g. in the picture below?

image

The input is a raster image.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
DeeDee
  • 807
  • 8
  • 13

7 Answers7

45

Obtain the image:

i = Import["https://i.stack.imgur.com/iab6u.png"];

Compute the distance transform:

k = DistanceTransform[ColorNegate[i]] // ImageAdjust;
ReliefPlot[Reverse@ImageData[k]] (* To illustrate *)

Relief plot

Identify the "peaks," which must bound the Voronoi cells:

l = ColorNegate[Binarize[ColorNegate[LaplacianGaussianFilter[k, 2] // ImageAdjust]]];

Clean the result and identify its connected components (the cells):

m = Erosion[Dilation[MorphologicalComponents[l] // Colorize, 2], 1];

Show this with the original features:

ImageMultiply[m, ColorNegate[i]]

Image

Edit

A cleaner solution--albeit one that takes substantially more processing time--exploits WatershedComponents (new in Version 8):

l = WatershedComponents[k];
m = Dilation[MorphologicalComponents[l] // Colorize, 1] (* Needs little or no cleaning *)
ImageMultiply[m, ColorNegate[i]] (* As before *)

Solution 2

I like this one better, but fear it might take too much processing for large complex images.

whuber
  • 20,544
  • 2
  • 59
  • 111
21

Here is a Nearest-based method. This is quite similar to what @Mr. Wizard did for approximating 3D (ordinary) Voronoi.

comps     = MorphologicalComponents[img];
cmap      = Flatten[MapIndexed[#2 -> #1 &, comps, {2}]];
comparray = DeleteCases[cmap, _ -> 0];
nf        = Nearest[comparray];

Now we build the table giving Voronoi components.

Timing[
 voronoi2 =
  Array[
   First @ nf[{##}] &,
   Length /@ {comps, comps[[1]]}
  ];
]

(* Out[138]= {0.640000, Null} *)

The picture:

MatrixPlot[voronoi2 + comps, ColorFunction -> "BrightBands", ImageSize -> 500]

enter image description here

Daniel Lichtblau
  • 58,970
  • 2
  • 101
  • 199
  • Good one. Thanks, Daniel Lichtblau. – DeeDee Mar 06 '13 at 04:37
  • +1 Nearest is flexible and squarely within the spirit of Voronoi tessellations. It is nice to see that it can be reasonably efficient with this problem. – whuber Mar 06 '13 at 18:12
  • +1 as well, and thanks for the mention, but why can't you just use comparray = DeleteCases[cmap, _ -> 0];? – Mr.Wizard Mar 06 '13 at 18:16
  • @Mr. Wizard Yes, DeleteCases is the better way to do this. I was floundering a bit there and just ran with the first thing that worked for me. – Daniel Lichtblau Mar 06 '13 at 18:50
17

OK, you can get the Voronoi diagram using raster-graphics tricks, but what if you want to do it the old-fashioned way?

Download the graphic from the net and parse its components:

img = Import["https://i.stack.imgur.com/iab6u.png"];
morph = MorphologicalComponents[img];
boundary = 
  N[{{0, 0}, {#1, 0}, {#1, #2}, {0, #2}} & @@ (ImageDimensions[img] + 
      1)];
comps = (boundary[[-1]] + {1, -1} Reverse[#] & /@ 
     Position[morph, #]) & /@ Range[Max[morph]];

Take the hulls of the solid shapes. You don't have to do this, but it should make things faster.

hulls = Parallelize[
  Function[set, 
    Cases[set, 
     x_ /; Count[
        MemberQ[set, x + #] & /@ 
         DeleteCases[Tuples[{-1, 0, 1}, {2}], {0, 0}], True] <= 5]] /@
    comps];

Deploy ComputationalGeometry! Start with the Delaunay triangulation, and then get the bounded diagram. (I had to cheat here. Due to some badly conditioned matrices, BoundedDiagram blows up if the boundary is too close.)

Needs["ComputationalGeometry`"]
del = DelaunayTriangulation[pts = N[Join @@ hulls]];
vor = BoundedDiagram[
  boundary /. {0. -> -400., x_?Positive :> x + 400}, pts, del];

Now we just need to paste these back together. This function will fuse adjacent polygons.

fuse[p_, q_] := 
 Module[{l = Intersection[p, q], al, nal, qrot, prot, qdrop}, 
  al = Alternatives @@ l; 
  nal = Except[Alternatives @@ l]; {qrot, prot} = 
   RotateLeft[#, (-Length[l] + 
          Position[
           Differences@Position[#, Alternatives @@ l] - 
            1, {_Integer?
             Positive}] /. {} -> {{Min[
              Position[#, Alternatives @@ l]] - 1}})[[1, 
       1]]] & /@ {DeleteDuplicates[q], p};
  If[Length[DeleteDuplicates[q]] == Length[l], 
   qrot = Take[prot, Length[l]]];
  qdrop = Drop[RotateLeft[qrot], Length[l] - 2];
  prot /. {al .., A : nal ...} :> 
    Join[If[Take[prot, Length[l]] =!= Take[qrot, Length[l]], Identity,
        Reverse][qdrop], {A}]]

mergevor = {vor[[1]], 
  MapIndexed[
   Function[{span, i}, {i[[1]], 
     Fold[fuse, First[#], Rest[#]] &@
      SortBy[vor[[2, Span @@ (span + {1, 0})]], 
        N@Norm[pts[[#[[1]]]] - pts[[vor[[2, span[[1]] + 1, 1]]]]] &][[
       All, 2]]}], 
   Partition[Prepend[Accumulate[Length /@ hulls], 0], 2, 1]]}

And we're done:

Graphics[MapThread[{Hue[#1/Length[hulls]], Opacity[0.5], 
    Polygon[mergevor[[1, #3]]], Opacity[1], PointSize[0.01], 
    Point[#2]} &, {Range[Length[hulls]], hulls, 
   mergevor[[2, All, 2]]}], 
 PlotRange -> ({0, #} & /@ ImageDimensions[img])]

Voronoi diagram of squiggles

Xerxes
  • 3,951
  • 20
  • 31
  • I like the approach. Did you notice a slight difference of your result and that of whuber? – DeeDee Mar 06 '13 at 00:33
  • I hope not; they should be identical. If I flip back and forth between our final images, I don't see any difference. – Xerxes Mar 06 '13 at 00:36
  • 2
    One difference is that whuber's method will handle Voronoi cells with holes; mine does not. My method will give you a list of Polygons when you're done; his will need further processing. – Xerxes Mar 06 '13 at 00:39
  • It is not the same as mine. Any ideas why? – DeeDee Mar 06 '13 at 00:50
  • 2
    I suppose because the Meyer algorithm for finding watersheds is not exactly the same as a Voronoi tessellation. I'm not familiar with the details, but you might be able to find them in this paper. – Xerxes Mar 06 '13 at 00:57
  • The watershed algorithm should have nothing of consequence to do with the result: the boundaries of the watersheds are determined solely by DistanceTransform; any two watershed algorithms (applied to the distance transform image) might differ on how to assign boundary cells, but that's all. Of course, applying a watershed algorithm to some other grid is unlikely to produce correct (or even predictable) results. – whuber Mar 06 '13 at 18:04
  • This is a real tour de force. Up to the resolution afforded by the original grid, it is fine. But using ComputationalGeometry implicitly suggests this approach could be applied to shapes given in vector format. That is not so: it only works for collections of points, not for general polylines or polygons. (The Delaunay triangulation no longer determines the Voronoi cells, which must have curvilinear boundaries containing portions of quadratic surfaces.) The reason it works here is that the original polygonal features have been closely approximated by sets of points on their boundaries. – whuber Mar 06 '13 at 18:10
  • @whuber I meant that a watershed algorithm might (but does not) attempt to derive a Voronoi diagram division of the space even without the distance transform. After all, a drop of water in a random location in the space in some sense is most likely to end up in the "basin" to which it is nearest by distance even if the "plateau" is totally flat. Using the distance transform enforces this behavior by tilting the plateaus toward the appropriate basins, but one might expect it to happen regardless. – Xerxes Mar 06 '13 at 18:40
  • Thanks--I did not initially understand that the watershed algorithm would be applied to anything but the distance transform; it was only after I encountered another attempted answer that I understood that such a misapplication of the algorithm would be considered at all. – whuber Mar 06 '13 at 18:46
16

While I cannot match @whuber's simple elegance, I will show a bit of brutishness by using Fast Marching from scratch. This finds distances from a specified boundary. I'll modify it so that, for each pixel, it returns the value of the nearest boundary components.

The code is a bit long but mostly cribbed, from this blog. The only modification is the extra bookkeeping and alteration in the returned result noted above. I include it below the example.

For the example itself, there's not much work for me there either because I cribbed that from @whuber.

img = Import["https://i.stack.imgur.com/iab6u.png"];

comps = MorphologicalComponents[img];

negcomps = -comps;
Timing[voronoi = findNearestIndexC[negcomps];]

(* Out[84]= {2.200000, Null} *)

(Not as fast as @whuber's, but not bad either *)

So let's have a look.

MatrixPlot[voronoi + comps, ColorFunction -> "BrightBands", 
 ImageSize -> 500]

Voronoi diagram

Not too bad. There is a bit of jaggedness which might be from resolving seeming ties in the "wrong" way. or maybe they really should be there, I'm not sure.

--- edit ---

Or, more likely, the jaggedness is due to the nature of the algorithm. This is a way to handle certain diffusion-like problems; that is, it is in effect solving a PDE of some sort. Since all steps are essentially "local" (that is, based directly on what territory we have recently traversed but not on past regions), my guess is we get some jaggedness due to global accumulation of error.

--- end edit ---

Code used:

frozen = -1.;
frozenQ[aa_] := aa < 0.
unseen = 0.;
far = 30000.;
outofbounds = 100000.;
bigstate = 10000;
band = 0.;

Clear[FastCompile];
SetAttributes[FastCompile, HoldAll];
FastCompile[stuff__] := 
  Compile[stuff, 
   CompilationOptions -> {"InlineCompiledFunctions" -> False, 
     "InlineExternalDefinitions" -> True}, RuntimeOptions -> "Speed", 
   CompilationTarget -> "C"];

state = FastCompile[{{states, _Real, 
     2}, {x, _Integer}, {y, _Integer}}, 
   If[x > Length[states] || x < 1 || y > Length[states[[x]]] || y < 1,
     bigstate, states[[x, y]]]];

distance = 
  FastCompile[{{distances, _Real, 2}, {x, _Integer}, {y, _Integer}}, 
   If[x > Length[distances] || x < 1 || y > Length[distances[[x]]] || 
     y < 1, outofbounds, distances[[x, y]]]];

neighborValue = 
  FastCompile[{{l1, _Integer, 1}, {l2, _Integer, 1}, {states, _Real, 
     2}, {distances, _Real, 2}}, 
   Module[{s1, s2, d1, l11, l12, l21, l22}, {l11, l12} = l1;
    {l21, l22} = l2;
    s1 = state[states, l1[[1]], l1[[2]]];
    s2 = state[states, l2[[1]], l2[[2]]];
    d1 = distance[distances, l1[[1]], l1[[2]]];
    Which[s1 >= 0. && s2 >= 0., outofbounds, s1 <= -1. && s2 <= -1., 
     Min[distance[distances, l1[[1]], l1[[2]]], 
      distance[distances, l2[[1]], l2[[2]]]], s1 <= -1., 
     distance[distances, l1[[1]], l1[[2]]], True, 
     distance[distances, l2[[1]], l2[[2]]]]]];

distanceToBoundary2 = 
  FastCompile[{v1, v2, 
    f}, (Sqrt[(-f^2)*(-2 + f^2*(v1 - v2)^2)] + f^2*(v1 + v2))/(2*f^2)];

distanceToBoundary1 = FastCompile[{v1, f}, v1 + 1/f];

newDistance = 
  FastCompile[{{x, _Integer}, {y, _Integer}, {states, _Real, 
     2}, {distances, _Real, 2}}, 
   Module[{up, down, left, right, f = 1., res, xvalue, yvalue}, 
    up = {x, y + 1}; down = {x, y - 1}; left = {x - 1, y};
    right = {x + 1, y};
    xvalue = neighborValue[right, left, states, distances];
    yvalue = neighborValue[up, down, states, distances];
    res = Which[xvalue == yvalue == outofbounds, outofbounds,
      xvalue != outofbounds && yvalue != outofbounds, 
      distanceToBoundary2[xvalue, yvalue, f], xvalue != outofbounds, 
      distanceToBoundary1[xvalue, f],
      True, distanceToBoundary1[yvalue, f]];
    res]];

findNearestIndexC = 
  FastCompile[{{ll, _Real, 2}}, 
   Module[{hindex = 0, dist, j1, j2, nbrs, pt, x, y, x1, y1, x2, y2, 
     next, prev, done, cond = False, len, wid, hsize, distancetable, 
     statetable, statetable2, heaptable, bandheap},
    len = Length[ll];
    wid = Length[ll[[1]]];
    hsize = len*wid;
    distancetable = ll;
    statetable = Map[If[TrueQ[# == unseen], far, #] &, ll, {2}];
    statetable2 = ll;
    heaptable = Table[0, {len}, {wid}];
    bandheap = Table[{0., 0., 0.}, {hsize}];
    Do[If[statetable[[ii, jj]] >= 0., Continue[]];
     nbrs = {{ii, jj + 1}, {ii, jj - 1}, {ii - 1, jj}, {ii + 1, jj}};
     Do[{x, y} = nbrs[[kk]];
      If[! (0 < x <= len && 0 < y <= wid && statetable[[x, y]] == far),
       Continue[]];
      hindex++;
      statetable[[x, y]] = band;
      statetable2[[x, y]] = statetable2[[ii, jj]];
      dist = newDistance[x, y, statetable, distancetable];
      distancetable[[x, y]] = dist;
      bandheap[[hindex]] = {dist, N[x], N[y]};
      j1 = hindex;
      While[(j2 = Floor[j1/2]) >= 1 && 
        bandheap[[j2, 1]] > bandheap[[j1, 1]], 
       bandheap[[{j1, j2}]] = bandheap[[{j2, j1}]];
       {x1, y1} = Round[Rest[bandheap[[j1]]]];
       heaptable[[x1, y1]] = j1;
       j1 = j2;];
      heaptable[[x, y]] = j1, {kk, Length[nbrs]}], {ii, len}, {jj, 
      wid}];
    While[hindex > 0, pt = bandheap[[1]];
     {x, y} = Round[Rest[pt]];
     statetable[[x, y]] = frozen;
     bandheap[[1]] = bandheap[[hindex]];
     done = False;
     prev = 1; next = 1;
     {j1, j2} = 2*prev + {0, 1};
     While[j1 < hindex && ! done, 
      If[j2 < hindex, 
       If[TrueQ[bandheap[[j1, 1]] <= bandheap[[j2, 1]]], next = j1, 
        next = j2], next = j1];
      cond = bandheap[[prev, 1]] > bandheap[[next, 1]];
      If[TrueQ[cond], 
       bandheap[[{prev, next}]] = bandheap[[{next, prev}]];
       {x1, y1} = Round[Rest[bandheap[[prev]]]];
       heaptable[[x1, y1]] = prev;
       prev = next;
       {j1, j2} = 2*prev + {0, 1};
       , done = True];
      ];
     {x1, y1} = Round[Rest[bandheap[[prev]]]];
     heaptable[[x1, y1]] = prev;
     nbrs = {{x, y + 1}, {x, y - 1}, {x - 1, y}, {x + 1, y}};
     Do[{x2, y2} = nbrs[[kk]];
      If[! (0 < x2 <= len && 0 < y2 <= wid && 
          statetable[[x2, y2]] == band),
       Continue[]];
      dist = newDistance[x2, y2, statetable, distancetable];
      distancetable[[x2, y2]] = dist;
      statetable2[[x2, y2]] = statetable2[[x, y]];
      j1 = heaptable[[x2, y2]];
      bandheap[[j1]] = {dist, N[x2], N[y2]};
      While[(j2 = Floor[j1/2]) >= 1 && 
        bandheap[[j2, 1]] > bandheap[[j1, 1]], 
       bandheap[[{j1, j2}]] = bandheap[[{j2, j1}]];
       {x1, y1} = Round[Rest[bandheap[[j1]]]];
       heaptable[[x1, y1]] = j1;
       j1 = j2;];
      heaptable[[x2, y2]] = j1, {kk, Length[nbrs]}];
     hindex--;
     Do[{x2, y2} = nbrs[[kk]];
      If[! (0 < x2 <= len && 0 < y2 <= wid && 
          statetable[[x2, y2]] == far),
       Continue[]];
      hindex++;
      statetable[[x2, y2]] = band;
      dist = newDistance[x2, y2, statetable, distancetable];
      distancetable[[x2, y2]] = dist;
      statetable2[[x2, y2]] = statetable2[[x, y]];
      bandheap[[hindex]] = {dist, N[x2], N[y2]};
      j1 = hindex;
      While[(j2 = Floor[j1/2]) >= 1 && 
        bandheap[[j2, 1]] > bandheap[[j1, 1]], 
       bandheap[[{j1, j2}]] = bandheap[[{j2, j1}]];
       {x1, y1} = Round[Rest[bandheap[[j1]]]];
       heaptable[[x1, y1]] = j1;
       j1 = j2;];
      heaptable[[x2, y2]] = j1, {kk, Length[nbrs]}];];
    statetable2(*distancetable*)]];
J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Daniel Lichtblau
  • 58,970
  • 2
  • 101
  • 199
10

I think this works too.

i = Import["https://i.stack.imgur.com/iab6u.png"];
cn = ColorNegate[i];
iws = Image[WatershedComponents[cn]];
ImageMultiply[cn, iws]

enter image description here

DeeDee
  • 807
  • 8
  • 13
  • 1
    I believe this method was already covered in whuber's answer before you posted, was it not? – Mr.Wizard Mar 06 '13 at 00:33
  • I see now. I didn't notice it before. – DeeDee Mar 06 '13 at 00:37
  • You can delete it if you wish but no one is forcing your hand. Usually we try not to post answers that duplicate earlier ones but it does happen; further, sometimes the simply-stated answer is best, and obviously five people liked this answer. Mostly I wondered if you felt this was a different approach to the one whuber showed. – Mr.Wizard Mar 06 '13 at 00:41
  • 1
    By the way the result is slightly different. E.g. the border between "S" element in the upper left corner and the element above the pentagon is slightly different than in other solutions. And so is border around the ellipse. Which result is more accurate? – DeeDee Mar 06 '13 at 00:47
  • 5
    Your method appears to be in error. The key difference is whuber's use of the DistanceTransform prior to using the watershed method. – Xerxes Mar 06 '13 at 01:30
  • You are correct. – DeeDee Mar 06 '13 at 01:56
9

Using ImageMesh[] to convert the image into polygons, along with a strategy similar to this answer, here is how to generate a fake Voronoi diagram for the OP's image:

img = Import["https://i.stack.imgur.com/iab6u.png"];
polys = Flatten[MeshPrimitives[#, 2] & /@ ConnectedMeshComponents[ImageMesh[img]]];
rdf = RegionDistance /@ polys;

(* Schlick's "bias" function,
   http://books.google.com/books?hl=en&id=brDfBAAAQBAJ&pg=PA401 *)
bias[a_, t_] := t/((1/a - 2) (1 - t) + 1)

DensityPlot[bias[0.6, (HarmonicMean[#] - First[#]) &[
            MinimalBy[Through[rdf[{x, y}]], Identity, 2]]],
            {x, 5, 451}, {y, 13, 287}, AspectRatio -> Automatic,
            ColorFunction -> GrayLevel, Exclusions -> None,
            PlotPoints -> 105, PlotRange -> All]

Voronoi diagram

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
8

As it turns out, there is an easy way to get a MeshRegion corresponding to the Voronoi diagram derived from the OP's image. The method, as described in Okabe et al.'s "Spatial Tessellations", is very easily implemented in Mathematica (altho I had to use a bunch of undocumented routines):

img = Import["https://i.stack.imgur.com/iab6u.png"];
polys = Flatten[MeshPrimitives[#, 2] & /@
                ConnectedMeshComponents[ImageMesh[img, Method -> "LinearSeparable"]]];

ptset = polys /. Polygon -> Sequence;
vm = VoronoiMesh[Flatten[ptset, 1]];

facs = MeshPrimitives[vm, 2];
cif = Region`Mesh`MeshMemberCellIndex[vm];

vmp = DiscretizeGraphics[Graphics[Graphics`PolygonUtils`PolygonCombine[facs[[#]]] & /@ 
      Internal`PartitionRagged[cif[Flatten[ptset, 1]][[All, -1]], Length /@ ptset]]]

Voronoi diagram for shapes

Show Voronoi diagram with the generators:

Show[vmp, Graphics[{Red, polys}], PlotRange -> {{5, 451}, {13, 287}}]

with generators

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574