Имея матрицу MxN целых чисел, как сгруппировать их в многоугольники с геометрией буста? - PullRequest
2 голосов
/ 07 ноября 2011

У нас есть матрица данных целых чисел (от 1 до INT_MAX), например

1 2 3
1 3 3
1 3 3
100 2 1

Мы хотим создать полигоны одинакового цвета для каждого уникального целого в матрице, чтобы наши полигоны имели координаты / группировки, как показано здесь.

enter image description here

и мы можем сгенерировать изображение следующим образом:

enter image description here

Который * (из-за проведенной векторизации масштабируется до таких размеров, как):

enter image description here (извините за дерьмовые рисунки)

Возможно ли и как это сделать с помощью геометрии буста?

Обновление:

Итак @ sehe sad: I'd simply let Boost Geometry do most of the work. поэтому я создал этот растровый класс aeria для каждого пикселя, используя чисто Boost.Geometry, компилирует, запускает, но мне нужно, чтобы он работал на кластеризованном данные ... и у меня есть 1000 на 1800 файлов учаров (каждый уникальный uchar == данные принадлежат этому кластеру). Проблема с этим кодом: в 18-й строке он становится настолько ОСТОРОЖНЫМ МЕДЛЕННЫМ, что каждое создание точки начинает занимать более одной секунды = (

код:

//Boost
#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>

#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>

//and this is why we use Boost Geometry from Boost trunk 
//#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>

BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)


void make_point(int x, int y,  boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > & ring)
{
    using namespace boost::assign;

    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x-1, y-1));
    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x, y-1));
    boost::geometry::append(  ring,      boost::geometry::model::d2::point_xy<double>(x, y));
    boost::geometry::append(  ring,      boost::geometry::model::d2::point_xy<double>(x-1, y));
    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x-1, y-1));
    boost::geometry::correct(ring);
}

void create_point(int x, int y, boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > & mp)
{
    boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > temp;
    boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > ring;
    make_point(x, y, ring);
    boost::geometry::union_(mp, ring, temp);
    boost::geometry::correct(temp);
    mp=temp;
}

int main()
{
    using namespace boost::assign;


    boost::geometry::model::multi_polygon< boost::geometry::model::polygon < boost::geometry::model::d2::point_xy<double> > > pol, simpl;

    //read image
    std::ifstream in("1.mask", std::ios_base::in | std::ios_base::binary);
    int sx, sy;
    in.read(reinterpret_cast<char*>(&sy), sizeof(int));
    in.read(reinterpret_cast<char*>(&sx), sizeof(int));
    std::vector< std::vector<unsigned char> > image(sy);

    for(int i =1; i <= sy; i++)
    {
        std::vector<unsigned char> row(sx);
        in.read(reinterpret_cast<char*>(&row[0]), sx);
        image[i-1] = row;
    }

    //
    std::map<unsigned char,  boost::geometry::model::multi_polygon < boost::geometry::model::polygon < boost::geometry::model::d2::point_xy<double> > >  > layered_image;

    for(int y=1; y <= sy; y++)
    {
        for(int x=1; x <= sx; x++)
        {
            if (image[y-1][x-1] != 1)
            {
                create_point(x, y, layered_image[image[y-1][x-1]]);
                std::cout << x << " : " << y << std::endl;
            }
        }
    }
}

Итак, как вы можете видеть, мой код отстой ... поэтому я решил создать рендер для @ sehe code :

#include <iostream>
#include <fstream>

#include <vector>
#include <string>
#include <set>

//Boost
#include <boost/assign.hpp>
#include <boost/array.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/mersenne_twister.hpp>


//and this is why we use Boost Geometry from Boost trunk 
#include <boost/geometry/extensions/io/svg/svg_mapper.hpp>

BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)

    namespace mxdetail
{
    typedef size_t cell_id; // row * COLS + col

    template <typename T> struct area
    {
        T value;
        typedef std::vector<cell_id> cells_t;
        cells_t cells;
    };

    template <typename T, size_t Rows, size_t Cols>
    std::vector<area<T> > getareas(const boost::array<boost::array<T, Cols>, Rows>& matrix)
    {
        typedef boost::array<boost::array<T, Cols>, Rows> mtx;
        std::vector<area<T> > areas;

        struct visitor_t
        {
            const mtx& matrix;
            std::set<cell_id> visited;

            visitor_t(const mtx& mtx) : matrix(mtx) { }

            area<T> start(const int row, const int col)
            {
                area<T> result;
                visit(row, col, result);
                return result;
            }

            void visit(const int row, const int col, area<T>& current)
            {
                const cell_id id = row*Cols+col;
                if (visited.end() != visited.find(id))
                    return;

                bool matches = current.cells.empty() || (matrix[row][col] == current.value);

                if (matches)
                {
                    visited.insert(id);
                    current.value = matrix[row][col];
                    current.cells.push_back(id);

                    // process neighbours
                    for (int nrow=std::max(0, row-1); nrow < std::min((int) Rows, row+2); nrow++)
                        for (int ncol=std::max(0, col-1); ncol < std::min((int) Cols, col+2); ncol++)
                            /* if (ncol!=col || nrow!=row) */
                            visit(nrow, ncol, current);
                }
            }
        } visitor(matrix);

        for (int r=0; r < (int) Rows; r++)
            for (int c=0; c < (int) Cols; c++)
            {
                mxdetail::area<int> area = visitor.start(r,c);
                if (!area.cells.empty()) // happens when startpoint already visited
                    areas.push_back(area);
            }

            return areas;
    }
}




