2015年7月27日月曜日

プロジェクタカメラキャリブレーション

公開されているプロジェクタカメラキャリブレーションツール。

Projector-Camera Calibration

http://mesh.brown.edu/calibration/

Projector-Camera Calibration Toolbox

https://code.google.com/p/procamcalib/

ofxCvCameraProjectorCalibration

https://github.com/kikko/ofxCvCameraProjectorCalibration

Self-Calibration of Projector Camera Systems

http://www.dh.aist.go.jp/~shun/research/calibration/

The Laser-Camera Calibration Toolbox

http://www.cs.cmu.edu/~ranjith/lcct.html

2015年7月14日火曜日

カメラキャリブレーションツール

公開されているカメラキャリブレーションツール。

Microsoft Easy Camera Calibration Tool

http://research.microsoft.com/en-us/downloads/7e9de40f-06db-452c-a0f2-4fabb4f20f52/

Agisoft Lens

http://www.agisoft.com/downloads/installer/

ROCHADE

http://www.metrilus.de/blog/portfolio-items/rochade/

LIBCBDETECT

http://www.cvlibs.net/software/libcbdetect/

Camera Calibration Toolbox for Matlab

http://www.vision.caltech.edu/bouguetj/calib_doc/

OpenCV 3.0.0サンプルプログラムcalibration.exeの出力ファイルの読み方

OpenCV2サンプルプログラムcalibration.exeの出力ファイルの読み方メモ。
// init intrinsic parameters (XML file)
cv::Mat intrinsic;
cv::Mat distortion;

cv::FileStorage cvfs("calibration.xml", CV_STORAGE_READ);
bool isOpened = cvfs.isOpened();
if(isOpened)
{
   cvfs["mat_intrinsicMat"] >> intrinsic;
   cvfs["mat_distortionMat"] >> distortion;
   cvfs.release();
   std::cout << intrinsic << std::endl;
   std::cout << distortion << std::endl;
}

画角と解像度から内部パラメータ行列を作る

ピンホールカメラモデルのモデルパラメータである内部パラメータは、様々な形式で記述されるが 画角と解像度だけが分かっている場合に、どのようにOpenCVなどで用いられる内部パラメータ行列(camera matrix)を構成すればよいか解説する。

画角はカメラが映す範囲を直接角度として測ることができるので 画角だけがおおよそ分かっておりスペック等から解像度だけが 知り得る内部パラメータに関係する情報である場合が時々ある。 主点は画像のど真ん中、アスペクト比は1:1だとして、 このような場合、内部パラメータ行列はどのように設定すれば良いのか?

  • $X$, $Y$, $Z$: シーン点のカメラ座標
  • $f$: 焦点距離
  • $U$, $V$: シーン点の画像上の座標 [mm]
  • $u$, $v$: シーン点の画像上の座標 [pixel]
  • $w$, $h$: 撮像素子の画素数
  • $W$, $H$: 撮像素子のサイズ

ピンホールカメラにおける透視投影変換といえば 教科書などでは大抵次のような式が登場する。 \[ U=f\frac{X}{Z} \] ただし、大抵単位の変換が無視されていて三次元座標$X,Z$も焦点距離$f$も、 画像上の座標$U$もmm単位が前提で、単位は省略して書かれることが多い。 ここでは画素単位の画像上の座標$u$もちゃんと考慮する。

mm単位での撮像素子のサイズ$W$と撮像素子の画素数$w$の比から 画素単位の画像上の座標$u$次の様に書ける。 \[ u = \frac{w}{W}U=w\frac{f}{W}\frac{X}{Z} \]

ここで画角$\theta$がわかっていれば$\tan\theta=\frac{W/2}{f}$と書けるので、 次式のように書け、この$\tan\theta$こそがOpenCVなどの内部パラメータ行列の要素として登場する$f_x$に相当することが分かる。 \[ u = \frac{w}{2\tan\theta}\frac{X}{Z}=f_x\frac{X}{Z} \] 画角が本質的なパラメータなので実際の焦点距離$f$も撮像素子のサイズ$W$も必要ないことがここから分かる。

上の図は画像の水平方向のみのものだけど,もう一方も同様になる.

\[ v=\frac{h}{H}V=h\frac{f}{H}\frac{Y}{Z} \]

ここで$w:h=W:H$なので$\frac{h}{H}=\frac{w}{W}$であり

