ぬの部屋(仮)
nu-no-he-ya
  •   12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
       1234
    567891011
    12131415161718
    19202122232425
    26272829   
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28      
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
    1234567
    891011121314
    15161718192021
    22232425262728
           
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
         12
    3456789
    10111213141516
    17181920212223
    242526272829 
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
        123
    45678910
    11121314151617
    18192021222324
    25262728   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    15161718192021
    293031    
           
         12
    3456789
    10111213141516
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    30      
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    2627282930  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728     
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
        123
    45678910
    11121314151617
    18192021222324
    252627282930 
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
     123456
    78910111213
    14151617181920
    21222324252627
    28293031   
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
     123456
    78910111213
    14151617181920
    21222324252627
    282930    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    31      
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
      12345
    6789101112
    13141516171819
    20212223242526
    27282930   
           
          1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031     
          1
    2345678
    9101112131415
    16171819202122
    232425262728 
           
       1234
    567891011
    12131415161718
    19202122232425
    262728293031 
           
    1234567
    891011121314
    15161718192021
    22232425262728
    293031    
           
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
           
      12345
    6789101112
    13141516171819
    20212223242526
    2728293031  
           
    1234567
    891011121314
    15161718192021
    22232425262728
    2930     
           
        123
    45678910
    11121314151617
    18192021222324
    25262728293031
           
  • Welzlの最小球を求めるアルゴリズムをC++で実装

    点群の最小球を求めるのにWelzlのアルゴリズムというのがあるのを知った。

    とりあえずコピペで動くことを確認したのでここに置く。何をやっているのかわからないのでこれから調べていく。

    あと、そのままだと再帰が激しすぎてたった5000頂点でオーバーフローした。スタックサイズを変えればいけるのだろうがそういう問題ではない。

    #include <iostream>
    #include <unordered_map>
    #include <set>
    
    #pragma warning(disable:4996)
    
    #include <vtkUnsignedCharArray.h>
    #include <vtkPointData.h>
    #include <vtkActor.h>
    #include <vtkRenderer.h>
    #include <vtkRenderWindow.h>
    #include <vtkRenderWindowInteractor.h>
    
    #include <vtkProperty.h>
    
    #include <vtkSphere.h>
    
    #include <vtkVertexGlyphFilter.h>
    
    //球とその表示に必要
    #include <vtkSphereSource.h>
    #include <vtkPolyDataMapper.h>
    
    //VTK_MODULE_INITに必要
    #include <vtkAutoInit.h>
    
    //必須
    VTK_MODULE_INIT(vtkRenderingOpenGL2);
    VTK_MODULE_INIT(vtkInteractionStyle);
    
    #pragma comment(lib, "ws2_32.lib")
    #pragma comment(lib, "Psapi.lib")
    #pragma comment(lib, "Dbghelp.lib")
    
    
    #include <Eigen/Dense>
    #include <random>
    
    #pragma comment(lib,"opengl32.lib")
    
    // 点群型
    struct MyCloud {
        std::vector<Eigen::Vector3d> points;
        std::vector<Eigen::Vector3d> colors;
    };
    
    namespace welzl {
    
    
        // 球を表す
        struct Sphere {
            Eigen::Vector3d center;
            double radius;
            Sphere() : center(Eigen::Vector3d::Zero()), radius(0.0) {}
            Sphere(const Eigen::Vector3d& c, double r) : center(c), radius(r) {}
        };
        
        //-------------------------------------
        // R(最大4点)から決まる球を求める補助関数群
        //-------------------------------------
    
    
        // 1点からなる球(その点が中心、半径0)
        Sphere ball_from_one_point(const Eigen::Vector3d& a) {
            return Sphere(a, 0.0);
        }
        
          
        // 2点からなる最小球(2点の中点が中心、半径は両点から中点までの距離)
        Sphere ball_from_two_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
            Eigen::Vector3d c = 0.5 * (a + b);
            double r = (a - c).norm();
            return Sphere(c, r);
        }
        
        
        // 3点から決まる球(3点が共通で外接する円=2次元平面での外接円を3D空間で表現)
        // 外接円の中心は3点からなる三角形の外心
        Sphere ball_from_three_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c) {
            // 2次元外心計算を3次元対応。3点は同一平面上にあるため、
            // 外心は、(b - a), (c - a)に直交する2本の垂直二等分線の交点。
            Eigen::Vector3d ab = b - a;
            Eigen::Vector3d ac = c - a;
    
            Eigen::Matrix3d M;
            M << ab.x(), ab.y(), ab.z(),
                ac.x(), ac.y(), ac.z(),
                ab.cross(ac).x(), ab.cross(ac).y(), ab.cross(ac).z();
    
            // 上記はそのままでは重複があり、もう少し安定的な方法を取る必要がある。
            // 簡易的には、外心は以下の方程式で求められる:
            // |x - a|^2 = |x - b|^2 = |x - c|^2 を解く。
            // この方程式は線形代数で解ける。
            // 以下では直接外心を求める一般的な手続きの1つを示す。
    
            // 行列方程式で外心を求めるための設定
            // (参考実装) 外心は幾何的には以下で求まる:
            // Let A, B, C be points. 外心Oは:
            // solve for O:
            // (O - A)・(B - A) = (|B-A|^2)/2
            // (O - A)・(C - A) = (|C-A|^2)/2
            // この2式からOが求まる。(O - A)を未知変数にした線形方程式となる
    
            Eigen::Matrix2d A_mat;
            A_mat << ab.dot(ab), ab.dot(ac),
                ab.dot(ac), ac.dot(ac);
            Eigen::Vector2d rhs;
            rhs << 0.5 * ab.dot(ab),
                0.5 * ac.dot(ac);
    
            // 解く
            Eigen::Vector2d uv = A_mat.colPivHouseholderQr().solve(rhs);
            Eigen::Vector3d O = a + uv[0] * ab + uv[1] * ac;
    
            double r = (O - a).norm();
            return Sphere(O, r);
        }
        
        
        // 4点から決まる球(4点が共通球面上にある場合、その外接球を求める)
        // 4点外接球計算は3x3の線形方程式を解くことで可能。
        Sphere ball_from_four_points(const Eigen::Vector3d& a, const Eigen::Vector3d& b,
            const Eigen::Vector3d& c, const Eigen::Vector3d& d) {
            // 方針:
            // (O - a)・(O - a) = (O - b)・(O - b)
            // (O - a)・(O - a) = (O - c)・(O - c)
            // (O - a)・(O - a) = (O - d)・(O - d)
            // という3つの方程式を立て、Oを求める。
    
            Eigen::Vector3d A = a;
            Eigen::Vector3d B = b;
            Eigen::Vector3d C = c;
            Eigen::Vector3d D = d;
    
            // 方程式組み立て
            // (O - A)² = (O - B)² などは展開すると
            // O² - 2O·A + A² = O² - 2O·B + B² → 2O·(B - A) = B² - A²
            // 同様にC, Dについても同様の式を立てられる
            // よって:
            // 2(B - A)·O = B² - A²
            // 2(C - A)·O = C² - A²
            // 2(D - A)·O = D² - A²
    
            // ベクトルX = Oについて
            Eigen::Matrix3d M;
            M << 2 * (b.x() - a.x()), 2 * (b.y() - a.y()), 2 * (b.z() - a.z()),
                2 * (c.x() - a.x()), 2 * (c.y() - a.y()), 2 * (c.z() - a.z()),
                2 * (d.x() - a.x()), 2 * (d.y() - a.y()), 2 * (d.z() - a.z());
    
            auto sq = [](const double& val) { return val * val; };
    
            Eigen::Vector3d RHS;
            RHS << (b.squaredNorm() - a.squaredNorm()),
                (c.squaredNorm() - a.squaredNorm()),
                (d.squaredNorm() - a.squaredNorm());
    
            Eigen::Vector3d O = M.colPivHouseholderQr().solve(RHS);
    
            double r = (O - a).norm();
            return Sphere(O, r);
        }
        
        
        // Rには最大4頂点が入っている。
        // それに応じて最小包含球を求める
        Sphere ball_from_R(const std::vector<Eigen::Vector3d>& R) {
            switch (R.size()) {
            case 0:
                return Sphere(Eigen::Vector3d::Zero(), 0.0);
            case 1:
                return ball_from_one_point(R[0]);
            case 2:
                return ball_from_two_points(R[0], R[1]);
            case 3:
                return ball_from_three_points(R[0], R[1], R[2]);
            case 4:
                return ball_from_four_points(R[0], R[1], R[2], R[3]);
            default:
                // それ以上はありえない
                return Sphere();
            }
        }
        
        //-------------------------------------
        // 点 p が球 S の中に入っているか判定
        //-------------------------------------
    
        inline bool is_in_sphere(const Eigen::Vector3d& p, const Sphere& S) {
            return (p - S.center).squaredNorm() <= S.radius * S.radius + 1e-14; // 浮動小数対策
        }
        //-------------------------------------
        // Welzlの再帰関数
        //-------------------------------------
        // 引数:
        // points: 対象の点集合(インデックス0〜endで使用)
        // R: 球を定義するための点集合
        // end: 再帰処理内での有効点数境界
    
        Sphere welzl(const std::vector<Eigen::Vector3d>& points, std::vector<Eigen::Vector3d> R, int end) {
    
            // 再帰の末端、これ以上頂点がないか、Rが4点になったら球を求める
            if (end < 0 || R.size() == 4) {
                return ball_from_R(R);
            }
    
            // 頂点を一つ取り出して、それ以外の点で再帰処理
            Eigen::Vector3d p = points[end];
            Sphere D = welzl(points, R, end - 1);
    
            // 今取り出した点が、それ以外の点で求めた球に入っている場合
            // 球を再計算する必要が無いので、そのまま返す
            if (is_in_sphere(p, D)) {
                return D;
            }
    
            // pがD内に収まらない場合
            R.push_back(p);
            return welzl(points, R, end - 1);
        }
        
        //-------------------------------------
        // 公開関数: 最小包含球を計算
        //-------------------------------------
        Sphere minimum_enclosing_sphere(const std::vector<Eigen::Vector3d>& points) {
    
            // 頂点をシャッフルすると期待線形時間になるが
            // 頂点順を変えたくないため今回は使用しない
            // なお引数pointsがconst参照のため、ここを有効にしてもエラーになる。
            // 有効化するときは引数のconstを外すか、コピーを作成すること。
    #if 0
            // ポイントシャッフル推奨(期待線形時間のため)
            std::random_device rd;
            std::mt19937 g(rd());
            std::shuffle(points.begin(), points.end(), g);
    #endif
            std::vector<Eigen::Vector3d> dumyR;
            return welzl(points, dumyR, (int)points.size() - 1);
        }
        
    }
    
    
    std::pair<Eigen::Vector3f,float> CalcBoundingSphere(MyCloud& cloud) {
        welzl::Sphere S = welzl::minimum_enclosing_sphere(cloud.points);
        return { S.center.cast<float>(), S.radius };
    
    }
    
    // 点群をVTK用のActorに変換
    vtkSmartPointer<vtkActor> ConvertCloudVTKActor(const MyCloud& cloud);
    
    // 座標と半径から球のActorを作成
    vtkSmartPointer<vtkActor> CreateSphereActor(const std::pair<Eigen::Vector3f, float>& sphere_data);
    
    // ランダムな点群を作成
    MyCloud CreateRandomCloud(int num_points);
    
    
    
    // 表示データを作成
    bool PrepareData(vtkSmartPointer<vtkRenderer> renderer) {
    
        // ランダムな点群を作成
        auto cloud = CreateRandomCloud(1000);
    
        // welzlでBounding Sphereを計算
        auto sphere = CalcBoundingSphere(cloud);
        std::cout << "center : " << sphere.first.transpose() << std::endl;
        std::cout << "radius : " << sphere.second << std::endl;
    
        // VTK Actorを作成
        auto cloudActor = ConvertCloudVTKActor(cloud);
        auto sphereActor = CreateSphereActor(sphere);
        
        // Create a vtkSphereSource
        vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
        sphereSource->SetCenter(sphere.first.x(), sphere.first.y(), sphere.first.z());
        sphereSource->SetRadius(0.3);
        sphereSource->SetThetaResolution(32);
        sphereSource->SetPhiResolution(32);
    
        // Create a vtkPolyDataMapper
        vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
        mapper->SetInputConnection(sphereSource->GetOutputPort());
    
        // Create a vtkActor and assign the mapper
        vtkSmartPointer<vtkActor> CenterActor = vtkSmartPointer<vtkActor>::New();
        CenterActor->SetMapper(mapper);
    
    
        renderer->AddActor(cloudActor);
        renderer->AddActor(sphereActor);
        renderer->AddActor(CenterActor);
        return true;
    
    }
    
    
    // 座標と半径から球のActorを作成
    vtkSmartPointer<vtkActor> CreateSphereActor(const std::pair<Eigen::Vector3f, float>& sphere_data) {
        // 球の中心と半径を取得
        const Eigen::Vector3f& center = sphere_data.first;
        float radius = sphere_data.second;
    
        // vtkSphereSourceを設定
        vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
        sphereSource->SetCenter(center.x(), center.y(), center.z());
        sphereSource->SetRadius(radius);
        sphereSource->SetThetaResolution(32); // 解像度設定
        sphereSource->SetPhiResolution(32);  // 解像度設定
    
        // Mapperを作成
        vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
        mapper->SetInputConnection(sphereSource->GetOutputPort());
    
        // Actorを作成
        vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
        actor->SetMapper(mapper);
    
        // 半透明に設定
        actor->GetProperty()->SetOpacity(0.5); // 透明度(0.0~1.0)
        actor->GetProperty()->SetColor(1.0, 1.0, 1.0); // 白
    
        return actor;
    }
    
    // ランダムな点群を作成
    MyCloud CreateRandomCloud(int num_points) { MyCloud cloud; std::random_device seed_gen; std::mt19937 engine(seed_gen()); std::uniform_real_distribution<> dist(-1.0, 1.0); for (int i = 0; i < num_points; i++) { cloud.points.emplace_back(dist(engine), dist(engine), dist(engine)); cloud.colors.emplace_back(dist(engine), dist(engine), dist(engine)); } return cloud; }
    // 点群をVTK用のActorに変換
    vtkSmartPointer<vtkActor> ConvertCloudVTKActor(const MyCloud& cloud) { // VTK用のポイントリストを作成 auto points = vtkSmartPointer<vtkPoints>::New(); for (const auto& point : cloud.points) { points->InsertNextPoint(point.x(), point.y(), point.z()); } // VTK用の頂点色リストを作成(Open3Dのcolors_を使用) auto colors = vtkSmartPointer<vtkUnsignedCharArray>::New(); colors->SetNumberOfComponents(3); // RGB if (!cloud.colors.empty()) { for (const auto& color : cloud.colors) { unsigned char r = static_cast<unsigned char>(color.x() * 255); unsigned char g = static_cast<unsigned char>(color.y() * 255); unsigned char b = static_cast<unsigned char>(color.z() * 255); colors->InsertNextTuple3(r, g, b); } } // ポリデータに点とオプションの属性(色、法線)を設定 auto polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(points); if (!cloud.colors.empty()) { polyData->GetPointData()->SetScalars(colors); } // 点を頂点として描画可能にするフィルター auto glyphFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New(); glyphFilter->SetInputData(polyData); glyphFilter->Update(); // マッパーを作成 auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(glyphFilter->GetOutput()); if (!cloud.colors.empty()) { mapper->ScalarVisibilityOn(); // 頂点色を使用する } else { mapper->ScalarVisibilityOff(); // 頂点色がない場合はオフ } // Actorを作成 auto actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); actor->GetProperty()->SetPointSize(5.0); actor->GetProperty()->SetInterpolationToFlat(); return actor; } int main() { vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); vtkSmartPointer<vtkRenderWindow> window = vtkSmartPointer<vtkRenderWindow>::New(); window->AddRenderer(renderer); vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); window->SetInteractor(interactor); PrepareData(renderer); window->SetSize(700, 400); interactor->Initialize(); window->Render(); interactor->Start(); }