Поиск отверстий в 2d точечных множествах?

У меня есть набор 2d points. Они X,Y coordinates на стандартной декартовой сетке (в этом случае a UTM zone). Мне нужно найти отверстия в этой точке, установленные предпочтительно с некоторой способностью задавать чувствительность алгоритма, который находит эти отверстия. Обычно эти наборы точек очень плотные, но некоторые из них могут быть гораздо менее плотными.

Какими они являются, если это помогает, являются точками, на которых была выбрана почва в поле для различных свойств, которые люди в сельском хозяйстве, по-видимому, находят полезными. Иногда в этих точечных образцах есть гигантские скалы или болотистые места или полные на маленьких озерах и прудах.

Из набора точек они хотят, чтобы я нашел вогнутый многоугольник, который примерно определяет эти отверстия.

Я уже написал алгоритм, который находит внешний вогнутый граничный многоугольник и позволяет установить чувствительность к тому, насколько грубым или гладким является многоугольник, который формируется алгоритмом. После этого они хотели бы найти дыры и иметь эти отверстия, данные им как вогнутый многоугольник, который, как я думаю, в некоторых случаях может быть выпуклым, но это будет краевой случай, а не норма.

Проблема в том, что единственными документами, которые я когда-либо слышал по этому вопросу, являются те, которые сделаны астрономами, которые хотят найти большие карманы пустоты в космосе, и я никогда не мог найти одну из своих работ с фактическим алгоритмом, показанным в них не что иное, как грубое концептуальное описание.

Я пробовал Google и различные научные поиски и т.д., но я не нашел много полезного до сих пор. Это заставляет меня задаться вопросом, может быть, есть имя для такого рода проблем, и я не знаю, поэтому я искал неправильную вещь или что-то еще?

Итак, после этого длинного объяснения, мой вопрос: кто-нибудь знает, что я должен искать, чтобы найти документы по этому предпочтительно с четко определенными алгоритмами или кто-нибудь знает алгоритм, который сделает это, чтобы они могли указать мне?

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

Язык, в котором он будет реализован, будет С#, но примеры из чего-либо из пакетов Mathematica в MATLAB or ASM, C, C++, Python, Java or MathCAD и т.д. были бы хороши, пока в примере не было некоторых вызовов, которые бы переходили к вещам типа FindTheHole и т.д. Где FindTheHole не определено или является собственностью внедряющего программного обеспечения, например MathCAD примеры обычно имеют много.

Ниже приведены два примера реальных наборов точек, один плотный и один разреженный, а также области, в которых мне нужно найти: Sparse points set exampleDense points set example

Ответ 1

как насчет некоторого растрового изображения + вектор:

  • получить ограничивающий прямоугольник с областью области точечных облаков

    Сделайте это, если оно еще не известно. Это должно быть простым циклом O(N) через все точки.

  • создать map[N][N] области

    Это "растровое изображение" области для удобного вычисления плотности данных. Просто создайте проекцию из area(x,y) -> map[i][j], например, с помощью простого масштабирования. Размер сетки N также является точностью для вывода, а должен быть больше среднего расстояния точки., поэтому каждая ячейка внутри map[][] охватывает область, по крайней мере, с одной точкой (если не в области отверстия).

  • вычислить плотность данных для каждой ячейки map[][]

    Просто как pie просто очистите map[][].cnt (счетчик точек) до zero и вычислите простым циклом O(N), где do map[i][j].cnt++ для всех points(x,y)

  • создать список неиспользуемой области (map[][].cnt==0) или (map[][].cnt<=treshold)

    Я делаю это по горизонтальной и вертикальной линиям для простоты

  • сегментный вывод

    Только групповые линии одного и того же отверстия вместе (пересекающиеся... векторный подход), а также могут выполняться в пуле # 4 путем заливки заливки (растровый подход)

  • вывод многоугольника

    Возьмите все граничные точки H, V строк того же самого отверстия/группы и создайте многоугольник (сортируйте их, чтобы их соединение не пересекалось ни с чем). Существует множество библиотек, алгоритмов и исходного кода.

Мой исходный код для этого подхода:

void main_compute(int N)
    {
    // cell storage for density computation
    struct _cell
        {
        double x0,x1,y0,y1; // bounding area of points inside cell
        int cnt;            // points inside cell
        _cell(){}; _cell(_cell& a){ *this=a; }; ~_cell(){}; _cell* operator = (const _cell *a) { *this=*a; return this; }; /*_cell* operator = (const _cell &a) { ...copy... return this; };*/
        };
    // line storage for hole area
    struct _line
        {
        double x0,y0,x1,y1; // line edge points
        int id;             // id of hole for segmentation/polygonize
        int i0,i1,j0,j1;    // index in map[][]
        _line(){}; _line(_line& a){ *this=a; }; ~_line(){}; _line* operator = (const _line *a) { *this=*a; return this; }; /*_line* operator = (const _line &a) { ...copy... return this; };*/
        };

    int i,j,k,M=N*N;        // M = max N^2 but usualy is much much less so dynamic list will be better
    double mx,my;           // scale to map
    _cell *m;               // cell ptr
    glview2D::_pnt *p;      // point ptr
    double x0,x1,y0,y1;     // used area (bounding box)
    _cell **map=NULL;       // cell grid
    _line *lin=NULL;        // temp line list for hole segmentation
    int lins=0;             // actual usage/size of lin[M]

    // scan point cloud for bounding box (if it is known then skip it)
    p=&view.pnt[0];
    x0=p->p[0]; x1=x0;
    y0=p->p[1]; y1=y0;
    for (i=0;i<view.pnt.num;i++)
        {
        p=&view.pnt[i];
        if (x0>p->p[0]) x0=p->p[0];
        if (x1<p->p[0]) x1=p->p[0];
        if (y0>p->p[1]) y0=p->p[1];
        if (y1<p->p[1]) y1=p->p[1];
        }
    // compute scale for coordinate to map index conversion
    mx=double(N)/(x1-x0);   // add avoidance of division by zero if empty point cloud !!!
    my=double(N)/(y1-y0);
    // dynamic allocation of map[N][N],lin[M]
    lin=new _line[M];
    map=new _cell*[N];
    for (i=0;i<N;i++) map[i]=new _cell[N];
    // reset map[N][N]
    for (i=0;i<N;i++)
     for (j=0;j<N;j++)
      map[i][j].cnt=0;
    // compute point cloud density
    for (k=0;k<view.pnt.num;k++)
        {
        p=&view.pnt[k];
        i=double((p->p[0]-x0)*mx); if (i<0) i=0; if (i>=N) i=N-1;
        j=double((p->p[1]-y0)*my); if (j<0) j=0; if (j>=N) j=N-1;
        m=&map[i][j];
        if (!m->cnt)
            {
            m->x0=p->p[0];
            m->x1=p->p[0];
            m->y0=p->p[1];
            m->y1=p->p[1];
            }
        if (m->cnt<0x7FFFFFFF) m->cnt++;    // avoid overflow
        if (m->x0>p->p[0]) m->x0=p->p[0];
        if (m->x1<p->p[0]) m->x1=p->p[0];
        if (m->y0>p->p[1]) m->y0=p->p[1];
        if (m->y1<p->p[1]) m->y1=p->p[1];
        }
    // find holes (map[i][j].cnt==0) or (map[i][j].cnt<=treshold)
    // and create lin[] list of H,V lines covering holes
    for (j=0;j<N;j++) // search lines
        {
        for (i=0;i<N;)
            {
            int i0,i1;
            for (;i<N;i++) if (map[i][j].cnt==0) break; i0=i-1; // find start of hole
            for (;i<N;i++) if (map[i][j].cnt!=0) break; i1=i;   // find end of hole
            if (i0< 0) continue;                // skip bad circumstances (edges or no hole found)
            if (i1>=N) continue;
            if (map[i0][j].cnt==0) continue;
            if (map[i1][j].cnt==0) continue;
            _line l;
            l.i0=i0; l.x0=map[i0][j].x1;
            l.i1=i1; l.x1=map[i1][j].x0;
            l.j0=j ; l.y0=0.25*(map[i0][j].y0+map[i0][j].y1+map[i1][j].y0+map[i1][j].y1);
            l.j1=j ; l.y1=l.y0;
            lin[lins]=l; lins++;
            }
        }
    for (i=0;i<N;i++) // search columns
        {
        for (j=0;j<N;)
            {
            int j0,j1;
            for (;j<N;j++) if (map[i][j].cnt==0) break; j0=j-1; // find start of hole
            for (;j<N;j++) if (map[i][j].cnt!=0) break; j1=j;   // find end of hole
            if (j0< 0) continue;                // skip bad circumstances (edges or no hole found)
            if (j1>=N) continue;
            if (map[i][j0].cnt==0) continue;
            if (map[i][j1].cnt==0) continue;
            _line l;
            l.i0=i ; l.y0=map[i][j0].y1;
            l.i1=i ; l.y1=map[i][j1].y0;
            l.j0=j0; l.x0=0.25*(map[i][j0].x0+map[i][j0].x1+map[i][j1].x0+map[i][j1].x1);
            l.j1=j1; l.x1=l.x0;
            lin[lins]=l; lins++;
            }
        }
    // segmentate lin[] ... group lines of the same hole together by lin[].id
    // segmentation based on vector lines data
    // you can also segmentate the map[][] directly as bitmap during hole detection
    for (i=0;i<lins;i++) lin[i].id=i;   // all lines are separate
    for (;;)                            // join what you can
        {
        int e=0,i0,i1;
        _line *a,*b;
        for (a=lin,i=0;i<lins;i++,a++)
            {
            for (b=a,j=i;j<lins;j++,b++)
             if (a->id!=b->id)
                {
                // do 2D lines a,b intersect ?
                double xx0,yy0,xx1,yy1;
                double kx0,ky0,dx0,dy0,t0;
                double kx1,ky1,dx1,dy1,t1;
                double x0=a->x0,y0=a->y0;
                double x1=a->x1,y1=a->y1;
                double x2=b->x0,y2=b->y0;
                double x3=b->x1,y3=b->y1;
                // discart lines with non intersecting bound rectangles
                double a0,a1,b0,b1;
                if (x0<x1) { a0=x0; a1=x1; } else { a0=x1; a1=x0; }
                if (x2<x3) { b0=x2; b1=x3; } else { b0=x3; b1=x2; }
                if (a1<b0) continue;
                if (a0>b1) continue;
                if (y0<y1) { a0=y0; a1=y1; } else { a0=y1; a1=y0; }
                if (y2<y3) { b0=y2; b1=y3; } else { b0=y3; b1=y2; }
                if (a1<b0) continue;
                if (a0>b1) continue;
                // compute intersection
                kx0=x0; ky0=y0; dx0=x1-x0; dy0=y1-y0;
                kx1=x2; ky1=y2; dx1=x3-x2; dy1=y3-y2;
                t1=divide(dx0*(ky0-ky1)+dy0*(kx1-kx0),(dx0*dy1)-(dx1*dy0));
                xx1=kx1+(dx1*t1);
                yy1=ky1+(dy1*t1);
                if (fabs(dx0)>=fabs(dy0)) t0=divide(kx1-kx0+(dx1*t1),dx0);
                else                      t0=divide(ky1-ky0+(dy1*t1),dy0);
                xx0=kx0+(dx0*t0);
                yy0=ky0+(dy0*t0);
                // check if intersection exists
                if (fabs(xx1-xx0)>1e-6) continue;
                if (fabs(yy1-yy0)>1e-6) continue;
                if ((t0<0.0)||(t0>1.0)) continue;
                if ((t1<0.0)||(t1>1.0)) continue;
                // if yes ... intersection point = xx0,yy0
                e=1; break;
                }
            if (e) break;                       // join found ... stop searching
            }
        if (!e) break;                          // no join found ... stop segmentation
        i0=a->id;                               // joid ids ... rename i1 to i0
        i1=b->id;
        for (a=lin,i=0;i<lins;i++,a++)
         if (a->id==i1)
          a->id=i0;
        }

    // visualize lin[]
    for (i=0;i<lins;i++)
        {
        glview2D::_lin l;
        l.p0.p[0]=lin[i].x0;
        l.p0.p[1]=lin[i].y0;
        l.p1.p[0]=lin[i].x1;
        l.p1.p[1]=lin[i].y1;
//      l.col=0x0000FF00;
        l.col=(lin[i].id*0x00D00C10A)+0x00800000;   // color is any function of ID
        view.lin.add(l);
        }

    // dynamic deallocation of map[N][N],lin[M]
    for (i=0;i<N;i++) delete[] map[i];
    delete[] map;
    delete[] lin;
    }