\[ v=\frac{w}{W}f\frac{Y}{Z}=\frac{w}{2\tan\theta}\frac{Y}{Z} \]

と,水平方向と同じになる.画像の左上が原点だとすると結局内部パラメータ行列$K$は次のようになる。

\[ K=\left[ \begin{array}{ccc} \frac{w}{2\tan\theta} & 0 & w/2 \\ 0 & \frac{w}{2\tan\theta} & h/2 \\ 0 & 0& 1 \end{array} \right] \]

2015年7月7日火曜日

wgetによるデータの一括のダウンロード方法

ステレオ画像処理の評価標準画像データであるMiddlebury Stereo Datasetsのダウンロードを題材にして、 Windowsへのwgetのインストール方法と使い方をメモしておく。

Chocolateyからwgetをインストール

ChocolateyはUbuntuのapt-getのようなもの。 使えばインストールは簡単なのでインストールされていなければまずこれをインストール。 インストール方法も簡単で下記の>以降をコマンドプロンプトにコピペしてエンターを押すだけ。

> @powershell -NoProfile -ExecutionPolicy Bypass -Command \"iex ((new-object net.webclient).DownloadString(\'https://chocolatey.org/install.ps1\'))\" && SET PATH=%PATH%;%ALLUSERSPROFILE%\\chocolatey\\bin

wgetのインストールもコマンドプロンプトでこの一行。

> choco install wget

データのダウンロード

データダウンロード用のbatファイルを作って一括ダウンロードする。 正規表現などで自動的にディレクトリを列挙してダウンロードするなど、 もう少し賢い方法は今後検討する。

:: 10 evaluation training sets with GT
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Adirondack-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Jadeplant-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Motorcycle-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Piano-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Pipes-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Playroom-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Playtable-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Recycle-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Shelves-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Vintage-imperfect/

:: 13 additional datasets with GT
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Backpack-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Bicycle1-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Cable-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Classroom1-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Couch-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Flowers-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Mask-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Shopvac-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Sticks-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Storage-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Sword1-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Sword2-imperfect/
wget -r -np -nH -N --cut-dirs=4 http://vision.middlebury.edu/stereo/data/scenes2014/datasets/Umbrella-imperfect/

cv::FASTの落とし穴?

特徴点検出クラスcv::FASTの落とし穴発見。 カラー画像を入力しても実行時エラーもでず、 間違った位置の特徴点が返ってくる。 ただし、マニュアルには入力はgray imageと書かれているのでバグではない。

入力画像例

グレースケール画像を入力した場合

cv::Mat_<cv::Vec3b> color = cv::imread("im0.png");
cv::Mat gray, features;
std::vector<cv::KeyPoint> keypoints;

cv::FAST(gray, keypoints, 10.0);
cv::drawKeypoints(gray, keypoints, features);
cv::imwrite("features_gray.png", features);

カラー画像を入力した場合

cv::Mat_<cv::Vec3b> color = cv::imread("im0.png");
cv::Mat gray, features;
std::vector<cv::KeyPoint> keypoints;

cv::FAST(color, keypoints, 10.0);
cv::drawKeypoints(gray, keypoints, features);
cv::imwrite("features_gray.png", features);

2015年7月6日月曜日

科研費の英語/English of KAKENHI

科研費公募要領に登場する単語

研究種目

  • 科研費
    Grant-in-Aid for Scientific Research (KAKENHI)
  • 特別推進研究
    Grant-in-Aid for Specially Promoted Research
  • 特定領域研究
    Grant-in-Aid for Scientific Research on Priority Areas
  • 新学術領域研究
    Grant-in-Aid for Scientific Research on Innovative Areas
  • 基盤研究(S・A・B・C)
    Grant-in-Aid for Scientific Research (S) or (A) or (B) or (C)
  • 萌芽研究
    Grant-in-Aid for Exploratory Research
  • 挑戦的萌芽研究
    Grant-in-Aid for Challenging Exploratory Research
  • 若手研究(S・A・B・スタートアップ)
    Grant-in-Aid for Young Scientists (S) or (A) or (B) or(Start-up)
  • 研究活動スタート支援
    Grant-in-Aid for Research Activity Start-up
  • 特別研究促進費
    Grant-in-Aid for Special Purposes
  • 研究成果公開促進費
    Grant-in-Aid for Publication of Scientific Research Results
  • 特別研究員奨励費
    Grant-in-Aid for JSPS Fellows
  • 学術創成研究費
    Grant-in-Aid for Creative Scientific Research

