6
$\begingroup$

I'm trying to find a way to calculate the area of the gaps in a sample of spider web in a ring. The web strands are very thin and difficult to segment, since there appear to be breaks in the line where the strand is very faint. The best attempt so far has been TopHat Transform. I've tried using ImageLines to approximate the strands, but there are still breaks. Using MorphologicalGraph gets me to a similar place. So my question is twofold. What method would work to identify the lines, and how can I calculate the areas of the gaps in the webs?

web sample Note: I am a beginner to the Wolfram Language, and may be missing some basic knowledge on how to do this. I've added a sample image to this post, for reference.

lines = ImageLines[Blur[TopHatTransform[web, 5]], Method -> {"Segmented" -> True}];

HighlightImage[web5, lines]

MorphologicalGraph[Binarize[Blur[TopHatTransform[web, 5]], 0.05]]

image lines results

morphological binaries results

$\endgroup$
6
  • 3
    $\begingroup$ Perhaps first binarize the image, then use Dilation to repair the lines, then invert the image and use MorphologicalComponents to get the areas of the regions. Optionally, Thinning could be applied after Dilation. I'm not at a computer with Mathematica at the moment so I can't try this out. $\endgroup$
    – C. E.
    Commented Sep 19, 2021 at 19:38
  • 2
    $\begingroup$ This might be a good starting point: DeleteSmallComponents[LocalAdaptiveBinarize[img, 20], 2000] $\endgroup$
    – Domen
    Commented Sep 19, 2021 at 22:07
  • $\begingroup$ And also, can you please clarify what exactly is a gap in a spider web? Do you just want to get the areas of all the polygons? $\endgroup$
    – Domen
    Commented Sep 19, 2021 at 22:26
  • $\begingroup$ Yes, I just want to get the areas of the polygons $\endgroup$
    – dinesh
    Commented Sep 19, 2021 at 23:34
  • $\begingroup$ How representative of your images is the example? Are they all circles with a few well-spaced threads? Do they all have the same scale? Is the thickness (I presume a diameter but I don't know much about spiders or webs) a constant or would you want to measure that from the image? $\endgroup$ Commented Sep 20, 2021 at 10:40

1 Answer 1

5
$\begingroup$

This answer is based on the initial solution by @dinesh (OP) and suggestions (very much on point) from @C. E. and @Domen.

The short version. Input: OP's provided image.

Output:

Web skeleton overlay + circular boundary (contraction explained below).

Before explaining, I would like to note the main issues:

  • The uppermost and the lowermost web thread vertices are slightly distorted
  • Some of the field of view is lost
  • The two above issues mean that there are possible non-negligible (up to the OP) errors in the web gap areas
  • Based on only one image it is hard to say how robust/automatable this is

Might be able to fix these in the future, unless someone else beats me to it. For now, here is how the solution works.

It seemed from the OP's attempt that the object in the smaller gap might be a problem, so it should be removed. First one needs a mask:

inpaintMask = Dilation[#, DiskMatrix@3] &@Closing[#, DiskMatrix@5] &@(
  DeleteSmallComponents[#, 15] &@Binarize@# -
   ColorNegate@FillingTransform@Binarize@ColorNegate@#
  ) &@webInput;

ImageAdd[
 webInput,
 ColorReplace[#, White -> Yellow] &@EdgeDetect@inpaintMask
 ]

where webInput is the OP's image; DeleteSmallComponents[#, 15] &@Binarize@# detects the artefact and -ColorNegate@FillingTransform@Binarize@ColorNegate@# excludes the segments at the circular field of view boundary.

Dilation "bloats" the mask to better remove the artefact with inpainting:

webClean = Inpaint[
  webInput, inpaintMask,
  Method -> {"TextureSynthesis", "MaxSamples" -> 500, "NeighborCount" -> 50}
  ]

Artefact removed

which thankfully correctly fills in the upper web thread, but alas deforms the lower thread.

Now use the OP's initial solution:

webTopHat = ImageAdjust@TopHatTransform[#, 2] &@webClean

which by the way can be visualized more clearly with 2*webTopHat. Now the threads with breaks in them can be repaired and binarized. Taking the suggestion of @C. E. but replacing dilation with Gaussian filtering before binarizing as suggested by @Domen

webBinarized = LocalAdaptiveBinarize[#, 15] &@
    ImageAdjust@GaussianFilter[#, 2] &@webTopHat

one has this:

Binarization estimate

where one has two problems: the artefacts in the web gaps and at the circular boundary. Note that applying this step without top-hat transforming the input produces coarser artefacts which are a lot harder (may not be possible in other OP's images) to get rid of. Also, the artefact that was removed previously interferes with binarization rather badly and corrupts the smaller web gap.

To remove the interior/boundary artefacts, one first needs a mask of the circular field of view. Not knowing if the other OP's images contain any boundary artefacts, the circular mask is found as follows:

circleMaskEstimate = 
 ImagePad[#, -10, Black] &@FillingTransform@Closing[#, 3] &@
    ImagePad[#, 10, Black] &@ColorNegate@Binarize@webClean


circleMaskEstimateContracted = 
 ImagePad[#, -10, Black] &@Erosion[#, DiskMatrix@6] &@
    ImagePad[#, 10, Black] &@circleMaskEstimate

where ImagePad (removed after operations) prevents boundary artefacts and the contracted mask circleMaskEstimateContracted will be used a bit later on. As a safeguard from boundary artefacts in the source images, this estimate is refined by fitting a circle:

    whitePixels = 
      PixelValuePositions[EdgeDetect@circleMaskEstimateContracted, 1];
    
    fitError = 
      Total[Map[Function[whitePixel, (Norm[{cx, cy} - whitePixel] - r)^2],
         whitePixels]];
    
    optimalCircle = FindMinimum[fitError, {cx, cy, r}];
    
    Image[#, ImageSize -> Medium] &@Show[
      HighlightImage[
       webClean,
       Style[Image[EdgeDetect@circleMaskEstimateContracted], Yellow]
       ],
      Graphics[
       {Red, Dashed, Circle[{cx, cy}, r] /. optimalCircle[[2]]}
       ]
      ]

imageMask = Rasterize[#, RasterSize -> ImageDimensions@webInput] &@
  Binarize@FillingTransform@HighlightImage[
     ImageAdjust@Binarize[#, 1] &@webInput,
     
     Graphics[
      {White, Thin, Circle[{cx, cy}, r] /. optimalCircle[[2]]}
      ]
     ]

to make sure the mask is actually circular.

Now the artefacts can be removed as follows:

webSkeleton = Thinning@ImageMultiply[#, imageMask] &@webBinarized

webSkeletonClean = DeleteSmallComponents[#, 500] &@
    Pruning[#, 15] &@webSkeleton

webSkeletonCorrected = 
 Pruning[#, 10] &@Thinning@Binarize@GaussianFilter[#, 5] &@
  webSkeletonClean

where webSkeleton crops the segment with the contracted circular mask and then performs thinning; webSkeletonClean performs pruning, then size thresholding (easier to clean artefacts this way, since they are finer); webSkeletonCorrected removes any remaining "parasitic" branches and loops.

Preliminary skeleton

From here the strategy is this:

  • Use the branch points of the web skeleton and its intersection points with the circular boundary to reconstruct corrupted/incomplete web gaps (like the smaller one in this case)
  • Invert the reconstructed skeleton + the circular boundary (with some modifications)
  • Get the web gap segments and measure their areas

Start by joining (as non-destructively as I could manage) the clean web skeleton with the circular boundary

webSkeletonPlusCirclePadded = ImagePad[#, 25, Black] & /@ {
    EdgeDetect@imageMask,
    webSkeletonCorrected
    } // ImageAdd


webPlusCircleSkeleton = 
 Binarize[#, 10^-6] &@Thinning@ImageMultiply[#, imageMask] &@
      ImagePad[#, -25, Black] &@
    Binarize@GaussianFilter[#, 4] &@webSkeletonPlusCirclePadded

where webSkeletonPlusCirclePadded adds the circle and the web skeleton, and prepares some safe space for operations with ImagePad; a merged skeleton is then produced with webPlusCircleSkeleton by bridging the gaps using Gaussian filtering and binarization followed by thinning.

Then the branch points are found for the resulting skeleton:

allBranchPoints = MorphologicalBranchPoints@webPlusCircleSkeleton;

HighlightImage[
 webPlusCircleSkeleton,
 Style[#, Red] &@allBranchPoints
 ]

Joined skeleton + highlighted branchpoints

The fragments of incomplete edges are detected with

incompleteEdges = 
 ImageDifference @@ {#, Pruning@#} &@webPlusCircleSkeleton

and the vertices that must be joined to reconstruct the missing edges are found via

verticesToJoin = ImageSubtract[#, incompleteEdges] &@
    DeleteSmallComponents[#, 1] &@ImageAdd[
   incompleteEdges,
   allBranchPoints
   ]

where ImageDifference @@ {#, Pruning@#} prunes the branches of incomplete edges to then find them as a difference from the unpruned image. verticesToJoin identifies branch points to be connected as those that are adjacent to the the branches of incomplete edges (DeleteSmallComponents[#, 1] removes all other points).

Here only one pair of vertices must be joined, but in general one would need to first identify pairs. Here the OP's idea with ImageLines could be useful, since the point pairs to be joined belong to branches of incomplete edges - their line orientations can be found (e.g. using RANSAC as a Method, since they may be rather jagged) and then pairs can be identified based on combined angle/Euclidean distances between their respective lines (i.e. using CosineDistance as a DistanceFunction).

Once that is done, the corrupted edges are recovered using the Bresenham's line algorithm (credit to @halirutan, Bresenham's line algorithm):

bresenhamLine[p0_, p1_] := 
 Module[{dx, dy, sx, sy, err, newp}, {dx, dy} = Abs[p1 - p0];
  {sx, sy} = Sign[p1 - p0];
  err = dx - dy;
  newp[{x_, y_}] := 
   With[{e2 = 2 err}, {If[e2 > -dy, err -= dy; x + sx, x], 
     If[e2 < dx, err += dx; y + sy, y]}];
  NestWhileList[newp, p0, # =!= p1 &, 1]]

recoveredEdgePixelCoordinates = 
  bresenhamLine @@ PixelValuePositions[#, 1] &@verticesToJoin;

recoveredEdges = ReplacePixelValue[
  verticesToJoin,
  # -> 1 & /@ recoveredEdgePixelCoordinates
  ]

allowing to finally get the solution skeleton:

webPlusCircleSkeletonFixed = ImageAdd[
  webPlusCircleSkeleton,
  -incompleteEdges,
  recoveredEdges
  ]

Perhaps there is a better way of doing this, but the web gaps can now be resolved as follows:

resolvedSegments = 
  ColorNegate@Dilation[#, [email protected]] &@webPlusCircleSkeletonFixed;

and visualized like this:

resolvedSegmentsColorized = 
 Colorize@MorphologicalComponents@resolvedSegments

Web gaps resolved

Alternatively, the very first image in this answer was generated with

finalSkeletonOverlay = ImageAdd[
  webInput,
  webPlusCircleSkeletonFixed
  ]

At last, the segment areas can be determined:

segmentStats = ComponentMeasurements[
     #, {"MaskedImage", "Area"}
     ] &@resolvedSegments;

Last /@ segmentStats[[2 ;; All]] // TableForm

enter image description here

But whether the quality is acceptable is, of course, for the OP to decide. This should be rather robust, but also has a few minor issues that are best answered in comments, should they become critical. Also, I would like to thank the OP for an interesting problem! Hope this helps.

$\endgroup$
6
  • $\begingroup$ Wow, Thank you so much for all the effort you put into this. I deeply appreciate it. You have introduced me to several concepts and it will take some time for me to assimilate this. I will update here when I try it on other images. $\endgroup$
    – dinesh
    Commented Dec 9, 2021 at 2:24
  • $\begingroup$ Glad to hear this is of use! If some of the steps are unclear, please let me know. I can add more images now, if need be. If you run into problems with your other images and are willing to send a few more examples, I could have another look. Also, depending on your hardware and the number of images, optimization/parallelization could be worth it. Haven't tried putting things together in one Module and running on multiple (duplicate) images. $\endgroup$
    – MBir
    Commented Dec 9, 2021 at 17:37
  • $\begingroup$ looks like you were right about other images, I'm having trouble reproducing the output if there are differences in the images $\endgroup$
    – dinesh
    Commented Jan 21, 2022 at 21:34
  • $\begingroup$ What exactly are the differences? Your OP mentions that illumination and scales should be the same. Could you show a few more representative examples? $\endgroup$
    – MBir
    Commented Jan 23, 2022 at 16:55
  • $\begingroup$ I have a folder here of the ring images. Note that in these, the rings are not circularly cropped as in the example above dropbox.com/sh/dbd1c674k6oh87i/AABqA93LcnCdXSspQnYnTCq4a?dl=0 $\endgroup$
    – dinesh
    Commented Jan 24, 2022 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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