Неожиданное поведение алгоритма выпуклой оболочки

Я пытался реализовать алгоритм, чтобы получить выпуклую оболочку для заданного набора точек и визуализировать результат с помощью opencv, используя следующий код на С++:

#include "opencv2/opencv.hpp"
#include "StdAfx.h"
#include <vector>
#include <cv.h>
#include <cv.hpp>
#include <cxcore.h>
#include <highgui.h>
#include <iostream>
#include <utility>
#include <math.h>
#include <time.h>  
#include <algorithm>
#include <stack>
using namespace std; 
using namespace cv;

vector<pair<int,int> > testPoints;
pair<int , int> pivot;
stack< pair<int , int> > hull;
const int frameSize=600;
const double PI = 3.141592653589793238462;
vector< pair <int,int> > points;
Mat img=Mat::zeros(frameSize,frameSize, CV_8UC1);

void drawFilledCircle( Mat img, Point center ,int radius)
{
 int thickness = -1;
 int lineType = 8;

 circle( img,
     center,
     radius,
     255,
     thickness,
     lineType );
}

void drawLine( Mat img, Point start, Point end ,int shade,int thickness)
{
  int lineType = 8;
  shade=shade%256;
  line( img,
    start,
    end,
    shade,
    thickness,
    lineType );
}

float getAngle(pair<int,int> p1 , pair<int,int>p2){
     return atan2(static_cast<double>(p1.second-p2.second) , static_cast<double>       (p1.first-p2.first))*180/PI;
}

bool compareFunction(pair<int,int> p1 , pair<int,int> p2){
    return ( getAngle(p1,pivot)>getAngle(p2,pivot) );
}

void printPoints(vector< pair<int,int> > points){
    for(int i=0;i<points.size();i++)
            cout<<"( "<<(points[i].first-frameSize/2)<<" , "<<(points[i].second-    frameSize/2)<<" )  "<<getAngle(points[i],pivot)<<endl;
     cout<<endl;
}

void mouseEvent(int evt, int x, int y, int flags, void* param){
    if(evt==CV_EVENT_LBUTTONDOWN){
        cout<<"Click at : ( "<<(x-frameSize/2)<<" , "<<(y-frameSize/2)<<" ) , Polar Angle: "<<getAngle(make_pair(x,y),pivot)<<endl;
    }
}

void setUpFrame(){
    int n;
    cout<<"Enter number of points: ";
    cin>>n;

    for(int i=0;i<n;i++)
        points.push_back(make_pair((rand()%static_cast<int>(frameSize*0.75)     +frameSize*0.125) , (rand()%static_cast<int>(frameSize*0.75)  +frameSize*0.125)));

    for(int i=0;i<n;i++)
        drawFilledCircle( img, Point(points[i].first, points[i].second) ,2);
    drawLine(img , Point(frameSize/2,0) , Point(frameSize/2,frameSize),100,1);
    drawLine(img , Point(0,frameSize/2) , Point(frameSize,frameSize/2),100,1);

    cout<<"Settingup frame...\nPress enter to continue."<<endl;
}

void setPivot(){
    int max=-2*frameSize , index=0;
    for(int i=0;i<points.size();i++){
        if(points[i].second>max){
            max=points[i].second;
            index=i;
        }
    }
    pivot=points[index];
    points.erase(points.begin()+index);

    drawFilledCircle( img, Point(pivot.first, pivot.second) ,6);
    cout<<"Pivot chosen at: ( "<<(pivot.first-frameSize/2)<<" , "<<(pivot.second-    frameSize/2)<<" )\nPress enter to continue."<<endl;
}

void sortPoints(){
    cout<<"Sorting points on the basis of polar angles."<<endl;
    std::sort(points.begin(),points.end(),compareFunction);
}

void createHull(){
    pair<int,int> previous,next;
    for(int i=0;i<points.size();i++){
        previous= (hull.size()==0) ? pivot : hull.top();
        next= (i==points.size()-1) ? pivot : points[i+1];

        int x1=(previous.first-points[i].first);
        int x2=(points[i].first-next.first);
        int y1=(previous.second-points[i].second);
        int y2=(points[i].second-next.second);

        if( x1*y2-x2*y1 <0 ) 
                hull.push(points[i]);
        drawFilledCircle( img, Point(hull.top().first, hull.top().second) ,4);
    }

    int size=hull.size();
    pair<int,int> pt;

 drawLine(img,Point(pivot.first,pivot.second),Point(hull.top().first,hull.top().second),200,1);
    for(int i=1;i<size;i++){
        pt=hull.top();
        hull.pop();
        drawLine(img,Point(pt.first,pt.second),Point(hull.top().first,hull.top().second),200,1);
    }
    drawLine(img,Point(pivot.first,pivot.second),Point(hull.top().first,hull.top().second),200,    1);
}

int main(int, char**)
{
    srand (time(NULL));
    namedWindow("Output",1);
    cvSetMouseCallback("Output", mouseEvent, 0);

    setUpFrame();
    setPivot();
    sortPoints();
    createHull();

    imshow("Output", img);
    waitKey(0);
    return 0;
}

Я знаю, что для этого есть встроенные библиотеки, но я хотел сам реализовать алгоритм. Кажется, все работает хорошо, когда количество баллов меньше, например, для 30 баллов, но я начинаю получать странную форму по мере увеличения количества баллов (например, показанные ниже для 200 и 1000). Алгоритм, кажется, выбирает дополнительные точки (точки, которые не должны быть включены в корпус) среди тех, которые лежат по диагонали напротив точки поворота (обозначены закрашенным кругом с максимальным радиусом). Может ли кто-нибудь помочь мне понять, где я ошибся, или предложить некоторые изменения, которые я могу внести в код? введите описание изображения  здесь введите здесь описание изображения

введите здесь описание изображения


person user2131678    schedule 22.05.2013    source источник


Ответы (1)


Похоже, вы хотели внедрить сканирование Грэма, но безуспешно.

Сканирование Грэма имеет следующую структуру:

Find the bottom-most point, then sort the points clockwise around it.
Set hull to the first two points.
For each point, say p, from the third onward:
  While you make a left turn from the last edge of hull to the line from hull.back to p:
    popback(hull);
  push(hull, p)

Обратите внимание на разницу в инициализации --- сканирование Грэма начинается с края, а вы начинаете без точек.

Обратите также внимание на разницу внутри цикла: вы использовали критерий левого поворота в качестве своего рода проверки отбраковки, которая включала другую точку из points, в то время как сканирование Грэма использует новую точку для удаления точек, которые больше не являются частью корпуса.

person tmyklebu    schedule 22.05.2013