Nodes/ComfyUI CV/CV Delaunay / Voronoi
ComfyUI Node

CV Delaunay / Voronoi

Scattered points in, mesh out — Delaunay and its Voronoi dual in one node

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Delaunay / Voronoi
  • points
  • bounds
  • triangles
  • edges
  • facets
  • centers
  • count
◄margin10►

CV Delaunay / Voronoi takes a pile of 2D points and connects them into a triangle mesh - plus, for free, the dual Voronoi diagram, which is the answer to "which of these points is nearest to any given pixel".

Delaunay is the standard way to turn scattered points into a mesh, and the reason is geometric rather than aesthetic: it maximises the smallest angle in every triangle, so the triangles are as well-shaped as your point distribution allows. No slivers, no arbitrary choices about which triples to join.

Three jobs it unlocks: piecewise-affine warping and face morphing (triangulate landmarks on both images, warp triangle by triangle), surface reconstruction from a sparse cloud when you'd rather not run a full mesher, and nearest-neighbour region maps via the Voronoi cells. Feed it any Nx2 set - detected corners, blob centres, k-means centroids, points you annotated by hand.

How it works

Under the hood it's cv2.Subdiv2D, which is a stateful class - the reason a hand-written node exists rather than a raw wrapper, since the auto-generated nodes only cover plain top-level functions. Subdiv2D needs an enclosing rectangle and refuses points sitting exactly on its edge, so when no bounds image is connected the node builds the rectangle from the points' bounding box plus margin. Triangles that touch the virtual outer vertices get dropped, so everything you receive lies properly inside.

The inputs and outputs that matter

Two inputs, really:

  • points - Nx2 (or Nx1x2) NPARRAY. Duplicates collapse to a single vertex.
  • margin - padding around the bounding box, default 10. It exists purely to keep points off the rectangle's edge; a pixel or two is enough.

Optional bounds takes an image whose size defines the rectangle. Connect the image the points came from and your Voronoi cells are clipped to the visible frame instead of sprawling across an arbitrary box - this is the difference between a Voronoi diagram you can draw and one you can't.

Five outputs. triangles is one 3-point contour per triangle as CV_CONTOURS, ready for CV Draw Contours. edges is an Mx4 float32 (x1, y1, x2, y2) array - feed CV Draw Segments for a clean wireframe, drawn once per edge rather than three times per triangle. facets is one contour per Voronoi cell, ordered to match centers (the input points after dedupe and clamping). And count is the triangle total.

Fewer than 3 distinct points is not an error. You get empty outputs and count = 0. Branch on it.

Install

Ships in comfyui_cv (bmad4ever/comfyui_cv). ComfyUI Manager, search "ComfyUI CV", or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart ComfyUI when it's done. The single dependency is pinned deliberately:

pip install "opencv-contrib-python-headless~=5.0.0.93"

Python ≥ 3.12, recent ComfyUI with the V3 node API, and it must be a contrib wheel - a plain opencv-python install over it empties the contrib submodules and takes nodes with it. tools/repair_opencv_contrib.py --check tells you if that's happened.

Common issues

  • Nothing comes out with 3+ points. You probably have three nearly collinear points, or points identical after dedupe. Delaunay has nothing to work with.
  • Voronoi cells extend past the image. Wire bounds.
  • Edges look doubled. You drew triangles instead of edges. edges is right there and is both cheaper and cleaner.
  • Points that sit exactly on the boundary. That's what margin is for; if you shrank it to 0 to "fit exactly", you asked for this.

If your goal is the warp rather than the mesh, this node is one piece of it: CV Thin Plate Spline Warp does a smooth global deformation from control points and will often beat a triangle mesh for face morphs, because it doesn't show the triangle edges as creases. Reach for Delaunay when you want the mesh itself - for a viewer, for reconstruction, or for a per-cell region map.

Categoryimage/CV/points

Inputs (3)

NameTypeDefaultDescription
pointsNPARRAYNx2 (or Nx1x2) point set to triangulate. Duplicate points collapse to one vertex.
marginFLOAT100–10000Padding around the points' bounding box when no 'bounds' image is connected. Subdiv2D needs an enclosing rectangle and rejects points on its edge, so leave at least a pixel or two.
boundsoptNPARRAY,IMAGEOptional image whose SIZE defines the enclosing rectangle - connect the image the points came from so the Voronoi cells are clipped to it. Without it the rectangle is the points' bounding box plus 'margin'. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.

Outputs (5)

NameTypeDescription
trianglesCV_CONTOURSOne 3-point contour per Delaunay triangle. Triangles touching Subdiv2D's virtual outer vertices are dropped, so every one lies inside the rectangle.
edgesNPARRAYMx4 float32 (x1, y1, x2, y2) mesh edges - feed 'OpenCV Draw Segments' for a clean wireframe (each edge drawn once instead of three times per triangle).
facetsCV_CONTOURSOne contour per VORONOI cell, in the same order as 'centers' - the region of the plane closest to that point. Draw with 'CV Draw Contours'.
centersNPARRAYKx2 float32 the facets belong to: the input points after de-duplication and clamping into the rectangle.
countINTNumber of triangles; 0 for fewer than 3 distinct points - a valid result, not an error.