2016年12月31日

麻雀プログラムの作成 その4

もう年末ですね。テレビを見ながらゆっくり実装です。今回は七対子の判定を追加しました。七対子は、名前の通り対子が7個ですので、それぞれの種類の牌の数を数えて、2個のものが7個あれば完成となります。分割したパターンでは、槓子を対子2つと区切る場合もありますが、4枚使いの七対子は無しの方針なので、そこの判定にだけ注意が必要です。

後は[1]の背景画像を使用させていただき、上がった時に表示される画面を追加してみました。次は他の役判定と点数計算です。今使っているビットマップフォントはアルファベットのみなので、役の表示とかどうしましょうかね。

実行結果

mj_result_007.png

[1] 4K バックグラウンド 無料素材
web拍手 by FC2
posted by シンドラー at 00:32 | Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2016年12月28日

麻雀プログラムの作成 その3

続きです。リーチ判定ができるようになっていたので、ツモ及びロン判定はすぐできました。ツモの時は手牌の14枚、ロンの時は手牌13枚+捨て牌の組を作成し、それの待ちが1個(対子)のみであれば上がれるという判定です。七対子と国士無双は後回しです。

実行結果

mj_result_006.png

これで後は役判定ができれば、一人で四人打ち麻雀ができるようになります。役判定も基本的には力押しで組み合わせパターンを列挙して一番役数が多いものを採用になると思います。
web拍手 by FC2
posted by シンドラー at 03:31 | Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2016年12月25日

麻雀プログラムの作成 その2

前回の続きです。リーチ判定をするためには、役が揃うまでに後何手か判定できる必要があるので、まずは役判定の前にリーチ判定を実装しました。

鳴き判定をするために、萬子、索子、筒子、字牌それぞれで対子や順子、両面待ちなどの数を数えていますので、それで判定します。リーチできるのは後2つ必要な牌が来れば上がれる状態ですので、頭の対子を除いて待ちの数が2の場合リーチ可能とします。そして、リーチ可能となる捨て牌の判定ですが、これも力業で(最大)14個の手牌のうち、それぞれ1個取り除いて待ちの数が1になるものを見つけました。

実行結果

mj_result_004.png
リーチするための捨て牌候補を赤枠表示

mj_result_005.png
リーチ後

少し試した範囲では問題なく判定できているようですので、次はロンやツモできるようにしたいと思います。
web拍手 by FC2
posted by シンドラー at 19:59 | Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2016年12月23日

麻雀プログラムの作成 その1

いつの間にか年末ですね…。今回は麻雀プログラムの作成です。役判定とかAIとかかなり面倒な実装が必要ですね。参考文献は[1][2]などです。本はあまりなく、ネットで検索した方が色々情報がありそうでした。言語としてはC++でOpenGL使っています。OpenGLは一枚の平面に画像貼って表示してるだけです。

とりあえずは役判定やAIは後回しにして、配牌や自摸切り、鳴きを実装してみました。牌画像は[3]のものを使用させていただいております。

実行結果

mj_result_001.png

縦に手牌と捨て牌並べているので見えづらいですね。加槓が捨て牌に重なってたり文字表示もビットマップフォント使っていたり手作り感満載です。

ここまでで問題になったのは、鳴けるかどうかの判定処理です。これをするためには、手牌を解析してトイツや両面待ちなどがあるかどうか調べておく必要があります。最近は計算機の性能が上がっていてゴリ押しでも十分速いので、スタックを使ってすべての組み合わせを列挙して保持しておくことにしました。

mj_algo_001.png

最初に上記のように1個だけ、一番左を処理候補としたデータをスタックに積みます。

mj_algo_002.png

その後、1個取り出して取りうる候補(A〜F)を列挙して、またスタックに積みます。次はFを取り出して…とFILO方式ですべての候補がなくなるまで繰り返します。

上の方法の場合、対子―単騎と単騎―対子などの組み合わせがダブってしまいますが、とりあえず列挙して後で被っているものは削除しました。ちゃんと考えれば被らずに一度で終わらせる方法もあるとは思います。

チーの場合は複数候補が考えられますので、それを表示して選択するようにしました。ここまでで上がれないけれど一応打つことはできるようになったので、次は役判定に入りたいと思います。

mj_result_003.png