//---------------------------------------------------------------------------

Просто проигнорируйте мои вещи glview2D (это мой механизм рендеринга gfx для геометрии)

  • view.pnt[] - это динамический список ваших точек (сгенерированный случайным образом)
  • view.lin[] - это вывод динамического списка H, V строк только для визуализации
  • lin[] - ваш вывод линий

Это выводится:

holes preview

Мне слишком ленив, чтобы добавить многоугольник, теперь вы можете видеть, что сегментирование работает (раскраски). Если вам также нужна помощь с polygonize, тогда комментируйте меня, но я думаю, что это не должно быть проблемой.

Оценка сложности зависит от общего охвата покрытия

но для большей части кода O(N) и для поиска/сегментации дыр ~O((M^2)+(U^2)) где:

  • N - количество точек
  • M - размер сетки карты.
  • U H, V строк, зависящих от отверстий...
  • M << N, U << M*M

как вы можете видеть для 3783 точек 30x30 сетки на изображении выше, она приняла почти 9ms в моей настройке

[Edit1] играет с векторным полигонированием немного

bordered holes

для простых отверстий хорошо, но для более сложных есть еще какие-то завитки

[Edit2], наконец, получил немного времени для этого, вот он:

Это простой класс для поиска дыр/полигонов в более приятной/управляемой форме:

