catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

Rでコスプレ用スカートの可視化

結城先生の数学ガールを読み始めてみました。かたりぃなす。
もっとゆるい系の本かと思っていましたが、中身はすごく真面目かつ分かりやすく説明されていて、良書だと感じました。
20年近く前にフーリエの冒険を読んで以来のよい数学書です。
こういう本は非常に良い刺激になります。

そんなわけで、少しばかり数学的なことをやってみようと思いました。

コスプレ用のスカートの寸法の自動計算と可視化

コスプレ用のスカートの型紙についてですが、これが結構色々な局面で使います。
魔導士系のローブなども全円スカートを応用して広がりを少なくすることでそれっぽく作れたりもするので。
有名どころのサイトではこちらで簡単な計算ができます。
http://yousai.net/nui/seizu/furea/zenengomu.htm

全円スカートなら丈を設定して、出てきた寸法をそのまま使えば事足ります。
ただ、先にあげたように応用して作るときは型紙の書き直しが必要だったりするので、出来上がりがイメージしづらいです。
ロング丈のローブやスカートになると型紙作るだけでも大変なので実験するにも躊躇してしまいます。

よくよく考えると、衣装を作るときは毎回紙に書いて似たような計算しています。
ここはエンジニアらしく解決してみたいところです。
数学ガールも読んで気分も上がってきています。
そうだ、久しぶりにR言語を叩こう。
データの可視化だ。
というわけで作ってみました。

私の型紙づくりのやり方で特によく使う数学的なものとして、

  • 三角形の合同・相似
  • 円錐・円柱・球などの空間図形
  • 一次関数に関する基礎的なもの
  • 円の公式に関するもの
  • 垂直二等分線、平行線などを利用した作図

などです。
義務教育で習う内容がほとんどですね。

実際にどんな局面で利用するのかというと、すぐ思い浮かぶものとして

  • スカートの丈を調整するとき
    • 円錐として考える
      • 空間図形
      • 円の公式一般
      • 直角三角形の一般的なもの
  • 製図するとき
    • 垂直二等分線を使って正確な垂直線を書く
    • 大きな円弧を描くとき、三角形からの近似(歪まずに書ける程度まで三角形分割する)

などです。

今回作ったプログラムは次の3つのパラメータをもとにどんなスカートができるのか可視化します。

  1. スカート丈(パニエなどで広げている前提)
  2. ヒップサイズ
  3. 着用時のスカート裾部分の半径(広がり具合)

ヒップサイズ80cm, 丈100cmのロングスカートを可視化する実験です。
見栄えに一番大きく影響するパラメータはスカート半径なので、このパラメータを変動させてどうなるか見てみることにします。

スカート半径40cm

それほど広がらない魔導士向けローブなどで使えそうです。
女性向けなら丈を短くしてタイトスカートなどに使えるかもしれません。
f:id:Catalina1344:20160306154322p:plain

スカート半径70cm

ワイヤーパニエなどでスカートを広げたロリータ系の衣装やドレスでよく見かける形状です。
これはいわゆるAラインと呼ばれているものですが、個人的にはプリンセスラインのほうが好みです。
R言語なら数式回りの機能は豊富なので、工夫すればプリンセスラインの曲線くらい簡単に表現できる気がします。
しかし疲れたので今日はこれくらいにしておきます。
f:id:Catalina1344:20160306154331p:plain

読みづらいですがコードはこちら。
とりあえず書いてみたレベルなので間違いあるかもしれません。

# ヒップサイズ(cm単位)
west_size <- 80
# パニエ着用のうえ最大限に広がったスカートの半径(cm単位)
skirt_diameter <- 50
# スカートの丈(cm単位)
skirt_length <-	100

# ヒップサイズから円錐上部の円の半径を求める
top_R <- west_size / (2*pi)

# 三平方の定理を使ってスカートの高さを求める
# スカート丈=斜辺, X=スカート半径-ヒップサイズ, Y=hight
dx <- skirt_diameter - top_R
hight <- sqrt(skirt_length^2 - dx^2)	# 実際に生地を使用するY軸の寸法

# スカートの半径(xy平面)とスカートのY寸法から、中心からの距離に対するZの増分を求める
deltaZ <- hight / dx

# Zの増分をもとに、円錐の頂点を求める(x=0,y=0のときのZの値)
y_max <- deltaZ * skirt_diameter

# ヒップラインを求める
# 円錐の頂上はヒップラインでカットしてスカートらしく見せたいので
hip_line <- y_max - hight

# 以下はグラフを書くための処理
# 3Dグラフ用のX,Y軸のデータを準備
x <- seq(-skirt_diameter, skirt_diameter)
y <- x

# z値を求めて3Dグラフ化用のデータセットを準備
zh_func <- function(x,y){
	xy_2dvec <- sqrt(x^2 + y^2)	# XY平面における中心からの距離
	xy_2dvec * -deltaZ		# 中心から遠ざかるほどスカートは垂れ下がる
}
z <- outer(x,y,zh_func)

# 最小値,最大値を超える値の切り捨て(ヒップラインとスカートの裾)
threshold <- function(v){
	if(v > -hip_line)	return (-hip_line)
	if(v < -y_max)		return (-y_max)
	else			return (v)
}
z2 <- apply(z, c(1,2), threshold)


png("output.png")

# 三次元グラフ出力
v_theta <- 10
v_phi <- 40
persp(x,y,z2, 
	scale=FALSE,
	theta=v_theta, phi=v_phi,ticktype="detail", shade=0.5)

dev.off()

感想

せっかくのR言語なのに統計的なことは何もやっていないですね。
今回はただ可視化してみただけなので、三次元グラフのスケールが統一できていないのがちょっと残念です
グラフのスケールが毎回異なってしまうと、たとえばスカートの丈だけが異なるようなパラメータでグラフを生成したとき、見た目からは差が分かりにくいです(軸の値を読まなければいけない)。
まずは可視化できたのは一つの成果なので、良しとします。
色々と思うところはありますが、今は自分が楽しいと思えることに打ち込みたいですね。

今後の展望

これをそのまま型紙に起こせるようになれば色々と便利に使えそうな気がします。
今回のような全円スカートであれば、どこか一か所をカットしてそこから展開できるかもしれません。
プロットした座標間の距離を保ったまま平面上に落とし込んであげればそれっぽく展開できるだろうとは思うのですが…。
また気が向いたら試してみます。
あとコスプレ用衣装にありがちなフリルやギャザーを入れた場合も同じように可視化できると楽しそうです。

ARの原理実験

ARの原理について色々と調べてみました。
結論からいうと、ライブラリを2個組み合わせるだけでそれらしいことはできそうです。
この原理試験を簡単ではありますが試してみたいと思います。
世間ではOpenGVとかvuforiaとかAR向けのフレームワークが出てきていますが、
今回は原理試験が目的なので、フレームワークは使わずにOpenCVOpenGLを使ってみたいと思います。


ARを実現するための基礎技術として

  1. カメラ内部パラメータの推定
  2. カメラ外部パラメータの推定(物体の姿勢推定)

が大きな課題だと思います。
検出対象物の姿勢と位置さえわかれば、あとは3Dグラフィックエンジンを叩いてレンダリングするだけです。
今回はこの2つの基礎技術について実験をしてみました。

実験の目的

今回の実験の目的ですが、
カメラキャリブレーション(カメラ内部パラメータ推定)を行わずに手軽にARを実現できないか?
と思ったのが事の発端です。
例えばARアプリを作ったとしてもキャリブレーション用の器具(チェスボードとか)をユーザーに持ち歩けというのはあまりに無茶ですし、
何かしらの既知の大きさをもった物体(例えば後述のカード)を机に並べるとかでできなくはないですが、手間がかかってしまいます。
色々と調べていくうちに、そもそもキャリブレーションしなくてもいいんじゃないか?という結論に至ったので、
こんな理屈で本当にARらしいことができるのか?を確認することが目的です。