[1] 有馬元嗣著,"ゲームプログラミング 遊びのレシピ", ソフトバンク株式会社,1997
[2] 石畑恭平著, "コンピュータ麻雀のアルゴリズム", 株式会社工学社,2007
[3] 【保存版】商用無料の高クオリティーの麻雀画像の無料素材まとめ
web拍手 by FC2
posted by シンドラー at 21:41 | Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2016年11月02日

RANSACのテスト その2

前回は2次曲線で近似しましたが、今回はPCLに付属のサンプル点群をオーソドックスな平面で近似してみました。PCLで使用されているPCDデータですが、ファイルフォーマット的には簡単なのですが、binary_compressedデータは圧縮が入ってきて面倒だったので、結局PCLを使うことにしました。表示は自前でOpenGL使ってやっています。

PCLは以前all-in-one-installerを使用してインストールしていたのですが、すっかり忘れていてソースからCMake使ってコンパイルしました。FLANNとかBoostとか必要で結構面倒でした。

平面近似についてはこちらも前回同様特異値分解を用いてパラメータ推定をしました。平面の方程式は以前の記事でまとめていましたので、そこを参考にしました。

実行結果

PCL付属のfive_people.pcdを読み込んで表示した結果です。PCDファイルとはY軸が反転しているのか、上下が逆さまになっていましたが、気にしないことにしました。

pcl_test_001.png

以前の記事でOctreeを使った近傍探索も試していましたので、おまけでマウスでクリックしたところに赤い球を表示して、そこの座標を表示するようにしてみました。

続いて、平面近似した結果です。未知数4個なので、4点使って点と平面の距離が0.1以下の点群の数を、10000パターン試して最も一致数が多いものをRANSACで選んでいます。こちらもおまけで平面の領域+閾値の幅を適当に区切った直方体と、範囲内の点群を緑色で表示しています。

pcl_test_002.png

前回も書きましたが、この辺は自分で実装しなくてもPCLでできるのでほぼ確認の意味ですが、RANSACは大体使えるようになったので何か機会があれば使ってみたいと思います。
web拍手 by FC2
posted by シンドラー at 19:03 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2016年10月15日

RANSACのテスト

論文などでよく見るRANSACですが、試してなかったので試してみました。RANSACについては[1]を参考にさせていただきました。アルゴリズム的には簡単ですね。モデルも自由に変更できますし。今回は2次曲線の方程式を使ってみます。

equation_001.png

テスト画像は下記のものです。ペイントで適当に書きました。楕円で近似できればいいなといった図形です。

circle_test.png

白画素を点群として、2次曲線で近似します。未知数が6個ですので、6点以上使って特異値分解します。この辺のやり方は過去記事を参考にしました。全ての点群を使って特異値分解した場合には、左上の円に引っ張られて誤差の大きな近似曲線になります。

use_all_points.png

今回はRANSACで6点使って特異値分解で近似して、点群との距離?が閾値以下の点の数が最も多いパラメータを選んでみました。本当は点と曲線の距離を計算しないといけないのですが、近似したパラメータに点の座標を代入して、0に近いかどうかで判定したので正確ではありません。

実行結果

use_ransac_001.png

赤が全画素に対して行った閾値以下の点の領域で、緑が点群の中で閾値以下のものです。適当に描いた絵なのできれいにとはいきませんが、2次曲線で大きい方の楕円が近似できています。切り出しを行いたい場合は、範囲内の点で再度パラメータを計算して、閾値以下の点を取り除いて、残った点でまたRANSACということを行えば、2つの2次曲線を取り出せるみたいですが、今回はそこまでは試していません。

本当は3次元でKinectなどで取得した点群から球や平面を抽出したりといったことをしようと思っていたのですが、その辺はまた今度試してみたいと思います。まぁPCLなどで実装されているので、自分でやる意味はあまりないのですが。

[1] 【お勉強してみた】RANSACのおはなし - Qiita
web拍手 by FC2
posted by シンドラー at 18:18 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする

2016年09月27日

Alembic形式の読込みに関するメモ

MMDBridgeなどでAlembic形式が使われていますので、Alembicのライブラリのコンパイルとか使用方法のメモです。最初は[2]を参考に、HDF5を使ったAlembicファイルを読み込んでいたのですが、なぜか1000フレームを超えるデータを読み込もうとするとエラー終了してしまうので、諦めてOgawa形式のAlembicファイルを使用することにしました。MMDBridgeでは、mmdbridge_alembic_for_houdini.pyをそのまま使っています。

