Search code examples
c++opencvimage-processingobject-detection

How to detect white blobs using OpenCV


I paint a picture to test:

enter image description here

And I want to know how much blobs I have in the black circle and what is the size of each blobs (all blobs are ~white).

For example, in this case I have 12 spots:

enter image description here

I know how to found white pixels and it easy to verify sequence from left:

int whitePixels = 0;
for (int i = 0; i < height; ++i)
{
    uchar * pixel = image.ptr<uchar>(i);
    for (int j = 0; j < width; ++j)
    {
        if (j>0 && pixel[j-1]==0)   // to group pixels for one spot
            whitePixels++;
    }
}

but it's clear that this code is not good enough (blobs can be diagonally, etc.).

So, the bottom line: how can I define the blobs?


Solution

  • Following code finds bounding rects (blobs) for all white spots.

    Remark: if we can assume white spots are really white (namely have values 255 in grayscaled image), you can use this snippet. Consider putting it in some class to avoid passing uncecessary params to function Traverse. Although it works. The idea is based on DFS. Apart from the gryscaled image, we have ids matrix to assign and remember which pixel belongs to which blob (all pixels having the same id belong to the same blob).

    void Traverse(int xs, int ys, cv::Mat &ids,cv::Mat &image, int blobID, cv::Point &leftTop, cv::Point &rightBottom) {
        std::stack<cv::Point> S;
        S.push(cv::Point(xs,ys));
    
        while (!S.empty()) {
            cv::Point u = S.top();
            S.pop();
    
            int x = u.x;
            int y = u.y;
    
            if (image.at<unsigned char>(y,x) == 0 || ids.at<unsigned char>(y,x) > 0)
                continue;
    
            ids.at<unsigned char>(y,x) = blobID;
            if (x < leftTop.x)
                leftTop.x = x;
            if (x > rightBottom.x)
                rightBottom.x = x;
            if (y < leftTop.y)
                leftTop.y = y;
            if (y > rightBottom.y)
                rightBottom.y = y;
    
            if (x > 0)
                S.push(cv::Point(x-1,y));
            if (x < ids.cols-1)
                S.push(cv::Point(x+1,y));
            if (y > 0)
                S.push(cv::Point(x,y-1));
            if (y < ids.rows-1)
                S.push(cv::Point(x,y+1));
        }
    
    
    }
    
    int FindBlobs(cv::Mat &image, std::vector<cv::Rect> &out, float minArea) {
        cv::Mat ids = cv::Mat::zeros(image.rows, image.cols,CV_8UC1);
        cv::Mat thresholded;
        cv::cvtColor(image, thresholded, CV_RGB2GRAY);
        const int thresholdLevel = 130;
        cv::threshold(thresholded, thresholded, thresholdLevel, 255, CV_THRESH_BINARY);
        int blobId = 1;
        for (int x = 0;x<ids.cols;x++)
            for (int y=0;y<ids.rows;y++){
                if (thresholded.at<unsigned char>(y,x) > 0 && ids.at<unsigned char>(y,x) == 0) {
                    cv::Point leftTop(ids.cols-1, ids.rows-1), rightBottom(0,0);
                    Traverse(x,y,ids, thresholded,blobId++, leftTop, rightBottom);
                    cv::Rect r(leftTop, rightBottom);
                    if (r.area() > minArea)
                        out.push_back(r);
                }
            }
        return blobId;
    }
    

    EDIT: I fixed a bug, lowered threshold level and now the output is given below. I think it is a good start point.

    Output

    EDIT2: I get rid of recursion in Traverse(). In bigger images recursion caused Stackoverflow.