typedef boost::array<int, 4> row;

template <typename T, size_t N>
boost::array<T, N> make_array(const T (&a)[N])
{
    boost::array<T, N> result;
    std::copy(a, a+N, result.begin());
    return result;
}


void make_point(int x, int y,  boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > & ring)
{
    using namespace boost::assign;

    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x-1, y-1));
    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x, y-1));
    boost::geometry::append(  ring,      boost::geometry::model::d2::point_xy<double>(x, y));
    boost::geometry::append(  ring,      boost::geometry::model::d2::point_xy<double>(x-1, y));
    boost::geometry::append(  ring,     boost::geometry::model::d2::point_xy<double>(x-1, y-1));
    boost::geometry::correct(ring);
}

void create_point(int x, int y, boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > & mp)
{
    boost::geometry::model::multi_polygon< boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > > temp;
    boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > ring;
    make_point(x, y, ring);
    boost::geometry::union_(mp, ring, temp);
    boost::geometry::correct(temp);
    mp=temp;
}
boost::random::mt19937 rng;  
boost::random::uniform_int_distribution<> color(10,255);

std::string fill_rule()
{ 
    int red, green, blue;
    red = color(rng);
    green = color(rng);
    blue = color(rng);

    std::ostringstream rule;
    rule << "fill-rule:nonzero;fill-opacity:0.5;fill:rgb("
        << red  << ","  << green << "," << blue
        << ");stroke:rgb("
        << (red - 5) << "," << (green - 5) << "," << (blue -5) 
        <<  ");stroke-width:2";
    return rule.str();
}

int main()
{
    int sx = 4;
    int sy = 5;

    int row0[] = { 1  , 2, 3, 3, };
    int row1[] = { 1  , 3, 3, 3,};
    int row2[] = { 1  , 3, 3, 3, };
    int row3[] = { 2  , 2, 1, 2, };
    int row4[] = { 100, 2, 2, 2, };

    boost::array<row, 5> matrix;
    matrix[0] = make_array(row0);
    matrix[1] = make_array(row1);
    matrix[2] = make_array(row2);
    matrix[3] = make_array(row3);
    matrix[4] = make_array(row4);

    typedef std::vector<mxdetail::area<int> > areas_t;
    typedef areas_t::value_type::cells_t cells_t; 

    areas_t areas = mxdetail::getareas(matrix);

    using namespace boost::assign;

    typedef boost::geometry::model::polygon
        <
        boost::geometry::model::d2::point_xy<double>
        > polygon;

    typedef boost::geometry::model::multi_polygon<polygon> mp;

    typedef boost::geometry::point_type<mp>::type point_type;

    std::string filename = "draw.svg";
    std::ofstream svg(filename.c_str());


    boost::geometry::svg_mapper<point_type> mapper(svg, 400, 400);
    for (areas_t::const_iterator it=areas.begin(); it!=areas.end(); ++it)
    {
        mp pol;
        std::cout << "area of " << it->value << ": ";
        for (cells_t::const_iterator pit=it->cells.begin(); pit!=it->cells.end(); ++pit)
        {
            int row = *pit / 3, col = *pit % 3;
            std::cout << "(" << row << "," << col << "), ";
            create_point( (row+1), (col+1), pol);
        }

        std::cout << std::endl;
        mapper.add(pol);
        mapper.map(pol, fill_rule());
    }
    std::cout << "areas detected: " << areas.size() << std::endl;
    std::cin.get();
}

этот код компилируется, но он отстой (кажется, я не понял, как работать с массивами в конце концов ...):

enter image description here

Ответы [ 2 ]

3 голосов
/ 08 ноября 2011

Короче, если я правильно понял вопрос, я бы просто позволил Boost Geometry выполнить большую часть работы.

Для образца матрицы NxM создайте прямоугольные многоугольники NxM 'flyweight', соответствующие каждой ячейке матрицы визуально .

Теперь, используя алгоритм итеративного углубления, найдите все группы:

* for each _unvisited_ cell in matrix
  * start a new group
  * [visit:] 
     - mark _visited_
     - for each neighbour with equal value: 
          - add to curent group and
          - recurse [visit:]

Обратите внимание, что результатом этого алгоритма могут быть отдельные группы с одинаковыми значениями (представляющие несвязанные многоугольники). Например. значение 2 из выборки в OP приведет к двум группам.

Теперь для каждой группы просто вызовите алгоритм Union_ Boost Geometry , чтобы найти консолидированный многоугольник, представляющий эту группу.

Пример реализации