Alembicのホームページは[1]にあります。SourceをクリックするとGitHubのページに移動しますので、そこからソースコードをダウンロードします。必要なのかどうかわかりませんが、ilmbaseのライブラリも準備しました。これは以前OpenEXRの準備をしたときに作っていたものを使用しました。

Alembicのコンパイルは、CMakeを使ってやりました。generate先はbuild64というディレクトリを作成してそこにソリューションファイルを生成するようにしました。設定した項目は下記の感じです。

ilmbaseのライブラリやインクルードディレクトリの設定
ALEMBIC_ILMBASE_HALF_LIB
ALEMBIC_ILMBASE_IEXMATH_LIB
ALEMBIC_ILMBASE_IEX_LIB
ALEMBIC_ILMBASE_ILMTHREAD_LIB
ALEMBIC_ILMBASE_IMATH_LIB
ILMBASE_INCLUDE_DIR

USE_HDF5などもあるのですが、最初に書いたようにうまくいかなかったので今回はOFFのままにしました。後はVisual Studioを起動してビルドすれば、Alembic.libができているはずです。

使用方法

ライブラリを使用する場合の設定です。Cドライブに直入れしている場合の例なので適当です。

追加するインクルードディレクトリ
C:\alembic-1.6\lib
C:\alembic-1.6\build64\lib
↑ Config.hのために必要
C:\OpenEXR\include

追加するライブラリディレクトリ
C:\alembic-1.6\build64\lib\Alembic\Release
C:\OpenEXR\lib
追加するライブラリファイル
Alembic.lib

ソースコードの一部
#include <Alembic/AbcCoreAbstract/All.h>
#include <Alembic/Abc/ErrorHandler.h>
#include <Alembic/Abc/All.h>
#include <Alembic/AbcGeom/All.h>
#include <Alembic/AbcCoreOgawa/All.h>

using namespace Alembic::Abc;
using namespace Alembic::AbcGeom;

...

  // Ogawa形式のAbcファイル読込み
  Alembic::Abc::IArchive archive(Alembic::AbcCoreOgawa::ReadArchive(), FileName);
  Alembic::Abc::IObject obj = archive.getTop();
  // 子データ数の取得
  unsigned int numChildren = obj.getNumChildren();
  std::cout << "found " << numChildren << " children in file\n";

  // 子データの数だけ繰り返し
  for (int i = 0; i < numChildren; ++i)
  {
    // 子データのオブジェクトを取得
    IObject child(obj, obj.getChildHeader(i).getName());
    // さらに子データの数を取得
    std::cout << "Children " << child.getNumChildren() << "\n";
    const MetaData &md = child.getMetaData();
    std::cout << md.serialize() << "\n";

    // 子データが見つかった場合
    for (int x = 0; x<child.getNumChildren(); x++)
    {
      // 子データのオブジェクトを取得
      IObject child2(child, child.getChildHeader(x).getName());
      const MetaData &md2 = child2.getMetaData();
      std::cout << md2.serialize() << "\n";

      // 子データがメッシュデータの場合(今回はカメラの時は未処理)
      if (IPolyMeshSchema::matches(md2) || ISubDSchema::matches(md2))
      {
        std::cout << "Found a mesh " << child2.getName() << "\n";
        // we have a mesh so build a mesh object
        IPolyMesh mesh(child, child2.getName());
        // grab the schema for the object
        IPolyMeshSchema schema = mesh.getSchema();

        // now grab the time sample data for the object
        IPolyMeshSchema::Sample mesh_samp;
        // ISampleSelectorを使って取得するフレームを設定する(今回は11200番目のフレーム)
        // HDF5だと1000以上の値を設定するとなぜかエラーが発生した
        ISampleSelector ss(Abc::index_t(11200));
        schema.get(mesh_samp, ss);
        // get how many points (positions) there are
        Alembic::Util::v8::uint32_t size = mesh_samp.getPositions()->size();
        // 面数の取得
        Alembic::Abc::Int32ArraySamplePtr faceNum = mesh_samp.getFaceCounts();
        // 法線情報の取得
        IN3fGeomParam N = schema.getNormalsParam();
        // we didn't set any on write, so on read, it should be an invalid container
        // getExpandedValue() takes an optional ISampleSelector;
        // getVals() returns a TypedArraySamplePtr
        N3fArraySamplePtr nsp = N.getExpandedValue().getVals();
        // UV情報の取得
        IV2fGeomParam uv = schema.getUVsParam();
        IV2fGeomParam::Sample uvsamp = uv.getIndexedValue();
        std::cout << "size=" << size << ", faceCount=" << faceNum->size() << "\n";
        // 面の数だけ繰り返す
        for (Alembic::Util::v8::uint32_t m = 0; m < size; ++m)
        {
          // get the current point
          V3f p = mesh_samp.getPositions()->get()[m];
          N3f Normal = nsp->get()[m];
          V2f uv2 = (*(uvsamp.getVals()))[m];
          // 以上で頂点座標/法線ベクトル/テクスチャ座標を取得
        }
        for (Alembic::Util::v8::uint32_t m = 0; m < faceNum->size(); ++m)
        {
          unsigned int index[3];
          // get the current face
          for (int k = 0; k < 3; k++)
          {
            index[k] = mesh_samp.getFaceIndices()->get()[3 * m + k];
          }
          // 以上で頂点インデックスを取得
        }
      }
    }
  }

  ...

