OpenCV:如何使用kmeans()按角度进行聚类?

6
问题是如何通过它们的角度将一些单位的对聚类起来?问题在于,kmeans 操作基于欧几里得空间距离的概念,并不知道角度的周期性。因此,为了使其工作,需要将角度转换为欧几里得空间,但必须保持以下条件:
  1. 接近的角度在欧几里得空间中也是相近的值;
  2. 远离的角度在欧几里得空间中也是相距较远的。
这意味着,90 和 -90 是远离的值,180 和 -180 相同,170 和 -170 是接近的(角度从左上到右:0-+180,从左下到右:0--180)。
我尝试使用各种 sin() 函数,但它们都存在第 1 和第 2 点中提到的问题。最有前途的一个是 sin(x * 0.5f),但它也存在 180 和 -180 在欧几里得空间中是远离的值的问题。

1
最好的做法是先发布问题,然后再将答案发布为答案。 - rbaleksandar
他们肯定需要添加发布帖子的功能。 - Cynichniy Bandera
1
完全不是。这是一个问答网站。如果问题(以及答案)被认为有价值,它可以成为社区维基的一部分。就像你现在的情况一样,只需考虑我之前评论中描述的方式是正确的。 - rbaleksandar
Umka,做这件事的正确方式是发布一个问题,并写下你自己的答案。然后你可以接受自己的答案,或者如果有更好的答案,你也可以接受其他人的答案。这是你的选择。通过你的答案获得赞同票,你还可以获得声望。 - Miki
顺便说一下,有点啰嗦...但干得好;D - Miki
抱歉,我找不到如何回答自己的问题。它与其他问题不同,没有备忘录字段。 - Cynichniy Bandera
1个回答

0
我找到的解决方法是将角度转换为圆上的点,并将它们输入到kmeans中。这样我们就可以比较点之间的距离,这种方法非常有效。
重要的一点需要提醒大家。在终止准则中,Kmeans中的@eps用样本单位来表示。在我们的例子中,最远的两个点的距离为200个单位(2倍半径)。这意味着使用1.0f完全没有问题。如果在调用kmeans()之前对你的样本进行cv::normalize(samples, samples, 0.0f, 1.0f)规范化,请相应地调整你的@eps。例如eps=0.01f 在这里效果更好。
祝好运!希望能够帮助到某些人。
static cv::Point2f angleToPointOnCircle(float angle, float radius, cv::Point2f origin /* center */)
{
        float x = radius * cosf(angle * M_PI / 180.0f) + origin.x;
        float y = radius * sinf(angle * M_PI / 180.0f) + origin.y;
        return cv::Point2f(x, y);
}

static std::vector<std::pair<size_t, int> > biggestKmeansGroup(const std::vector<int> &labels, int count)
{
        std::vector<std::pair<size_t, int> > indices;
        std::map<int, size_t> l2cm;

        for (int i = 0; i < labels.size(); ++i)
                l2cm[labels[i]]++;

        std::vector<std::pair<size_t, int> > c2lm;
        for (std::map<int, size_t>::iterator it = l2cm.begin(); it != l2cm.end(); it++)
                c2lm.push_back(std::make_pair(it->second, it->first)); // count, group

        std::sort(c2lm.begin(), c2lm.end(), cmp_pair_first_reverse);
        for (int i = 0; i < c2lm.size() && count-- > 0; i++)
                indices.push_back(c2lm[i]);
        return indices;
}

static void sortByAngle(std::vector<boost::shared_ptr<Pair> > &group,
                        std::vector<boost::shared_ptr<Pair> > &result)
{
        std::vector<int> labels;
        cv::Mat samples;

        /* Radius is not so important here. */
        for (int i = 0; i < group.size(); i++)
                samples.push_back(angleToPointOnCircle(group[i]->angle, 100, cv::Point2f(0, 0)));

        /* 90 degrees per group. May be less if you need it. */
        static int PAIR_MAX_FINE_GROUPS = 4;

        int groupNr = std::max(std::min((int)group.size(), PAIR_MAX_FINE_GROUPS), 1);
        assert(group.size() >= groupNr);
        cv::kmeans(samples.reshape(1, (int)group.size()), groupNr, labels,
                   cvTermCriteria(CV_TERMCRIT_EPS/* | CV_TERMCRIT_ITER*/, 30, 1.0f),
                   100, cv::KMEANS_RANDOM_CENTERS);

        std::vector<std::pair<size_t, int> > biggest = biggestKmeansGroup(labels, groupNr);
        for (int g = 0; g < biggest.size(); g++) {
                for (int i = 0; i < group.size(); i++) {
                        if (labels[i] == biggest[g].second)
                                result.push_back(group[i]);
                }
        }
}

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