2017年11月3日金曜日

【魚突き】 メグシの作り方

メグシとは?

メグシ とは,魚突きなどで魚を捕まえたときに魚をキープしておくための道具. 似たような用途の道具としてストリンガーがある. 棒の真ん中にひも状のものがついているのが最もシンプルなメグシ. 棒がついてない側のひもをどこかにくくりつけておき, 棒をエラから口に通すと逆方向には外れにくくなるので複数の魚を逃がしたり紛失したりしないですむ.

どんなメグシがいい?

  • 錆びない・臭くならない→ステンレス
  • かさばらない→
  • エラ通しし易いように棒の先がとがっている
  • 複数の魚をキープできる

今回作ったメグシがこれ.ワイヤをかしめて固定するオーバルスリーブがアルミ製だけどそれ以外は全てステンレス製.

メグシはホームセンターで作れてしまう

例えばコーナンというホームセンターにはセルフ工房とかDIY Labという設備があってその店舗で購入した部品や材料なら金属加工の道具も自由に使える. 上のメグシはとあるコーナンで材料と工具のみ買い,同じ店舗のセルフ工房で作ったもの.

買ったものはこのステンレス製の杭のような棒とドリルの刃先,ワイヤーとオーバルスリーブ,環付きカラビナ(?)だけ.

使った工具はグラインダーとカッター,ドリル.グラインダーで中央の穴を空ける部分を平らにし,輪っかの部分をカッターで切断し,平らな部分にドリルで穴をあけただけ.ポンチもおいてあったので穴を開ける前に軽く凹みををつけてドリルの刃先が滑らないようにした.グラインダーとかカッターは火花が散るので,置いてあったゴーグルを使った.

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

2017年9月8日金曜日

Boostを使って日付と時間をファイル名に

日付と時間をファイル名を含めて毎回新しいログファイルを保存するサンプルコード.
Boostライブラリのインストールが必要.

Visual Studioのプロジェクト:
https://github.com/r168xr169/NameDateTime

#include <string>
#include <fstream>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

void main(void) {
    using boost::posix_time::ptime;
    using boost::posix_time::second_clock;
    using namespace boost::gregorian;

    date today = day_clock::local_day();
    ptime now = second_clock::local_time();

    //open file
    std::ofstream ofs;

    //create a new file
    std::string filename = (boost::format("%s_%s.txt")
        % to_iso_string(today) % to_iso_string(now.time_of_day())).str();
    ofs.open(filename);

    //output "[time]: test"
    ofs << to_iso_string(now.time_of_day()) << ": test" << std::endl;
}

2017年9月2日土曜日

OpenCVでウィンドウ枠を消す

OpenCV (version 3.1.0)でウィンドウを表示するのはすごく便利だけど, プロジェクタに映像を出す場合などでフルスクリーンにしてもなぜか枠が消えない. 最も単純な方法はこれ.

int w = 1920; //メインディスプレイの水平解像度
cv::namedWindow("projector", 0);
cv::moveWindow("projector", w, 0);
cv::setWindowProperty("projector", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
HWND hwnd = FindWindow(0, L"projector");
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);

SetWindowLong()で枠なしを指定する次のような方法も解説で書かれているけどやっぱり枠が消えない.

int w = 1920; //メインディスプレイの水平解像度
cv::namedWindow("projector", 0);
cv::moveWindow("projector", w, 0);
cv::setWindowProperty("projector", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
HWND hwnd = FindWindow(0, L"projector");
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);

枠の色を黒に変更することでとりあえず枠が見えないようにすることはできたのでメモ.

int w = 1920; //メインディスプレイの水平解像度
cv::namedWindow("projector", 0);
cv::moveWindow("projector", w, 0);
cv::setWindowProperty("projector", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
HWND hwnd = FindWindow(0, L"projector");
SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG)CreateSolidBrush(RGB(0, 0, 0)));

2017年7月24日月曜日

車載LiDAR 比較

Velodyne VLP Puck (VLP-16)

Velodyne VLP Puck LITE (VLP-16-LITE)

Velodyne VLP Puck Hi-Res

Quanergy M8

  • URL:http://quanergy.com/m8/
  • 価格:1000ドル未満
  • 水平画角:360度
  • 垂直画角:??度
  • レート:420,000 点/秒

2017年4月26日水曜日

Epson BT-300の提示画像更新レート

Epson BT-300のHMDとして重用なスペックを調べたのでメモしておく.

スクリーン更新レート

ある程度更新レートが高ければ(例えば60Hz以上なら)ヒトの目では画面が更新されていることが分からないのでこのスペックは重用ではない. カメラで映像を撮影する場合にカメラ側の画像取得レートを一致させないと画面のチラツキが生じるので重用になる.

この動画を見るとCGのティーポット上に明るい水平線が一本見える. この線は 左上の数値Requested(Requested Framesの更新レートの意味)を見ると21.36fps→21.35fps→21.37fps→21.36fpsと変化し, 21.36fpsの時のみ変化しないので,BT-300の更新レートは21.36と言える.

2017年2月22日水曜日

OpenCVのウィンドウ上にOpenGLで連番画像再生

OpenCVのウィンドウ上にOpenGLで連番画像を動画として再生するサンプルコード. GLUTよりもはるかに簡単なことに気づく.

OpenCV3.1.0でWITH_OPENGLを有効にしてCMAKEしたものを使用. OpenGLはc:\Program Files (x86)\Windows Kits\8.1\Include\um\gl\GL.hを直接includeしてもエラーだらけになったので https://www.opengl.org/resources/libraries/glut/のglut.hをincludeした.

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

#include <opencv2/opencv.hpp>
#include <opencv2/core/opengl.hpp>
#include <GL/glut.h>
#include "cvlibs.h"

void on_opengl(void* userdata);
using namespace cv;

int main(void)
{
    VideoCapture video("./jpg/img%03d.jpg");
    int w = video.get(CV_CAP_PROP_FRAME_WIDTH);
    int h = video.get(CV_CAP_PROP_FRAME_HEIGHT);
    int n = video.get(CV_CAP_PROP_FRAME_COUNT);

    namedWindow("window", CV_WINDOW_OPENGL);
    resizeWindow("window", w, h);

    Mat frame;
    ogl::Texture2D tex;

    setOpenGlDrawCallback("window", on_opengl, &tex);
    glEnable(GL_TEXTURE_2D);

    for(int i =0; i<n && waitKey(30); i++)
    {
        video >> frame;
        tex.copyFrom(frame);
        updateWindow("window");
    }

    return 0;
}

void on_opengl(void* userdata)
{
    ogl::Texture2D* pTex = static_cast<ogl::Texture2D*>(userdata);
    if (pTex->empty())
        return;

    pTex->bind();

    glLoadIdentity();

    double x = +1.0;
    double y = -1.0;
    glColor3d(1.0, 1.0, 1.0);
    glBegin(GL_QUADS);
    glTexCoord2d(0.0, 0.0); glVertex2d(-x, -y);
    glTexCoord2d(1.0, 0.0); glVertex2d(+x, -y);
    glTexCoord2d(1.0, 1.0); glVertex2d(+x, +y);
    glTexCoord2d(0.0, 1.0); glVertex2d(-x, +y);
    glEnd();
}