基本的に[2]を参考にさせていただいて、Alembic 1.6でちょっと仕様が変わっている感じです。

実行結果

AlembicTest_001.png

変換のサンプルがAlembicに入っているので意味はないですが、Alembicを読み込んでObjで出力してMeshLabで表示したものです。Tda式ミクさんとマリエルさんステージを使用させていただいております。

[1] http://www.alembic.io/
[2] http://jonmacey.blogspot.jp/2011/12/getting-started-with-alembic.html

web拍手 by FC2
posted by シンドラー at 22:14 | Comment(0) | TrackBack(0) | OpenGL & Metasequoia | このブログの読者になる | 更新情報をチェックする

2016年09月11日

Oculus SDK 1.7.0とGLUTのテスト

前回はサンプルを改変しましたので、今回は自分のプログラムで使用する場合のテストです。といってもウィンドウ作成などをglut/glewで行って、残りはほぼサンプルのWin32_GLAppUtil.hを書き換えた感じです。

元のサンプルの部分がそうなのかどうか詳しく見てないですが、面の方向が逆だったのかglFrontFace(GL_CW)で時計回りにしないとうまく表示されませんでした。他にもGL_CULL_FACEを有効化しないとおかしくなったので、何か間違っているかもしれません。

実行結果

ovr170_test_001.png

以下ソース
Win32_GLAppUtil_Custom.h
OVR1.7.0OpenGL.cpp
simple170.vert
simple170.frag

OculusRoomTiny(GL)からの転用が多いので、その辺のライセンスになるかと思います。シェーダはWin32_GLAppUtil.hに直書きしていたのをそのままファイルに出力しただけです。何か問題が起こっても責任はとりません。
web拍手 by FC2
posted by シンドラー at 22:17 | Comment(0) | TrackBack(0) | Oculus Rift | このブログの読者になる | 更新情報をチェックする

2016年08月27日

Oculus SDK v1.7.0のOculusRoomTiny(GL)に関するメモ

Oculus Rift製品版届いていたのですが、あまり使っていなかったのでボチボチ触ってみようかと思います。今回はOculusSDK/SamplesのひとつであるOculusRoomTiny(GL)に関するメモです。一応OpenGLのサンプルですが、Oculus的にはDirectXがメインで、OpenGLはあまりサポートしていないみたいです。

サンプルプログラムの構成としては、main.cppと、OculusRoomTiny_Advanced/Common/Win32_GLAppUtil.hの2つがメインで、Win32_GLAppUtil.hの方で色々定義されているようです。

サンプルは単純なRoomのデモということですので、今回はこれを書き換えて、Tda式ミクさんを表示してみることにします。シーンの生成は、Sceneクラスで行っているため、この辺りを書き換えていくことになります。

// シーンの生成
roomScene = new Scene(false);

Sceneは構造体で、最大モデル数10個の配列になっていますので、モデル数を増やしたい場合はここも書き換える必要があります。

また、Init関数でシェーダをテキスト形式で直書きしているので、ここをファイル読込みに書き換えれば、GLSLのシェーダを使用できるようになると思います。デフォルトはかなり単純にテクスチャの色などをそのまま表示しているようです。その後、タイルパターンのテクスチャを生成しているので、ここを画像を読み込むようにすれば、モデルのテクスチャも使用できるようになるはずです。最終的に、Model構造体にテクスチャバッファを割り当てて、頂点やIndexに必要なメモリ領域を確保してからSceneに追加しています。