//---------------------------------------------------------------------------
class holes
    {
public:
    int xs,ys,n;            // cell grid x,y - size  and points count
    int **map;              // points density map[xs][ys]
                            // i=(x-x0)*g2l;    x=x0+(i*l2g);
                            // j=(y-y0)*g2l;    y=y0+(j*l2g);
    double mg2l,ml2g;       // scale to/from global/map space   (x,y) <-> map[i][j]
    double x0,x1,y0,y1;     // used area (bounding box)

    struct _line
        {
        int id;             // id of hole for segmentation/polygonize
        int i0,i1,j0,j1;    // index in map[][]
        _line(){}; _line(_line& a){ *this=a; }; ~_line(){}; _line* operator = (const _line *a) { *this=*a; return this; }; /*_line* operator = (const _line &a) { ...copy... return this; };*/
        };
    List<_line> lin;
    int lin_i0;             // start index for perimeter lines (smaller indexes are the H,V lines inside hole)

    struct _point
        {
        int i,j;            // index in map[][]
        int p0,p1;          // previous next point
        int used;
        _point(){}; _point(_point& a){ *this=a; }; ~_point(){}; _point* operator = (const _point *a) { *this=*a; return this; }; /*_point* operator = (const _point &a) { ...copy... return this; };*/
        };
    List<_point> pnt;

    // class init and internal stuff
    holes()  { xs=0; ys=0; n=0; map=NULL; mg2l=1.0; ml2g=1.0;  x0=0.0; y0=0.0; x1=0.0; y1=0.0; lin_i0=0; };
    holes(holes& a){ *this=a; };
    ~holes() { _free(); };
    holes* operator = (const holes *a) { *this=*a; return this; };
    holes* operator = (const holes &a)
        {
        xs=0; ys=0; n=a.n; map=NULL;
        mg2l=a.mg2l; x0=a.x0; x1=a.x1;
        ml2g=a.ml2g; y0=a.y0; y1=a.y1;
        _alloc(a.xs,a.ys);
        for (int i=0;i<xs;i++)
        for (int j=0;j<ys;j++) map[i][j]=a.map[i][j];
        return this;
        }
    void _free() { if (map) { for (int i=0;i<xs;i++) if (map[i]) delete[] map[i]; delete[] map; } xs=0; ys=0; }
    void _alloc(int _xs,int _ys) { int i=0; _free(); xs=_xs; ys=_ys; map=new int*[xs]; if (map) for (i=0;i<xs;i++) { map[i]=new int[ys]; if (map[i]==NULL) { i=-1; break; } } else i=-1; if (i<0) _free(); }

    // scann boundary box interface
    void scann_beg();
    void scann_pnt(double x,double y);
    void scann_end();

    // dynamic allocations
    void cell_size(double sz);      // compute/allocate grid from grid cell size = sz x sz

    // scann holes interface
    void holes_beg();
    void holes_pnt(double x,double y);
    void holes_end();

    // global(x,y) <- local map[i][j] + half cell offset
    inline void l2g(double &x,double &y,int i,int j) { x=x0+((double(i)+0.5)*ml2g); y=y0+((double(j)+0.5)*ml2g); }
    // local map[i][j] <- global(x,y)
    inline void g2l(int &i,int &j,double x,double y) { i=     double((x-x0) *mg2l); j=     double((y-y0) *mg2l); }
    };
//---------------------------------------------------------------------------
void holes::scann_beg()
    {
    x0=0.0; y0=0.0; x1=0.0; y1=0.0; n=0;
    }
//---------------------------------------------------------------------------
void holes::scann_pnt(double x,double y)
    {
    if (!n) { x0=x; y0=y; x1=x; y1=y; }
    if (n<0x7FFFFFFF) n++;  // avoid overflow
    if (x0>x) x0=x; if (x1<x) x1=x;
    if (y0>y) y0=y; if (y1<y) y1=y;
    }
//---------------------------------------------------------------------------
void holes::scann_end()
    {
    }
//---------------------------------------------------------------------------
void holes::cell_size(double sz)
    {
    int x,y;
    if (sz<1e-6) sz=1e-6;
    x=ceil((x1-x0)/sz);
    y=ceil((y1-y0)/sz);
    _alloc(x,y);
    ml2g=sz; mg2l=1.0/sz;
    }
//---------------------------------------------------------------------------
void holes::holes_beg()
    {
    int i,j;
    for (i=0;i<xs;i++)
     for (j=0;j<ys;j++)
      map[i][j]=0;
    }
//---------------------------------------------------------------------------
void holes::holes_pnt(double x,double y)
    {
    int i,j;
    g2l(i,j,x,y);
    if ((i>=0)&&(i<xs))
     if ((j>=0)&&(j<ys))
      if (map[i][j]<0x7FFFFFFF) map[i][j]++;    // avoid overflow
    }
