Pythonの文字列はutf8で扱われているが、C++側はUnicodeに弱い。使用しているライブラリがマルチバイト(非utf8なstd::string)しか受け付けてないような場合はPython側でshift_jisに変換して渡してやると都合がいい。
#include <pybind11/pybind11.h> #include <cstdio> namespace py = pybind11; // 名前空間を指定 void func_setstring(std::string text) { FILE* fp = fopen("test.txt", "wb"); if (fp == NULL) { return; } fwrite(text.c_str(), sizeof(char), text.size(), fp); fclose(fp); } // PYBIND11_MODULE(モジュール名, モジュール変数名) // 生成物はmy_module_string.pydという名前にしなければならない PYBIND11_MODULE(my_module_string, m) { // 関数を定義 m.def("func_string", &func_setstring, "Write text to file", py::arg("text")); }
import my_module_string text = "こんにちは、世界!" # テキストを書き込む my_module_string.func_string(text.encode("shift_jis"))
エディタで確認すると、SJISと判別されている。
そのまま渡すとutf-8で受け取る。C++はstd::stringでutf8を扱いたがるので紛らわしい。高度な文字列処理が必要ないならそのまま使用できる。ファイルに保存してやれば受け取った内容がutf-8であることをテキストエディタで確認できる。
import my_module_string text = "こんにちは、世界!" # テキストを書き込む my_module_string.func_string(text)
class mykdtree { std::vector<Eigen::Vector3d>& _points; node* node_root; public: mykdtree(std::vector<Eigen::Vector3d>& points) : _points(points) {} // ツリーの構築 void build() { // 最初のindexリストを作成 std::vector<size_t> indices(_points.size()); for (size_t i = 0; i < _points.size(); i++) { indices[i] = i; } // 再帰的にツリー作成 node_root = buildnode(_points, indices, 0/*X軸*/); } node* getRoot() { return node_root; }
// 半径内の頂点を探索する関数 // 再帰的に探索する関数 // nd: 現在のノード // query: 探索する点 // radius: 探索半径 // results: 探索結果のindexリスト void radiusSearch_core( node* nd, const Eigen::Vector3d& query, double radius, std::vector<size_t>& results) { if (nd == nullptr) { return; } const auto& nodePt = _points[nd->index]; // ノード と query の距離を計算 float dist = (query - nodePt).norm(); // 現在のノードがqueryを中心とした探索範囲内にあるか確認 if (dist <= radius) { results.push_back(nd->index);//現在のノードを結果に追加 } // サブツリーの探索条件 // 軸に沿った距離を計算 /////////////////////////////////////////////////////////// // // | // ------------●-------------○---------------- // query nodePt // | // // query.x - nodePt.x < 0 // queryが現在のノードの左側にある → nodePtの左側を探索 /////////////////////////////////////////////////////////// // // | // ------------○-------------●---------------- // nodePt query // | // // query.x - nodePt.x > 0 // queryが現在のノードの右側にある → nodePtの右側を探索 /////////////////////////////////////////////////////////// float axisDist = query[nd->axis] - nodePt[nd->axis]; // ケース1 queryがノードの左側にある if (axisDist < 0) { // 左側にあるので、左側を探索 radiusSearch_core(nd->left, query, radius, results); // ケース1(2)nodePtが、queryの半径以内にあるなら、右側にもあるかもしれない if (axisDist * axisDist <= radius * radius) { radiusSearch_core(nd->right, query, radius, results); } } else { // ケース2 queryがノードの右側にある // 右側にあるので、右側を探索 radiusSearch_core(nd->right, query, radius, results); // ケース2(2)nodePtが、queryの半径以内にあるなら、左側にもあるかもしれない if (axisDist * axisDist <= radius * radius) { radiusSearch_core(nd->left, query, radius, results); } } }
// 半径内の頂点を探索する関数 // query: 探索する点 // radius: 探索半径 // results: 探索結果のindexリスト void radiusSearch( const Eigen::Vector3d& query, double radius, std::vector<size_t>& results) { results.clear(); radiusSearch_core(node_root, query, radius, results); }
};
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> //円筒とその表示に必要 #include <vtkPolyDataMapper.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") #include <Eigen/Core> #include <array> #include <vtkActor.h> #include <vtkPoints.h> #include <vtkPolyData.h> #include <vtkUnsignedCharArray.h> #include <vtkPointData.h> #include <vtkVertexGlyphFilter.h> #include <vtkProperty.h> #include "mykdtree.hpp" //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); // VTK表示用 struct MyVtkCloud { std::vector<Eigen::Vector3d> points; std::array<unsigned char, 3> color; std::vector<std::array<unsigned char, 3> > color_array; vtkSmartPointer<vtkActor> actor; void makeActor() { // VTKのデータ構造に変換 vtkSmartPointer<vtkPoints> vtk_points = vtkSmartPointer<vtkPoints>::New(); vtkSmartPointer<vtkUnsignedCharArray> vtk_colors = vtkSmartPointer<vtkUnsignedCharArray>::New(); vtk_colors->SetNumberOfComponents(3); // RGB vtk_colors->SetName("Colors"); for (size_t i = 0; i < points.size(); ++i) { // 点を追加 vtk_points->InsertNextPoint(points[i].x(), points[i].y(), points[i].z()); if (color_array.size() == 0) { vtk_colors->InsertNextTypedTuple(color.data()); } else { vtk_colors->InsertNextTypedTuple(color_array[i].data()); } } //////////////////////////////////////////////////////////// vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(vtk_points); polyData->GetPointData()->SetScalars(vtk_colors); //////////////////////////////////////////////////////////// vtkSmartPointer<vtkVertexGlyphFilter> vertexFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New(); vertexFilter->SetInputData(polyData); vertexFilter->Update(); //////////////////////////////////////////////////////////// vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(vertexFilter->GetOutputPort()); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // 頂点サイズを指定 actor->GetProperty()->SetPointSize(5); // ここで頂点サイズを指定します this->actor = actor; } };
int main(int /*argc*/, char** /*argv*/) { MyVtkCloud cloud1; std::vector<std::array<unsigned char, 3> > color_array; srand((unsigned int)5); //// ランダムな点群を作成 for (size_t i = 0; i < 10000; ++i) { cloud1.points.push_back(Eigen::Vector3d::Random()); color_array.push_back({ 255,0,0 }); } cloud1.color = std::array<unsigned char, 3>{ 0, 255, 0 }; cloud1.makeActor(); mykdtree kdtree(cloud1.points); kdtree.build(); node* root = kdtree.getRoot(); std::vector<size_t> indices; kdtree.radiusSearch( Eigen::Vector3d(0.3,0.3,0.3), 0.3, indices); for(auto i:indices){ color_array[i] = { 0,255,0 }; } cloud1.color_array = color_array; cloud1.makeActor(); // 表示 std::vector<size_t> indices_look = indices; // 昇順ソート std::sort(indices_look.begin(), indices_look.end()); // コンソールに表示 for (auto i : indices_look) { std::cout << i << std::endl; } ////////////////////////////////////// auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(cloud1.actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }
partitionを使い、quick selectアルゴリズムを書く。quick selectでは「配列がソートされたときに、n番目に来る値」を取得できる。これを利用して中央値を取得できる。n番目の要素を特定するのに必要な処理が完了したらクイックソートを中断することで実現するので、データの順番が入れ替わるが、全部ソートするほどの時間はかからない。
C++でpartitionするプログラムを変更して[pivot未満][pivot][pivotより大きい]の領域に分割する
#include <iostream> #include <vector> #include <algorithm> #include "partition_3area.hpp"
void quick_select(std::vector<PairTypeFloat>& arr,size_t low,size_t high, size_t target_index) { size_t nlow = low; size_t nhigh = high; size_t tmp_pivot_index = low + (nhigh - nlow) / 2; //なにかしらpivotを決める // pivotでパーティション PartitionFuncFloat cond(arr, tmp_pivot_index); std::array<int64_t, 3> ret= partition_3area(arr, nlow, nhigh, cond); nlow = ret[0]; // 左側開始位置 size_t newpivot = ret[1]; // 処理後のpivotの位置 nhigh = ret[2]; // 右側終了位置 // [nlow~newpivot-1][newpivot][newpivot+1~nhigh] に分割される // このとき、 if (newpivot == target_index) { // [nlow~newpivot-1][target_index][newpivot+1~nhigh] であれば、 // target_indexの要素番号のデータはtarget_index番目の要素になっている return ; } else if (target_index < newpivot) { // [target_indexを含む][newpivot][newpivot+1~nhigh] であれば、 // 左側を処理 quick_select(arr, nlow, newpivot, target_index); } else { // [nlow~newpivot-1][newpivot][target_indexを含む] であれば、 // 右側を処理 quick_select(arr, newpivot+1, nhigh, target_index); } }
int main() { std::vector<PairTypeFloat> arr; arr.push_back(std::make_pair(3.7, "a")); arr.push_back(std::make_pair(2.3, "b")); arr.push_back(std::make_pair(7.5, "c")); arr.push_back(std::make_pair(1.2, "d")); arr.push_back(std::make_pair(6.8, "e")); arr.push_back(std::make_pair(5.6, "f")); arr.push_back(std::make_pair(2.4, "g")); arr.push_back(std::make_pair(0.9, "h")); arr.push_back(std::make_pair(6.1, "i")); arr.push_back(std::make_pair(5.8, "j"));// arr.push_back(std::make_pair(4.5, "k")); arr.push_back(std::make_pair(8.2, "l")); arr.push_back(std::make_pair(9.1, "m")); arr.push_back(std::make_pair(1.7, "n")); arr.push_back(std::make_pair(5.3, "o")); arr.push_back(std::make_pair(7.7, "p")); arr.push_back(std::make_pair(3.8, "q")); arr.push_back(std::make_pair(4.2, "r")); std::cout << "処理前の配列: " << std::endl; for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } size_t target_index = arr.size() / 2; std::cout << target_index << "番目の要素: " << arr[target_index].first << " " << arr[target_index].second << std::endl; quick_select(arr, 0, arr.size(), target_index);// 中央値を求める std::cout << "--------------------------------------------" << std::endl; std::cout << "処理後の配列: " << std::endl; for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } std::cout << target_index << "番目の要素: " << arr[target_index].first << " " << arr[target_index].second << std::endl; std::cout << "--------------------------------------------" << std::endl; std::cout << "ソート結果: " << std::endl; arr.clear(); arr.push_back(std::make_pair(3.7, "a")); arr.push_back(std::make_pair(2.3, "b")); arr.push_back(std::make_pair(7.5, "c")); arr.push_back(std::make_pair(1.2, "d")); arr.push_back(std::make_pair(6.8, "e")); arr.push_back(std::make_pair(5.6, "f")); arr.push_back(std::make_pair(2.4, "g")); arr.push_back(std::make_pair(0.9, "h")); arr.push_back(std::make_pair(6.1, "i")); arr.push_back(std::make_pair(5.8, "j")); // arr.push_back(std::make_pair(4.5, "k")); arr.push_back(std::make_pair(8.2, "l")); arr.push_back(std::make_pair(9.1, "m")); arr.push_back(std::make_pair(1.7, "n")); arr.push_back(std::make_pair(5.3, "o")); arr.push_back(std::make_pair(7.7, "p")); arr.push_back(std::make_pair(3.8, "q")); arr.push_back(std::make_pair(4.2, "r")); std::sort(arr.begin(), arr.end(), [](const PairTypeFloat& a, const PairTypeFloat& b) {return a.first < b.first; }); for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } }
#include <iostream> #include <vector> #include <array>
// 配列の分割を行う関数 template<typename Container, class PCond> int64_t partition(Container& arr, int64_t low, int64_t high, PCond& pivot) { int64_t i = low; int64_t j = high-1; int64_t bound; while (true) { // 条件に合致するものを探す while ( (pivot.IsTrue(i) == true) ) { i++; if (i >= high) break; } // 条件に合致しないものを探す while ( (pivot.IsFalse(j) == true) ) { j--; if (j == -1) break; } // 左右の走査が交差した場合に分割終了 if (i >= j) { bound = i; break; } // 条件に合致しない要素とする要素を交換 pivot.Swap(i, j); // 走査を進める i++; j--; } return bound; }
enum BoolType { True = 1, False = 0 }; using PairTypeBool = std::pair<BoolType, std::string>; using PairTypeFloat = std::pair<float, std::string>;
// データを分割する時に必要な関数をまとめたもの struct PartitionFuncBool { std::vector<PairTypeBool>& _array; int64_t _pivot; float _value; PartitionFuncBool(std::vector<PairTypeBool>& arr,int64_t pivot_index):_array(arr) { _pivot = pivot_index; _value = _array[_pivot].first; } // 要素の交換関数 void Swap(size_t i, size_t j) { // pivotが移動したので最新のindexを保存 if(i==_pivot) _pivot = j; else if(j==_pivot) _pivot = i; std::swap(_array[i], _array[j]); } // 条件判定関数 bool IsTrue(size_t i) { return _array[i].first == _value; } bool IsFalse(size_t i) { return !IsTrue(i); } };
// データを分割する時に必要な関数をまとめたもの struct PartitionFuncFloat { std::vector<PairTypeFloat>& _array; int64_t _pivot; float _value; PartitionFuncFloat(std::vector<PairTypeFloat>& arr, int64_t pivot_index) :_array(arr) { _pivot = pivot_index; _value = _array[_pivot].first; } // 要素の交換関数 void Swap(size_t i, size_t j) { // pivotが移動したので最新のindexを保存 if (i == _pivot) _pivot = j; else if (j == _pivot) _pivot = i; std::swap(_array[i], _array[j]); } // 条件判定関数 bool IsTrue(size_t i) { return _array[i].first <= _value; } bool IsFalse(size_t i) { return !IsTrue(i); } };
template<typename Container, class PCond> std::array<int64_t, 3> partition_3area(Container& arr, int64_t low, int64_t high, PCond& cond) { int64_t bound_idx = partition< Container, PCond>(arr, low, high, cond); // cond._pivot ... 移動後のpivotのindex 前半にあるはず // bound_idx ... 条件に合致しない要素の最初のindex 後半にあるはず // pivotが前半の末尾以外にある場合は、前半末尾へ移動 if(cond._pivot != bound_idx-1) cond.Swap(cond._pivot, bound_idx-1); return { low, bound_idx-1,// 移動後のpivot high }; }
void testbool() { std::vector<PairTypeBool> arr; arr.push_back(std::make_pair(True, "a")); arr.push_back(std::make_pair(False, "b")); arr.push_back(std::make_pair(False, "c")); arr.push_back(std::make_pair(True, "d")); arr.push_back(std::make_pair(True, "e")); arr.push_back(std::make_pair(True, "f")); arr.push_back(std::make_pair(False, "g")); arr.push_back(std::make_pair(True, "h")); arr.push_back(std::make_pair(False, "i")); arr.push_back(std::make_pair(True, "j")); std::cout << "分割前の配列: " << std::endl; for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } PartitionFuncBool cond(arr, arr.size() / 2); // ピボットの値を取得 // ピボット表示 std::cout << "ピボット: " << "[" << cond._pivot << "]" << arr[cond._pivot].first << " " << arr[cond._pivot].second << std::endl; auto ret = partition_3area(arr, (size_t)0, arr.size(), cond); std::cout << "分割後の配列: " << std::endl; for (size_t i = ret[0]; i < ret[1]; i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } std::cout << std::endl; std::cout << "[" << ret[1] << "] " << arr[ret[1]].first << " " << arr[ret[1]].second << std::endl; std::cout << std::endl; for (size_t i = ret[1] + 1; i < ret[2]; i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } }
void testfloat() { std::vector<PairTypeFloat> arr; arr.push_back(std::make_pair(3.7 , "a")); arr.push_back(std::make_pair(2.3 , "b")); arr.push_back(std::make_pair(7.5 , "c")); arr.push_back(std::make_pair(1.2 , "d")); arr.push_back(std::make_pair(6.8 , "e")); arr.push_back(std::make_pair(5.6 , "f")); arr.push_back(std::make_pair(2.4 , "g")); arr.push_back(std::make_pair(0.9 , "h")); arr.push_back(std::make_pair(6.1 , "i")); arr.push_back(std::make_pair(5.3 , "j")); arr.push_back(std::make_pair(4.5 , "k")); arr.push_back(std::make_pair(8.2 , "l")); arr.push_back(std::make_pair(9.1 , "m")); arr.push_back(std::make_pair(1.7 , "n")); arr.push_back(std::make_pair(5.3 , "o")); arr.push_back(std::make_pair(7.7 , "p")); arr.push_back(std::make_pair(3.8 , "q")); arr.push_back(std::make_pair(4.2 , "r")); std::cout << "分割前の配列: " << std::endl; for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } PartitionFuncFloat cond(arr, arr.size()/2); // ピボットの値を取得 // ピボット表示 std::cout << "ピボット: " << "[" << cond._pivot << "]" << arr[cond._pivot].first << " " << arr[cond._pivot].second << std::endl; auto ret = partition_3area(arr, (size_t)0, arr.size(), cond); std::cout << "分割後の配列: " << std::endl; for (size_t i = ret[0]; i < ret[1]; i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } std::cout << std::endl; std::cout << "[" << ret[1] << "] " << arr[ret[1]].first << " " << arr[ret[1]].second << std::endl; std::cout << std::endl; for (size_t i = ret[1] + 1; i < ret[2]; i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } }
int main() { testfloat(); std::cout << "-----------------------------------\n"; testbool(); return 0; }
分割前の配列:
[0] 3.7 a
[1] 2.3 b
[2] 7.5 c
[3] 1.2 d
[4] 6.8 e
[5] 5.6 f
[6] 2.4 g
[7] 0.9 h
[8] 6.1 i
[9] 5.3 j
[10] 4.5 k
[11] 8.2 l
[12] 9.1 m
[13] 1.7 n
[14] 5.3 o
[15] 7.7 p
[16] 3.8 q
[17] 4.2 r
ピボット: [9]5.3 j
分割後の配列:
[0] 3.7 a
[1] 2.3 b
[2] 4.2 r
[3] 1.2 d
[4] 3.8 q
[5] 5.3 o
[6] 2.4 g
[7] 0.9 h
[8] 1.7 n
[9] 4.5 k
[10] 5.3 j
[11] 8.2 l
[12] 9.1 m
[13] 6.1 i
[14] 5.6 f
[15] 7.7 p
[16] 6.8 e
[17] 7.5 c
分割前の配列:
[0] 1 a
[1] 0 b
[2] 0 c
[3] 1 d
[4] 1 e
[5] 1 f
[6] 0 g
[7] 1 h
[8] 0 i
[9] 1 j
ピボット: [5]1 f
分割後の配列:
[0] 1 a
[1] 1 j
[2] 1 h
[3] 1 d
[4] 1 e
[5] 1 f
[6] 0 g
[7] 0 c
[8] 0 i
[9] 0 b
KdTreeを愚直に作成してみる。
#include<eigen/Dense> #include <vector>
struct node{ node(size_t index, int axis) : index(index), axis(axis){} size_t index; // 頂点ID int axis; // 0:x, 1:y, 2:z node* left; // ツリー1 node* right; // ツリー2 };
// pがpivotの左側にあるかどうかを判定 bool isLeft(const Eigen::Vector3d& pivot, const Eigen::Vector3d& p, int axis) { return p[axis] < pivot[axis]; }
// 再帰的にツリー作成
node* buildnode(const std::vector<Eigen::Vector3d>& points, std::vector<size_t>& indices,int axis) { if(indices.size()==0) return nullptr; if(indices.size()==1){ // 最後のノードに頂点idを与えて返る node* mynode = new node(indices[0],axis); mynode->left = nullptr; mynode->right = nullptr; return mynode; } // pivot を決定 size_t pivotIndex = indices.size() / 2; // indexの配列の中央をpivotとする Eigen::Vector3d Pivot = points[indices[pivotIndex]]; std::vector<size_t> left_indices; // ツリーの左側にセットするindexリスト std::vector<size_t> right_indices; // ツリーの右側にセットするindexリスト // pivot で分割 for (size_t i = 0; i < indices.size(); i++) { if (i == pivotIndex) { continue; } if (isLeft(Pivot, points[indices[i]], axis)) { // ツリーの左側にセットするindexリスト更新 left_indices.push_back(indices[i]); } else { // ツリーの右側にセットするindexリスト更新 right_indices.push_back(indices[i]); } } // 新たなノード作成 node* mynode = new node(indices[pivotIndex],axis); mynode->left = buildnode(points, left_indices, (axis+1) % 3); // 左側を構築 mynode->right = buildnode(points, right_indices, (axis+1) % 3); // 右側を構築 return mynode; }
class mykdtree { std::vector<Eigen::Vector3d>& _points; node* node_root; public: mykdtree(std::vector<Eigen::Vector3d>& points) : _points(points) {} // ツリーの構築 void build() { // 最初のindexリストを作成 std::vector<size_t> indices(_points.size()); for (size_t i = 0; i < _points.size(); i++) { indices[i] = i; } // 再帰的にツリー作成 node_root = buildnode(_points, indices, 0/*X軸*/); } node* getRoot() { return node_root; } };
// デバッグ用 ツリー内のすべてのindexを取得する関数 void getAllIndices(node* root, std::vector<size_t>& indices) { if (root == nullptr) { return; } // 現在のノードのindexを追加 indices.push_back(root->index); // 左部分木のindexを取得 getAllIndices(root->left, indices); // 右部分木のindexを取得 getAllIndices(root->right, indices); }
点群を分割できていることを確認するために、ツリーで分割した領域を着色してみる。
#include <iostream> //VTK_MODULE_INITに必要 #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkPolyDataMapper.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"psapi.lib") #pragma comment(lib,"dbghelp.lib") #pragma comment(lib,"ws2_32.lib") #include <Eigen/Core> #include <array> #include <vtkActor.h> #include <vtkPoints.h> #include <vtkPolyData.h> #include <vtkUnsignedCharArray.h> #include <vtkPointData.h> #include <vtkVertexGlyphFilter.h> #include <vtkProperty.h> #include "mykdtree.hpp" //必須 VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); // VTK表示用 struct MyVtkCloud { std::vector<Eigen::Vector3d> points; std::array<unsigned char, 3> color; std::vector<std::array<unsigned char, 3> > color_array; vtkSmartPointer<vtkActor> actor; void makeActor() { // VTKのデータ構造に変換 vtkSmartPointer<vtkPoints> vtk_points = vtkSmartPointer<vtkPoints>::New(); vtkSmartPointer<vtkUnsignedCharArray> vtk_colors = vtkSmartPointer<vtkUnsignedCharArray>::New(); vtk_colors->SetNumberOfComponents(3); // RGB vtk_colors->SetName("Colors"); for (size_t i = 0; i < points.size(); ++i) { // 点を追加 vtk_points->InsertNextPoint(points[i].x(), points[i].y(), points[i].z()); if (color_array.size() == 0) { vtk_colors->InsertNextTypedTuple(color.data()); } else { vtk_colors->InsertNextTypedTuple(color_array[i].data()); } } //////////////////////////////////////////////////////////// vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New(); polyData->SetPoints(vtk_points); polyData->GetPointData()->SetScalars(vtk_colors); //////////////////////////////////////////////////////////// vtkSmartPointer<vtkVertexGlyphFilter> vertexFilter = vtkSmartPointer<vtkVertexGlyphFilter>::New(); vertexFilter->SetInputData(polyData); vertexFilter->Update(); //////////////////////////////////////////////////////////// vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputConnection(vertexFilter->GetOutputPort()); vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); // 頂点サイズを指定 actor->GetProperty()->SetPointSize(5); // ここで頂点サイズを指定します this->actor = actor; } }; int main(int /*argc*/, char** /*argv*/) { MyVtkCloud cloud1; srand((unsigned int)7); // ランダムな点群を作成 for (size_t i = 0; i < 50000; ++i) { cloud1.points.push_back(Eigen::Vector3d::Random()); } cloud1.color = std::array<unsigned char, 3>{ 0, 255, 0 }; cloud1.makeActor(); // ツリー作成 mykdtree kdtree(cloud1.points); kdtree.build(); //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// // 各領域を着色して表示 std::vector<size_t> indices_left; std::vector<size_t> indices_right_right; std::vector<size_t> indices_right_left_right; std::vector<size_t> indices_right_left_left; node* root = kdtree.getRoot(); // X軸の分割、左側のノードを取得 (赤) getAllIndices(root->left, indices_left); // X軸の分割、右側のノードの右を取得 (緑) getAllIndices(root->right->right, indices_right_right); // X軸の分割、右側のノードの左の右を取得 (青) getAllIndices(root->right->left->right, indices_right_left_right); // X軸の分割、右側のノードの左の左を取得 (紫) getAllIndices(root->right->left->left, indices_right_left_left); MyVtkCloud cloud_L; for (auto i : indices_left)cloud_L.points.push_back(cloud1.points[i]); cloud_L.color = std::array<unsigned char, 3>{ 255, 0, 0 }; cloud_L.makeActor(); MyVtkCloud cloud_RR; for (auto i : indices_right_right)cloud_RR.points.push_back(cloud1.points[i]); cloud_RR.color = std::array<unsigned char, 3>{ 0, 255, 0 }; cloud_RR.makeActor(); MyVtkCloud cloud_RLR; for (auto i : indices_right_left_right)cloud_RLR.points.push_back(cloud1.points[i]); cloud_RLR.color = std::array<unsigned char, 3>{ 0, 0, 255 }; cloud_RLR.makeActor(); MyVtkCloud cloud_RLL; for (auto i : indices_right_left_left)cloud_RLL.points.push_back(cloud1.points[i]); cloud_RLL.color = std::array<unsigned char, 3>{ 255, 0, 255 }; cloud_RLL.makeActor(); //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// ////////////////////////////////////// // 表示 auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(cloud_L.actor); renderer->AddActor(cloud_RR.actor); renderer->AddActor(cloud_RLR.actor); renderer->AddActor(cloud_RLL.actor); renderer->ResetCamera(); ////////////////////////////////////// auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); ////////////////////////////////////// auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); renderWindow->SetInteractor(interactor); renderWindow->Render(); interactor->Start(); //イベントループへ入る return 0; }
partitionは配列を[条件を満たす領域][満たさない領域]に分割する。C++の標準ライブラリにはstd::partitionという関数が用意されているので、用途によってはこれを使ってもよい。
#include <iostream> #include <vector> #include <algorithm> // 偶数かどうかを判定する関数 bool is_even(int n) { return n % 2 == 0; } int main() { std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 偶数の要素を表示 std::cout << "全データ: "; for (int i = 0; i < numbers.size(); i++) { std::cout << "[" << i << "] " << numbers[i] << std::endl; } std::cout << std::endl; // std::partitionで偶数と奇数に分ける auto it = std::partition(numbers.begin(), numbers.end(), is_even); size_t bound = std::distance(numbers.begin(), it); // 偶数の要素を表示 std::cout << "偶数: " << 0 << " ... " << bound-1 << std::endl; for(int i=0;i< bound;i++){ std::cout << "[" << i << "] " << numbers[i] << std::endl; } std::cout << std::endl; // 奇数の要素を表示 std::cout << "奇数: " << bound << " ... " << numbers.size() << std::endl; for (int i = bound; i < numbers.size(); i++) { std::cout << "[" << i << "] " << numbers[i] << std::endl; } std::cout << std::endl; return 0; }
クイックソートと同じ原理で、配列の先頭と末尾から探索し、条件に合うものと合わないものを交換していけばできる。というかパーティションを再帰的に繰り返したのがクイックソートだといえる。
#include <iostream> #include <vector>
// 配列の分割を行う関数 template<typename Container, class PCond> int64_t partition(Container& arr, int64_t low, int64_t high, PCond pivot) { int64_t i = low; int64_t j = high-1; int64_t bound; while (true) { // 条件に合致するものを探す while ( (pivot.IsTrue(i) == true) ) { i++; if (i >= high) break; } // 条件に合致しないものを探す while ( (pivot.IsFalse(j) == true) ) { j--; if (j == -1) break; } // 左右の走査が交差した場合に分割終了 if (i >= j) { bound = i; break; } // 条件に合致しない要素とする要素を交換 pivot.Swap(i, j); // 走査を進める i++; j--; } return bound; }
enum MyBOOL { True, False }; using PairType = std::pair<MyBOOL, std::string>;
// データを分割する時に必要な関数をまとめたもの struct PartitionFunc { std::vector<PairType>& _array; PartitionFunc(std::vector<PairType>& arr):_array(arr) {} // 要素の交換関数 void Swap(size_t i, size_t j) { std::swap(_array[i], _array[j]); } // 条件判定関数 bool IsTrue(size_t i) { return _array[i].first == True; } bool IsFalse(size_t i) { return !IsTrue(i); } };
int main() { std::vector<PairType> arr; arr.push_back(std::make_pair(True , "a")); arr.push_back(std::make_pair(False, "b")); arr.push_back(std::make_pair(False, "c")); arr.push_back(std::make_pair(True , "d")); arr.push_back(std::make_pair(True , "e")); arr.push_back(std::make_pair(True , "f")); arr.push_back(std::make_pair(False, "g")); arr.push_back(std::make_pair(True , "h")); arr.push_back(std::make_pair(False, "i")); arr.push_back(std::make_pair(True , "j")); std::cout << "分割前の配列: " << std::endl; for (size_t i = 0; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } PartitionFunc cond(arr); size_t bound_idx = partition< std::vector<PairType>, PartitionFunc>(arr, (size_t)0, arr.size(), cond); std::cout << "分割後の配列: " << std::endl; for (size_t i = 0; i < bound_idx; i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } std::cout << std::endl; for (size_t i = bound_idx; i < arr.size(); i++) { std::cout << "[" << i << "] " << arr[i].first << " " << arr[i].second << std::endl; } return 0; }
分割前の配列:
[0] 0 a
[1] 1 b
[2] 1 c
[3] 0 d
[4] 0 e
[5] 0 f
[6] 1 g
[7] 0 h
[8] 1 i
[9] 0 j
分割後の配列:
[0] 0 a
[1] 0 j
[2] 0 h
[3] 0 d
[4] 0 e
[5] 0 f
[6] 1 g
[7] 1 c
[8] 1 i
[9] 1 b
再帰関数のバグが治らないので再帰を可視化するコードを書いた。
.dotフォーマットで出力し、Graphvizで画像化する。
#include <iostream> #include <vector> #include <string> #include <fstream> #include <optional> struct TraceNode { std::string name; std::string retval; std::optional<std::string> text; std::vector<TraceNode> children; };
class RecursiveTrace { TraceNode root; std::vector<TraceNode*> stack; public: void setText(std::string text) { stack.back()->text = text; } void addText(std::string text) { stack.back()->text = stack.back()->text.value() + text; } RecursiveTrace() { stack.push_back(&root); } TraceNode* getRoot() { return &root; } void push(std::string name="") { stack.back()->children.emplace_back(); stack.push_back(&stack.back()->children.back()); stack.back()->name = name; } void pop() { stack.pop_back(); } void setReturn(std::string retval) { stack.back()->retval = retval; } };
RecursiveTrace ftrace;
int fibonacci(int n) { if (n <= 1) return n; // 基底条件 ftrace.setText( std::string("in=") + std::to_string(n)+"\\n"); ftrace.push(std::string("a:")+std::to_string(n) + "-1"); int a = fibonacci(n - 1);// 再帰呼び出し ftrace.pop(); ftrace.push(std::string("b:") + std::to_string(n) + "-2"); int b = fibonacci(n - 2);// 再帰呼び出し ftrace.pop(); ftrace.addText("a:"+std::to_string(a)+", b:"+std::to_string(b)); ftrace.setReturn(std::to_string(a+b)); return a + b; }
//////////////////////////////////////////////// //////////////////////////////////////////////// ////////////////////////////////////////////////
// ノードを再帰的に出力する void writeNodes(std::ofstream& file, const TraceNode& node, int& nodeId) { if (!node.text) return; // 無効なノードで出力を終了 // ノードの名前を出力 std::string name = (node.name.length()==0)?"":node.name + "\\n"; std::string text = (node.text.value().length()==0)?"":node.text.value() + "\\n"; std::string retval = (node.retval.length()==0)?"":"("+node.retval + ")"; // ノードのテキストを出力 int currentId = nodeId++; file << " " << currentId << " [label=\"" << name << text << retval << "\"];\n"; // 子ノードの出力 for (const auto& child : node.children) { if (child.text) { // 子ノードが有効か確認 int childId = nodeId; file << " " << currentId << " -> " << childId << ";\n"; writeNodes(file, child, nodeId); } } }
// Graphviz用のDOTファイルを生成する void generateDotFile(const TraceNode& root, const std::string& filename) { std::ofstream file(filename); file << "digraph TraceTree {\n"; file << " node [shape=circle];\n"; int nodeId = 0; // ノードIDのカウンタ writeNodes(file, root, nodeId); file << "}\n"; file.close(); }
//////////////////////////////////////////////// //////////////////////////////////////////////// //////////////////////////////////////////////// int main() { int ret = fibonacci(7); auto rr = ftrace.getRoot(); // dotコマンドで画像を生成できる // 例 // dot -Tpng RecursiveTrace.dot -o RecursiveTrace.png generateDotFile(*rr, "RecursiveTrace.dot"); }
以下のコマンドで出力された.dotを画像化すると呼び出し履歴が見られる
nanoflannというヘッダオンリーのライブラリがあり、KdTreeを実装している。
flannとついているがFLANN(Fast Library for Approximate Nearest Neighbors)とは別物らしい。
https://github.com/jlblancoc/nanoflann
ヘッダオンリーで、nanoflann.hppをプロジェクトにインクルードするだけで使える。
さらに、点群型をテンプレートで指定するのでOpen3D等の他のライブラリと併用するときも便利。
#include <iostream> #include <vector> #include <array> #include "nanoflann.hpp"
// 点群データを表現するクラス struct MyPointCloud { // 点群データ std::vector< std::array<float,3> > points; // nanoflannで必要となるインタフェース関数 inline size_t kdtree_get_point_count() const { return points.size(); } // nanoflannが呼び出す、点群の座標を返す関数 // dim [0] [1] [2] は、それぞれ x, y, z inline float kdtree_get_pt(const size_t idx, const size_t dim) const { return points[idx][dim]; } // BBOXは使わなくても定義は必要 template <class BBOX> bool kdtree_get_bbox(BBOX&) const { return false; } };
/////////////////////////////////////////////////////////// // KdTreeの定義(L2ノルム L2_Simple_Adaptor を使う3次元の点群) // L2ノルムは、ユークリッド距離の2乗 // L1ノルムは、マンハッタン距離 using KdTree = nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor<float, MyPointCloud>, MyPointCloud, 3 /* 次元数 */>; ///////////////////////////////////////////////////////////
void createCloud(MyPointCloud& cloud) { // サンプル点群を作成 for (size_t i = 0; i < 30000; i++) { cloud.points.push_back(std::array<float, 3>{ static_cast<float>(rand()) / RAND_MAX, static_cast<float>(rand()) / RAND_MAX, static_cast<float>(rand()) / RAND_MAX } ); } }
int main() { MyPointCloud cloud; createCloud(cloud); //////////////////////////////////////////////////////////////////////// // leaf max size は1にしているので、最後まで分割される // leaf max size を2以上にすると、ツリーの末端では配列になり、総当たりでの探索になる KdTree tree( 3 /* 次元数 */, cloud, nanoflann::KDTreeSingleIndexAdaptorParams(1 /* leaf max size */) ); tree.buildIndex(); //////////////////////////////////////////////////////////////////////// float P[3] = { 0.5, 0.5, 0.5 }; float radius = 0.2; float search_radius = radius * radius; // 半径の二乗 // 結果の格納先 std::vector<nanoflann::ResultItem<unsigned int, float>> ret_matches; nanoflann::SearchParameters params; // 探索 const size_t nMatches = tree.radiusSearch( P, search_radius, ret_matches, params ); //////////////////////////////////////////////////////////////////////// std::cout << "found " << nMatches << " points\n"; for (size_t i = 0; i < nMatches; i++) { std::cout << i << " point index: " << ret_matches[i].first << " distance: " << sqrt(ret_matches[i].second) << std::endl; } }
ubidiでテキストが左から右か、右から左か判定する。
#include <iostream> #include <unicode/ubidi.h> #include <unicode/unistr.h> #include <unicode/utypes.h> #if defined(_DEBUG) #pragma comment(lib, "icuucd.lib") #else #pragma comment(lib, "icuuc.lib") #endif // SetConsoleOutputCP #include <windows.h> #include <codecvt>
bool isRTL(const std::u16string& text) { UErrorCode errorCode = U_ZERO_ERROR; const char16_t* buffer16 = reinterpret_cast<const char16_t*>(text.c_str()); size_t buffer_length = text.length(); // UBiDi オブジェクトを作成 UBiDi* bidi = ubidi_openSized(buffer_length, 0, &errorCode); // テキストの方向性を解析 ubidi_setPara(bidi, buffer16, buffer_length, UBIDI_DEFAULT_LTR, nullptr, &errorCode); if (U_FAILURE(errorCode)) { ubidi_close(bidi); return false; } // 方向を取得 UBiDiDirection direction = ubidi_getDirection(bidi); // UBiDi オブジェクトを解放 ubidi_close(bidi); // RTL なら true, LTR なら false return direction == UBIDI_RTL; }
int main() { SetConsoleOutputCP(CP_UTF8); // コンソール出力にUTF8を指定 std::u16string text[] = { u"Hello, how are you?", // 英語(左から右) u"こんにちは、お元気ですか?", // 日本語(左から右) u"안녕하세요, 어떻게 지내세요?", // 韓国語(左から右) u"مرحباً، كيف حالك؟", // アラビア語(右から左) u"שלום, מה שלומך", // ヘブライ語(右から左) u"ܫܠܡܐ ܐܝܟ ܐܢܬܐ", // シリア語(右から左) u"ހެޔޮން، ކިހިންދީ؟" // ディベヒ語(右から左) }; for(auto& t : text) { if (isRTL(t)) { std::cout << " RTL "; // u16string を UTF-8 に変換 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; std::string utf8str = converter.to_bytes(t); std::cout << utf8str << std::endl; } else { std::cout << " LTR "; // u16string を UTF-8 に変換 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; std::string utf8str = converter.to_bytes(t); std::cout << utf8str << std::endl; } } return 0; }
方向 | 例文 | 言語 |
---|---|---|
LTR | Hello, how are you? | 英語 |
LTR | こんにちは、お元気ですか? | 日本語 |
LTR | 안녕하세요, 어떻게 지내세요? | 韓国語 |
RTL | مرحباً، كيف حالك؟ | アラビア語 |
RTL | שלום, מה שלומך | ヘブライ語 |
RTL | ܫܠܡܐ ܐܝܟ ܐܢܬܐ | シリア語 |
RTL | ހެޔޮން، ކިހިންދީ؟ | ディベヒ語 |
CLD3はGoogleが提供する、文字列から言語を推測するためのライブラリ。Apache License 2.0。
推測するものなので、例えば「漢字」は中国語として判断される。
サンスクリット語とヒンディー語、ロシア語とウクライナ語など、同じ文字や単語を使っている言語の識別制度は低くなる。
今回はvcpkgでインストールする。
#pragma warning(disable:4996) #ifdef _DEBUG #pragma comment(lib,"libprotobufd.lib") //#pragma comment(lib, "abseil_dll.lib") #else #pragma comment(lib,"libprotobuf.lib") #endif #include <cld3/nnet_language_identifier.h> #pragma comment(lib,"cld3.lib") // SetConsoleOutputCP #include <windows.h> int main() { SetConsoleOutputCP(CP_UTF8); // コンソール出力にUTF8を指定 // 検出器を初期化(最小/最大テキスト長を指定) chrome_lang_id::NNetLanguageIdentifier lang_id(0, 512); std::string text8[] = { u8"日本", u8"你好", u8"汉字", u8"榊峠", // 榊も峠も和製漢字なので、日本語として判断されてほしい。が。。。 u8"中国語では你好", u8"英語ではHello", u8"Hello", u8"Bonjour", // フランス語 u8"안녕하세요", u8"こんにちは", u8"Здравствуйте", // ロシア語 u8"Це моя книга", // ウクライナ語 u8"Это моя книга", // ロシア語 u8"مرحبا", // アラビア語 u8"नमस्ते, आप कैसे हैं?", // ヒンディー語 u8"नमस्ते", // ナマステ(サンスクリット語) u8"नमः सर्वेभ्यः।",// サンスクリット語 u8"Olá" // ポルトガル語 }; std::cout << u8"文,言語,精度" << std::endl; for( const auto& text : text8 ) { // 言語を検出 auto result = lang_id.FindLanguage(text); std::cout << text << ","; std::cout << result.language << ","; std::cout << result.probability << std::endl; } }
文 | 言語 | 精度 |
---|---|---|
日本 | zh | 0.976997 |
你好 | zh | 0.999996 |
汉字 | zh | 0.998365 |
榊峠 | zh | 0.998102 |
中国語では你好 | ja | 0.999899 |
英語ではHello | ja | 0.999996 |
Hello | sr | 0.830728 |
Bonjour | no | 0.933856 |
안녕하세요 | ko | 0.999985 |
こんにちは | ja | 1 |
Здравствуйте | ru | 0.314022 |
Це моя книга | uk | 0.902219 |
Это моя книга | ru | 0.996748 |
مرحبا | fa | 0.996155 |
नमस्ते, आप कैसे हैं? | hi | 0.999293 |
नमस्ते | mr | 0.999077 |
नमः सर्वेभ्यः। | mr | 0.722388 |
Olá | ga | 0.997063 |