OpenCV欧几里得聚类与findContours的比较

8
我有以下图像掩模:

mask

我想应用类似于cv::findContours的算法,但该算法仅将同一组中相连的点连接在一起。 我想通过某些容差来实现这一点,即我想在给定半径容差内添加彼此附近的像素:这类似于欧几里得距离分层聚类。 OpenCV中是否已经实现了这个功能? 或者是否有快速的方法来实现这一点? 我想要的是类似于这个网站所示的东西:

http://www.pointclouds.org/documentation/tutorials/cluster_extraction.php

应用于此掩模的白色像素。 谢谢。

你想让算法做什么并不清楚。你能展示另一张带有期望结果的图片吗?在某种程度上,形态学运算符似乎可以提供你所需的一切,所以我确定这不是问题所在。我们需要看到你试图实现的效果。 - Roger Rowland
@RogerRowland,形态学运算符不是我的选择,因为它们会扭曲我的边缘。我想要的是通过它们之间的欧几里得距离将掩模图像中的边缘分组。类似于http://www.pointclouds.org/documentation/tutorials/cluster_extraction.php的东西。 - manatttta
1
我认为@Humam的建议是不错的,尽管没有OpenCV实现。 在OpenCV中进行聚类任务时,您不会得到比k-means或mean shift更多的东西。 但是,既然您已经链接了一个示例算法,将其移植到OpenCV可能更简单(并且很可能您不需要3D)。 - Roger Rowland
1
@RogerRowland cv::partition非常适合这项任务。 - Miki
1
@Miki 很好的东西,这对我来说是新的,+1 为你的评论和答案。 - Roger Rowland
2个回答

12

你可以使用partition来实现此功能:

partition将一个元素集合分割成等价类。您可以将等价类定义为给定欧几里得距离(半径公差)内的所有点。

如果你使用C++11,你可以简单地使用lambda函数:

int th_distance = 18; // radius tolerance

int th2 = th_distance * th_distance; // squared radius tolerance
vector<int> labels;

int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) {
    return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2; 
});

否则,您可以构建一个函数对象(在下面的代码中有详细说明)。

使用适当的半径距离(我发现18对这张图效果不错),我得到了:

enter image description here

完整代码:

#include <opencv2\opencv.hpp>
#include <vector>
#include <algorithm>

using namespace std;
using namespace cv;

struct EuclideanDistanceFunctor
{
    int _dist2;
    EuclideanDistanceFunctor(int dist) : _dist2(dist*dist) {}

    bool operator()(const Point& lhs, const Point& rhs) const
    {
        return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < _dist2;
    }
};

int main()
{
    // Load the image (grayscale)
    Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE);

    // Get all non black points
    vector<Point> pts;
    findNonZero(img, pts);

    // Define the radius tolerance
    int th_distance = 18; // radius tolerance

    // Apply partition 
    // All pixels within the radius tolerance distance will belong to the same class (same label)
    vector<int> labels;

    // With functor
    //int n_labels = partition(pts, labels, EuclideanDistanceFunctor(th_distance));

    // With lambda function (require C++11)
    int th2 = th_distance * th_distance;
    int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) {
        return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2;
    });

    // You can save all points in the same class in a vector (one for each class), just like findContours
    vector<vector<Point>> contours(n_labels);
    for (int i = 0; i < pts.size(); ++i)
    {
        contours[labels[i]].push_back(pts[i]);
    }

    // Draw results

    // Build a vector of random color, one for each class (label)
    vector<Vec3b> colors;
    for (int i = 0; i < n_labels; ++i)
    {
        colors.push_back(Vec3b(rand() & 255, rand() & 255, rand() & 255));
    }

    // Draw the labels
    Mat3b lbl(img.rows, img.cols, Vec3b(0, 0, 0));
    for (int i = 0; i < pts.size(); ++i)
    {
        lbl(pts[i]) = colors[labels[i]];
    }

    imshow("Labels", lbl);
    waitKey();

    return 0;
}

+1 对于OpenCV方法。-1 对于非密度基础的方法。距离阈值将是一个噩梦。如果您能分享一种通用的方式来调整此问题中的阈值,我会很高兴听取您的想法;) - Humam Helfawi
@HumamHelfawi 好的,谢谢你的+1 ;D。关于-1,让我回答一下:1)DBSCAN(至少在原始公式中)需要两个参数,其中一个恰好是距离阈值(如此处)。2)问题明确要求在半径容差内进行聚类,而不是基于密度的方法。3)问题没有提到要对异常值具有鲁棒性。所以这就是为什么我提出了这个解决方案的原因。 - Miki
@HumamHelfawi 关于调整阈值,这实际上取决于 OP 的最终目标。他可能需要将属于同一圆的所有点放在同一类中。在这种类型的图像(基本上没有噪声)中,可以使用一些随机 Hough 变换的变体轻松完成此操作(但仍然需要设置一些参数)。让我再想一会儿其他的方法:D - Miki
@Miki 我并没有说我的答案很好。我只是批评了你的答案:D,开个玩笑.. DBSCAN需要距离参数,这是真的。然而,DBSCAN的工作方式是距离阈值表示点与类内任何其他点之间的最大有效距离,因此一个静态阈值应该适用于所有示例。但由于OP直接声明了容差关键字,所以我撤回了我的-1并保持+1。如果我有时间,我可能会尝试DBSCAN并在这里发布结果...祝你有愉快的一天 :) - Humam Helfawi
@HumamHelfawi 当然没问题 :D 我非常欣赏建设性的批评。我真的很好奇DBSCAN的结果,请在发布时通知我。祝你有愉快的一天!(可惜对我来说是一个工作夜晚) - Miki
显示剩余4条评论

2
我建议使用 DBSCAN 算法。它正是你正在寻找的。使用简单的欧几里得距离或曼哈顿距离可能效果更好。输入是所有白点(经过阈值处理)。输出是一组点(您的连通分量)。
这里有一个DBSCAN C++实现
编辑:我自己尝试了 DBSCAN,这是结果:enter image description here 如您所见,只有真正相连的点被视为一个簇。
此结果是使用标准的 DBSCAN 算法获得的,其中 EPS=3(静态不需要调整),MinPoints=1(也是静态的)和 曼哈顿距离

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