Loading web-font TeX/Math/Italic

2013年6月17日月曜日

計算幾何 〜 2次元凸包 理論と実践 〜

in English

はじめに

 前回すこしふれた2次元凸包アルゴリズムについて説明し、C++による実装例を示す。

アルゴリズム

 今回紹介するアルゴリズムは逐次添加法と呼ばれ、O(n\log{n})の計算量を持つ1

入力:A=\{P_1,P_2,\cdots,P_n\}(添字を固有番号と呼ぶことにする)
出力:凸包 C(A)
  1. Aに属する点を x 座標の小さい順にならべる。この点列を A^{\prime}=\{P_{(1)},P_{(2)},\cdots,P_{(n)}\} と書き、添字を整列番号と呼ぶことにする。x 座標が同じ値を持つ場合は固有番号の大きいものから並べる。整列番号から固有番号へ変換するテーブル T_{{\rm sorted}\rightarrow{\rm original}} を保持しておく。
  2. A^{\prime} の最初の3点から三角形を作り、これを C(\{P_{(1)},P_{(2)},P_{(3)}\}) とする。時計回りにたどるときの点列 V_{\rm clockwise} と反時計回りにたどるときの点列 V_{\rm counterclockwise} を保持する。たとえば、時計回りにまわったとき整列番号 a の次に整列番号 b の点がくるならV_{\rm clockwise}(a)=bとなるようにすればよい。
  3. 整列番号 (3) から (n) までの点を順に追加する。各点の追加ごとにアルゴリズム X(後述)を実行する。
  4. 出力値 C(\{P_{(1)},P_{(2)},\cdots,P_{(n)}\}) を得る。V_{\rm clockwise} あるいは V_{\rm counterclockwise} をたどれば、凸包を得ることができる。
 先のページで述べた記号摂動法を適用する際、各座標に以下のように摂動を加える。 \begin{eqnarray} (x_i,y_i)&\rightarrow&(x_i+\epsilon^{M^i},y_i+\epsilon^{M^{n+i}}) \nonumber \\ &\equiv&(x_i^{\ast},y_i^{\ast}) \end{eqnarray}
ここで、n は点の総数、M は十分大きな量、\epsilon は十分小さな量である。添字 i として固有番号を採用することにより、たとえ x 座標値が同じであっても、仮想的に x 座標値の小さな順に点を添加していくことができる。たとえば、x座標値が同じ3点を考えてみる(下図)。括弧で囲んだ数字は整列番号、囲んでいない番号は固有番号である。x 座標が同じ値を持つ場合は固有番号の大きいものから並べたので、各点の摂動量(青線)と摂動後の点(赤丸)は図のようになる。整列番号の小さい点は摂動量も小さくなることが分る。

 次にアルゴリズム X について述べる。

入力:C(\{P_{(1)},P_{(2)},\cdots,P_{(k)}\})P_{(k+1)}V_{\rm clockwise}V_{\rm counterclockwise}T_{{\rm sorted}\rightarrow{\rm original}}
出力:C(\{P_{(1)},P_{(2)},\cdots,P_{(k+1)}\})
  1. P_{(s)}\leftarrow P_{(k)}
  2. 3点 P_{(k+1)},P_{(s)},P_{(t)} を考える。ここで、t=V_{\rm counterclockwise}(s)である。
  3. 3点をこの順にたどったとき右に曲がるなら s\leftarrow t として2へ、左に曲がるなら4へ(下図参照)。
  4. 左に曲がった整列番号(u)を保持する。
  5. P_{(s)}\leftarrow P_{(k)}
  6. 3点 P_{(k+1)},P_{(s)},P_{(t)} を考える。ここで、t=V_{\rm clockwise}(s)である。
  7. 3点をこの順にたどったとき左に曲がるなら s\leftarrow t として6へ、右に曲がるな8へ。
  8. 右に曲がった整列番号(l)を保持する。
  9. V_{\rm counterclockwise}(l)=k+1V_{\rm counterclockwise}(k+1)=u とする。
  10. V_{\rm clockwise}(u)=k+1V_{\rm clockwise}(k+1)=l とする。

 右に曲がる、左に曲がるという判定は先のページで取り上げた判定式 F を使えば良い。
  1. F(P_{(i)},P_{(j)},P_{(k)}) を考える。
  2. T_{{\rm sorted}\rightarrow{\rm original}} を使って、整列番号を固有番号に変換する。 \begin{eqnarray} a&=&T_{{\rm sorted}\rightarrow{\rm original}}(i) \nonumber \\ b&=&T_{{\rm sorted}\rightarrow{\rm original}}(j) \nonumber \\ c&=&T_{{\rm sorted}\rightarrow{\rm original}}(k) \nonumber \end{eqnarray}
  3. 固有番号のリスト L=(a,b,c) を大きい順に並べ替え、リスト L^{\prime}=(a^{\prime},b^{\prime},c^{\prime}) を作成する。並べ替えの際、奇置換なら p=-1、偶置換なら p=1 とする。
  4. F(P_{a^{\prime}},P_{b^{\prime}},P_{c^{\prime}}) の正負を判定する。その結果にpの値を乗じたものが F(P_{a},P_{b},P_{c}) の符号となる。正なら左に折れ、負なら右に折れる。

C++による実装

ソースはここmain関数は以下の通りである。
  1. 16行目から39行目:乱数による点の生成を行っている。
  2. 43行目:テンプレートクラスConvexHull2dのオブジェクトを作成している。ここではテンプレート引数としてintを指定したが、boost::multiprecision::cpp_intを指定することもできる。
  3. 44行目:アルゴリズムを実行している。
  4. 46行目以降:最初に生成した点と凸包を描画している。
  5. 57行目:凸包を構成する点を取り出している。

デモ

開発環境

  1. Mac OS X 10.8.4
  2. プロセッサ:3.06 GHz Intel Core 2 Duo
  3. メモリ:4GB
  4. Xcode4.6.2 with Apple LLVM 4.2(C++ Language Dialect → C++11, C++ Standard Library → libc++)
  5. boost-1.53.0(Apple LLVM 4.2でコンパイルしたもの。こちら
  6. opencv-2.4.5(Apple LLVM 4.2でコンパイルしたもの。こちら

参考文献

  1. 計算幾何プログラミング, 杉原厚吉, 岩波書店

0 件のコメント:

コメントを投稿