Author: Lait-au-Cafe

2018年、Vの年

サークルメンバーで作ったVTuber配信ツール”KCSTuber”のHPをchoko君と作成しました.
https://kcs1959.github.io/kcstuber

記事を投稿しました.
https://kcs1959.jp/archives/4064/research/rust_image-crate_example

動画を投稿しました(VTuber活動).
https://youtu.be/oqtV6uOIpeU
https://youtu.be/dff4qQsI-XU

少し前に投稿したサークル製作のゲーム紹介
https://youtu.be/jHxQcV7AeV8
https://youtu.be/XK3xnx-_oQw


あらゆるすべてがアツ過ぎた2018年12月01日は過ぎ去りました.
あの滾るような1分1秒は過去のものとなり,人々の記憶からもいずれはなくなることでしょう.
我々は書物に残された記録から過去を復元しようとしますが,決して実感として体験することはできません.
一人の人間が経験し覚えていられることなど,所詮長い時間の中の一瞬に過ぎず,そういった意味では未来も過去も等しく未知といえます.

世界中の死んでいないみなさん,こんにちは.
Lait-au-Cafeです.
みなさんいかがお過ごしでしょうか.

今年はバーチャルユーチューバー(以下VTuber)が話題となった年かと思います.
世間一般の健常なみなさんがどう受け止めているかは想像もつきませんが,私にとってはかなりインパクトが大きいコンテンツでした.

理想的な作業環境
理想的な作業環境の図

アニメーションや漫画のキャラクターにも魂はありますが,アニメーションや漫画のキャラクターの魂は物語の文脈の中で描き出されるのに対し,VTuberというコンテンツではキャラクターが自発的に動き回るという特性のためか,VTuberには魂の存在を強く感じます.
アニメーションや漫画は作者→視聴者・読者という方向性が強く,基本的には作者の用意した文脈の中にキャラクターの魂を見出していく形になりますが,VTuberはそのインタラクティブ性質上,受け手側から堀り下げられる余地があり,魂の奥行きを実感することが出来るように思います.

そうした奥行きゆえか,一見して似たようなカテゴリに属するライバーたちでも,ライブを見ていると思わぬところで決定的な違いを感じ,驚かされることがしばしばあります.
そういった個別性・多様性がVTuberというコンテンツの魅力の一つだと考えます.

特に意味はないです.

←ふつかめ よっかめ→

Rustで画像入出力

みなさん,Rust使ってますか?
新元号がRustに決まったことは記憶に新しいですね.
まだ使っていないよ!という方もこれを機に始めてみてはいかがでしょうか.

さて今回はRustで画像処理を行うために画像入出力用のcrateであるimageを使ってみたいと思います.
https://docs.rs/image/0.20.1/image/

GolangではC++実装のOpenCVを直接呼出しました( GolangでもOpenCVしたい!!! ).
RustでもOpenCVのラッパであるcv-rsを使ってもよいのですが,どのみち処理はすべて自分で書くので入出力だけできればよく,今回はimageを選びました.(imshowが出来ないのはちょっと不便かも).

今回はサンプルとしてグレースケール化を実装します.
もちろん画像読み込み時に直接グレースケールとして解釈すればよいのですが,一連の処理フローを確認するという意味でいくつかの方法でべた書きで実装します.

コードは以下.

使い勝手はそれなりにいいですが,いくつかうーんと思う仕様もあります.
たとえばget_pixelは範囲外にアクセスした際にpanicしてしまいますが,RustなのだからOptionで返すなどしてほしいです.
またget_pixel時のオプションとして範囲外アクセス時の挙動(clamp, wrapなど,CUDAでいうところのアドレッシングモード)の指定ができるとよいですね.
他にもimage::Rgbで各要素に.r()/.g()/.b()でアクセスできるようにしたり,まだまだ開発途上感はありますが,PNGやJPEGを自前でエンコード/デコードしなくてよいのはそれだけで非常にありがたい事です.

FnMutを画像として読み込むことが出来るのも極めて興味深い点です.
テスト用の合成画像を作りたい場合や簡単なフィルタを適用したいときに便利かと思います.

最後に,image crateに限らず,もしライブラリのビルドに失敗する場合はrustup updateを適用してみてください.rustcのバージョン違いでビルドできていない可能性があります(一敗).

それではみなさん,Rustで楽しい画像処理ライフを.

OpenCVでカメラキャリブレーション

OpenCVを使ったカメラキャリブレーションをググると,まずCでの実装例が出てきて,次に比較的新しいpythonでの実装例が出てくるが,C++での実装例がいまいちヒットしない.

加えて,カメラキャリブレーションは基本的にあらかじめ用意したデータについて行うが,私は普段Webカメラを利用するので,データの収集とキャリブレーションを同時にできるといいな,というのがあった.

ので,作った.