理論的なもの

ARにおける物体の姿勢推定の前に基本的なカメラモデルの理論です。
多くの場合では問題を簡単にするためにピンホールカメラモデルが利用されているようです。
ピンホールカメラモデルは次のような式で表されます。
式:撮影画像上の点 = 内部パラメータ行列 x 外部パラメータ行列 x マーカー座標系の点
論文やwebサイトをあちこち見て回りましたが、最終的にはこのような式で表現されます。
この式には含まれていませんが、実際には歪み補正パラメータというものもあるのですが、次の2つの理由から無視することにしました

  1. 広角レンズでも使っていない限りはそれほど大きくない
  2. 最近の一般的なwebカメラ(特に手元で実験に使うwebカメラ)はそこまで歪みがない

なお、OpenCVで提供されているカメラモデルも同様のものです。
カメラキャリブレーションと3次元再構成 — opencv 2.2 documentation

上記式のパラメータの詳細は論文をあたってもらったほうが正確な情報が得られるので、ここでは深く説明はしません。
(私自身も突っ込んだ質問されても正確に答えられる自信がないというのもあります。)
この式で示されている各パラメータをソフトウェア上で求める方法について軽く見ていきます。
「内部パラメータ行列」ですが、これを求める(推定する)行為がカメラキャリブレーションと呼ばれています。
「撮影画像上の点」はカメラから取り込んだ映像フレームに対して物体検出などの画像処理を行うことで求めることができます。
「マーカー座標系の点」も同様です。

残ったの「外部パラメータ行列」は今回のエントリの主な着目点です。
マーカー座標系の点と撮影画像上の点を正しく対応付けられているとき、上記式のうち未知である外部パラメータ行列を推定することができます。
この行為をARの世界ではマーカーの姿勢推定などと呼ばれています。(アルゴリズム的にはPNP問題を解くとも呼ばれます)
ここでのPNP問題とは「Perspective-n-Pont Problem」のことで、P≠NP問題のことではないです。

今回は平面マーカーを利用するので、全ての点が平面上にあるという前提のもとに姿勢推定を行います。
そのためには4つの対応点が必要とされるようですが、まだ詳しい理屈がわかっていないです。
この点についても追々調べていきたいですね。

さて、画像処理に対するお話は一旦おいて、3Dグラフィックスの概念レベルのお話です。
3Dグラフィックスでジオメトリをレンダリングするための頂点情報の変換手順は概ね
クリッピング座標系 = 射影変換行列 x ビュートランスフォーム x ワールドトランスフォーム x ジオメトリ座標の点
となっています。
実際のレンダリングパイプラインではこのクリッピング座標系から正規化デバイス座標系を経て、スクリーン座標へと変換されます。

この3Dグラフィックレンダリングの手順とカメラモデル、どちらも「スクリーン投影までの変換手順」を定義しているといえます。

座標変換の手順は表現方法や途中で経由する座標系こそ違いますが、
ピンホールカメラモデルも3Dレンダリングパイプラインも目的は同じです。

本題のARですが、ARとは「実世界から取り込んだ映像上に任意の3Dモデルをオーバーレイする」と考えたとき、カメラモデルとレンダリングパイプラインで同一の変換が行えるようになっていれば、話は簡単になりそうです。
具体的には
映像入力について

映像出力について

  • ソフトウェア内の3Dモデル->変換(3Dレンダリング)->オーバーレイ表示する画像

の「変換」にあたる部分が同一の変換を行うのであれば、簡単にARが実現できそうです。


数式的な証明はできていませんが、おいおいやっていきたいと思います。(ARには魅力を感じているので、基礎は押さえておきたい。)
大昔に3Dグラフィックスやり始めた頃にスクリーン投影に関する部分は深くは追っていなかったのが悔やまれます。
やっぱり基礎って大事ですね。

さて、ここまでの考えが正しいとした場合、

などが考えられます。

実世界のカメラをプレビューのみに利用することになるので多少の誤差が出ることは想定されますが、エンターテインメントとしてARアプリを作るなら多少の誤差であれば許容されそうです。(手軽に使えるほうが大事)
逆にミッションクリティカルな分野ではもっと正確なアプローチが求められると思っています。

というわけで本題の実験です。
姿勢推定の実験が目的なので、まずはそれ以外の部分は適当にでっちあげます。

物体検出

本格的に作るのであれば適切な特徴量の検出器などの採用などを検討するところですが、今回の実験でそこまですると本題から逸れてしまいます。
というわけで、実験環境を限定して目的の機能を作ることにします。

  • カメラは手元のwebカメラ
  • 机の真上から、真下に向かって撮影する
  • 検出対象は机の上に転がってたMTG(MagicTheGatharing)の黒枠カード
  • ノイズが邪魔なので、白の厚紙の上にカードを置く

この限定条件下であれば、次のような簡易的な手順でカードを識別できます

  1. カードのイメージを事前にスキャナで取り込む
  2. 入力映像フレームに対する事前処理
  3. 輪郭抽出
  4. スキャナ取り込み画像と入力映像、それぞれの輪郭のモーメントの比較

若干の誤検出はありますが、とりあえずの実験としては目的を充分に果たせます。

物体の点を求める

物体の輪郭のままではマーカー座標系の点との対応付けが困難なので、適当にそれっぽくなるようにしました

  1. 輪郭を長方形に近似し、4つの点を求める
  2. 4つの点をマーカー座標系と対応付けるため、並び順を決める(右上から時計回りに4つ)

これに対応するマーカー座標上の点は物理的に対処します。

  1. 道具箱を開ける
  2. ノギスもしくは物差しなどの測定器を取り出す
  3. 測定(物理)
  4. 物体の点と同様の並び順で座標をソースコード中に定義する

これでマーカーとの点の対応付けもできました。(画像中の座標、物理座標どちらも右上から時計回りに4点あります。)
とりあえずの実験らしく超乱暴な逃げ方ですね。


やっと実験の目的のところまで来ました。

OpenGLレンダリングした結果を使ってキャリブレーションする

キャリブレーションの関数自体はOpenCVに用意されているのでそれを使います。
方法は何でもいいのですが、よく使われているZhangの手法を使います。

  1. チェスボードのイメージを作る
  2. チェスボードのイメージをテクスチャとしてOpenGLに転送する
  3. 板ポリゴンに上記テクスチャを貼ったものをレンダリングして、その結果を取り出す
  4. 取り出したレンダリング結果の画像を使ってキャリブレーションを行う

ここまでで、以下のパラメータがすべてそろいました。

  • 撮影画像上の物体の点(物体検出)
  • カメラ内部パラメータ(キャリブレーション)
  • マーカー上の点(物理的に測定)

カメラからの入力画像中に含まれる物体の姿勢推定を行う

これらを使ってマーカーの位置・姿勢推定を行います。
OpenCVの関数でいうとsolvePnpです。歪み補正パラメータは無視したいのでcv::Mat()を与えておけばいいです。
推定結果が正しいかをとりあえず確認してみると、どうやらそれっぽい推定はできています。
こういったものの確認にはOpenCV3.0のarucoにあるDrawaxis関数がとても便利です。

推定した姿勢情報、位置情報をもとに3Dオブジェクトをレンダリングする

あとは簡単!
とか思っていたら足元をすくわれました。
しくじったポイントをメモに残しておきます。

回転の定義がOpenCVOpenGLで違う