Обновление Вот неоптимизированная реализация в C ++ 11:

Редактировать Смотрите здесь для C ++ 03 версии (с использованием Boost)

Данные выборки, использованные в тесте, соответствуют матрице из вопроса.

#include <iostream>
#include <array>
#include <vector>
#include <set>

namespace mxdetail
{
    typedef size_t cell_id; // row * COLS + col

    template <typename T> struct area
    {
        T value;
        std::vector<cell_id> cells;
    };

    template <typename T, size_t Rows, size_t Cols>
        std::vector<area<T> > getareas(const std::array<std::array<T, Cols>, Rows>& matrix)
    {
        typedef std::array<std::array<T, Cols>, Rows> mtx;
        std::vector<area<T> > areas;

        struct visitor_t
        {
            const mtx& matrix;
            std::set<cell_id> visited;

            visitor_t(const mtx& mtx) : matrix(mtx) { }

            area<T> start(const int row, const int col)
            {
                area<T> result;
                visit(row, col, result);
                return result;
            }

            void visit(const int row, const int col, area<T>& current)
            {
                const cell_id id = row*Cols+col;
                if (visited.end() != visited.find(id))
                    return;

                bool matches = current.cells.empty() || (matrix[row][col] == current.value);

                if (matches)
                {
                    visited.insert(id);
                    current.value = matrix[row][col];
                    current.cells.push_back(id);

                    // process neighbours
                    for (int nrow=std::max(0, row-1); nrow < std::min((int) Rows, row+2); nrow++)
                    for (int ncol=std::max(0, col-1); ncol < std::min((int) Cols, col+2); ncol++)
                        /* if (ncol!=col || nrow!=row) */
                            visit(nrow, ncol, current);
                }
            }
        } visitor(matrix);

        for (int r=0; r < Rows; r++)
            for (int c=0; c < Cols; c++)
            {
                auto area = visitor.start(r,c);
                if (!area.cells.empty()) // happens when startpoint already visited
                    areas.push_back(area);
            }

        return areas;
    }
}

int main()
{
    typedef std::array<int, 3> row;
    std::array<row, 4> matrix = { 
        row { 1  , 2, 3, },
        row { 1  , 3, 3, },
        row { 1  , 3, 3, },
        row { 100, 2, 1, },
    };

    auto areas = mxdetail::getareas(matrix);

    std::cout << "areas detected: " << areas.size() << std::endl;
    for (const auto& area : areas)
    {
        std::cout << "area of " << area.value << ": ";
        for (auto pt : area.cells)
        {
            int row = pt / 3, col = pt % 3;
            std::cout << "(" << row << "," << col << "), ";
        }
        std::cout << std::endl;
    }
}

Скомпилировано с gcc-4.6 -std=c++0x Выход:

areas detected: 6
area of 1: (0,0), (1,0), (2,0), 
area of 2: (0,1), 
area of 3: (0,2), (1,1), (1,2), (2,1), (2,2), 
area of 100: (3,0), 
area of 2: (3,1), 
area of 1: (3,2), 
0 голосов
/ 02 ноября 2013

Когда количество точек велико (скажем, больше 1000x1000), решение, приведенное выше, поглотит много памяти.И это именно то, что случилось с создателем темы.

Ниже я покажу более масштабируемый подход.

Здесь я бы выделил две проблемы: одна - найти области, другая - преобразовать ихв многоугольники.

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

#include <boost/graph/grid_graph.hpp>
// Define dimension lengths, a MxN in this case
boost::array<int, 2> lengths = { { M, N } };

// Create a MxN two-dimensional, unwrapped grid graph 
grid_graph<2> graph(lengths);

Далее, мы должны преобразовать данную матрицу M в фильтр границ: края сетки присутствуют, если «цвет»соседи одинаковы.

template <class Matrix>
struct GridEdgeFilter
{  
    typedef grid_graph<2> grid;
    GridEdgeFilter(const Matrix & m, const grid&g):_m(&m),_g(&g){}

    /// \return true iff edge is present in the graph
    bool operator()(grid::edge_descriptor e) const
    {
        grid::vertex_descriptor src = source(e,*_g), tgt = target(e,*_g);
        //src[0] is x-coord of src, etc. The value (*m)[x,y] is the color of the point (x,y).
        //Edge is preserved iff matrix values are equal
        return (*_m)[src[0],src[1]] == (*_m)[tgt[0],tgt[1]];
    }

    const Matrix * _m;
    const grid* _g;
};

Наконец, мы определяем boost::filtered_graph сетки и EdgeFilter и вызываем алгоритм Boost.Graph для связанных компонентов.

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

Здесь у нас есть еще один вопрос.Boost.Geometry позволяет объединять полигоны только один за другим.Следовательно, он становится очень медленным, когда количество полигонов велико.

Лучше всего использовать Boost.Polygon, а именно его Property Merge функциональность.Один начинается с пустого property_merge объекта и продолжается, вставляя прямоугольники данного цвета (вы можете установить цвет как свойство).Затем вызывается метод merge и в качестве вывода получается polygon_set.

...