リポジトリはこっち
https://github.com/Lait-au-Cafe/calibration

本当はできるだけシンプルにしたかったのだが,冒頭で述べたようにプリセットのデータがなくてもできるようにしつつ,でもプリセットのデータがあってもできる,という仕様にした結果少し複雑になってしまったように思う.

当該リポジトリを利用する際にはビルドにOpenCV3.xとpkg-configが必要なのに注意.

P行列の自由度とパラメータ

物の本によると,カメラによる物体の投影を表現する “P行列” は以下のようにあらわされる.

PMat1

なるほどな.

別の本では以下のように記述されている.

PMat2

まあそういうこともあるだろう.

ところで,P行列の自由度(DOF, Degree of Freedom)は一般に11とされる.
P行列は全部まとめると3×4行列になるが,定数倍しても意味が変わらないのでここから1を引いて11,という説明がされているのを見かける.

さて,上の二つの式をもう一度見てみよう.
回転行列Rと並進ベクトルtは二つの式で共通で,自由度はともに3であるから,一番左の行列だけで自由度は3+3=6.
真ん中の行列は焦点距離fがあって自由度1.
左の行列は,上の式では変数がa, s, c_x, c_yの4つで自由度4.
下の式では変数がδ_x, δ_y, c_x, c_yの4つで自由度4.

上の式も下の式も全部合わせて4+1+6=11自由度!
世界は今日も平和だなぁ…





ほんまか???

上の式と下の式で異なる点は以下の二点である.

  • 上の式ではx軸方向のスキューsが考慮されている.
  • 上の式ではアスペクト比aで表しているものを,下の式ではピクセルの物理的なサイズδ_x, δ_yで表している.

これを見て私が抱いた疑問が以下の2点.

  • 上の式でもaではなく物理的なサイズδ_x, δ_yで表せば自由度が12になる??
  • さらにy軸方向のスキューも考慮すれば自由度が13になる???

式にするとこんな感じ.

PMat3

しかし自由度12はギリギリ許容できても,3×4行列の自由度が13になることは逆立ちしてもあり得ない.
ではどれが正しくてどれがどう間違っているのか.

順に確かめていこう.

アスペクト比aとピクセルの物理的なサイズδ_x, δ_yについて



結論
“焦点距離f”と”アスペクト比a”のペアで表すか,”ピクセルの(相対的な)物理的サイズδ_x, δ_y”で表すのが冗長の無い表現である.

先程のP行列の式で,下の方の式では焦点距離fとピクセルの物理的なサイズδ_x, δ_yを用いてP行列を表現していた.
これは解釈上はいいのだが,自由度の観点ではこのうちの一つは冗長なので誤解を生じやすい.
変数はf, δ_x, δ_yの三つあるが,実際には自由度は2しかない.

式的な解釈としては,先ほどの式を少し変形して以下の形にするとわかる

PMat4

ここの式を見ると,1/δ_x’=f/δ_x, 1/δ_y’=f/δ_yと置き直しても普遍性を失わないことがわかる.
よって,スキューを考えない場合P行列の自由度は10.
あるいはf’=f/δ_x, a=δ_x/δ_yと置きなおすと,焦点距離とアスペクト比で表現できる.

f, δ_x, δ_yの変数のうち一つが不要である理由を言葉で表現するとどうなるか.
ここでもう一度焦点距離fの意味について問い直すと,これはカメラ座標系のスケールを正規化するためのパラメータとして解釈できる.
焦点距離fによる正規化を行った後,再度ピクセルの物理的なサイズδ_x, δ_yを用いてスケーリングを行うのだが,別に一度正規化を挟まずとも直接,カメラ座標系のスケールに対するピクセルの物理的なサイズδ_x’, δ_y’によってスケーリングしてしまえばそれでよい.
わざわざ一度fでスケーリングを行う必要はない.
(もちろん,処理に物理的な解釈を与えるという観点では重要な操作である).

もしスキューを考慮しないP行列で自由度が11になると説明する人間が居たら「ほんまか??」をぶつけよう.

y軸方向のスキューについて

結論
y軸方向のスキューは回転R,並進t,焦点距離f,アスペクト比aを取り直すことで消えるので考慮しなくてもよい.仮にy軸方向のスキューを明示的にP行列の式に組み込んでも自由度は変わらず11となる.

証明は以下.

PMat5

かなり雑に言葉で説明すると,カメラを90度回転させればx軸方向のスキューがy軸方向のスキューになる,という感じ.

画像作り終えてからsinの符号が逆なのに気づいた….
眠い…,適宜読み替えて読んでください….

まとめ

P行列の自由度は,
焦点距離f + アスペクト比a + スキューs + 画像中心の座標c_x, c_y
+ カメラの回転R(3自由度) + カメラの並進t(3自由度)= 11自由度.

