2017年10月17日火曜日

AR関連学会の予稿集と論文誌

論文誌

予稿集

IEEE Xplore

ACM Digital Library

個々の年度の学会のページに行くにはPublication Archiveというタブをクリックしないといけない.

2017年10月7日土曜日

HoloLensの光学シースルー像をカメラで撮影する

HoloLensで体験できるCGと実シーンが融合された見たままの風景を撮影したい. HoloLensのカメラで利用者視点のビデオシースルー像を撮影したい場合はMixed Reality Captureを,第三者視点のビデオシースルー像の撮影はSpectator Viewを使えばできる. でも,これらはカメラで撮影された画像の上にCGを合成した像で,人間が見たままの像(光学シースルー像)ではない. 見たままの像を撮影するにはHoloLensの目の位置にカメラを置いて撮影するしかないのでやってみた.

撮影動画

使った物

固定方法

不満点

  • レンズの選択がまずく画角が狭い.
  • HoloLensの結像面が2m先くらいで実シーンとCGの両方にピントを合わせるにはCGが遠くなり,その分運動視差で立体的に見せることが難しい.
  • Flea3のキャプチャソフトFlyCap2では,パラメータを全部固定にしてもゲインや色調が変わっているように見える.
  • このクランプでクリアに像が見える位置にカメラを固定するのは結構難しくて,一度固定したら外したくなくなる.

2017年10月2日月曜日

std::threadでバッファリングして連番画像再生

メインスレッドの裏で連番画像をロード(バッファリング)しておきながら OpenCVのウィンドウ上に連番画像を動画として再生するサンプルコード. C++11のstd::threadを使ってなるべく単純なコードになるように必要最小限に.C++11とスレッドは難しい...

サンプルコード:
https://github.com/r168xr169/AsyncLoader

プログラムの重用なところの解説

まずはmain関数.バッファはvector配列bufでn_buffer個のcv::Matを格納するようにしている. フレーム番号i_currentを用いて配列の添字をi_current % n_bufferとしているので範囲外にならずに最初に戻るようになっている.

int main(void)
{
    //load関数をスレッドとして起動
    thread th = thread(load);

//(中略)

    //動画表示のループ(waitKeyを呼ばないとimshowがうまく動作しない)
    while(waitKey(1) != 27)
    {

//(中略)

        //開始からの時刻をフレーム番号に
        i_current = ms / (1000.0 / fps);

        //最後のフレームまでいくと終了
        if (n_frames <= i_current) break;

//(中略)

        //バッファ中の画像フレームを再生
        imshow("window", buf[i_current % n_buffer]);
    }

    return 0;
}

次に,スレッドとして呼び出されるload関数.

void load(void)
{
    while (is_alive)
    {
        //最後にロードしたもの(i_last)が
        //再生中のフレーム(i_current)からバッファサイズ(n_bufffer)分先にあれば
        //これ以上バッファが足らないのでロードを待機する
        if (i_current + (n_buffer - 1) <= i_last)
        {
            is_ready = true;
        }
        else {

//(中略)
            //最後のロードしたものの次のフレームのファイル名を作成
            int i_last_pp = ++i_last;
            std::ostringstream oss;
            oss << "jpg/img" << setfill('0') << setw(3) << i_last_pp << ".jpg";

//(中略)
            //画像をロード
            buf[i_last_pp % n_buffer] = imread(oos.str());
        }

        //適当にsleepを入れてこのスレッドだけでCPU負荷が一杯になるのを防ぐ
        this_thread::sleep_for(milliseconds(1));
    }
}

細かい部分の解説

準備部分

DLLのロードやウィンドウの生成の負荷が高いので,初期フレーム付近で描画遅れたり, フレームレートが低下したりする問題があるので, 完全に準備できてから再生開始するためにこのようにしている. is_readyが準備できた合図になる.

while (!is_ready) { 
    this_thread::sleep_for(milliseconds(1)); 
}

フレームレートの制御

制御というより開始時刻から現在のフレーム番号を計算しているだけ. C++11のstd::chronoを使ってみた.使い方が正しいか自信がない.

auto diff = system_clock::now() - start;
long long ms = duration_cast<milliseconds>(diff).count();
i_current = ms / (1000.0 / fps);

デバッグ用のcoutにはmutexを使う

このlock()とunlock()無しでcoutすると,メインスレッドとload関数のスレッドでcout出力された文字列が 混ざり合ってしまう.mutexを使ってロックすると1つのスレッドがcoutし終わるまで, 他のスレッドの動作は停止するので出力が混ざることは

mtx.lock();
cout << this_thread::get_id() << "; i_current = " << i_current << endl;
mtx.unlock();

再生が速過ぎる場合の対処

ここは再生が速過ぎる場合の対処だけども,むしろ起動時にロードが遅れて再生に抜かれることが多いのでこのようにしている. 初期状態ではi_last=-2,i_current=-1としている.

if (i_last < i_current) {
    i_last = i_current;
    is_ready = false;
}

ファイル名のインクリメント方法

sstreamを使うとこういう風にできるけど直観的でないしこんなの誰も覚えてない.

std::ostringstream oss;
oss << "jpg/img" << setfill('0') << setw(3) << i_last_pp << ".jpg";
C言語のstdio.hならすごくシンプルだけど256の部分は文字数をよく考えないといけない.
char fname[256];
sprintf(fname, "jpg/img%03d.jpg", i_last_pp);
boost::formatならこんな風にシンプルで美しくできる.スピードは遅いらしい.
string sequence = "jpg/img%03d.jpg";
string fname = (boost::format(sequence) % i_last_pp ).str();

参考資料

変更部分が全て排他制御されている場合でもatomicは必要か?(質問者も回答者もレベルが高い)
https://teratail.com/questions/54740