二次元画像のぼかしフィルタのガウシアンフィルタについて。検索すると3x3のフィルタとして以下が出てくる。
それとは別に以下の式が出てくる。
この式でどんな数字が出来上がるのかが気になったのでExcelで計算してみる。
Excelを開き、C5→I1に式を張り付ける。例えばC6の式は以下のようになる。
=(1/(2*PI()*$B$2*$B$2))*EXP(-(C$4*C$4+$B6*$B6)/(2*$B$2*$B$2))
※B2にρ、B3に分母を入れておく。
上図左で得られた結果は最終結果なので、分数にしたときに1/16,2/16,...になっているはずなので、16倍する。そのうえで四捨五入すると上図右の結果となる。
上の結果は検索して出てくる下のような5x5のフィルタと違う。
結論、7x7くらいまでは検索してすぐ出てくるフィルタを直打ちして使う、より大きなフィルタや個性的なフィルタを使いたい場合は式から作成する
C++のEigenライブラリで標準化までやってみる。本当はPCAもやりたいしEigenSolverを使えばいい事まではわかっているが結果の確認ができないので今はやらない。
#include <iostream> #include <vector> #include <Eigen/Dense> int main() { std::vector<std::array<float, 3> > cloud; cloud.push_back({ -1.841658711, 0.324050009, 1.041258931 }); cloud.push_back({-1.449076772, 0.431593835, 0.987673104 }); cloud.push_back({-1.04681015, 0.415599823, 1.049820662 }); cloud.push_back({-0.646572888, 0.42549479, 1.087714672 }); cloud.push_back({-0.25920701, 0.599574149, 0.971796691 }); cloud.push_back({0.149769783, 0.497985959, 1.114130974 }); cloud.push_back({-1.883069754, 0.6849733, 1.249212146 }); cloud.push_back({-1.481496215, 0.677821219, 1.303076267 }); cloud.push_back({-1.083532453, 0.716715276, 1.313803315 }); Eigen::MatrixXf mat(9,3); for (size_t i = 0; i < cloud.size();i++) { mat.row(i) << cloud[i][0] , cloud[i][1] , cloud[i][2]; } // センタリングしたデータを作成 Eigen::MatrixXf centered = mat.rowwise() - mat.colwise().mean(); std::cout << "---" << std::endl;
// センタリングされたデータに対して、列ごとに標準偏差を求め、各列の要素を標準偏差で割る for (int c = 0; c < 3; c++) { auto col = mat.col(c).array(); double std_dev = sqrt((col - col.mean()).square().sum() / col.size()); centered.col(c) /= std_dev; }
std::cout << std::endl << centered << std::endl;// 表示 std::cout << std::endl << std::endl; ////////////////////// // 分散共分散行列 Eigen::MatrixXf cov = (centered.adjoint() * centered) / double(centered.rows());
std::cout << cov << std::endl;// 表示 return 0; }
Excelと同じ結果が出ている。
データのセンタリングは、データの平均値を0にすることなので、全てのデータから平均値を引けばよい。
rowwise()-colwise().mean()で行える。
// センタリングしたデータを作成 Eigen::MatrixXf centered = mat.rowwise() - mat.colwise().mean();
rowwise(),colwise()は全ての行に対して、あるいは全ての列に対して処理をしたい時に使う演算子みたいなもの。
例えばmean()は平均を計算する関数なので、colwise().mean()で各列の平均値を求めることができる
Eigen::MatrixXf mat(2, 3); mat.row(0) << 1, 3, 5; mat.row(1) << 2, 4, 6; std::cout << mat.colwise().mean() << std::endl;
結果:
つまり、各行の値に対して、これらを引けばよいので、
を呼び出す。
標準偏差は、全ての列のデータに対して、平均値を引いてそれを二乗したものを合計するわけだが、その計算をcolwise()で(なぜか)行えないので、一度arrayに変換する必要がある。
auto col = mat.col(c).array();
arrayに変換したら、arrayの各要素に対して演算を行える関数が用意されているので、それを駆使して列ごとに標準偏差を求める。
double std_dev = sqrt( (col/*各要素から*/ - col.mean()/*全要素の平均を引き*/) .square()/*それぞれ二乗して*/ .sum()/*全て合計して*/ / col.size()/*要素数で割り*/ )/*平方根をとる*/;
各列の要素を、標準偏差で割る。
centered.col(c) /= std_dev;
さっぱりわからん
主成分分析はEigenSolverに上記で計算した分散共分散行列のcovを入れて行うらしい。
//Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> es(cov); Eigen::EigenSolver<Eigen::MatrixXf> es(cov); Eigen::MatrixXcf eigen_vectors = es.eigenvectors(); Eigen::VectorXcf eigen_values = es.eigenvalues();
そもそもcovは分散共分散行列というヤツなのか?私は主成分分析をするにはそれにする必要があるらしいので、それに用いることができるならそれであろうという認識であって数学的な事は何一つ理解していないのだが。
厳密には、したほうがいい場合としないほうがいい場合があるらしい。
標準化の式は以下。各値から値全体の平均を引き、その値を標準偏差で割る。これをすべての値に対して行う。
エクセルにはSTANDARDIZEという関数がある。ただしこれを使うためには平均と標準偏差をそれぞれ別途計算する必要がある。
自前実装したコードと上記Excelで半自動計算したものと比較。同じ値が出ている。
#include <iostream> #include <vector> #include <array> using vector3f = std::vector<std::array<float, 3>>;
//! @brief データの平均を求める //! @param [in] vec データの配列 //! @return 各要素の平均 std::array<float, 3> Average(const vector3f& vec) { // 初期化 std::array<float, 3> ave; ave[0] = 0; ave[1] = 0; ave[2] = 0; // 各要素平均 for (size_t i = 0; i < vec.size(); i++) { ave[0] += vec[i][0]; ave[1] += vec[i][1]; ave[2] += vec[i][2]; } ave[0] /= vec.size(); ave[1] /= vec.size(); ave[2] /= vec.size(); return ave; }
//分散 std::array<float, 3> Distributed(const std::array<float, 3>& average, const vector3f& vec) { int n = vec.size(); std::array<float, 3> s2{ 0,0,0 }; for (int j = 0; j < 3; j++) { for (const auto& v : vec) { s2[j] += std::pow(v[j] - average[j], 2); } s2[j] = 1.f / n * s2[j]; } return s2; }
//標準偏差 std::array<float, 3> StandardDeviation(const std::array<float, 3>& average, const vector3f& vec) { std::array<float, 3> d = Distributed(average, vec); d[0] = sqrt(d[0]); d[1] = sqrt(d[1]); d[2] = sqrt(d[2]); return d; }
//標準化 平均を引いて標準偏差で割る // average 平均 // sd 標準偏差 void Standardization(vector3f& src, const std::array<float, 3>& average, const std::array<float, 3>& sd) { for (auto&& p : src) { p[0] = (p[0] - average[0]) / sd[0]; p[1] = (p[1] - average[1]) / sd[1]; p[2] = (p[2] - average[2]) / sd[2]; } }
int main() { vector3f data; data.push_back({ -1.841658711,0.324050009,1.041258931 }); data.push_back({-1.449076772,0.431593835,0.987673104 }); data.push_back({-1.04681015,0.415599823,1.049820662 }); data.push_back({-0.646572888,0.42549479,1.087714672 }); data.push_back({-0.25920701,0.599574149,0.971796691 }); data.push_back({ 0.149769783,0.497985959,1.114130974 }); data.push_back({-1.883069754,0.6849733,1.249212146 }); data.push_back({-1.481496215,0.677821219,1.303076267 }); data.push_back({-1.083532453,0.716715276,1.313803315 }); std::array<float, 3> _s = Average(data); std::array<float, 3> sd = StandardDeviation(_s, data); Standardization(data, _s, sd); for (size_t i = 0; i < data.size(); i++) { printf("%lf , %lf , %lf\n", data[i][0], data[i][1], data[i][2]); } }
U16_NEXT_UNSAFEを使ってutf16文字列を一文字ずつ処理する。
#include <cstdio> #pragma warning(disable:4996) #include <unicode/utf16.h> #include <locale> int main() { std::setlocale(LC_CTYPE, ""); const char16_t* str = u"い👨👦は"; const char16_t* p = str; int i = 0; while (p[i]) { bool bs = U16_IS_SURROGATE(p[i]);//サロゲートペアかどうか int iback = i; char32_t u32c; U16_NEXT_UNSAFE(p, i,u32c); if (bs==false) { if (p[iback] == 0x200d) {//サロゲートペアではないが、結合文字 printf("[%x] Combine string\n", p[iback]); } else {//一文字 printf("%s [%x] (%lc) -> %x\n", " true ", p[iback], p[iback], u32c ); } } else {//サロゲートペア printf("%s [%x,%x] -> %x ", " false", U16_LEAD(u32c),//u32からサロゲートペアを求める U16_TRAIL(u32c), u32c ); printf(" * [%x,%x]", p[iback], p[iback + 1]);//u16のサロゲートペアを表示 puts(""); } } }
DreamStudioは今話題の、、流行りのAIでキーワードから画像を生成するサービスで、現時点でかなり性能の高いものらしい。人類の歴史が動く!!と興奮する者さえいる。
AIを作る場合、今主流のDeepLearningなんかだと、たくさん教えるほど精度が上がる。資料が少ないものほど精度が落ちる。つまり資料(ネット上に落ちていた画像)の量と質で結果に偏りが生じるはず。
利用にはユーザー登録が必要。
https://beta.dreamstudio.ai/
登録出来たらログインし、画面下のテキストフィールドにキーワードを入力して[Dream]ボタンを押す。
率直に言うと、
1.景観、植物などはそのまま使っても問題ないほど優れたものが出てくる。
2.動物などは時々はずれが出る。
3.人間は結構気持ち悪いのが生成される。
4.開発者の文化圏の影響がなんとなくわかる
まずは「tree」。かなり綺麗。大体はそのまま使ってしまえそうなものが出てくる。
「owl」フクロウについて詳しくないのでどこかおかしくてもよくわからない。
「woman blonde」ここまで整ったものが出来上がる確率は低いが、それでも結構きれいなものが出てくる。というかなんか見たことある気がする。
ただし次で言うように、日本人の脳はブロンドの見分けがそんなに得意ではないので、おかしくても気づいていないだけかもしれない。
「woman asian」blondeとの差が傾向として出てしまう…。
日本人であれば、脳がアジア系の顔の見分けに最適化されているはずなので、どうしても粗が見えやすくなるはず。
「man asian」男性で整った顔が出る確率は結構低い。つまりそういうことorz。
「Manju」饅頭。たこ焼きの間違いでは。
「Hamburger」さすがにこいつの精度は高い。
「onigiri」どう見ても巻き寿司。
「rice ball」イメージと違う。嘘は言ってない、みたいな結果になる。
「butter」バターの精度は高い。
単語だけでやってみたが、多分ある程度長い文で出したほうがずっと面白いものができる。
「christmas night tree in snow」
ただし、人間、動物、昆虫などはメンタルが強くないならお勧めできない。あたりを引くまでに何度も生成を繰り返すことになるが悪夢になりかねない。
あと集合体恐怖症への配慮はない。これは本当にきつい。
著者は今ゼノブレイド3をやっていてガチで記事を書く時間がないのだがそもそもネタがなくてやばい。
まずはモデルを読み込む。
[Shift+A]→[Volume]→[Empty] でボリュームを追加。
モディファイアの設定で、Objectにモデルを選択する。
上記方法でボリュームを作った後、これをメッシュ化する。
[Shift+A]→[Mesh]→[Cube]で立方体を追加する。
Cubeに対してVolume to Meshモディファイアを適用。
ObjectにVolumeを選択し、Applyする。
このチュートリアルは解像度が低いこともあり正確に数字が取れない(し、取れてもたいていうまくいかない)のでざっくりとやってみる。
まず以下が基本となる。他はすべて、以下のコピーとなる。
上をコピーして、色を変える。さらにMappingのLocationを6.5にする。
そしてそれをAdd Shaderする。
上記二つをコピーし、計四つにする。Voronoi Textureのsizeを60に設定。他はいじらない。
Voronoiのscaleが60のものを一つだけコピーし、Locationを変更。あと色を変更。
上記をコピーし三つ作成する。scaleを上から120,180,320に設定。
結果
OpenGLの描画内にFreeType2で簡単に文字列を描画できるFTGLだが改行がやや面倒。
FTGLのFTPixmapFontオブジェクトからBBoxメンバ関数を呼び出す。
この関数はテキストを与えるとそのテキストのバウンディングボックスを返すので、これで一行の高さが分かる。
ただし、このサイズは単位がピクセルらしいので、表示中のワールド座標系に変換してやる必要がある。
#include <iostream> #include <Windows.h> #include <gl/GL.h> #include <gl/GLU.h> #include <gl/freeglut.h> #include <FTGL/ftgl.h> #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"freeglut.lib") #pragma comment(lib,"ftgl.lib") //ウィンドウの幅と高さ int width, height;
// FTGLを管理しやすくするクラス struct CFtglObject { const char* FONT_PATHNAME = "C:\\Windows\\Fonts\\msgothic.ttc"; FTPixmapFont* g_pFont; unsigned long g_ulFontSize; // フォントサイズ ~CFtglObject() { delete g_pFont; } // フォントの初期化 void init(const unsigned long fontsize) { g_ulFontSize = fontsize; if (!g_pFont) { g_pFont = new FTPixmapFont(FONT_PATHNAME); if (g_pFont->Error()) { delete g_pFont; g_pFont = nullptr; } else { g_pFont->FaceSize(g_ulFontSize); } } } // FTGLで文字列を描画 void print(const std::wstring& wstr, float x, float y) { if (g_pFont) { glRasterPos2f(x, y); g_pFont->Render(wstr.c_str()); } } FTPixmapFont& get_font() { return *g_pFont; } };
CFtglObject ftglo; //描画関数 void disp(void) { glViewport(0, 0, width, height); glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-1, 1, -1, 1, 1, -1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_CULL_FACE); double v = 0.7; glBegin(GL_QUADS); glColor3d(0, 0, 1); glVertex2d(-v, -v); glColor3d(1, 0, 1); glVertex2d(v, -v); glColor3d(1, 1, 1); glVertex2d(v, v); glColor3d(0, 1, 1); glVertex2d(-v, v); glEnd(); glLineWidth(2); glBegin(GL_LINES); glColor3d(1, 1, 0); glVertex2d(-v, 0); glColor3d(1, 1, 1); glVertex2d(v, 0); glColor3d(1, 1, 0); glVertex2d(0, -v); glColor3d(1, 1, 1); glVertex2d(0, v); glEnd(); ///////////////////////////////// ///////////////////////////////// ///////////////////////////////// const wchar_t* text; text = L"いろはに"; ftglo.print(text, 0, 0); FTBBox bbox = ftglo.get_font().BBox(text); float BoxHeightPixel = bbox.Upper().Yf() - bbox.Lower().Yf();//文字列の高さを求める // BoxHeightの単位がピクセルらしいので、これを現在の文字幅に修正する。 // 現在は -1 ~ 1の範囲を0~height-1ピクセルで表示しているので、 float ratioh = 2.f/height; float pixelh = ratioh * BoxHeightPixel; text = L"ほへと"; ftglo.print(text, 0, pixelh); glFlush(); } //ウィンドウサイズの変化時に呼び出される void reshape(int w, int h) { width = w; height = h; disp(); } //エントリポイント int main(int argc, char** argv) { glutInit(&argc, argv); glutInitWindowPosition(100, 50); glutInitWindowSize(500, 500); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA); ftglo.init(32); glutCreateWindow("sample"); glutDisplayFunc(disp); glutReshapeFunc(reshape); glutMainLoop(); return 0; }
二次元の、ある点が三角形の内側にあるか外側にあるかを判定するコードを以前紹介したのだが、点が三角形の境界ぎりぎりにある場合問題を起こすことがある。
判定方法はs,tを計算し、それが0~1に収まっていれば内側と判断するので、この範囲を0.1~0.9などに変更すると誤差を指定することができるようになる。
//https://suzulang.com/2d-triangle-in-out-check/ template<typename real_t> inline bool isInTheTriangle( real_t px, real_t py, real_t p0x, real_t p0y, real_t p1x, real_t p1y, real_t p2x, real_t p2y ) { real_t Area = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y); real_t s = 1.0 / (2.0 * Area) * (p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py); real_t t = 1.0 / (2.0 * Area) * (p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py); real_t e = 0.05; real_t tmin = 0.0 - e; real_t tmax = 1.0 + e; if ( (tmin < s) && (s < tmax) && (tmin < t) && (t < tmax) && (tmin < (1 - s - t)) && ((1 - s - t) < tmax) ) { return true; } else { return false; } }
auto p0 = glm::vec3(-0.913841, 0.999223, 0); auto p1 = glm::vec3(0.032665, -0.787554, 0); auto p2 = glm::vec3(0.853559, 0.974719, 0); glBegin(GL_TRIANGLES); glVertex2fv(glm::value_ptr(p0)); glVertex2fv(glm::value_ptr(p1)); glVertex2fv(glm::value_ptr(p2)); glEnd(); glPointSize(5); glBegin(GL_POINTS); for (float x = -2.f; x < 2.f; x += 0.1f) { for (float y = -2.f; y < 2.f; y += 0.1f) { bool hit = isInTheTriangle( x, y, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y ); if (hit) glColor3d(1, 0, 0); else glColor3d(0.7, 0.7, 0.7); glVertex2f(x,y); } } glEnd();
ICUライブラリでのutf16の扱い方を調べていてマクロがあることを知ったのでいくつか列挙しておきたい。
ICU 71.1:utf16.h File Reference
https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/utf16_8h.html
utf16文字からコードポイントを得る
#pragma warning(disable:4996) #include <iostream> #include <unicode/utf16.h> int main() { const char16_t* u16c = u"あいう"; char32_t u32c; FILE* fp = fopen("test.txt", "w"); U16_GET_UNSAFE(u16c, 0, u32c); fwrite(&u32c, 4, 1, fp); U16_GET_UNSAFE(u16c, 2, u32c); fwrite(&u32c, 4, 1, fp); fclose(fp); }
与えられたコードポイントが、utf16で何要素になるかを返す。返却値は 1 または 2。
std::u32string cc; // U16_LENGTH 与えられたu32がu16で何要素になるか int len; cc = U"a"; len = U16_LENGTH(cc[0]); printf("--- %d\n", len); // 1 utf16一文字 cc = U"あ"; len = U16_LENGTH(cc[0]); printf("--- %d\n", len); // 1 utf16一文字 cc = U"👨👩👧👦"; len = U16_LENGTH(cc[0]); printf("--- %d\n", len); // 2 結合文字の最初の一文字 cc = U"𠀋"; len = U16_LENGTH(cc[0]); printf("--- %d\n", len); // 2 サロゲートペア cc = U"𠀋𡚴"; len = U16_LENGTH(cc[0]); printf("--- %d\n", len); // 2 最初の一文字がサロゲートペア
U16_IS_SINGLE(c) utf16文字cが、サロゲートペアでなければtrue。
U16_IS_SURROGATE(c) utf16文字cが、サロゲートペアであればtrue。
U16_IS_SURROGATE_LEAD(c) utf16文字cが、上位サロゲートであればtrue。ただし、cがサロゲートペアであることが分かっている必要がある。
U16_IS_SURROGATE_TRAIL(c) utf16文字cが、下位サロゲートであればtrue。ただし、cがサロゲートペアであることが分かっている必要がある。
U16_IS_SINGLE | U16_IS_SURROGATE | U16_IS_SURROGATE_LEAD | U16_IS_SURROGATE_TRAIL | |
u"a"[0] | true | false | true | false |
u"あ"[0] | true | false | true | false |
u"👨👩👧👦"[0] | false | true | true | false |
u"𠀋"[0] | false | true | true | false |
u"a"[1] | true | false | true | false |
u"あ"[1] | true | false | true | false |
u"👨👩👧👦"[1] | false | true | false | true |
u"𠀋"[1] | false | true | false | true |
コードポイントから上位サロゲート(lead)、下位サロゲート(trail)を算出する
#pragma warning(disable:4996) #include <iostream> #include <unicode/utf16.h> int main() { // UTF32 //char32_t u32c = U'あ'; char32_t u32c = U'𠀋'; // char32->char16に変換したとき何文字になるかをチェック if (U16_LENGTH(u32c) == 2){
// utf16に変換したときサロゲートペアになる場合は上下サロゲートを算出 char16_t aa[2]; aa[0] = U16_LEAD(u32c); // 上位サロゲート aa[1] = U16_TRAIL(u32c); // 下位サロゲート // ファイル出力 FILE* fp = fopen("test.txt", "w"); fwrite(aa, 2, 2, fp); fclose(fp);
} else {
//サロゲートペアに変換する必要がなければコードユニット値がutf16文字
char16_t u16 = char16_t(u32c); // ファイル出力 FILE* fp = fopen("test.txt", "w"); fwrite(&u16, 2, 1, fp); fclose(fp);
} }