//---------------------------------------------------------------------------
void holes::holes_end()
    {
    int i,j,e,i0,i1;
    List<int> ix;       // hole lines start/stop indexes for speed up the polygonization
    _line *a,*b,l;
    _point *aa,*bb,p;
    lin.num=0; lin_i0=0;// clear lines
    ix.num=0;           // clear indexes

    // find holes (map[i][j].cnt==0) or (map[i][j].cnt<=treshold)
    // and create lin[] list of H,V lines covering holes
    for (j=0;j<ys;j++) // search lines
     for (i=0;i<xs;)
        {
        int i0,i1;
        for (;i<xs;i++) if (map[i][j]==0) break; i0=i-1;    // find start of hole
        for (;i<xs;i++) if (map[i][j]!=0) break; i1=i;      // find end of hole
        if (i0<  0) continue;               // skip bad circumstances (edges or no hole found)
        if (i1>=xs) continue;
        if (map[i0][j]==0) continue;
        if (map[i1][j]==0) continue;
        l.i0=i0;
        l.i1=i1;
        l.j0=j ;
        l.j1=j ;
        l.id=-1;
        lin.add(l);
        }
    for (i=0;i<xs;i++) // search columns
     for (j=0;j<ys;)
        {
        int j0,j1;
        for (;j<ys;j++) if (map[i][j]==0) break; j0=j-1;    // find start of hole
        for (;j<ys;j++) if (map[i][j]!=0) break; j1=j  ;    // find end of hole
        if (j0<  0) continue;               // skip bad circumstances (edges or no hole found)
        if (j1>=ys) continue;
        if (map[i][j0]==0) continue;
        if (map[i][j1]==0) continue;
        l.i0=i ;
        l.i1=i ;
        l.j0=j0;
        l.j1=j1;
        l.id=-1;
        lin.add(l);
        }
    // segmentate lin[] ... group lines of the same hole together by lin[].id
    // segmentation based on vector lines data
    // you can also segmentate the map[][] directly as bitmap during hole detection
    for (i=0;i<lin.num;i++) lin[i].id=i;    // all lines are separate
    for (;;)                            // join what you can
        {
        for (e=0,a=lin.dat,i=0;i<lin.num;i++,a++)
            {
            for (b=a,j=i;j<lin.num;j++,b++)
             if (a->id!=b->id)
                {
                // if a,b not intersecting or neighbouring
                if (a->i0>b->i1) continue;
                if (b->i0>a->i1) continue;
                if (a->j0>b->j1) continue;
                if (b->j0>a->j1) continue;
                // if they do mark e for join groups
                e=1; break;
                }
            if (e) break;                       // join found ... stop searching
            }
        if (!e) break;                          // no join found ... stop segmentation
        i0=a->id;                               // joid ids ... rename i1 to i0
        i1=b->id;
        for (a=lin.dat,i=0;i<lin.num;i++,a++)
         if (a->id==i1)
          a->id=i0;
        }
    // sort lin[] by id
    for (e=1;e;) for (e=0,a=&lin[0],b=&lin[1],i=1;i<lin.num;i++,a++,b++)
     if (a->id>b->id) { l=*a; *a=*b; *b=l; e=1; }
    // re id lin[] and prepare start/stop indexes
    for (i0=-1,i1=-1,a=&lin[0],i=0;i<lin.num;i++,a++)
     if (a->id==i1) a->id=i0;
      else { i0++; i1=a->id; a->id=i0; ix.add(i); }
    ix.add(lin.num);

    // polygonize
    lin_i0=lin.num;
    for (j=1;j<ix.num;j++)  // process hole
        {
        i0=ix[j-1]; i1=ix[j];
        // create border pnt[] list (unique points only)
        pnt.num=0; p.used=0; p.p0=-1; p.p1=-1;
        for (a=&lin[i0],i=i0;i<i1;i++,a++)
            {
            p.i=a->i0;
            p.j=a->j0;
            map[p.i][p.j]=0;
            for (aa=&pnt[0],e=0;e<pnt.num;e++,aa++)
             if ((aa->i==p.i)&&(aa->j==p.j)) { e=-1; break; }
            if (e>=0) pnt.add(p);
            p.i=a->i1;
            p.j=a->j1;
            map[p.i][p.j]=0;
            for (aa=&pnt[0],e=0;e<pnt.num;e++,aa++)
             if ((aa->i==p.i)&&(aa->j==p.j)) { e=-1; break; }
            if (e>=0) pnt.add(p);
            }
        // mark not border points
        for (aa=&pnt[0],i=0;i<pnt.num;i++,aa++)
         if (!aa->used)                     // ignore marked points
          if ((aa->i>0)&&(aa->i<xs-1))      // ignore map[][] border points
           if ((aa->j>0)&&(aa->j<ys-1))
            {                               // ignore if any non hole cell around
            if (map[aa->i-1][aa->j-1]>0) continue;
            if (map[aa->i-1][aa->j  ]>0) continue;
            if (map[aa->i-1][aa->j+1]>0) continue;
            if (map[aa->i  ][aa->j-1]>0) continue;
            if (map[aa->i  ][aa->j+1]>0) continue;
            if (map[aa->i+1][aa->j-1]>0) continue;
            if (map[aa->i+1][aa->j  ]>0) continue;
            if (map[aa->i+1][aa->j+1]>0) continue;
            aa->used=1;
            }
        // delete marked points
        for (aa=&pnt[0],e=0,i=0;i<pnt.num;i++,aa++)
         if (!aa->used) { pnt[e]=*aa; e++; } pnt.num=e;

        // connect neighbouring points distance=1
        for (i0=   0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
         if (aa->used<2)
          for (i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
           if (bb->used<2)
            {
            i=aa->i-bb->i; if (i<0) i=-i; e =i;
            i=aa->j-bb->j; if (i<0) i=-i; e+=i;
            if (e!=1) continue;
            aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
            bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
            }
        // try to connect neighbouring points distance=sqrt(2)
        for (i0=   0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
         if (aa->used<2)
          for (i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
           if (bb->used<2)
            if ((aa->p0!=i1)&&(aa->p1!=i1))
             if ((bb->p0!=i0)&&(bb->p1!=i0))
            {
            if ((aa->used)&&(aa->p0==bb->p0)) continue; // avoid small closed loops
            i=aa->i-bb->i; if (i<0) i=-i; e =i*i;
            i=aa->j-bb->j; if (i<0) i=-i; e+=i*i;
            if (e!=2) continue;
            aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
            bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
            }
        // try to connect to closest point
        int ii,dd;
        for (i0=   0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
         if (aa->used<2)
            {
            for (ii=-1,i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
             if (bb->used<2)
              if ((aa->p0!=i1)&&(aa->p1!=i1))
               if ((bb->p0!=i0)&&(bb->p1!=i0))
                {
                i=aa->i-bb->i; if (i<0) i=-i; e =i*i;
                i=aa->j-bb->j; if (i<0) i=-i; e+=i*i;
                if ((ii<0)||(e<dd)) { ii=i1; dd=e; }
                }
            if (ii<0) continue;
            i1=ii; bb=&pnt[i1];
            aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
            bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
            }

        // add connected points to lin[] ... this is hole perimeter !!!
        // lines are 2 x duplicated so some additional code for sort the order of line swill be good idea
        l.id=lin[ix[j-1]].id;
        for (i0=0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
            {
            l.i0=aa->i;
            l.j0=aa->j;
            // [edit3] this avoid duplicating lines
            if (aa->p0>i0) { bb=&pnt[aa->p0]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            if (aa->p1>i0) { bb=&pnt[aa->p1]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            //if (aa->p0>=0) { bb=&pnt[aa->p0]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            //if (aa->p1>=0) { bb=&pnt[aa->p1]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            }
        }
    }
//---------------------------------------------------------------------------

Вам просто нужно заменить мой шаблон List<T> на std::list или что угодно (этот шаблон я не могу предоставить). Это динамический 1D-массив T...

  • List<int> x; совпадает с int x[];
  • x.add(); добавить пустой элемент в x
  • x.add(a); добавить элемент в x
  • x.reset() очищает массив
  • x.allocate(size) prealocate space, чтобы избежать перераспределения в медленном запуске
  • x.num - количество элементов в x []... используемый размер в элементах

в исходном коде есть только статические массивы, поэтому, если вы путаете проверку с ним.

Теперь, как его использовать:

h.scann_beg(); for (i=0;i<view.pnt.num;i++) { p=view.pnt[i].p0.p; h.scann_pnt(p[0],p[1]); } h.scann_end();
h.cell_size(2.5);
h.holes_beg(); for (i=0;i<view.pnt.num;i++) { p=view.pnt[i].p0.p; h.holes_pnt(p[0],p[1]); } h.holes_end();

где view.pnt[] - список входных точек и внутри него: view.pnt[i].p0.p[ 2 ]= { x,y }

Вывод находится в h.lin[] и lin_i0 где:

  • h.lin[i] i= < 0,lin_i0 ) - внутренние строки H, V
  • h.lin[i] i= < lin_i0,h.lin.num ) - это периметр

Линии периметра не упорядочены и дублируются дважды, поэтому просто закажите их и удалите дубликаты (слишком ленив для этого). Внутри lin[] находятся id .. id отверстия 0,1,2,3,..., к которому принадлежит линия, и i,j координат внутри карты. поэтому для правильного вывода в ваши координаты мира сделайте что-то вроде этого:

int i,j;
holes h;                // holes class
double *p;              // input point list ptr

h.scann_beg(); for (i=0;i<view.pnt.num;i++) { p=view.pnt[i].p0.p; h.scann_pnt(p[0],p[1]); } h.scann_end();
h.cell_size(2.5);
h.holes_beg(); for (i=0;i<view.pnt.num;i++) { p=view.pnt[i].p0.p; h.holes_pnt(p[0],p[1]); } h.holes_end();

DWORD coltab[]=
    {
    0x000000FF,
    0x0000FF00,
    0x00FF0000,
    0x0000FFFF,
    0x00FFFF00,
    0x00FF00FF,
    0x00FFFFFF,
    0x00000088,
    0x00008800,
    0x00880000,
    0x00008888,
    0x00888800,
    0x00880088,
    0x00888888,
    };

for (i=0;i<h.lin.num;i++)                   // draw lin[]
    {
    glview2D::_lin a;
    holes::_line *b=&h.lin[i];
    h.l2g(a.p0.p[0],a.p0.p[1],b->i0,b->j0);
    h.l2g(a.p1.p[0],a.p1.p[1],b->i1,b->j1);
    if (i<h.lin_i0) // H,V lines inside hole(b->id) .. gray  [edit3] was <= which is wrong and miss-color first perimeter line
        {
        a.col=0x00808080;
        }
    else{               // hole(b->id) perimeter lines ... each hole different collor
        if ((b->id>=0)&&(b->id<14)) a.col=coltab[b->id];
        if (b->id==-1) a.col=0x00FFFFFF;    // special debug lines
        if (b->id==-2) a.col=0x00AA8040;    // special debug lines
        }
    view.lin.add(a); // here draw your line or add it to your polygon instead
    }
  • my view.lin[] имеет членов: p0,p1,, которые являются точками как view.pnt[] и col, которые являются цветными

Я видел только одну проблему с этим, когда отверстия слишком маленькие (diameter < 3 cells) в противном случае ОК

[edit4] переупорядочение по периметру

чтобы сделать это вместо этого:

        /* add connected points to lin[] ... this is hole perimeter !!!
        // lines are 2 x duplicated so some additional code for sort the order of line swill be good idea
        l.id=lin[ix[j-1]].id;
        for (i0=0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
            {
            l.i0=aa->i;
            l.j0=aa->j;
            // [edit3] this avoid duplicating lines
            if (aa->p0>i0) { bb=&pnt[aa->p0]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            if (aa->p1>i0) { bb=&pnt[aa->p1]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            //if (aa->p0>=0) { bb=&pnt[aa->p0]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            //if (aa->p1>=0) { bb=&pnt[aa->p1]; l.i1=bb->i; l.j1=bb->j; lin.add(l); }
            } */

сделайте следующее:

    // add connected points to lin[] ... this is hole perimeter !!!
    l.id=lin[ix[j-1]].id;
    // add index of points instead points
    int lin_i1=lin.num;
    for (i0=0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
        {
        l.i0=i0;
        if (aa->p0>i0) { l.i1=aa->p0; lin.add(l); }
        if (aa->p1>i0) { l.i1=aa->p1; lin.add(l); }
        }
    // reorder perimeter lines
    for (i0=lin_i1,a=&lin[i0];i0<lin.num-1;i0++,a++)
     for (i1=i0+1  ,b=&lin[i1];i1<lin.num  ;i1++,b++)
        {
        if (a->i1==b->i0) { a++; l=*a; *a=*b; *b=l;                                a--; break; }
        if (a->i1==b->i1) { a++; l=*a; *a=*b; *b=l; i=a->i0; a->i0=a->i1; a->i1=i; a--; break; }
        }
    // convert point indexes to points
    for (i0=lin_i1,a=&lin[i0];i0<lin.num;i0++,a++)
        {
        bb=&pnt[a->i0]; a->i0=bb->i; a->j0=bb->j;
        bb=&pnt[a->i1]; a->i1=bb->i; a->j1=bb->j;
        }

[Edit5] Как полигонируется внутри holes::holes_end работает

В качестве входных данных для этого вам нужен список всех строк H, V lin[] сегментированные/сгруппированные/отсортированные по отверстию и карта плотности map[][].

  • цикл через все отверстия

    1. цикл через все линии H, V обработанного отверстия

      Создать список всех уникальных конечных точек линии pnt[] (без дубликатов). Поэтому возьмите 2 конечных точки для каждой строки и посмотрите, есть ли каждая точка в списке. Если вы не добавите его, не игнорируйте его.

    2. удалить все неграничные точки из списка

      Итак, удалите все точки, которые не имеют контакта с областью без отверстия, просмотрев 4 соседей в плотности map[][]

    3. выполнить анализ связанных компонентов в точках

      • установить used=0; p0=-1; p1=-1; для всех точек в списке pnt[]
      • подключить точки с помощью distance=1

        проведите все точки pnt[] с помощью used<2, что означает, что они еще не используются полностью и для каждого такого точечного поиска pnt[] снова для другой такой точки, которая также имеет для нее distance = 1. Это означает, что он является его 4-соседями и должен быть подключен так добавить информацию о подключении к стенду из них (используйте p0 или p1 индекс, который никогда не используется (-1)), и увеличивайте использование обе точки.

      • попытайтесь подключить точки с помощью distance=sqrt(2)

        почти совпадает с # 2, за исключением расстояния, которое теперь выбирает диагонали 8-соседних. На этот раз также избегайте замкнутых контуров, поэтому не подключайте точку, которая уже подключена к ней.

      • попытаться подключить самые близкие точки

        снова почти совпадает с # 2, # 3, но вместо этого выбирайте ближайшую точку, а также избегайте замкнутых циклов.

      • сформировать многоугольник из pnt[]

        поэтому выберите первую точку в списке и добавьте ее в многоугольник. затем добавьте к нему подключенную точку (неважно, с какого пути вы начинаете p0 или p1). Затем добавьте свою связанную точку (отличную от предыдущей добавленной точки к многоугольнику, чтобы избежать контуров обратной и прямой). Добавьте столько очков, сколько у вас есть точек в pnt[].

Ответ 2

Тройная связь Делоне может помочь. Он обладает свойством, что никакая входная точка не находится внутри окружности любого треугольника при триангуляции. Из-за этого граничные точки отверстия будут соединены более крупными/более широкими треугольниками, покрывающими это отверстие. В ваших случаях триангуляция будет иметь много треугольников одинакового размера, а некоторые треугольники большего размера, которые покрывают дыры. Вероятно, достаточно фильтровать большие и подключать их, чтобы найти отверстие.

Ответ 3

Это мое научное решение для энтузиастов:

1 - Сканировать всю 2D-область с минимальным предопределенным шагом (dx, dy). Для каждого шага координируйте поиск большего круга, который может поместиться без какой-либо точки внутри. Отбросьте все круги с радиусом меньше предопределенного размера.

enter image description here

2 - Теперь найдите все группы сталкивающихся кругов, простую проверку расстояния и радиуса, сохраните и группируйте в отдельные списки. (Спросите, если вы хотите получить более подробную информацию о том, как их группировать, действительно легко)

enter image description here

3 - Найти вогнутый ограничивающий многоугольник для каждой группы окружностей, очень похожий на алгоритм, чтобы найти выпуклый многоугольник вокруг группы точек, которые вы уже написали, и ваши последние вопросительные углы между векторами были связаны.

enter image description here

Примечания

Советы по оптимизации. До шага 1 вы можете хранить все точки в матрице сетки, поэтому расчет расстояний упрощается и ограничивается квадратами сетки заданного радиуса круга.

Прецизионность: вы получаете больше точности для меньших значений шага сканирования и допустимого радиуса минимального круга.

Не проверен сам, но, я уверен, он работает. Удачи!

Ответ 4

Вероятно, вам лучше использовать триангуляцию Delaunay, чтобы найти график Gabriel. Затем вы сортируете граф Габриэля и выполняете круговые прогулки, чтобы создать список выпуклых многоугольников. Затем вы можете отсортировать эти полигоны по области. Вас будут интересовать те, у которых самая большая площадь.

Также будет более полезно изменить отсортированный по углу график, по которому вы можете следовать по пути от A до B, и посмотреть, что будет дальше либо по часовой стрелке, либо против часовой стрелки (из сортировки угла). Может быть полезен диктатор словарей, который определен как "график [A] [B] = (по часовой стрелке, против часовой стрелки)". Подходит примерный алгоритм (python) с использованием словаря словарей.

pre_graph = gabriel_graph(point)
graph = {}
for a in pre_graph:
    graph[a] = {}
    angle_sorted = sort(pre_graph[a], key=calc_angle_from(a))
    for i,b in enumerate(angle_sorted):
        clockwise = angle_sorted[(i - 1) % len(angle_sorted)]
        counterclockwise = angle_sorted[(i + 1) % len(angle_sorted)]
        graph[a][b] = (clockwise, counterclockwise)

polygons = []
for A in points:
    for B in graph[A]:
        for direction in [0,1]:
            polygon = [A]
            next_point = B:
            while next != A:
                polygon.append(next)
                next_point = graph[A][B][direction]
            if polygon[0] == min(polygon): # This should avoid duplicates
                polygons.add(polygon)

Также может быть полезно комбинировать с предложением Ante.

Ответ 5

Я не знаю ни одного алгоритма с головы, но одна вещь, которую вы можете попробовать (и это мой первый импульс здесь), - это нечто похожее на то, как вычисляется плотность в методах meshfree, таких как гидродинамика сглаженных частиц.

Если вы могли бы вычислить значение плотности для любой точки в пространстве (а не только на выбранных точках, которые вы указали), вы можете определить границы отверстий как кривые уровня уровня функции плотности. То есть дыры там, где плотность падает ниже некоторого порога (что вы, вероятно, разрешите пользователю настраивать). Вы можете найти границы, используя что-то вроде марширующих квадратов.

Если вы хотите получить разъяснения относительно того, как эти типы функций интерполяции плотности работают, я могу предоставить (насколько это возможно, насколько мне известно).

Я не знаю, как хорошо это работает, но, надеюсь, это даст вам какое-то направление.

Ответ 6

Вот мысль:

  • Для каждой точки x найдите расстояние d(x,y) (где y - ближайший сосед к x). Определите f(x)=d(x,y), как указано выше.
  • Найдите среднее значение и дисперсию f(x).
  • Найдите "выбросы" - точки, в которых их значения f очень далеки от средних значений, по крайней мере, от стандартных стандартных отклонений. (\ alpha - параметр для алгоритма).
  • Это обнаружит "дыры" - все, что вам нужно сделать, теперь задает outlier каждого отверстия.

Ответ 7

Кажется, что вы можете адресовать это посредством (двоичной) математической морфологии на изображениях.

Создайте белое изображение и зарисуйте все свои очки. Затем "раздуйте" их на прямоугольники, которые больше обычного горизонтального и вертикального интервалов. Вы делаете это с помощью так называемой операции эрозии с прямоугольным структурным элементом.

Выполняя это, вы заполняете самолет, за исключением мест, где точки слишком редки.

Незаполненные области, которые вы обнаруживаете таким образом, меньше, чем фактические пустоты. Вы вернетесь к полному размеру, применяя расширение, используя тот же элемент структурирования.

Оба комбинированных преобразования называются открытием.

http://en.wikipedia.org/wiki/Mathematical_morphology