用語

  • 研究者代表者
    Principal Investigator
  • 共同研究者
    Co-Investigator (kenkyu-buntansha)
  • 連携研究者
    Co-Investigator (renkei-kenkyusha)
  • 研究協力者
    Research Collaborator
  • 交付申請
    the Application for Funding
  • 分担者承諾書
    the Written Consent of the Co-Investigator

科研申請書に書く可能性のある単語

JST

  • 科学技術振興機構(JST)
    Japan Science and Technology Agency
  • 研究成果最適化展開支援プログラム(A-STEP)
    Adaptable and Seamless Technology transfer Program through target-driven R&D

論文誌

  • 映像情報メディア学会誌
    Journal of the Institute of Image Information and Television Engineers 
  • 日本色彩学会論文誌
    Journal of the Color Science Association of Japan
  • 電子情報通信学会論文誌(D)
    Journal of the Institute of Electronics, Information and Communication Engineers (D-II) 
  • 情報処理学会論文誌
    IPSJ Journal
  • 情報処理学会論文誌:コンピュータビジョンとイメージメディア
    IPSJ Transactions on Computer Vision and Applications
  • ヒューマンインタフェース学会論文誌
    The Transactions of Human Interface Society
  • 日本バーチャルリアリティ学会論文誌
    Transactions of the Virtual Reality Society of Japan
  • 生体医工学
    Transactions of Japanese Society for Medical and Biological Engineering
  • 電気学会論文誌C(電子・情報・システム部門誌)
    IEEJ Transactions on Electronics, Information and Systems

研究会・シンポジウム名、冊子名

  • 信学技報
    Technical Report of IEICE

参考

2015年7月5日日曜日

cv::Point_クラスの落とし穴?

OpenCVのcv::Point_クラスを積極的に使うと、添字の順序を間違える危険性も少なくなりコードが見やすくなる。 でも、OpenCVのマニュアルは読んでもソースコードまでは読まないような 私のような初心者にとってはなかなか気づきにくい落とし穴を発見した。(OpenCV version 3.0.0)

落とし穴の例

次のコードは12行目の「src(point2d)」の部分で、 行列srcの添字が範囲外となって実行時エラーになる可能性がある。 func(j, i)は、整数の座標(i, j)を入れたら浮動小数点の座標posを返す関数で 画像の回転など幾何変換などで頻繁に出くわす状況だと思う。 「posが行列の範囲外になってしまう可能せがあるからだ」、 と思いきやif文でしっかり範囲指定されていて問題ないかのように見える。

cv::Mat_<uchar> src(480, 640), dst(480, 640);

for (int j = 0; j < color.rows; j++)
{
    for (int i = 0; i < color.cols; i++)
    {
        cv::Point2d pos = func(j, i);

        if (0 <= pos.x && pos.x < src.cols &&
            0 <= pos.y && pos.y < src.rows)
        {
            dst(j, i) = src(pos);
        }
    }
}

原因はdoubleからintへの暗黙の型変換

実はこれposがcv::Point2dでdouble型、つまり浮動小数点であることが原因。 というよりOpenCVの仕様をよく知らずに上記の様に書いたことが本質的な原因だけど。 この原因を理解するために、srcのクラスcv::Mat_<uchar>のメンバ関数operator()()をを確認してみる。 というのもoperator()()は行列の要素にアクセスする関数なので、 行列の添字として整数しか受け付けないはずなので。