solvePnPが返してくる姿勢推定結果の回転の情報は、xyzの回転ベクトルであり、ベクトルの向きが回転軸、ベクトルの大きさが回転量を表します。
対するopenGLのglRotatefなんかは回転軸と角度を度数で指定します。
変換方法を考えましたが、ちょっと面倒ですね。

そもそも私がやりたいのは姿勢推定で得られた結果をOpenGLでのレンダリングに反映したいだけです。
OpenGL1.x系のAPIをわざわざ叩く必要はないということに気付いたので、glLoadMatrixで行列を直接投げ込むことにしました。
回転行列の生成はopenCVロドリゲスの公式によるものが実装されているので、コレを使います。
これで3x3の回転行列が生成できました。
行列が完成したら、OpenCVからopenGLの行列表現に変換するために転置してあげればOKです。
ついでに、OpenGLの行列は4x4なので、定義に従って残りの成分に平行移動成分などを設定します。

OpenCVOpenGLで座標系と行列の定義が違う

OpenCVのカメラキャプチャの映像をそのままOpenGLレンダリングバッファに転送してしまうと上下反転します。
座標系違いますもんね。。。

  • OpenCVはX+は画面右方向、Y+は画面下方向
  • OpenGLはX+は画面右方向、Y+は画面上方向

です。
カメラからの入力画像自体を毎フレーム上下反転するのはコスト高なので、OpenGLのカメラ行列で解決します。
カメラ行列の生成時にカメラの上向き方向を定義するとき、Y+が画面下方向になるように指定します。
カメラを逆さまにするってことですね。

カメラからの入力画像しかレンダリングされない

実験ではOpenGL1.x系を使っているのですが、レンダリングバッファに対してカメラからの映像を書き込んでしまうと、深度バッファも更新されてしまいます。
そんな深度バッファのところに3Dオブジェクトをレンダリングを試みても期待した結果は得られません。
カメラ映像を書き込んだ後で深度バッファのみクリアしておく必要があります。
ARでは奥から順に

  1. 現実世界の映像
  2. オーバーレイする3Dオブジェクト

となるので、普通のARの使い方であればこれで事足りそうです。

魔法っぽいものをレンダリングしてみた

3Dモデリングは苦手なので、なんとかARらしいことをしてみました。
ARって魔法みたいですよね。カードゲームの世界も剣と魔法のファンタジーですよね。
それっぽくなるようにレンダリングしてみました。

  • 板ポリゴンに六芒星を描いたα付きイメージ(inkscapeで作った)
  • パーティクル用のフレア画像α付き(gimpのグラデーションフレアで作った)
  • 六芒星、パーティクルともに加算半透明
  • 目立つように画面全体の輝度レベルを半分に落としています

f:id:Catalina1344:20160303172057p:plain

静止画なので分かりにくいですが、カードの姿勢推定も動いているので、傾けたり回転させたりするとそれに反応します。
(物体検出がただの輪郭抽出アルゴリズムのせいで、カードを直接手で持ってしまうと検出できなくなります。姿勢は台紙を傾けて確認しました。)

感想・今後の展望

妙なところで躓きましたが、今回の手法であればそれっぽくARできました。
まだ懸念点・疑問点が残っているので、ここに記しておきます

  • 使用したwebカメラは固定焦点だが、オートフォーカスなカメラではこの手法は通用するのだろうか?
  • solvePnp(PNP問題を解く関数)の具体的なアルゴリズムと特性を知っておきたい
  • レンダリングパラメータから一意にカメラ内部パラメータを算出できるのではないか?
  • 手などで遮蔽されるとカードが検出できなくなるのはかっこ悪いので、なんとかしたい。

キャリブレーション結果のカメラ内部パラメータをメモしておきます。しっかりと理論を理解してから計算で求めたほうがよさそうですね。

[580.5345906650882, 0, 320.354109100925;
 0, 580.5217311914437, 239.9422018792191;
 0, 0, 1]


参考にさせていただいたサイト、文献など

http://qiita.com/akira108/items/e5b43810b64cd921a947
http://www.cyber.t.u-tokyo.ac.jp/~tani/class/mech_enshu/enshu2011mi2.pdf
http://www.wakayama-u.ac.jp/~chen/education/cv/pinpara.pdf

TWE-Liteで外部センサとI2C通信

ずいぶんと長い間放置してたものを再開してみました。かたりぃなです。
再開したのはこれ。
catalina1344.hatenablog.jp
もう一年くらい前なんですね。

構成の再検討

TWE-LiteのライターがないからArduinoでなんとかできないかなーとかやっていましたが、
電圧が5Vと3.3Vで違っていたり、色々面倒になってきたのでTWE-Liteのライター買ってきました。
これでTWE-Liteとセンサ、LEDだけで目的達成できそうです。
http://akizukidenshi.com/catalog/g/gM-08264/

TWE-Lite Rの工夫ポイント

そのままではちょっと使いづらかったので、写真のように二階建てにしてみました。
これでブレッドボード上でなんでもできますし、USB給電の電流を気にする必要もほぼなくなります。
(ジャンパーピンでUSB給電OFFに設定して、外部から電源引っ張ってきてます)
f:id:Catalina1344:20160201220115j:plain

センサのアナログ値を直接TWE-Liteに突っ込んでみる

使うセンサは三軸加速度センサKXP84にしました。(道具箱に転がってた)
http://akizukidenshi.com/download/KXP84-2050_Specifications_Rev2.pdf
このコのアナログ出力のXYZの電圧をそのままTwe-Liteのアナログ入力に突っ込んでみましたが、ダメでした。
KXP84のレンジが1.2V~3.5Vであるのに対して、Twe-LiteのADCは0~2.4Vのようです。
アナログの世界では太刀打ちできないのでデジタル世界でなんとかできないかと考えてみました。
TWE-LiteではI2C使えるのでこいつでなんとかなりそうです。
センサ側はSPIもついてるのですが、TWE-Lite側に実装がなさそうなので、I2Cメインで考えます。

TWE-Liteのコードの全体を眺めてみる

東京コスモス様から公開されているソースコードを落としてきて全体を眺めてみました。
やってることは組み込みソフトウェア的だなぁといったところです。(当たり前か)
組み込み系特有な部分はありますが、今回の目的だけであればふつうにC言語ペリフェラルのライブラリ叩いてるだけなので、そこまで難しくはないと思います。
マニュアルも公開されているので、読みながらやれば目的の機能を追加するくらいはできそうです。(その結果、全体の整合性がどうなるかは未確認)
「超簡単!TWEアプリ」はやってることが多いので読むなら機能を絞っておかないと迷いそうです。
特に低消費電力向けにスリープモードがいくつもあったりとか。このあたりはたぶんZigBee固有なお話だったはず。
http://mono-wireless.com/jp/products/TWE-ZERO/index.html

I2Cの実装を眺めてみる

BH1715というチップとI2C通信する機能があったので眺めてみました。
センサの値が確定するまでタイマイベントを待つ以外はふつうのI2Cの通信のように見えます。
I2Cの仕様詳しくは知らなかったのですが、BH1715の資料が日本語だったこともあり、コードと照らし合わせやすかったです。
http://rohmfs.rohm.com/jp/products/databook/datasheet/ic/sensor/light/bh1715fvc-j.pdf?_ga=1.179572772.1693979872.1454157489

マネして書いてみたコードがこちら。
I2CプロジェクトのMain.cにあるvHandleSerialInputで適当なキー入力をトリガとして以下のコードを起動するようにしました。
BH1715と違ってセンサの測定結果が出るまで待たなくていいので単純です。

    bool_t bres = bKXP84startRead();
    if (bres) {
        vfPrintf(&sSerStream, LB "KXP 84 write success");
    } else {
        vfPrintf(&sSerStream, LB "KXP 84 is not found.");
    }
    // Sr + SAD + R + RA
    vfPrintf(&sSerStream, "I2C Bus read start");
    uint8* recv_data = i16KXP84readResult();
    vfPrintf(&sSerStream, "KXP84.read finish");
    vfPrintf(&sSerStream, "X=[%X%X],Y=[%X%X],Z=[%X%X]",
        recv_data[0],
        recv_data[1],
        recv_data[2],
        recv_data[3],
        recv_data[4],
        recv_data[5]);

