3

Assuming you have a grid of squares, where each grid item is either "filled" or "not-filled", is there a nice algorithm that can detect any "solid" shapes in the grid?

The image below shows an example of what I'm trying to accomplish. The image shows one enclosed shape, and then two potential shapes which have holes in them. Also note that shapes aren't necessarily simple squares or rectangles, although direct diagonals should not be present.

Example image with one filled shape and two potentials

  • Red squares denote "filled" squares.
  • Blue squares represent the inner area of an enclosed shape
  • White squares are unfilled

My question then is - is there an algorithm (something that can be followed by someone utterly useless at maths! and hopefully with a C# implementation) that can detect the single enclosed shape in this grid (or multiple shapes if present), including returning the actual squares inside the shape?

My rambling thoughts so far only cover using a linear flood fill, but that doesn't really seem suitable, for example there is no single source point, but the entire grid must be scanned for fillable shapes.

Richard Moss
  • 1,550
  • 3
  • 26
  • 43
  • 3
    I believe you need to know for certain at least one pixel which is outside of the shape, otherwise you will have two possible solutions for certain cases (i.e. what happens if you draw a single line vertically in the middle of the drawing?). But once you have this pixel which is certainly outside, then you can flood fill the outside of the drawing, and whatever remains unfilled is the closed shape. – vgru Dec 21 '15 at 12:15
  • 2
    In order to avoid too broad questions, it is required of you to supply some info on what you have tried so far and what problems you have run into. It is not allowed to simply post questions on the form "is there a tutorial in area X?" – sara Dec 21 '15 at 12:15
  • @Groo Thanks for the comment, I shall do some experimentation! – Richard Moss Dec 21 '15 at 12:19
  • @kai I haven't tried much so far - just been reading into various flood fill algorithms on CodeProject and while I could see how these could work if you knew in advanced fixed points to use, I didn't think they would be useful for this scenario. – Richard Moss Dec 21 '15 at 12:20
  • First time I've ever had down votes too, that hurts :( – Richard Moss Dec 21 '15 at 12:20
  • You can also check [this thread](http://stackoverflow.com/q/22240746/69809), it uses opencv but you can probably use a C# wrapper, or a opencv port. – vgru Dec 21 '15 at 12:40
  • @Groo I did read that as part of my scan of SO prior to posting this question (there's a few other similar questions) and that was one of the ones I considered, plus another on Python. However I discarded to opencv one as it was making use of some other library and ideally I need it to be native. Thanks though! – Richard Moss Dec 21 '15 at 12:45
  • 1
    @Groo I did a quick console application using a simple floodfill, but followed your suggestion of scanning from an outside point (and assuming that point never could be inside, which was not an assumption I started with and probably the mistake that lead to this question being posted!) and the initial tests look promising. If you wish to make your comment into an answer I'd be happy to mark it. – Richard Moss Dec 21 '15 at 12:50
  • @RichardMoss: I see that you posted the solution with some code and it looks pretty elaborate, so that should be the accepted answer imo. – vgru Dec 21 '15 at 14:02

3 Answers3

1

Thanks to a comment by @Groo, I realized that I had started off on somewhat the right foot, which is to use a flood fill. However, I was thinking of scanning inside the shapes to find the boundaries which is where I was going wrong. Groo suggested that there should always be a point outside, and perform the scanning from there.

enter image description here

I wrote a very basic test program using a simple 2D bool array (and a copy-and-pasted floodfill algorithm that was fairly easy to read) and this seems to work nicely. I'm posting this simple code as an answer in case it helps anyone else as muddled as I, and perhaps prevent another poorly phrased "question" being added.

  class Program
  {
    static void Main(string[] args)
    {
      bool[,] grid;
      int width;
      int height;

      Console.Title = "Floodfill Shape Test";

      /*
       * This is a simple test program to detect filled shapes by performing a flood fill
       * to convert all empty space to solid - unless the empty space is already surrounded
       * by solid cells
       *
       * In order to this to work, the assumption is made that the boundaries of the grid
       * can never be solid before the flood fill is executed
       */

      width = 12;
      height = 10;
      grid = new bool[width, height];

      // add a sample enclosed shape
      grid[1, 1] = true;
      grid[2, 1] = true;
      grid[3, 1] = true;
      grid[1, 2] = true;
      grid[3, 2] = true;
      grid[1, 3] = true;
      grid[2, 3] = true;
      grid[3, 3] = true;

      // another enclosed shape
      grid[7, 1] = true;
      grid[8, 1] = true;
      grid[9, 1] = true;
      grid[10, 1] = true;
      grid[7, 2] = true;
      grid[10, 2] = true;
      grid[7, 3] = true;
      grid[8, 3] = true;
      grid[10, 3] = true;
      grid[8, 4] = true;
      grid[10, 4] = true;
      grid[8, 5] = true;
      grid[9, 5] = true;
      grid[10, 5] = true;

      // this shape has a hole in it
      grid[1, 5] = true;
      grid[2, 5] = true;
      grid[3, 5] = true;
      grid[1, 6] = true;
      grid[3, 6] = true;
      grid[1, 7] = true;
      grid[3, 7] = true;

      // a line right down the middle for the edge case
      // Remember that the boundaries can never be filled
      // or this house of cards will fall
      for (int i = 1; i < height - 1; i++)
      {
        grid[5, i] = true;
      }

      // display the original grid
      PrintGrid(grid, width, height);

      // run a basic flood-fill algorithm to mark as "solid" anything not already surrounded by solid borders
      FloodFill(grid, width, height);

      // display the modified grid
      PrintGrid(grid, width, height);

      if (Debugger.IsAttached)
      {
        Console.WriteLine("(Press any key to exit)");
        Console.ReadKey(true);
      }
    }

    private static void PrintGrid(bool[,] grid, int width, int height)
    {
      // print out the results
      // # - solid
      // . - empty
      // X - disallowed
      for (int row = 0; row < height; row++)
      {
        for (int col = 0; col < width; col++)
        {
          char c;

          if (row == 0 || row == height - 1 || col == 0 || col == width - 1)
          {
            c = 'X';
          }
          else {
            c = grid[col, row] ? '#' : '.';
          }

          Console.Write(c);
        }

        Console.WriteLine();
      }

      Console.WriteLine();
    }

    static void FloodFill(bool[,] grid, int width, int height)
    {
      // Taken from http://rosettacode.org/wiki/Bitmap/Flood_fill#C.23
      Queue<Point> q = new Queue<Point>();
      q.Enqueue(Point.Empty);
      while (q.Count > 0)
      {
        Point n = q.Dequeue();
        if (grid[n.X, n.Y])
          continue;
        Point w = n, e = new Point(n.X + 1, n.Y);
        while ((w.X >= 0) && !grid[w.X, w.Y])
        {
          grid[w.X, w.Y] = true;

          if ((w.Y > 0) && !grid[w.X, w.Y - 1])
            q.Enqueue(new Point(w.X, w.Y - 1));
          if ((w.Y < height - 1) && !grid[w.X, w.Y + 1])
            q.Enqueue(new Point(w.X, w.Y + 1));
          w.X--;
        }
        while ((e.X <= width - 1) && !grid[e.X, e.Y])
        {
          grid[e.X, e.Y] = true;

          if ((e.Y > 0) && !grid[e.X, e.Y - 1])
            q.Enqueue(new Point(e.X, e.Y - 1));
          if ((e.Y < height - 1) && !grid[e.X, e.Y + 1])
            q.Enqueue(new Point(e.X, e.Y + 1));
          e.X++;
        }
      }
    }
  }
Richard Moss
  • 1,550
  • 3
  • 26
  • 43
0

There is simple, beautiful (and general!) approach based on computing Euler characteristic.

Basically, it's all about counting 2*2 squares (and trivially implemented in any language).

Closed curves (with 1 'hole') correspond to Euler characteristic equal to zero (equal number of 'blue' and 'red' squares in terms of presentation).

Alleo
  • 7,891
  • 2
  • 40
  • 30
0

Please take a look at this: EmbeddedVisionDesignKit

This is a project made by a teacher at the HAN (hogeschool Arnhem Nijmegen).

The project is for embedded design, but the functions in operators.cpp are operators to process images pixel by pixel.

Edit: I did not make the code, I just made it public.

Junky
  • 93
  • 9