【问题标题】:clustering image segments in opencv在opencv中聚类图像片段
【发布时间】:2014-07-13 14:56:42
【问题描述】:

我正在使用 opencv 使用非静态相机进行运动检测。 我正在使用一种非常基本的背景减法和阈值处理方法来广泛了解示例视频中正在移动的所有内容。阈值化后,我征集了所有可分离的白色像素“补丁”,将它们存储为独立的组件,并用红色、绿色或蓝色随机着色。下图显示了所有这些组件都可见的足球视频。

我在这些检测到的组件上创建了矩形,我得到了这个图像:

所以我可以看到这里的挑战。我想将所有“相似”和附近的组件聚集到一个实体中,以便输出图像中的矩形显示玩家作为一个整体移动(而不是他的独立肢体)。我尝试进行 K-means 聚类,但由于理想情况下我不知道移动实体的数量,所以我无法取得任何进展。

请指导我如何做到这一点。谢谢

【问题讨论】:

  • 您可以通过计算分割图像的下采样版本中的 blob 来估计聚类的 k。均值偏移可能是另一种方法。
  • @RogerRowland 将图像下采样到更少的位数有什么帮助?
  • 下采样会有所帮助,因为上图中的小斑点会合并成更大的单个斑点 - 所以你得到的东西更像是每个足球运动员(或其他移动物体)一个斑点。然后计算 blob 以粗略估计 k 进行聚类,然后在全尺寸图像上进行聚类。

标签: c++ c opencv image-processing cluster-analysis


【解决方案1】:

我想你可以通过使用形态变换来改进你最初的尝试。看看http://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html#gsc.tab=0。之后,您可能可以为每个实体处理一个封闭集,特别是在原始图像中获得单独的玩家。