ここで呼び出しているbKXP84startReadとi16KXP84readResultの関数もマネしながら書いてみました。

PUBLIC bool_t bKXP84startRead()
{
    bool_t bOk = TRUE;

    uint8	read_address = 0x00;
    bOk &= bSMBusWrite(KPX84_ADDRESS, read_address, 0, NULL);

    return bOk;
}

PUBLIC uint8* i16KXP84readResult()
{
    bool_t bOk = TRUE;
    bOk &= bSMBusSequentialRead(KPX84_ADDRESS, 6, WorkBuffer);
    return WorkBuffer;
}

ソースコードの説明

KXP80のI2C仕様の12,13ページがメインです。
今回の手順はこの資料のSequence 4に相当します。
レジスタマップは資料後半にありますが、アドレス0から2バイト単位にX,Y,Zの加速度が格納されるようです。
http://akizukidenshi.com/download/KXP84-2050_Specifications_Rev2.pdf
まずMasterが通信開始の合図を出し、SAD+Wを出力します。
その後ACKを受けたMasterはRA(Register Address)を出す必要があるので、このアドレスを指定しておきます。
ここまでbSMBusWriteでやってくれます。
続いてi16KXP84readResultでSrとSAD+RをMasterが出してあげると、先の手順で指定したレジスタアドレスから順にデータを送ってきてくれます。
ACK/NACKの処理はbSMBusSequentialReadでやってくれます。

回路を作る

KXP84-2050からみたつなぎ方を言葉でメモしていきます。
VCCとGNDはそのままつなぎます。
MOTとFFは今は使わないので一旦放置。自由落下と過度加速度の検知に使うらしいです。
SCL/SCLKをTWE-LITEのSCLに
SDA/SDOをTWE-LITEのSDAに
ちなみにこの2つの線がI2Cなのでプルアップ必要かなと思いましたが、完成済みKXP84モジュール内部でプルアップされているので不要っぽいです。
モジュール内部のプルアップを抵抗の先はIO_VDDになっているので、こいつを持ちあげておきます。
CSは今回は使いませんが、プルアップしておきます。
RESETもプルアップ。あとでTWE-Liteからリセット制御できるようにしましょう。
ADDR0はプルダウンしておきます。この信号線でKXP84のI2C-SlaveAddressの最下位ビットが決まるとのこと。(つまりプルダウンした状態であればアドレスは0x18)

動作確認

ファームウェア書き込み後、PC側で端末(TeraTerm)を起動して設定をTWE-Liteにあわせておく。
起動させてターミナルで出力を確認

read finishX=[9270],Y=[AE38],Z=[8F30]
read finishX=[9270],Y=[AE38],Z=[8F30]

2回採取してみましたがよさげな値です。
センサを傾けると値が変わります。
アナログで測定した値とだいたい合っているので、一旦これで良しとします。

感想

今回とれた値をTWE-Liteのパケットで送信してあげて、受けた側(魔法陣モジュール側)で適当にLED制御すれば目的のものが作れそうです。
デバッグソースコードと睨めっこになってしまうのがちょっと辛い。オシロかロジアナ欲しいです。
いっそ完成品買えば早いかもしれませんが、TWE-LiteからのI2Cの扱い方の勉強になったということで良しとします。
http://akizukidenshi.com/catalog/g/gM-09453/

最後にどうでもいいこと

TWE-Liteにファームウェアを書き込むとき、ライターに間違えたファイルを放り込むとこんなメッセージが出ます。
f:id:Catalina1344:20160201231155p:plain
ちょっぴりお茶目っ気があってクスっとしてしまいました。

画像認識で機械学習が必要そうという結論に至るまでのメモ

考えを整理するための個人的なメモ的なものです。かたりぃなです。

画像・動画・音声などから特定の「もの」を認識したいことって多々ありますよね。
少なくとも私の中ではしょっちゅうあります。

さて、今回の記事は物体認識の問題色々調べた結果を自分の中で整理するためのメモです。
基本概念の整理が目的です。

物体認識とは

まず物体認識とは何ぞや?という問いについて。物体認識を問題領域の観点から大別して2系統あるようです。

  1. 特定物体認識
  2. 一般物体認識

まず1について。これが私の欲しい物体認識です。
既知の物体Aについて、画像中のどこに物体Aが存在するか(もしくは存在しない)を調べる

2ですが、画像が何を示しているものなのかを言い当てる(車の画像!とか)物体認識です。
画像処理以外の方面の知識も必要となってくるので、こちらは当面は保留とします。

特定物体認識の古典的手法

古くからあるアルゴリズムとして、次の2つをあたってみました。

  1. 物体の形状認識
  2. テンプレートマッチング

まず形状認識ですが、これは物体の形状を表現しやすいデータへと変換し、目的の形状と一致しているかを調べる方法です。
OpenCVの関数でいうとFindContourなどで形状抽出し、shape::match(内部的にはモーメントの比較)などが考えられます。
輪郭線などの画像の特徴的な点は、後で述べる特徴量という考え方にも関連してきそうです。

テンプレートマッチングは使い古された手法で、対象画像とテンプレート画像を比較し、
テンプレート画像をスライドさせていき、もっともよくマッチする場所を探すというものです。

テンプレートマッチングの課題

テンプレートマッチングは外界からのノイズに弱いという課題があります。
外界からのノイズとして、カメラの性能もそうですが、カメラ自身の置かれている環境の影響もあります。
特にwebカメラから取り込む画像というものは外界の影響をふんだんにうけます。(部屋の照明、手ぶれ、など)
テンプレートマッチングでは、対象画像に事前処理を施すことによって不安定要素を取り除くというアプローチが見られます。
照明が安定しないのであれば、適応的に輝度調整を行って誤検出となりうる影を取り除くなど。
(ロバスト設計、ロバスト性などの言葉がキーワード)
このように外界の不安定要素に対して固有に対処して乗り切ることもできますが、外界のすべての事象を事前に予測しておくことは困難です。

またテンプレートマッチングのアルゴリズムの特性上、画像の回転・拡大に弱いという問題があります。
これは、テンプレートマッチングがピクセル単位での比較が基本となっていることに起因します。
OpenCVが提供している比較式を眺めてみても、これは明らかです。
http://docs.opencv.org/master/df/dfb/group__imgproc__object.html#gsc.tab=0
ピクセル単位での比較ではなく、もっと抽象的な比較が行えれば問題解決に向けて大きく前進しそうです。

特徴量という考え方

特徴量といえば顔検出のHaarLikeが真っ先にあがります。
あとはHOGで人体検出などといった文献も見受けられます。
SIFTやSURF,最近のOpenCVではAKAZEが実装されて、AKAZEが今の注目ではないでしょうか。

さて、特徴量の詳細はもっと有用なサイトに譲るとして、
特徴量による画像比較は、検出したい物体について「ピクセルの値(RGB値など)の表現ではなく、その物体をもっともよく表現するポイントを数値で表現し、そのポイントごとの数値で比較しよう」
というアプローチと解釈しています。

さて、特徴量の具体的な例ですが、HaarLikeでは明度差、HOGでは輝度の勾配方向を使って表現します。
このようにして求められた値が特徴的な場所(キーポイント)を特徴点と呼ぶようです。