Model構造体では、頂点数及び面数が最大値2000と少ないので、ここはファイル読込みで動的確保するように書き換える必要があります。サンプルとしてAddSolidColorBoxという関数があり、この関数でBoxの追加をすることができますが、基本的にはAddVertex関数とAddIndex関数で頂点情報とIndex情報を設定して、最後にAllocateBuffers関数を呼ぶ流れになるかと思います。

今回はMMDBridge[1]でTda式ミクさん[2]に[3]のポーズをしてもらったものをobjで出力して、それを読み込むことにしました。で、実際にやってみたところ、HMDの姿勢の追従はかなり高い精度でしてくれているのですが、どうもPosition trackingの方が殆ど効いていないようです。コントローラで操作することをメインにしているのか、顔を前後左右に平行移動しても、視点の位置が変わらない感じです。
(2016.9.13追記:環境の問題だったみたいです。違うPCだと位置も追従しました)

DK2の時にはできていたので、製品版でもできるはずなのですが、やり方がよくわかりませんので、どなたかわかる方がいましたら教えてください。UnityのUtilityでも使えば行けるのでしょうか。

頭の位置が固定で周りを見回すYouTubeの360度動画のような感じではなく、頭を動かしてのぞき込んだり近づいたりといった操作ができた方が直感的だと思いますので、main.cppのMainLoopを書き換えることにしました。元のサンプルでは下記のような動作を想定しているようです。

oculus_sdk_v170_test001.png

本来、shiftedEyePosには左右の眼の位置であるEyeRenderPose[eye].Positionが反映されているはずなので、平行移動なども効いてくれるはずなのですが、この位置をファイルに出力してみても、平行移動ではあまり数値が変わらなかったりよくわかりませんでした。平行移動の情報が入ってそうなovrPoseStatefのLinearVelocityもなぜかずっと0になっていたり。

そこで無理やりですが、注視点を動かすのではなく、注視点を固定して、視点位置の方を動かすように変更してみました。

oculus_sdk_v170_test002.png
これで頭を動かすと、平行移動というか円運動したような感じになります。

実行結果

targetPosが、見えづらいですが赤いBoxで表示している位置です。そこを周りから眺めるような動作になります。


Seesaaの動画はFlash使ったPlayerをやめたんですね…。wmvが表示されなくなったみたいです。

[1] MMDBridge
[2] Tda式初音ミク・アペンドVer1.00
[3] あぴミクさんのポーズ
web拍手 by FC2
posted by シンドラー at 02:33 | Comment(0) | TrackBack(0) | Oculus Rift | このブログの読者になる | 更新情報をチェックする

2016年07月22日

ビットマップフォントに関するメモ

ここのところ月1回しか更新してないですね…。困ったものです。

今日はビットマップフォントに関するメモです。画面や画像に文字を表示する方法は、OpenGLやOpenCVなどにも用意されているので、それを使えば問題ないかと思います。今回のメモはライブラリ等を使わずに無理矢理表示するためのものです。

フォントについては、大きく分けてビットマップフォントとアウトラインフォントがあります。綺麗な文字を描きたいのであればアウトラインフォントだと思いますが、今回は簡単のためビットマップフォントを使用します。

フォントに関しては、ライセンスに気を付ける必要があります。今回は[1]のm+ fontsを使用させていただくことにしました。

こちらのフォントですが、BDFというファイルフォーマットのフォントデータがあります。BDFはBitmap Distribution Formatの略のようで、UNIXで標準的に使用されていたフォーマットのようです。

テキストベースのファイルフォーマットですが、自分で読み込むのも面倒ですので、[2]のbdf2bmpというソフトウェアを使用させていただき、bdfファイルをbmpファイルに変換しました。

後は文字ごとに切り出して拡大/縮小し、白:背景、黒:文字として元の画像にmixしてあげれば画像上に文字を描くことができます。

実行結果

Lenna画像上にHello worldを表示してみました。

chartest.png

[1] m+ fonts
[2] bdf2bmp
web拍手 by FC2
posted by シンドラー at 19:00 | Comment(0) | TrackBack(0) | Image Processing | このブログの読者になる | 更新情報をチェックする