views:

146

answers:

5

I need to calculate length of the object in a binary image (maximum distance between the pixels inside the object). As it is a binary image, so we might consider it a 2D array with values 0 (white) and 1 (black). The thing I need is a clever (and preferably simple) algorithm to perform this operation. Keep in mind there are many objects in the image.

The image to clarify:

alt text

Sample input image:

alt text

+1  A: 

A very crude, brute-force approach would be to first identify all the edge pixels (any black pixel in the object adjacent to a non-black pixel) and calculate the distances between all possible pairs of edge pixels. The longest of these distances will give you the length of the object.

If the objects are always shaped like the ones in your sample, you could speed this up by only evaluating the pixels with the highest and lowest x and y values within the object.

MusiGenesis
+1  A: 

I would suggest trying an "reverse" distance transform. In the magical world of mathematical morphology (sorry couldn't resist the alliteration) the distance transform gives you the closest distance of each pixel to its nearest boundary pixel. In your case, you are interested in the farthest distance to a boundary pixel, hence I have cleverly applied a "reverse" prefix.

You can find information on the distance transform here and here. I believe that matlab implements the distance transform as per here. That would lead me to believe that you can find an open source implementation of the distance transform in octave. Furthermore, it would not surprise me in the least if opencv implemented it.

I haven't given it much thought but its intuitive to me that you should be able to reverse the distance transform and calculate it in roughly the same amount of time as the original distance transform.

ldog
+1  A: 

I think you could consider using a breadth first search algorithm.

The basic idea is that you loop over each row and column in the image, and if you haven't visited the node (a node is a row and column with a colored pixel) yet, then you would run the breadth first search. You would visit each node you possibly could, and keep track of the max and min points for the object.

Here's some C++ sample code (untested):

#include <vector>
#include <queue>
#include <cmath>
using namespace std;

// used to transition from given row, col to each of the
// 8 different directions
int dr[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
int dc[] = { -1, -1, -1, 0, 0, 1, 1, 1 };

// WHITE or COLORED cells
const int WHITE = 0;
const int COLORED = 1;

// number of rows and columns
int nrows = 2000;
int ncols = 2000;

// assume G is the image
int G[2000][2000];

// the "visited array"
bool vis[2000][2000];

// get distance between 2 points
inline double getdist(double x1, double y1, double x2, double y2) {
  double d1 = x1 - x2;
  double d2 = y1 - y2;
  return sqrt(d1*d1+d2*d2);
}

// this function performs the breadth first search
double bfs(int startRow, int startCol) {
  queue< int > q;

  q.push(startRow);
  q.push(startCol);

  vector< pair< int, int > > points;

  while(!q.empty()) {
    int r = q.front();
    q.pop();
    int c = q.front();
    q.pop();

    // already visited?
    if (vis[r][c])
      continue;


    points.push_back(make_pair(r,c));     

    vis[r][c] = true;

    // try all eight directions
    for(int i = 0; i < 8; ++i) {
      int nr = r + dr[i];
      int nc = c + dc[i];

      if (nr < 0 || nr >= nrows || nc < 0 || nc >= ncols)
        continue; // out of bounds

      // push next node on queue  
      q.push(nr);
      q.push(nc);

    }    
  }

  // the distance is maximum difference between any 2 points encountered in the BFS
  double diff = 0;
  for(int i = 0; i < (int)points.size(); ++i) {
    for(int j = i+1; j < (int)points.size(); ++j) {
      diff = max(diff,getdist(points[i].first,points[i].second,points[j].first,points[j].second));
    }
  }
  return diff;
}

int main() {

  vector< double > lengths;

  memset(vis,false,sizeof vis);  
  for(int r = 0; r < nrows; ++r) {
    for(int c = 0; c < ncols; ++c) {
      if (G[r][c] == WHITE)
        continue; // we don't care about cells without objects
      if (vis[r][c])
        continue; // we've already processed this object

      // find the length of this object
      double len = bfs(r,c);
      lengths.push_back(len);
    }
  }

  return 0;
}
dcp
Touching every pixel of a seed *sounds* inefficient if you only want to find two points on the boundary.
nikie
the list of includes is almost longer than the code itself! where do you use complex numbers?!
ldog
@gmatt - Sorry, this was from my programming contest template, I always have a habit of leaving them in there but I agree for this forum it's probably not a good idea. Thanks for pointing it out.
dcp
@nikie - Sometimes simple is best. If the image isn't too big, which in the example the OP provided above it's not, then I think the above algorithm is probably efficient enough. If we're talking a billion pixels, then I agree that BFS is probably not going to cut it.
dcp
On second thought, I don't think this would even work: you can't judge if a point is the "min" point without knowing the correspoinding "max" point. I've tried to understand the C++ code, but I'm pretty sure this can't work either: what do you think `max<pair<int,int> >` is doing?
nikie
@nikie - Yes, good point. I modified my code to keep track of the points encountered in the BFS, and added a loop at the end to find the max distance between any 2 points. Again, this probably isn't as efficient as other methods, but I think it's simple. :). Thanks for pointing out my mistake.
dcp
+3  A: 

I think the problem is simple if the boundary of an object is convex and no three vertices are on a line (i.e. no vertex can be removed without changing the polygon): Then you can just pick two points at random and use a simple gradient-descent type search to find the longest line:

Start with random vertices A, B
See if the line A' - B is longer than A - B where A' is the point left of A; if so, replace A with A'
See if the line A' - B is longer than A - B where A' is the point right of A; if so, replace A with A'
Do the same for B
repeat until convergence

So I'd suggest finding the convex hull for each seed blob, removing all "superfluos" vertices (to ensure convergence) and running the algorithm above.

Constructing a convex hull is an O(n log n) operation IIRC, where n is the number of boundary pixels. Should be pretty efficient for small objects like these. EDIT: I just remembered that the O(n log n) for the convex hull algorithm was needed to sort the points. If the boundary points are the result of a connected component analysis, they are already sorted. So the whole algorithm should run in O(n) time, where n is the number of boundary points. (It's a lot of work, though, because you might have to write your own convex-hull algorithm or modify one to skip the sort.)

Add: Response to comment

If you don't need 100% accuracy, you could simply fit an ellipse to each blob and calculate the length of the major axis: This can be computed from central moments (IIRC it's simply the square root if the largest eigenvalue of the covariance matrix), so it's an O(n) operation and can efficiently be calculated in a single sweep over the image. It has the additional advantage that it takes all pixels of a blob into account, not just two extremal points, i.e. it is far less affected by noise.

nikie
ahh yes compute connected components and the convex hull of each connected component will give you a good approximation provided the shapes are somewhat complex.
ldog
@gmatt: This isn't an approximation. I'm pretty sure the extremal points he's looking for are always on the convex hull. Using the convex hull doesn't add any new points, it only removes points that can't be solutions anyway.
nikie
actually, I think a simple edge detection may work better to get the outline rather than full out convex hull on each seperate blob/shape. Also I don't think gradient descent is the best way to do this, far too resource consuming.
ldog
@gmatt: This "gradient descent" algorithm has a worst-case performance of N distance calculations where N is the number of border points. I doubt you can get much faster. The convex hull is the bottleneck, but it's needed to ensure convergence. If you see any way to improve the algorithm, please post it!
nikie
Could anyone explain the downvote?
nikie
+2  A: 

Find the major-axis length of the ellipse that has the same normalized second central moments as the region. In MATLAB you can use regionprops.

Jacob