以上.

OpenCL with OpenCV (OpenCV-CL)を使ってみた.-RGB編-

前回は取得したフレームをcvtColorでグレー画像に変換してからOpenCL側へ渡す,というまどろっこしいことをしていましたが,実際カラー画像を扱えないとお話にならないので何とか使えないかと試行錯誤.

CUDAの場合はuchar3は構造体として実装されているようで[要出典],uchar(=グレー画像)の場合とほとんど同様に扱うことができるが,OpenCLのuchar3は少し違った仕様になっているようで,ucharの場合をそのまま拡張しただけではうまくいかない.(CUDAのコードも適当に整理して上げたいですね).

ucharなどをスカラ型と呼ぶのに対し,uchar3などはベクタ型と呼ばれますが,このベクタ型の配列にアクセスする場合はvloadnやvstorenを使う.

参考サイト
How vector pointers work in openCL
Vector Data Load and Store Functions

それを考慮して実際にRGB画像を扱って各要素にアクセスした例が以下.

やっていることは至極単純なグレースケール化のみ.
今回もよくわかっていない点として,widthがいつでもUMatのpitchと等しくなるのか,という点.
例えば,CUDAの場合はx+y * widthとやるとうまくいかなくて,x+y * pitchのようにしなければいけない.つまり要素数はwidth分だが,各行当たりのメモリはもう少し余裕をもって確保されている.このpitchの値はcudaMallocPitchでメモリをアロケートする際に一緒にもらえる.
今回のOpenCV-CLの場合はOpenCV側の提供するUMatを使っているため問題ないのだろうか.
一応UMatもプロパティとしてstepやoffsetを持っているため,これを考慮せずにindexingしても大丈夫なんだろうかいやでも今のところうまくいってるしなあ,という感じ.
つまるところ,まだまだ検証の足りないコードなのでindexing周りで不具合が生じる可能性がある.

ちなみにindexingまわりで不具合があると出力画像はこんな風になりがち.
DXsiYnIVwAAS8bV

今日は以上.

OpenCL with OpenCV (OpenCV-CL)を使ってみた.

ここに “””つらみ””” があります.

image

環境を統一しようにもなかなか険しいものがある.(NvidiaのOpenCLはCUDAのドライバ入れれば動く…?)
世界に試されている.
でも今出先なので(=Geforceを積んでいるデスクトップ環境が使えないので),ノートPC上でできる環境を適当に決めて実装しないと無限に「グレブナ基底と代数多様体入門Ⅰ」を読んでしまう.無限に某ーロッパ企画のゲームをプレイする某員長の動画を見てしまう.

色々と考えた結果,OpenCVの環境作ればOpenCLが使えるらしいことを知って,じゃあこのOpenCL使ったらいいか,という気持ちになった.
OpenCVは全体的にドキュメンテーションに難があってほとんど参考にならないが,神先駆者様がいたのでこれをほとんどコピペでとりあえずサンプルコードを動かしてみた.

negaposi

とりあえずサンプルは動いたが今後手を加えていくとうまくいかなくなるかもしれないのでしばらく様子見.
カーネルコードをロードしてコンパイルするあたりでRustのシャドーイングを使いたくなったが残念ながらこれはC++なのでそうは問屋が卸さない.
世界中のスクリプトがすべてRustに置き換わらないかな.
せめて世界中の言語がすべて強い静的型付き言語にならないかな.

RustからC++を呼び出すこともなんとなく考えたが,参考サイトに
“C++ exceptions can mostly be caught in D, but that’s not a thing in Rust at all. “
とあって,確かになぁ…という気持ちになった.
ただでさえセーフな言語からアンセーフな言語を呼び出すのは厳しいのに,エラーハンドリングのやり方などの言語仕様も異なるとなると,考えることが増えそう(曖昧).

コードの印象としては,カーネルコードのロード,コンパイル,呼び出しの流れはPyOpenCLのほうが若干すっきりしていた気がする.(以前書いたPyOpenCLではホストコード中にカーネルコードを書いていたのに対し,今回は別ファイルにカーネルコードを書いているため,ファイルを読み込む処理が追加されているため煩雑に感じるのかもしれない).
カーネルコードの引数指定の部分は知らないと絶対にわからない仕様(詳しくは参考サイト様参照)なのにドキュメンテーションには記載が見当たらなかったので,このあたり敷居が高いなと感じた.

今回のコードで少し不思議に感じたのは,フレームのグレースケール画像(gray)が更新されたときにデバイスのバッファ(d_frame)へデータを送らなくてもちゃんと更新が反映された点.UMatは今回の例のように直接imshowに渡せるようだし,情報の受け渡しはOpenCV側がやってくれているのだろう.
つまり,cv::UMat d_frame = gray.getUMat(…);が実行された時点で,grayへの書き込みが自動的にd_frameに反映されるようにお膳立てしてくれているのかもしれない.(というかそもそも実体がGPU上に移るのかもしれない).
確信はないけど.

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

