如何使用OpenCV检测白色斑点

3
我画了一幅测试图片: enter image description here 我想知道黑色圆圈中有多少个斑点以及每个斑点的大小(所有斑点都是白色)。
例如,在这种情况下,我有12个斑点: enter image description here 我知道如何找到白色像素,并且很容易从左边验证顺序:
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++;
    }
}

但很明显,这段代码不够好(blob可能会是对角线等形状)。

因此,最重要的是,我需要帮助:如何定义blob?

谢谢


1
我真的不会那样做,看一下http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#inrange - Marco A.
@DavidKernin:非常感谢!我会深入研究的。 - AsfK
尝试在谷歌上搜索“Blob Analysis”或访问http://en.wikipedia.org/wiki/Blob_detection。 - Mark Setchell
1个回答

5
以下代码可以找到所有白色斑点的边界矩形(blob)。
备注:如果我们可以假设白色斑点确实是白色的(即在灰度图像中具有值255),则可以使用此代码片段。考虑将其放入某个类中,以避免向Traverse函数传递不必要的参数。尽管它可以工作。这个想法基于DFS。除了灰度图像外,我们还有一个ids矩阵,用于分配和记住哪个像素属于哪个blob(所有具有相同id的像素属于同一个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;
}
编辑: 我修复了一个错误,降低了阈值水平,现在输出如下。我认为这是一个很好的起点。

输出

编辑2:Traverse() 中,我消除了递归。在较大的图像中,递归会导致堆栈溢出。

我检查了你的代码。这个想法很好,但是我收到的答案(计数器)是163644... 可能问题在条件语句上: if (r.area() > minArea),需要验证我们没有重复计算同一个blob。 - AsfK
我修复了这个 bug。如果你将阈值设置得更低,那么轮廓在阈值过滤器中会被过滤为白色像素,但我认为这是一个很好的起点。 - marol
我从Traverse()中移除了递归,请享用。 - marol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接