【讨论】:

    【解决方案2】:

    dbscan 聚类算法几乎可以完美解决这个问题。下面,我提供实现和结果图像。根据 dbscan,灰色 blob 表示异常值或噪声。我只是使用框作为输入数据。最初,盒子中心用于距离函数。然而对于盒子来说,正确地描述距离是不够的。因此,当前距离函数使用两个框的所有 8 个角的最小距离。

    #include "opencv2/opencv.hpp"
    using namespace cv;
    #include <map>
    #include <sstream>
    
    template <class T>
    inline std::string to_string (const T& t)
    {
        std::stringstream ss;
        ss << t;
        return ss.str();
    }
    
    class DbScan
    {
    public:
        std::map<int, int> labels;
        vector<Rect>& data;
        int C;
        double eps;
        int mnpts;
        double* dp;
        //memoization table in case of complex dist functions
    #define DP(i,j) dp[(data.size()*i)+j]
        DbScan(vector<Rect>& _data,double _eps,int _mnpts):data(_data)
        {
            C=-1;
            for(int i=0;i<data.size();i++)
            {
                labels[i]=-99;
            }
            eps=_eps;
            mnpts=_mnpts;
        }
        void run()
        {
            dp = new double[data.size()*data.size()];
            for(int i=0;i<data.size();i++)
            {
                for(int j=0;j<data.size();j++)
                {
                    if(i==j)
                        DP(i,j)=0;
                    else
                        DP(i,j)=-1;
                }
            }
            for(int i=0;i<data.size();i++)
            {
                if(!isVisited(i))
                {
                    vector<int> neighbours = regionQuery(i);
                    if(neighbours.size()<mnpts)
                    {
                        labels[i]=-1;//noise
                    }else
                    {
                        C++;
                        expandCluster(i,neighbours);
                    }
                }
            }
            delete [] dp;
        }
        void expandCluster(int p,vector<int> neighbours)
        {
            labels[p]=C;
            for(int i=0;i<neighbours.size();i++)
            {
                if(!isVisited(neighbours[i]))
                {
                    labels[neighbours[i]]=C;
                    vector<int> neighbours_p = regionQuery(neighbours[i]);
                    if (neighbours_p.size() >= mnpts)
                    {
                        expandCluster(neighbours[i],neighbours_p);
                    }
                }
            }
        }
    
        bool isVisited(int i)
        {
            return labels[i]!=-99;
        }
    
        vector<int> regionQuery(int p)
        {
            vector<int> res;
            for(int i=0;i<data.size();i++)
            {
                if(distanceFunc(p,i)<=eps)
                {
                    res.push_back(i);
                }
            }
            return res;
        }
    
        double dist2d(Point2d a,Point2d b)
        {
            return sqrt(pow(a.x-b.x,2) + pow(a.y-b.y,2));
        }
    
        double distanceFunc(int ai,int bi)
        {
            if(DP(ai,bi)!=-1)
                return DP(ai,bi);
            Rect a = data[ai];
            Rect b = data[bi];
            /*
            Point2d cena= Point2d(a.x+a.width/2,
                                  a.y+a.height/2);
            Point2d cenb = Point2d(b.x+b.width/2,
                                  b.y+b.height/2);
            double dist = sqrt(pow(cena.x-cenb.x,2) + pow(cena.y-cenb.y,2));
            DP(ai,bi)=dist;
            DP(bi,ai)=dist;*/
            Point2d tla =Point2d(a.x,a.y);
            Point2d tra =Point2d(a.x+a.width,a.y);
            Point2d bla =Point2d(a.x,a.y+a.height);
            Point2d bra =Point2d(a.x+a.width,a.y+a.height);
    
            Point2d tlb =Point2d(b.x,b.y);
            Point2d trb =Point2d(b.x+b.width,b.y);
            Point2d blb =Point2d(b.x,b.y+b.height);
            Point2d brb =Point2d(b.x+b.width,b.y+b.height);
    
            double minDist = 9999999;
    
            minDist = min(minDist,dist2d(tla,tlb));
            minDist = min(minDist,dist2d(tla,trb));
            minDist = min(minDist,dist2d(tla,blb));
            minDist = min(minDist,dist2d(tla,brb));
    
            minDist = min(minDist,dist2d(tra,tlb));
            minDist = min(minDist,dist2d(tra,trb));
            minDist = min(minDist,dist2d(tra,blb));
            minDist = min(minDist,dist2d(tra,brb));
    
            minDist = min(minDist,dist2d(bla,tlb));
            minDist = min(minDist,dist2d(bla,trb));
            minDist = min(minDist,dist2d(bla,blb));
            minDist = min(minDist,dist2d(bla,brb));
    
            minDist = min(minDist,dist2d(bra,tlb));
            minDist = min(minDist,dist2d(bra,trb));
            minDist = min(minDist,dist2d(bra,blb));
            minDist = min(minDist,dist2d(bra,brb));
            DP(ai,bi)=minDist;
            DP(bi,ai)=minDist;
            return DP(ai,bi);
        }
    
        vector<vector<Rect> > getGroups()
        {
            vector<vector<Rect> > ret;
            for(int i=0;i<=C;i++)
            {
                ret.push_back(vector<Rect>());
                for(int j=0;j<data.size();j++)
                {
                    if(labels[j]==i)
                    {
                        ret[ret.size()-1].push_back(data[j]);
                    }
                }
            }
            return ret;
        }
    };
    
    cv::Scalar HSVtoRGBcvScalar(int H, int S, int V) {
    
        int bH = H; // H component
        int bS = S; // S component
        int bV = V; // V component
        double fH, fS, fV;
        double fR, fG, fB;
        const double double_TO_BYTE = 255.0f;
        const double BYTE_TO_double = 1.0f / double_TO_BYTE;
    
        // Convert from 8-bit integers to doubles
        fH = (double)bH * BYTE_TO_double;
        fS = (double)bS * BYTE_TO_double;
        fV = (double)bV * BYTE_TO_double;
    
        // Convert from HSV to RGB, using double ranges 0.0 to 1.0
        int iI;
        double fI, fF, p, q, t;
    
        if( bS == 0 ) {
            // achromatic (grey)
            fR = fG = fB = fV;
        }
        else {
            // If Hue == 1.0, then wrap it around the circle to 0.0
            if (fH>= 1.0f)
                fH = 0.0f;
    
            fH *= 6.0; // sector 0 to 5
            fI = floor( fH ); // integer part of h (0,1,2,3,4,5 or 6)
            iI = (int) fH; // " " " "
            fF = fH - fI; // factorial part of h (0 to 1)
    
            p = fV * ( 1.0f - fS );
            q = fV * ( 1.0f - fS * fF );
            t = fV * ( 1.0f - fS * ( 1.0f - fF ) );
    
            switch( iI ) {
            case 0:
                fR = fV;
                fG = t;
                fB = p;
                break;
            case 1:
                fR = q;
                fG = fV;
                fB = p;
                break;
            case 2:
                fR = p;
                fG = fV;
                fB = t;
                break;
            case 3:
                fR = p;
                fG = q;
                fB = fV;
                break;
            case 4:
                fR = t;
                fG = p;
                fB = fV;
                break;
            default: // case 5 (or 6):
                fR = fV;
                fG = p;
                fB = q;
                break;
            }
        }
    
        // Convert from doubles to 8-bit integers
        int bR = (int)(fR * double_TO_BYTE);
        int bG = (int)(fG * double_TO_BYTE);
        int bB = (int)(fB * double_TO_BYTE);
    
        // Clip the values to make sure it fits within the 8bits.
        if (bR > 255)
            bR = 255;
        if (bR < 0)
            bR = 0;
        if (bG >255)
            bG = 255;
        if (bG < 0)
            bG = 0;
        if (bB > 255)
            bB = 255;
        if (bB < 0)
            bB = 0;
    
        // Set the RGB cvScalar with G B R, you can use this values as you want too..
        return cv::Scalar(bB,bG,bR); // R component
    }
    
    int main(int argc,char** argv )
    {
        Mat im = imread("c:/data/football.png",0);
        std::vector<std::vector<cv::Point> > contours;
        std::vector<cv::Vec4i> hierarchy;
        findContours(im.clone(), contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
    
        vector<Rect> boxes;
        for(size_t i = 0; i < contours.size(); i++)
        {
            Rect r = boundingRect(contours[i]);
            boxes.push_back(r);
        }
        DbScan dbscan(boxes,20,2);
        dbscan.run();
        //done, perform display
    
        Mat grouped = Mat::zeros(im.size(),CV_8UC3);
        vector<Scalar> colors;
        RNG rng(3);
        for(int i=0;i<=dbscan.C;i++)
        {
            colors.push_back(HSVtoRGBcvScalar(rng(255),255,255));
        }
        for(int i=0;i<dbscan.data.size();i++)
        {
            Scalar color;
            if(dbscan.labels[i]==-1)
            {
                color=Scalar(128,128,128);
            }else
            {
                int label=dbscan.labels[i];
                color=colors[label];
            }
            putText(grouped,to_string(dbscan.labels[i]),dbscan.data[i].tl(),    FONT_HERSHEY_COMPLEX,.5,color,1);
            drawContours(grouped,contours,i,color,-1);
        }
    
        imshow("grouped",grouped);
        imwrite("c:/data/grouped.jpg",grouped);
        waitKey(0);
    }
    

    【讨论】:

      【解决方案3】:

      我同意 Sebastian Schmitz 的观点:您可能不应该寻找集群。

      不要指望诸如 k-means 之类的不知情方法会为您工作。特别是像 k-means 一样粗略的启发式算法,它生活在一个理想化的数学世界中,而不是杂乱无章的真实数据中。

      您对自己想要的有很好的了解。尝试将这种直觉放入代码中。就您而言,您似乎正在寻找连接的组件。

      考虑将图像降采样到较低的分辨率,然后重新运行相同的过程!或者立即以较低的分辨率运行它(以减少压缩伪影并提高性能)。或添加过滤器,例如模糊。

      我希望通过查看下采样/过滤图像中的连接组件来获得最佳和最快的结果。

      【讨论】:

      • “粗略的启发式作为 k-means”:在我看来这有点消极。示例:如果您想将图像聚类到 10 个颜色桶中并且进行一些预处理(例如删除异常值),它的效果相当好。不过,我还没有在计算机视觉中看到 k-means。
      • 是的,对于在 RGB 空间等明显线性域中的向量量化,具有有限的异常值(由于有限域),k-means 工作得相当好。一旦你不知道 k,有非线性属性、缺失值、二值值、分类属性、不同尺度的属性等,k-means 往往会产生 as-good-as-random 的结果。
      • @Anony-Mousse 图像中的不同部分在下采样时不会完全融合在一起。所以它没有给出关于blob数量的想法。另外,不知道应该在哪里停止连接节点时,如何生成连接组件?
      • 您是否考虑过定义一个合并阈值,比如 5 个像素,然后使用它进行合并?仍然比 k-means 更有意义。
      【解决方案4】:

      我不完全确定您是否真的在寻找集群(在数据挖掘的意义上)。

      聚类用于根据距离函数对相似的对象进行分组。在您的情况下,距离函数只会使用空间质量。此外,在 k-means 聚类中,您必须指定一个 k,而您可能事先并不知道。

      在我看来,您只是想合并所有边界比某个预定阈值更近的矩形。因此,作为第一个想法,尝试合并所有接触或距离超过半个玩家高度的矩形。

      您可能希望包含一个尺寸检查,以最大程度地减少将两个玩家合并为一个的风险。

      编辑:如果您真的想使用一种聚类算法,请使用一种可以为您估计聚类数量的算法。

      【讨论】:

        猜你喜欢
        • 2011-10-24
        • 2017-08-28
        • 1970-01-01
        • 2016-05-11
        • 1970-01-01
        • 1970-01-01
        • 2014-04-13
        • 2015-03-08
        • 1970-01-01
        相关资源
        最近更新 更多