参考サイト様
OpenCV 3.0.0での独自カーネルOpenCL
UMatの内部処理(OpenCL編)
Any updates on calling C++ from Rust

Bash on Windows のxtermで日本語フォントがうまく使えなかったのでメモ

ラップトップのほうではもともと入っていたxtermを使ったのでうまくいったのかわからないが, デスクトップでやろうとしたら以下のエラーが出て日本語フォントが表示できなかった

xterm: cannot load font ‘-misc-fixed-medium-r-semicondensed–13-120-75-75-c-60-iso10646-1’

こちらを参考に, yumをubuntu用に読み替えればよいのかもしれないが, ubuntuに暗くそれができなかったので別の記事を参考に別途日本語フォントを入れた.
xtermコマンドだと~/.Xresourcesがロードされなかったのでuxtermを使うことにした.
おわり

CUDAのSurfaceを使ってみる

CUDAのTextureはreadonlyです. (唐突)
「なんでwriteできないんだ!HLSLにはRWTexture2Dがあるのに!」と思うかもしれませんが, Textureの特色はグローバルメモリからデータをフェッチしてくる際に利用されるキャッシュにあるため, そもそも書き込みではその恩恵を受けることができず, よって書き込みはできる必要がないと言えます. (書き込みの際には普通のメモリを使う).
SurfaceはCC2.0以上でしか利用できませんが, Textureと異なり書き込みも行うことができます. じゃあSurfaceには書き込みにもうま味があるんか?というと, 特にそういった記述はProgramming Guide中で見つけられませんでした. TextureとSurfaceは同列に語られているようなので, 単に書き込みの対象にも指定できるようになっただけなのかもしれません.
処理が一段だけであれば単純にTextureを使えばよいのですが, 処理が何段階もあり, 二つのTextureの間を行き来するようにして処理をしていく場合には少し不便なので(ほんまか?)Surfaceを利用してみました.

今回の知見ですが,

  • CUDAはバージョンによって結構仕様が変わっているっぽいのでちゃんと自分が使用しているバージョンの Programming Guide を読まないといけない(それはそう)
  • Textureの場合はバージョンの違いに加えて, Low-Level APIとHigh-Level APIの2種類のAPIが存在するため, 一方でうまくいかない場合は他方を試してみると良い

といったところでしょうか.

以下にSurfaceのサンプルコードを示します. cudaMallocPitchで確保した普通のバッファに入れてある, Webカメラから取得した色情報をSurfaceに移して(この時uchar->floatの変換とグレースケール化を行う), また戻すだけです. surfRef2は今後使う用で今は使っていないです. ガウス窓も今後使う用で今は使っていないです.

作業中に見た「イリヤの空、UFOの夏」がよかったです.
以上.

GolangでもOpenCVしたい!!!

人間の三大欲求の一つに「GolangでOpenCVをやりたい」というものがあります.
満たす必要があったので満たしました.

今回の環境はWindowsだったので, 基本的にはこちらで紹介されている通りにすればできました.
WindowsでgolangからOpenCVを呼び出してみた – ねずみとりんご

またWindowsでのGolang環境としてはVisual Studio Codeを使うといい感じです.
VSCodeでGoの開発環境を作成する方法まとめ

今回一点嵌ったのは, OpenCV3.0をGolangで使うことができないという点でした.
以前入れたOpenCVが3.0.0だったのでこれを流用しようと思ったのですが, gocはコンパイラとしてgccを利用するのに対し, OpenCV3.0以降ではgccでヘッダ周りをコンパイルできないため, GolangでOpenCVを用いるには適当に古いバージョンを持ってくる必要があります. 参考にしたサイトではOpenCV2.4.9を使用していたためこれを真似たところ, うまくいきました.
Opencv3 compilation issue with C API #6585

またこれ以外にも, C++のライブラリを使用する際には注意が必要です.
cgoでGolangとC++ライブラリをリンクするとき、何が起きているのか

以下はサンプルコードとなります. コードはこちらを参考にGoに移植しました.
カメラ画像を表示(C言語)

以上.

CUDA + OpenCVでWebカメラから取得した画像を変換

とりあえず簡単にグレースケールを作成.
実行すると画像の下の端が切れて悲しい. 理由は分からない.
実装した感想ですが, 「textureをつかえ」という感じなので, cudaBindTextureToArrayを使ってテクスチャを使っていきたい.
とりあえずミニマムで実装するとこんな感じになるんじゃないでしょうか.

参考 : http://fareastprogramming.hatenadiary.jp/entry/2016/11/10/181234
あとはCUDA落とした時に入ってるCUDA SamplesのsimpleTextureなども参考になるかと思います.