特徴記述

特徴量がわかったとして、その特徴量をソフトウェア内で表現する必要があります。
これが特徴記述と呼ばれるものです。
単純な差分値であればスカラー、勾配であればベクトルといった表現のほかにも、バイナリとしてどのように表現するかなど、色々なアプローチがあります。

特徴点マッチング

ここまでで特徴点とその表現方法が定義されました。
あとはマッチングするだけです。
OpenCVで提供されている特徴点マッチングのアルゴリズムも色々あって、
特徴記述によっては使える・使えないなどがあります。

特徴量とロバスト

さて、ロバスト性の話に戻ってきました。
画像のマッチングを行うとき、回転不変、拡縮不変、アフィン不変などと呼ばれていますが、
特徴量を使えばこれらの不変性を得られるのか(頑強であるか)といえばそうでもありません。
特徴量ごとににこれらの耐性は違っているので、適切な手法を選ぶ必要がありそうです。
こちらのサイトに代表的な特徴量ごとの測定結果が乗っていたので参考になりました。
AKAZE特徴量の紹介と他特徴量との比較 - 遥かへのスピードランナー

学術的には回転に対して頑健でない特徴量を回転不変にするための提案など色々あがっているようです。
そのあたりも含めて自分で実際に何かを作るときの手順は

  1. 特徴量をそのまま使った場合に「何ができるのか・何ができないのか」を理解し、選定する
  2. 目的機能を満足しない項目に対するフォロー(目的機能を満たすためのロバスト設計)

といった手順が考えられます。

じゃあAKAZE使えばいいんじゃない?と思われますが、私がやりたいことを実現するための課題はまだ残っています。
解決したもの

  • 回転不変、拡縮不変なアルゴリズムであること(そういう特性をもった特徴量を利用する。例えばAKAZE)

残っている課題

  • 外界からのノイズ耐性

機械学習

任意の特徴量のマッチングによって回転、拡大、照明に対してそれなりの耐性は付加できそうというところまで来ました。
それでも外界からの影響は少なからず受けますし、影響自体をゼロにすることは現実的ではありません。(専用部屋でのみ使うソフトですとかであれば話は別かもしれませんが。)
現実世界でのノイズとして

  • 撮影機材そのものの品質(解像度どのくらい?)
  • 検出対象物の個体差(検出対象物に傷、凹みなどがついているかもしれない)
  • 撮影環境(ピントが若干ずれているとか)

などが考えられます。
ここにあげた例が全てではなく、外界からの影響をすべて予測することは非常に難しい問題です。
検出対象の物体についても、品質ばらつきが少ない工業製品であればいいのですが、動物の種類や人間の顔に代表されるような「基本的な構造は同じだが、対象物固有の特徴が付加されているもの」は、これまでの手法だけでは個体差を吸収しきれないため、実現はやや困難です。

さて、いよいよ本題の機械学習です。概念的な部分を中心に調べてみました。
機械学習というアプローチに対する私なりの解釈は
「外界からの影響(撮影機材、環境、対象物の品質ばらつき)を完全に制御することは不可能なので、外界からどのような影響があるのかという統計をとって、その統計情報をもとに判断しよう」
というアプローチだと認識してます。
これは現実的な解ですし、最近はこういったアプローチ(機械でできることは機械でやろうよ)が増えていくと思っています。
機械学習って単語カッコイイです。ただ、本質的な部分は統計(だと私は勝手に思っている)なので、「何を学習させたいのか」を明確にしたうえで利用することが重要だと考えています。

私自身はまだ実際に動かしてはいないのですが、目的にあった機械学習のアプローチを選択しておかないと、学習結果が収束しない・期待した精度が得られないという問題に陥りがちのようです。
難しい分野ではありますが、後述のディープラーニング含めて伸びてくる分野だと思っているので、色々試してみたいところです。

ディープラーニング

機械学習のことを調べていると、このキーワードがよく引っかかります。
ディープラーニングの目的を機械学習の延長上から自分なりに解釈してみると、

  • 機械学習は、学習モデルをプログラマが決める(特徴量の抽出手法、比較方法など)
  • ディープラーニングは、学習モデルそのものも、学習させる

といったイメージです。このあたりはまだ深くは理解していないので、ちょっと自信ないです。
これが真だと仮定して続きを書きます。
こうして機械学習と比べてみるとディープラーニングのほうがさらに汎用化でき、実装コストも下がりそうな気がします。
現実的には学習のコスト(機械学習のコスト)が非常に大きいという課題もあるようです。
加えて、自分自身がスキルを身に着けるためにかかるコスト(時間)が極端に増大するうえに、基礎をおろそかにしていては「まずはやってみた」から抜け出せずに「工夫してみた」にたどり着けない恐れがあります。
現在の私の技術力では、まず機械学習から順に追っていくのが良さそうですし、モチベーションも保てそうです。

結論

ディープラーニング、機械学習、特徴量など色々調べてみましたが、
「ソフトウェア機能実現における課題解決手段であって、それ自体が目的ではない」といったところでしょうか。
プリミティブな技術要素だけでなく、その技術要素のあらまし、目的を明確にしておくという、ごく当たり前の結論に帰着しました。まる。
技術的な結論にしたかったのに、哲学的になってしまってちょっと残念。

今後の展望

あとで読むドキュメントのメモ。

OpenCV3.0についてる機械学習モジュール
OpenCV: Machine Learning

OpenCV3.0にOCR(文字認識)としてtextモジュールが増えてるのでどんなものか見てみるのもいいかもしれない。

上二つはまだよくわかりません。
OpenCV: cv::text::BaseOCR Class Reference

openGLES2.0で基本的なポリゴンレンダリングしてみた

適当にやっていると妙なところで躓きます。かたりぃなです。
今回はOpenGLES2.0を叩いてみました。

実験環境

今更GLES2.0でいいの?

最新のGLESを使ってみたいとは思うのですが、シェーダーの数が増えていて不安要因となっています。
シェーダー書いたことないので、まずは単純なシェーダー(vertex,flagmentの2つのみ)で実現するGLSL2.0をやってみることにしました。
一通り理解してからバージョンあげていったほうが最終的に近道になるだろうと思っています。
そもそも最新版で使いたい機能があるのかといわれると「?」という状態ですし。

躓いたポイント

  • シェーダープログラミングのデバッグ方法が分からない
  • 頂点、UVともに正しいのに、テクスチャが表示されない

躓いたポイントを整理したうえでコードを示します。

そもそもの根本で躓いてしまったのは、自分の中にある3Dグラフィックエンジンの知識が古すぎたのだと思います。
知識の古さについてですが、具体的には

になります。
上にあげた世代の古い3Dグラフィックエンジンは固定機能パイプラインのみ備えていて、基本的な処理は固定機能パイプラインが実現してくれていました。
固定機能としてライティング済み頂点とかトランスフォーム・ライティング済み頂点とか幾つかの頂点フォーマットに対するレンダリング機能がありました。
(DirectX5世代でいうとDrawPrimitiveの引数で指定する頂点形式でTLVERTEXとかLVERTEXとかあった気がします)

現代の3Dグラフィックでは固定機能パイプラインは存在せず、必要な機能はシェーダーとしてプログラミングする必要があります。
そのため「まず基本機能を実現するシェーダーを書く」ってところに時間がかかりました。

3DグラフィックAPIとしての汎用性は高くなりましたが、プログラミングのための最初の敷居が大幅に上がったと感じます。
理解してしまえばどうということはないのですが。

さて、躓いたポイントを順に整理してみます。
まずは一枚の四角形ポリゴンを出力してみて、頂点、色、UV、テクスチャを貼れるところまで。
VisualStudioでもAndroidStudioでもGLESプロジェクトのスケルトンを出力させると単純なプリミティブのレンダリングまでは行えるので、単純なプリミティブのレンダリングはできているという前提で話を進めます。