template<typename _Tp> inline
_Tp& Mat_<_Tp>::operator ()(Point pt)
{

引数としてPointクラスしか受け付けないことが分かったが、 Pointクラスってなんぞやと思いさらに定義を確認。

typedef Point_<int> Point2i;
(中略)
typedef Point2i Point;

Pointクラス=Point2iクラスであることが分かり、 Point2i=Point_<int>クラスであることが分かった。 問題のコードのコンパイルが通っていたということは、 Point2d、つまりPoint_<double>クラスから Point_<int>クラスに暗黙的に型変換されていたことが分かる。 では、その暗黙の型変換に使われたキャスト用の関数があるはずなので、次はこれも確認する。

saturate_castとは四捨五入する型変換

//! conversion to another data type
template<typename _Tp> template<typename _Tp2> inline
Point_<_Tp>::operator Point_<_Tp2>() const
{
    return Point_<_Tp2>(saturate_cast<_Tp2>(x), saturate_cast<_Tp2>(y));
}

これがその型変換の関数。 分かりにくいけど、これは同じテンプレートクラスPoint_の型だけが違うものどうしの変換を扱う関数。 つまり、ここでは_Tp2がint型で、_Tpがdouble型ということになり、 Pint_<double>::operator Point_<int>()という型変換関数だと思えばいい。 テンプレートを用いた型変換に、こんな書き方があること自体知らなかった。 単にPint_<_Tp>::Point_<_Tp>(const Pint_<_Tp2>& p)などと書いてしまいそう。

とにかく、その型変換用のoperator()()の中ではsaturate_castt<int>(double v)というのが呼ばれて、 double型で座標を入れてもちゃんとint型に変換されるようだ。 では、そのsaturate_castって何か?

template<> inline int saturate_cast<int>(double v)           { return cvRound(v); }

なんとこの型変換関数の中では四捨五入の関数cvRound()が呼ばれてる。 なるほどそれで問題の本当の原因が分かった!! つまり、問題のコードの中でif文中で範囲を限定してもsrc(pos)範囲外になる場合がある。

反例は、pos.xの値が639.9などの時。 pos.x < src.colsが真であったとしても、 src(pos)では最終的にcvRound(pos.x)が呼ばれて、640になってしまう。 srcの列数は640なのでもちろん添字は639が最大だからエラーになったという訳。

エラーの回避策

エラーの回避策の一つはif文で判定する前に整数に変換されるようにすること。 つまり次のように座標変換関数の出力が浮動小数点であっても、 整数の座標クラスで受けて整数に変換してしまえば、 if文の判定とsrc(pos)の範囲がズレることはない。

cv::Point pos = func(j, i);

2015年5月12日火曜日

OpenCVの再投影誤差の計算関数computeReprojectionErrors()

OpenCVには再投影誤差を最小化してカメラの姿勢を求める関数が用意されているのに、計算された再投影誤差を得る関数がない(Version 3.0RC1)。 サンプルコードには2つの似たような関数computeReprojectionErrors()が存在して、中身は殆ど同じ。 異なる部分は、型変換の扱いだけで、camera_calibration.cppにある関数が良さそう。 この関数を自分のコードにコピーしたらすぐに使える。

calibration.cpp

static double computeReprojectionErrors(
        const vector<vector<Point3f> >& objectPoints,
        const vector<vector<Point2f> >& imagePoints,
        const vector<Mat>& rvecs, const vector<Mat>& tvecs,
        const Mat& cameraMatrix, const Mat& distCoeffs,
        vector<float>& perViewErrors )
{
    vector<Point2f> imagePoints2;
    int i, totalPoints = 0;
    double totalErr = 0, err;
    perViewErrors.resize(objectPoints.size());

    for( i = 0; i < (int)objectPoints.size(); i++ )
    {
        projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
                      cameraMatrix, distCoeffs, imagePoints2);
        err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2);
        int n = (int)objectPoints[i].size();
        perViewErrors[i] = (float)std::sqrt(err*err/n);
        totalErr += err*err;
        totalPoints += n;
    }

    return std::sqrt(totalErr/totalPoints);
}

camera_calibration.cpp

static double computeReprojectionErrors( const vector<vector<Point3f> >& objectPoints,
                                         const vector<vector<Point2f> >& imagePoints,
                                         const vector<Mat>& rvecs, const vector<Mat>& tvecs,
                                         const Mat& cameraMatrix , const Mat& distCoeffs,
                                         vector<float>& perViewErrors)
{
    vector<Point2f> imagePoints2;
    size_t totalPoints = 0;
    double totalErr = 0, err;
    perViewErrors.resize(objectPoints.size());

    for(size_t i = 0; i < objectPoints.size(); ++i )
    {
        projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);
        err = norm(imagePoints[i], imagePoints2, NORM_L2);

        size_t n = objectPoints[i].size();
        perViewErrors[i] = (float) std::sqrt(err*err/n);
        totalErr        += err*err;
        totalPoints     += n;
    }

    return std::sqrt(totalErr/totalPoints);
}