先日入れたVisualStudio2017のOpenCVを使ってアナモルフォーズ原理を実装してみました。
OpenCV入れてない人はこっち→ https://harusamelab.com/2019/05/05/visual-studio-2017-%E3%81%ABopencv%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8Bc/
アナモルフォーズ原理ってなんなのだよ?
→ 画像や写真を平面でない物体に投影したり角度を変えて見てみたりすることで正常な形が見えるようになるデザイン技法のひとつ
今回作るのは、自動車に乗っていてよく見る、40km/h とかの速度標識が縦に長いのと少し原理が近いです。少しだけ。
まずは、利用者がどのようなパラメタで作って欲しいかを受け取ります。
標準入力を使用しますので、iostreamをインクルードしておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
double depth = 2.0f; double height = 0.5f; const double threshold = 15 * 3.14 / 360;//15度未満は拒否 const double A4_RATE = 210 / 297; const int A4_WIDTH_PIX = 2480; const int A4_HEIGHT_PIX = 3508; const string inname = "fubu.bmp"; const string outname = "output.bmp"; string buf; while (true) { std::cout << "視点と紙との奥行き距離を入力してください(単位:m, デフォルト:2.0)\n"; getline(cin, buf); if (!buf.empty()) { try { double num = stod(buf); if (num <= 0) { throw invalid_argument(""); } depth = num; } catch (const invalid_argument& e) { cout << "[!] 適切な数値を入力してください\n"; continue; } } std::cout << "奥行きは" << depth << "mに設定されました\n"; break; } buf.clear(); while (true) { std::cout << "視点と紙との高さ距離を入力してください(単位:m, デフォルト:0.5)\n"; getline(cin, buf); if (!buf.empty()) { try { double num = stod(buf); if (num <= 0) { throw invalid_argument(""); } height = num; } catch (const invalid_argument& e) { cout << "[!] 適切な数値を入力してください\n"; continue; } } std::cout << "高さは" << height << "mに設定されました\n"; break; } double rad = atan2(height, depth); cout << "[info] 視線入射の水平角は、約" << (int)(rad*180/3.14) << "°に設定されました\n"; if (rad < threshold) { cout << "[!] 入射角度が小さすぎます。プログラムを停止します\n"; this_thread::sleep_for(std::chrono::seconds(3)); return 0; } |
下の画像みたいに値を受け取ることが出来ます。
奥行きと高さの2つがわかれば、その直角三角形がなす角度は計算できますよね。
『約18°』と表示していますが、処理では厳密な値を使用します。
パラメタが受け取れたので計算処理。
A4サイズの紙(2480×3508[px])に印刷することを考えて処理しています。
0.297とか0.21とかは、A4紙の縦幅/横幅をメートル表記したものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/*画像処理部分*/ cv::Mat output = 0 * cv::Mat::ones(A4_HEIGHT_PIX, A4_WIDTH_PIX, CV_8UC3); cv::Mat image = cv::imread(inname); double imgWidth = image.cols; double imgHeight = image.rows; // 変換前の画像の座標 const Point2f src_pt[] = { Point2f(0 , 0 ), Point2f(0 , imgHeight), Point2f(imgWidth , 0 ), Point2f(imgWidth , imgHeight) }; // 変換後の画像の座標 const Point2f dst_pt[] = { Point2f((A4_WIDTH_PIX / 2) - ((depth + 0.297) * imgWidth / depth / 2), 0), Point2f((A4_WIDTH_PIX / 2) - (imgWidth / 2), A4_HEIGHT_PIX), Point2f((A4_WIDTH_PIX / 2) + ((depth + 0.297) * imgWidth / depth / 2), 0), Point2f((A4_WIDTH_PIX / 2) + (imgWidth / 2), A4_HEIGHT_PIX) }; const Mat homography_matrix = getPerspectiveTransform(src_pt, dst_pt); // 透視変換 warpPerspective(image, output, homography_matrix, output.size(), 1, 1); cv::imwrite(outname, output); |
このプログラムだとA4サイズだけに対応していますが、定数部分を書き換えればオールオッケーです。
Mayaで表示させてみるとこんな感じ。
(自宅のプリンターのインクが切れていたので検証はCGで…)
↓ 偉大すぎるイラスト
ブッキー pic.twitter.com/Po26mfOgpG
— おっweee (@Oweeek) February 12, 2019
若干足が細い気がするのでどこかに間違いがあるかも…。
階段の踊り場とかにこういうアートをおいておくと面白いかもしれませんね。
印刷してみたらどうなるのか今度やってみます。光の当たり具合で変わりそうな気が。