シェーダーのデバッグ方法がわからない

プログラム始めたばかりの初心者がデバッグ方法わからないというのと同じレベルですね。
まずシェーダーの概念と基本的な処理について理解して、いざ自分でコードを書き始めると詰まるポイントです。
シェーダーではC/C++でいうところのprintfデバッグはおろか、デバッガでのブレークポイントや変数ウォッチができないようです。
じゃあどうするか?
printfデバッグもデバッガの変数ウォッチも本質は「プログラムの状態を認識できる何かが出力できればいい」というところです。
シェーダーからの出力を視認できる形(とりあえず出力される色をデバッグとして使う)GLSL書いてみました。
(何かの拍子に詰まったときに振り返れるようにデバッグ用のコードも残しておきました。)

追加したデバッグ機能でシェーダーが期待通りに動いているかをアトリビュート単位で確認していきます。(頂点、UV、カラー、テクスチャetc.)
そんなこんなで一通り記述しおわったシェーダーがこちら。法線の処理はまだ入れていませんが、それはまたの機会に。(照明の機能を実装するときにいじくりまわすことでしょう。きっと。)
・頂点シェーダー

        // attributeは、頂点シェーダーへの入力の定義
        attribute vec4 vPosition;
        attribute vec4 color;
        attribute vec2 texcoord;
        // 頂点シェーダーでのverying修飾子はフラグメントシェーダーへ渡すことを意味する
        varying vec4 vColor;
        varying vec2 vTexcoord;
        // uniformはC/C++から固定で渡されるパラメータを意味する
        uniform mat4 projectionMatrix;
        void main() {
            gl_Position = projectionMatrix*vPosition;     // 頂点は透視変換かけるだけ
            vColor = color;                                   // 色はそのまま
            vTexcoord = texcoord;                            // テクスチャ座標もそのまま
        }

・フラグメントシェーダー

        precision mediump float;
        // フラグメントシェーダーでのverying修飾子は頂点シェーダーからの入力を意味する
        varying vec4 vColor;
        varying vec2 vTexcoord;
        // uniformはC/C++から固定で渡されるパラメータを意味する
        uniform sampler2D texture;
        void main() {
            gl_FragColor = texture2D(texture, vTexcoord)*vColor;
//            gl_FragColor = vColor;                                             // color情報のデバッグ用
//            gl_FragColor = vec4(vTexcoord.s, vTexcoord.t, vTexcoord.s, 1.0);   // uv座標のデバッグ用
//            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);                           // 頂点情報のデバッグ用(すべて白色ポリゴンで出す)
        }

テクスチャが表示されないとき(プリミティブが真っ黒になる)

さて、OpenGLのテクスチャ回りでは毎度のことながらテクスチャ画像のファイルフォーマットどうするかとか、本来のやりたいことから外れた話になってしまいます。
せっかく環境にOpenCVを組み込んだのでこいつを使うことにします。

肝心のGLES2.0ではテクスチャの扱いが大幅に変わっていたので苦労しました。
最初は適当にマニュアル流し読みしながらパラメータ書いていったのですが、妙なところで詰まりました。
具体的には、ミップマップOFFでテクスチャ作っておきながら、テクスチャの補完パラメータでミップマップ使うような指定したりなど。
テクスチャの設定回りですが、他にも変更点があるようで、GLES1.0世代ではglEnableでテクスチャ有効化などをやっていましたが、これはシェーダーに取って代わられたため不要となったようです。

glActiveTextureとglBindTextureですが、このあたりはまだ完全には理解していないので、またの機会に。
(マルチテクスチャでレンダリングするときはglActiveTextureを使って複数のテクスチャユニットにglBindTextureしていくことになりそう)



テクスチャ生成のコードはこちら。

GLuint create_texture(cv::Mat& fileImage)
{
    // opencvの機能を使ってテクスチャ画像をデコードする
    rawImage = cv::imdecode(fileImage, 1);  // 第二引数に1を指定し、RGBフォーマットで取り出す

    glActiveTexture(GL_TEXTURE0);
    GLuint tex_id;
    glGenTextures(1, &tex_id);    // テクスチャはまずは一個だけ実験
    glBindTexture(GL_TEXTURE_2D, tex_id);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D,
                 0,                              // ミップマップレベル
                 GL_RGB,                         // openGLの内部で保持するカラーフォーマット
                 rawImage.rows, rawImage.cols,   // 画像の幅、高さ
                 0,                              // 境界線の太さ
                 GL_RGB,                         // 元の画像カラーフォーマット
                 GL_UNSIGNED_BYTE,               // 画素のデータ型
                 rawImage.data);                 // 画像データの先頭アドレス
    // テクスチャの拡大縮小方法の指定。ミップマップなしで生成したテクスチャにミップマップありパラメータ指定すると真っ黒になったりするので注意
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    // テクスチャの繰り返し方法の指定
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    return tex_id;
}

後回しになりましたが、頂点バッファ、インデックスバッファなども作っておきます。
ほとんど同じような手順になるので頂点バッファを作る例。
パラメータは公式ドキュメントを参考に。
特にglBufferDataの最後の引数はデータがどこに置かれるかにも関わりそうなので、要注意です。

create_vertex_buffer(GLuint element_num, const GLfloat *elements)
{
    GLuint buff_id;
    glGenBuffers(buffer_num, &buff_id);

    glBindBuffer(GL_ARRAY_BUFFER, buff_id);

    GLuint  bufferSize_of_bytes = sizeof(GLfloat)*element_num;
    glBufferData(GL_ARRAY_BUFFER, bufferSize_of_bytes, elements, GL_STATIC_DRAW);

    return buff_id;
}


あとはシェーダーに対して頂点、UV、色情報、テクスチャ、変換行列を設定してレンダリングしてあげれば完成です。

    // 投影変換行列の設定
    glUniformMatrix4fv(projectionMatrixLocation, 1, GL_FALSE, projectionMatrix);

    // テクスチャユニットの利用準備
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, gTexture);
    glUniform1i(gtextureUniformLocation, 0);    // 0盤目のテクスチャユニット=GL_TEXTURE0を使うよう指示

    // 頂点シェーダーの利用準備
    glEnableVertexAttribArray(gvertexAttributeLocation);        // 頂点シェーダーを有効化
    glBindBuffer(GL_ARRAY_BUFFER, gVertexBuffer);     // 頂点バッファを関連付ける
    glVertexAttribPointer(gvertexAttributeLocation,     // 頂点シェーダーのハンドル
                          3,                    // 頂点フォーマットに含まれる要素数
                          GL_FLOAT,            // 頂点の各要素の型
                          GL_FALSE,            // 正規化の有無(法線で使う)
                          0,                    // 頂点間のストライド
                          0);   // 頂点データのアドレス

    // 頂点シェーダーでのUVバッファの利用準備
    glEnableVertexAttribArray(gtexcoordAttributeLocation);        // 頂点シェーダーを有効化
    glBindBuffer(GL_ARRAY_BUFFER, gUvBuffer);     // UVバッファを関連付ける
    glVertexAttribPointer(gtexcoordAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);

    // 頂点シェーダーでの色情報の利用準備
    glEnableVertexAttribArray(gcolorAttributeLocation);
    glBindBuffer(GL_ARRAY_BUFFER, gColorBuffer);
    glVertexAttribPointer(gcolorAttributeLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);

    // インデックスバッファを指定してレンダリング
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIndexBuffer);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

「~~AttributeLocation」は事前にglGetAttribLocation(program, "symbol")で取得しておきます。
これはシェーダーでattribute定義した変数への入力位置を示すハンドルです。

