In the article I will tell you how to quickly list the connected objects on a binary raster. We used this algorithm to recognize images and texts; it differs from similar high processing speed (in pictures up to 3200x2400, with some reservations, it works out in milliseconds) and availability in understanding (if you have some knowledge of C ++). I note that the original image will be interpreted by the algorithm as “read-only” (why spoil something with which other methods can work), and therefore, the algorithm will require a small amount of additional memory. In addition, external contours are a useful object for image analysis and vectorization.
So, the task: convert the original image (array of points Image) into a list of contours. A contour is simply a set of external points for a connected part of the image. For example, the contour for a filled circle is a circle obtained from all external points.
Eight-connectedness is used, that is, two points are connected if they are neighbors with respect to the directions up-left-right-down and along the diagonals. Thus, each point can have up to eight neighbors.class Contour : public Points
{
// references to the constructor params
const Image& SourceImage;
ImageMap& CurrentImageMap;
public :
/* here we assume that image is coherent in any of 8 ways,
* so if the pixel will not have neighbor in any of 4 diagonal and 4 straight directions
* then it will be considered as separate image part */
// extract contour and mark all coherent points on image map
Contour( const Image& img, ImageMap& map, const Point& start);
private :
// private methods for contour pass implementation only
void passDownLeft(Point& p, bool InvertedAxis = false );
Point movePoint( const Point& src, int x, int y, bool InvertedAxis = false );
Point commitPoint( const Point& p);
};
Point Contour::commitPoint( const Point& p)
{
if (!CurrentImageMap.isAssigned(p) && SourceImage.isFilled(p))
{
CurrentImageMap.assignSegment(p, this );
push_back(p);
}
return p;
}
Point Contour::movePoint( const Point& src, int x, int y, bool InvertedAxis)
{
Point p = src;
if (InvertedAxis)
{
x = -x;
y = -y;
}
pX += x;
pY += y;
return p;
}
void Contour::passDownLeft(Point& p, bool InvertedAxis)
{
while (SourceImage.isInside(p))
{
commitPoint(p);
// step one point down
p = movePoint(p, 0,1, InvertedAxis);
// select one of neighbors which is filled, prefer left one...
Point left = movePoint(p, -1,0, InvertedAxis);
if (SourceImage.isFilled(left))
{
p = commitPoint(left);
// ...and shift left as many as possible
while (SourceImage.isInside(p))
{
Point left = movePoint(p, -1,0, InvertedAxis);
if (!SourceImage.isFilled(left))
break ; // no more left neighbors
p = commitPoint(left);
Point up = movePoint(p, 0,-1, InvertedAxis);
if (SourceImage.isFilled(up))
return ; // crossed inside area
}
}
else
{
// selection still unfilled...
while (SourceImage.isInside(p) && !SourceImage.isFilled(p))
{
// ...shift right by connected points and test again
Point right = movePoint(p, 1,0, InvertedAxis);
Point rightUp = movePoint(right, 0,-1, InvertedAxis);
if (!SourceImage.isFilled(rightUp))
return ; // no more bottom right neighbors
commitPoint(rightUp);
p = commitPoint(right);
}
}
}
}
Having started this detour on the test picture (light green dots - starting points for traversing each object), we, as one would expect, will not get contours, since the construction process will rest on the lowest points of each object. Therefore, one such function is not enough. But, you can add a contour by going up and to the right, which is easily obtained by the InvertedAxis flag provided for this case! Thus, the constructor, which was left at last, will complement the contour construction algorithm:ontour::Contour( const Image& img, ImageMap& map, const Point& start)
: SourceImage(img), CurrentImageMap(map)
{
bool doneSomething = true ;
Point p = start;
for ( int iter = 0; doneSomething; iter++)
{
size_t count = size();
passDownLeft(p, iter % 2 == 1);
doneSomething = size() > count;
}
}
void Vectorization::getContours()
{
Point p(0,0); // line-by-line scan to extract contours of objects
for (pY = 0; pY < SourceImage.getHeight(); p.Y++)
{
for (pX = 0; pX < SourceImage.getWidth(); p.X++)
{
if (SourceImage.isFilled(p) && !ImageMap.isAssigned(p))
{
// that pixel is unprocessed yet, so extract the contour starting from it
Contour* c = new Contour(SourceImage, ImageMap, p);
// add new contour
ContoursStorage.push_back( c );
}
}
}
}void Vectorization::getContoursOptimized()
{
Point pIndex(0,0); // step-by-step scan
for (pIndex.Y = 0; pIndex.Y < SourceImage.getHeight(); pIndex.Y += STEP)
{
for (pIndex.X = 0; pIndex.X < SourceImage.getWidth(); pIndex.X += STEP)
{
if (!SourceImage.isEmptyIndex(pIndex))
{
Point pLocal = pIndex;
for (pLocal.Y = pIndex.Y ; pLocal.Y < STEP; pLocal.Y++)
{
for (pLocal.X = pIndex.X ; pLocal.X < STEP; pLocal.X++)
{
if (SourceImage.isFilled(pLocal))
{
if (Map.isAssigned(pLocal))
{
pLocal.X = dynamic_cast<Contour*>(Map.getSegment(pLocal))->getMaxX(pLocal.Y);
break ; /* skip already processed points by getting the max contour X for the specified Y */
}
// that pixel is unprocessed yet, so extract the contour starting from it
Contour* cntr = new Contour(SourceImage, Map, pLocal);
// add new contour
ContoursStorage.push_back( cntr ) ;
}
}
}
}
}
}
}Source: https://habr.com/ru/post/119461/
All Articles