シェーダーへの入力は次の3ステップで実現できました。
1, glEnableVertexAttribArrayでシェーダーへの入力を有効化し、
2, glBindBufferでシェーダーの入力に関連付けるバッファオブジェクトを指定
3, glVertexAttribPointerでシェーダーへの入力を指定
これらを各アトリビュート(頂点、UV、色 etc.)ごとに行います。

最後にインデックスバッファを関連付けてから、glDrawElementsを呼び出します。
その結果、ここまでに設定した各アトリビュートをシェーダーが読み込んでレンダリングしてくれます。

GLES2.0の感想

OpenGLを使うたびに思うのですが、「現在の状態」を意識しながらプログラミングする環境、ちょっと辛いです。
(bindBufferで何が関連付けられているかをプログラマが把握している必要がある。)
この辛みに拍車をかけるかのようにハンドルの指定方法などが、辛いと感じました(最初は特に)。
例えば、glActiveTextureではテクスチャユニットの指定はGL_TEXTURE0などを使うのにglUniform1iでシェーダーに指定するテクスチャユニットはただの値(0とか)だったりして、低レベルAPIであることを再認識させられます。
(ここでいう低レベルは、ハードウェアに近い抽象化されていないソフトウェアレイヤーという意味です。念のため。)

GPUというハードウェア相手にプログラミングしているのだから仕方のない部分だろうとは思います。
ただ、ふつうに叩き続けるにはちょっと厳しそうなので一枚レイヤーかぶせるとか、ひと工夫したいところです。

今後の展望

今回はアトリビュート単位にバッファを分けてレンダリングする実装にしました。
これをアトリビュートをすべてまとめて1つのバッファで済ませる実装もあるかと思います。
(例えば頂点情報をこんな構造体として定義して、頂点バッファはこれの配列として定義するなど。)

struct vertex{
    vec3 pos;
    vec3 normal;
    vec4 color;
    vec3 uv;
}

どちらのほうが高速かという疑問、そのうち試してみたいと思っています。
特にボーンアニメーションなどの実装をするときに関連するのですが、
ボーンの影響を受けて頂点情報を更新する場合、実際に更新が必要となるのはposとnormalだけかもしれません。
この場合、アトリビュートを分割しておいたほうが必要なものだけ転送できる=バス帯域が節約できる=結果として高速。だったりするのかもとか。(まだ試していない)

どこまでやるかはまだ未定ですが、シェーダー周りはなかなか興味深いものでした。
あとは設計思想的にDirectXのほうはどうなのだろうと気になっています。
MMEがHLSLで書かれてるらしいというのもDirectXが気になっている理由の一つです。

とりあえず今日はここまで。

OpenCVで気になったモジュールを試してみた

自分のやりたいことをやってる時間というのは充実しているものだと、最近はそう感じます。かたりぃなです。

FileStorageモジュール

XML/YAML形式でread/writeできる便利なモジュール。
ドキュメントはこちら。
OpenCV: cv::FileStorage Class Reference
今回の使い道は数学でいう行列(not画像)の保存/読み出しなどですね。

カメラキャリブレーションの結果の3つの行列(内部パラメータ、外部パラメータ、歪みパラメータ)をXML形式で出力する例。

	cv::FileStorage			fs(fileName, cv::FileStorage::WRITE);
	if (fs.isOpened()) {
		cv::write(fs, "extrinsic", cam_ext);
		cv::write(fs, "intrinsic", cam_int);
		cv::write(fs, "distortion", dist);
	}

こんな形でxmlファイルを吐き出してくれました。

<?xml version="1.0"?>
<opencv_storage>
<extrinsic type_id="opencv-matrix">
  <rows>4</rows>
  <cols>4</cols>
  <dt>d</dt>
  <data>
    -9.9305776780672794e-01 -3.4058957106407342e-02
    -1.1258888595035788e-01 -1.5680892918531333e+02
    1.1384691321616840e-03 -9.5990137467839998e-01
    2.8033561097112358e-01 2.2311113599467828e+02
    -1.1762216494672212e-01 2.7826127709644033e-01
    9.5327629152408078e-01 9.1508148149913336e+02 0. 0. 0. 1.</data></extrinsic>
<intrinsic type_id="opencv-matrix">
  <rows>3</rows>
  <cols>3</cols>
  <dt>d</dt>
  <data>
    9.2246418807201553e+02 0. 3.3045069724184663e+02 0.
    9.1528831192908842e+02 2.2281712474807887e+02 0. 0. 1.</data></intrinsic>
<distortion type_id="opencv-matrix">
  <rows>1</rows>
  <cols>5</cols>
  <dt>d</dt>
  <data>
    6.5901038525089195e-01 -6.1369184814174949e+00
    1.9503639590909172e-02 1.4560644371180317e-02 1.7415994956396972e+01</data></distortion>
</opencv_storage>

XMLを読み出すときも簡単に。

	cv::FileStorage fs(filename, cv::FileStorage::READ);
	fs["intrinsic"] >> intrinsic;
	fs["distortion"] >> distcoeffs;

このコード中では全部トップレベルに記述しましたが、fs["string"]の式はcv::FileNode型なので、階層構造も簡単に扱えそうですね。(試してはいない)

メモリ上にあるファイルイメージのデコード

画像ファイルのI/Oとファイルのデコードを分離したいケースがあります。
opencvの関数でメモリ上からデコードできそうなものがあったので試してみました。
ドキュメントはこちら
OpenCV: Image file reading and writing

まずファイルからメモリ上にファイルイメージそのまま展開します。
readでもfreadでもお好みのもので。環境によってはAAsset_readとかかもしれませんね。

    // メモリ上に展開されたpngファイルをデコードする
    cv::Mat fileImage(1, _size, CV_8UC1, _buf);     // _bufにはファイルのバイナリイメージそのまま入っている
    cv::Mat rawImage = cv::imdecode(fileImage, 1);  // 第二引数に1を指定し、RGBフォーマットで取り出す

これだけで_bufに格納してあった画像ファイルのデコードができました。
あとはrawImageを好きなように料理できます。

openCVのビルド情報をログに出す

ライブラリ使っているとありがちなのですが、バージョン違いだったり依存ライブラリの不足なんてことが稀によくあります。
問題を早く解決できるように、openCV自身のビルド情報をログに出しておくと便利です。

    std::cout << cv::getBuildInformation() << std::endl;

ArUcoモジュール

軽量なARライブラリらしいです。
opencv3.0以降、opencv_contribに含まれるようになってました。
ドキュメントはこちら。
OpenCV: ArUco Marker Detection

Arucoでの姿勢推定はcv::aruco::estimatePoseSingleMarkersとかで。
中身は当然のことながらcv::solvePnpでした。
パラレルforっぽい記述が見受けられたので、複数マーカーの場合はPNP問題を解く処理はパラレルに走るのかも?(詳しく調べてはいない)

cv::aruco::drawAxis()が何気に便利。デバッグとかの用途に使いやすいですね。
ARの姿勢推定もそうですが、三次元空間上の物体の姿勢を表示する機能って毎回作ってる気がします。
(加速度センサ・ジャイロセンサーから取ってきた値の確認とか)

oglモジュール

OpenCVOpenGLの相互運用のためのモジュールらしい。
このエントリ書き始めてから存在に気づいたので、また気が向いたら調べてみたいと思います。
ドキュメントはこちら。
OpenGL interoperability — OpenCV 3.0.0-dev documentation

AR,VRについて思うこと

今回のエントリとは若干話がそれますが、思っていることを書いてみます。
最近は某HMDが話題になったりと、VR界隈が賑やかでいいですね。
ARについても今回のエントリで書いたArUcoや一昔前のAR-Toolkitのようなマーカー型ARではなくマーカーレスARの研究も進んでいるようです。
技術的には新しいことが出てきて興味深いのですが、個人的にはVRもARも目的は「従来とは異なる特別な(新しい)ユーザー体験を提供する」ってところにあると思っています。

さて、この「特別な体験」というものが今のARでは作り手側から受け手側への一方通行になってしまっているのがすごく勿体ないと思います。
以前「グリコのポッキーのドラえもんの絵柄を専用アプリで撮影すると立体表示される」というARアプリがありましたが、
これって一発目のインパクトの強さだけで終わってしまうんですよね。「おお、すげー。」って。
VRも同様で、某イベントで「任意の角度から女性キャラの3Dモデルを眺めることができる」というものがありました。
これらのARやVR技術を使った表現方法はインパクトは強く、技術的にも興味深いものですが、何か物足りないと私は感じています。

先にあげた「一方通行」ですが、従来型のテレビや新聞などのマスメディアによる情報の伝達は、基本的に一方通行です。
ARにしてもVRにしても、ほとんどのケースでは従来型マスメディアと同様に一方通行になっているのが残念です。
さらにVRについては、HMDを利用する場合は「周囲の人間が楽しめない」という課題もあると考えています。

じゃあARに関して、どのような方式であれば先にあげた物足りなさ、残念さが解消できるのかを考えると、、、

  • 利用するコンテンツ(3Dモデルなど)はユーザー自身が選び、
  • 現実世界の物体との紐づけもユーザー自身が行える

などが考えられます。
ただ、このままでは「自分だけのAR環境に閉じこもる」だけになってしまうので、MMDが発展してきたように何らかの形で「発表・共有する場」があるといいかもしれません。

なんか整理つかなくなってきたのでこのくらいで。

androidアプリの開発環境を作り直してみた(androidstudio)

今度はandroidに手を出してみてます。かたりぃなです。
ずいぶんと前にいじってみたときはeclipseでコード書いて色々遊んでた気がします。

今どうなってるのかと調べてみると、eclipse用のandroid開発アドオンのサポート終わったらしく、
今後はAndroidStudioでよろしくとのこと。
http://developer.android.com/intl/ja/tools/help/adt.html

私は言われたとおりにやるのが嫌いな天邪鬼な性格なので、なんか他の方法でanroidで遊べないかなぁと調べてみました。
できればマルチプラットフォームAndroidWindows両方一度に開発みたいにできたらかっこいい。

候補

  1. C#でゴリゴリ書く for VS2015
  2. JavaScriptで書く for VS2015
  3. SilverLight+C# for VS2015

なんかVisualStudioに偏ってるけど、どんなものか大雑把に整理

VisualStudio2015を使って、C#でゴリゴリ書く

C#で記述したコードを走らせるためには.Netフレームワークが必要。
windows以外の環境ではxamalin上で走らせる(昔はmonoとか呼ばれてたアレですね)

VisualStudio2015を使って、JavaScriptで書く

JavaScriptHTML5使ってブラウザ上で動作するものを書く。
ブラウザを内包するアプリという形。下回りはCordova。

上記2つはこのページで軽く説明がありました。
特集:次期Visual Studioの全貌を探る:Visual Studio 2015におけるクロスプラットフォーム開発の選択肢 (2/3) - @IT
MSDNのページのほうが情報量は多いけど、偏ってる。当然といえば当然ですが。

SilverLight + C#

そろそろ終わるんじゃないかな。
Flashの対抗馬として頑張ろうとしてたけど、イマイチ感が拭えない。

時代はJavaScriptHTML5な感はあるけど、そこまで魅力を感じない。
やっぱり自分はネイティブな言語のほうが好きなんだなぁと。
というわけでAndroidStudio試してみることにしました。
Androidでhelloworldやっててもつまらないので、opencv走らせてみた。

AndroidStudioのインストール

そのままインストール。
JavaSDK, NDKなど関連するものまとめて入れる。

OpenCV3.0のインストール

OpenCV4Androidがあるのでそれを使いましょう。
このあたりの情報を参考にすればいけるはず。
Android StudioでOpenCV for Androidを使用する | Smartphone-Zine
OpenCV3.0.0 for AndroidStudio - Qiita

opencv.orgではeclipseを使った設定方法しか記述されていないのが辛い。
ANDROID | OpenCV

GradleでNDK-Buildできるよう設定する

C/C++でコード書きたい病を患っているので、設定する。
せっかくopencv放り込むんだから、C/C++から叩きたいですよね。
こういうビルドツールの情報調べるたびに思うのだけど、統一できないものなのかな。
C/C++出身としてはmakeでいいやんとか思ってしまう。

NDK-Buildの設定の前に、Gradleを2.5にアップグレードしないとAndroidStudioに怒られるのでやっておく。
表記法が大幅に変わっている様子。

このあたりのサイトを参考にしました
AndroidStudioでNDK(JNI)を活用する 準備編 - Qiita
Experimental Plugin User Guide - Android Tools Project Site

OpenCVのサンプルコードを動かしてみる

OpenCV-android-sdkのsamplesにあるコード動かすだけ。
OpenCVがあればカメラなんかの入出力回りも面倒見てくれるのでラクですね。
ちょっと気になったのでAndroidAPIを直接叩いてカメラ動かす方法を調べてみた。

AndroidのカメラAPI

Androidとしてはカメラ周りのAPI(android.hardware.camera)が非推奨になってた。
特定のAPIバージョン以上では、cameraの代わりcamera2を使いなさいとのこと。
android.hardware.camera2 | Android Developers
仕様が随分と変わってイベントハンドラを登録して、それに対応する処理を書いていくことになりそう。
カメラプレビューに対する加工も楽にできるかもしれません。
(OpenCVJava-IFにあるカメラAPI叩けば、Mat形式でフレームデータもらえるのでこれが一番ラクだと思います。)

WindowsのカメラAPI

ついでに調べてみました。
こちらは撮影・記録時はイベントハンドラによる処理を行えますが、プレビューの各フレームに対する処理はできなさそうです。
(たとえば色調補正をしたいとか。)
MediaCapture Class (Windows)

Windowsのカメラを使って、プレビューの各フレームに対する処理をしたい時は、Microsoft Media Foundationに従ってプログラミングする必要がありそうです。
たとえば、mediacapture-API一覧にあるStartPreviewToCustomSinkAsync と AddEffectAsync を使うために、引数IMediaExtensionを自作するなど。

Overview of the Media Foundation Architecture (Windows)
メディアストリームに対する変換の例はmediacaptureのサンプルでグレースケール化するというものがあります。
またカスタムメディアシンクを使ったサンプルはカスタムメディアシンクから得た映像をDirect3Dレンダリングするというものがあります。
どちらもMediaFoundationを使ったサンプルプログラムに含まれているので、Windowsでも何かやりたい時はこのあたりを参考にすればよさそうです。
軽く流し読みしたところ、ただのCOMっぽい。

Gradle使ってみた感想

一通り設定した結果、、、Javaの開発では必要なんですね。こういうの。
makeとshellで頑張ればいけなくはないですが、そうしてオレオレビルドツール作るくらいなら、こういうの使うほうが正解ですね。
とりあえずGradleでやってみたけど、自分の設定が悪いのか、非常に重いです。ストレスたまります。

AndroidStudioでandroid開発してみた感想

サンプルコードもそうだけど、UIの制御とビジネスロジックが混ざるのがいただけない。
AndroidGUIよくわかってない部分が多いけど、もっと綺麗に分離できる方法あるのだろうか?


実験や調べものしつつエントリを書いていたのでまとまりがなくなってしまいました。
今日はこのあたりで。