かみのメモ

プログラムな話題中心の勉強メモ

OpenCVのカメラ歪みシミュレータを作ってみた

OpenCVのカメラキャリブレーションの際に取得できる歪みパラメータを可視化するWebアプリを作ってみました。

キャリブレーション結果の確認などにお使いください。

https://kamino410.github.io/cv-snippets/camera_distortion_simulator/

screen shot

ソースコードこちら

できるだけの動作確認はしていますが、もしバグを見つけた場合はGitHubで報告していただけると幸いです。

実装をざっくり解説

OpenCV3.4では、カメラ歪みモデルを以下のように仮定しています。

{} $$ \begin{array}{l} x' = x _ {undistort} \\ y' = y _ {undistort} \\ r ^ 2 = x' ^ 2 + y' ^ 2 \\ x'' = x' \frac{1 + k _ 1 r ^ 2 + k _ 2 r ^ 4 + k _ 3 r ^ 6}{1 + k _ 4 r ^ 2 + k _ 5 r ^ 4 + k _ 6 r ^ 6} + 2 p _ 1 x' y' + p _ 2(r ^ 2 + 2 x' ^ 2) + s _ 1 r ^ 2 + s _ 2 r ^ 4 \\ y'' = y' \frac{1 + k _ 1 r ^ 2 + k _ 2 r ^ 4 + k _ 3 r ^ 6}{1 + k _ 4 r ^ 2 + k _ 5 r ^ 4 + k _ 6 r ^ 6} + p _ 1 (r ^ 2 + 2 y' ^ 2) + 2 p _ 2 x' y' + s _ 3 r ^ 2 + s _ 4 r ^ 4 \\ x _ {distort} = f _ x x'' + c _ x \\ y _ {distort} = f _ y y'' + c _ y \end{array} $$

すなわち、歪んだ後の画像座標を歪む前の画像座標の陽関数として定義しています。 このように定義すると、歪んだ画像を元に戻す処理(Undistort)はremap()を使って比較的簡単に実装できます。

一方、このモデルのとおりに画像を歪ませる処理は割合難しいものになります。 詳しい解説は省きますが、上記の多項式の逆問題を解くか、もしくは格子状に並んでいない点群の間での補間処理を実装する必要があるためです。

ということで、今回はWebGL上で適当な粗さの三角メッシュを歪めて、メッシュの中では線形補間を行う、という近似的な方法で実装しました。

放射状歪みの係数を負にしたとき背景に針山が現れるバグがあるんですが、WebGLはGeometry Shaderに対応していないらしく、修正困難なので放置しています。

OpenCVで取得したカメラパラメータをUnityで使う

今回はOpenCVで取得したカメラパラメータ(内部パラメータ・外部パラメータ)をUnityのカメラオブジェクトに反映させる方法についてまとめたいと思います。{}

プロジェクションマッピングとかARをするときによくやる手順ですので、誰かの参考になれば嬉しいです。

もくじ

1. 作成したUnityスクリプト

github.com

最初に今回作成したC#スクリプトを晒しておきます。 「解説とかいらないよ」って人は、このスクリプトをカメラオブジェクトにアタッチして使ってください。

OpenCVCameraParamsが内部パラメータを反映させるスクリプトOpenCVTranslateが親オブジェクトからの相対位置を外部パラメータの通りに設定するスクリプトです。

f:id:kamino-dev:20181102153011p:plain

2. 概要

まずは、カメラパラメータの話と、OpenCVとUnityでは座標系が違うよという話をおさらいしておきます。

2.1. OpenCVのカメラパラメータ

OpenCVで取得できるカメラパラメータは内部パラメータ・歪みパラメータ・外部パラメータの3種類に分けられます(歪みパラメータを内部パラメータの一部とすることもあります)。 各パラメータは次の要素を含んでいます。

  • 内部パラメータ(intrinsic parameters)
  • 歪みパラメータ(distortion parameters)
  • 外部パラメータ(extrinsic parameters)
    • 回転(rotation)
    • 並進移動(translation)

この記事では、この内の内部パラメータ・外部パラメータをUnityに反映させる方法を紹介します。 歪みパラメータを再現するには、Shaderを自前で実装する必要があるので今回は据え置きです(後日、別記事で書くかもしれません)。

2.2. OpenCVとUnityの座標系の違い

OpenCVとUnityを連携させる上では、座標系に関していくつか面倒な点があります。

1つ目は、OpenCVが右手座標系であるのに対し、Unityが左手座標系である点です。 右手座標系と左手座標系は軸のどれか1本を反転させた関係にあります。 また、右手座標系における回転は軸の正方向を向いて時計回りを正としますが、左手座標系では反時計回りを正とします。 そのため、座標値をそのまま移植すると、OpenCVのシーンとUnityのシーンが鏡対称になってしまいます。

2つ目は、回転・並進移動の表現方法の問題です。 OpenCVでは座標系の関係を同次座標系で記述します。 一方、Unityでは内部的には同次座標系を使っているのでしょうが、表面的にはtransform.rotationtransform.positionを使用する仕様になっています。

両者の違いですが、前者では回転ベクトルもしくは回転行列を用いて回転を記述しますが、後者では主にクォータニオンを用いて記述することになります。 また後述しますが、前者では回転→並進移動の順に処理が行われるのに対し、後者では並進移動→回転の順に処理が行われます。

とまあ、このような違いを同時に考慮しながら実装しなければならない、ということになります。 左手座標系とか滅べばいいのに。

3. 内部パラメータを反映させる

それでは、内部パラメータについて考えてみます。

Unityのカメラはデフォルトで透視投影モデル(perspective projection model)になっていますが、残念ながら光学中心を動かしたりX/Y方向の画角を別々に設定したりすることはできません。 そこでCamera.projectionMatrixを直接設定してやることにします。 Camera.projectionMatrixOpenGL形式の投影行列を受け入れてくれるらしく、座標系も右手系準拠でよいそうです。

ということで、OpenCVの内部パラメータを反映した透視投影行列を作ってみます。 よく見る透視投影行列はnearfartopbottomrightleftの6つのパラメータで定義するタイプですが、これをnearfarfxfycxcywidthheightを用いた表現に置き換えます。

$$ \begin{bmatrix} \frac{2f_x}{w} & 0 & 1-\frac{2c_x}{w} & 0 \\ 0 & \frac{2f_y}{h} & -1+\frac{2c_y}{h} & 0 \\ 0 & 0 & -\frac{f + n}{f-n} & -\frac{fn}{f-n} \\ 0 & 0 & -1 & 0 \end{bmatrix} $$

OpenGLでは上がY軸正方向ですが、OpenCVでは下がY軸正方向になるので、2行3列目の符号が反転しています。

コードにするとこんなかんじです。

Matrix4x4 PerspectiveMat() {
    var m = new Matrix4x4();
    m[0, 0] = 2 * Fx / ImageWidth;
    m[0, 1] = 0;
    m[0, 2] = 1 - 2 * Cx / ImageWidth;
    m[0, 3] = 0;

    m[1, 0] = 0;
    m[1, 1] = 2 * Fy / ImageHeight;
    m[1, 2] = -1 + 2 * Cy / ImageHeight;
    m[1, 3] = 0;

    m[2, 0] = 0;
    m[2, 1] = 0;
    m[2, 2] = -(Far + Near) / (Far - Near);
    m[2, 3] = -2 * Far * Near / (Far - Near);

    m[3, 0] = 0;
    m[3, 1] = 0;
    m[3, 2] = -1;
    m[3, 3] = 0;
    return m;
}

また、透視投影行列で画像サイズを固定すると、ウィンドウサイズを変更したときにアスペクト比がおかしくなってしまいます。 そこで以下のようにゲーム画面のアスペクト比を固定するためのコードを加えておきます。

float screenAspect = (float)Screen.height / Screen.width;
float aspect = (float)ImageHeight / ImageWidth;
if (screenAspect < aspect) {
   float scale = (float)ImageHeight / Screen.height;
   float camWidth = ImageWidth / (Screen.width * scale);
   cam.rect = new Rect((1 - camWidth) / 2, 0, camWidth, 1);
} else {
   float scale = (float)ImageWidth / Screen.width;
   float camHeight = (float)ImageHeight / (Screen.height * scale);
   cam.rect = new Rect(0, (1 - camHeight) / 2, 1, camHeight);
}

以上で、キャリブレーションしたカメラと同じ画角・縦横比のカメラをUnity上に作ることができました。

4. 外部パラメータを反映させる

次に外部パラメータについて考えてみます。

外部パラメータを決めるためには、OpenCVとUnityの間でどの座標軸が反転していることにするかを決める必要があります。 OpenCVとUnityは共通して「カメラの向きはZ軸の正方向」と定義していますので、Z軸を反転させてしまうとカメラを180°回す操作が必要になって面倒です。 またX軸とY軸はどちらでもいいのですが、座標系の扱いを間違えたときに左右が逆になるより上下逆さまになる方が間違いに気付きやすいです。 ということで、ここではY軸を反転させることにします。 つまり、OpenCV上の点(1, 2, 3)はUnity上の(1, -2, 3)と対応していることにします。

4.1. 回転

まずは回転成分について。

OpenCVでは座標系の回転を回転ベクトルないし回転行列で出力させることができますが、ここでは回転ベクトルを利用します。

OpenCVの回転ベクトルは変換後の座標系から変換前の座標系への回転を「ベクトル軸vを中心にベクトルの長さ|v|分の角度だけ右回転させる」という形式で記述したものです。 そして回転ベクトルの面白い性質なのですが、この定義に従ったとき変換前後のどちらの座標系で見ても回転ベクトルの成分は同じ値を取ります。 そのため、変換前の座標系から変換後の座標系への回転は「ベクトル軸vを中心にベクトルの長さ|v|分の角度だけ左回転させる」という風に表現できます。

それでは、この回転ベクトルをUnityに移植しましょう。 OpenCVで取得した回転ベクトルをv = (v_x, v_y, v_z)とします。

Unityではtransform.localRotation親オブジェクトから自身の座標系への回転をクォータニオン形式で入力することでオブジェクトの回転を定義できます。 そしてUnityではQuaternion.AngleAxis()で回転ベクトルをクォータニオン表記に変換できるので、この関数を利用します。

まずUnityは左手系ですので、回転ベクトルはUnity上で(v_x, -v_y, v_z)と表現されます。 次に回転量ですが、ここで表現したいのは親オブジェクトから自身の座標系への回転なので|v|だけ左回転させればよいことになります。 左手系では左回転を正とするので、結局+|v|だけ回せばよいことになります。

これをコードに書き起こすと次のようになります。

var rod = new Vector3(RotationX, -RotationY, RotationZ);
this.transform.localRotation = Quaternion.AngleAxis(rod.magnitude * 180 / Mathf.PI, rod);

4.2. 並進移動

最後に並進移動について。

OpenCVから取得できる並進ベクトルは、変換後の座標系原点から変換前の座標系原点までの距離を回転後の座標系において表現したものです。 一方Unityは、親オブジェクトの座標系でlocalPositionに移動させたあとにlocalRotationの分だけ回転させます。 そのため、取得した並進ベクトルを変換前の座標系での表現に直してから方向を反転させてlocalPositionに代入してやる必要があります。

これをコードに書き起こすと次のようになります。 Y軸が反転しているのでy成分だけ+になっている点に注意してください。

var xdir = this.transform.localRotation * Vector3.right;
var ydir = this.transform.localRotation * Vector3.up;
var zdir = this.transform.localRotation * Vector3.forward;
this.transform.localPosition = -xdir * TranslationX + ydir * TranslationY - zdir * TranslationZ;

以上をまとめたのが、冒頭のGitHubのリンクのソースコードです。

CGの座標系はなかなか面倒ですね。

EDSDKを使ってCanon EOSカメラをPCから制御してみた

またまたニッチなネタ。

Canon一眼レフカメラであるEOSシリーズをPCから制御するプログラムを書いてみたよ、というお話です。

ソースコード

カメラと接続して写真を1枚撮って保存して終了する、という基本動作のソースコードGitHubにアップしました。 言語はC++です。

github.com

詳細はGitHubの方を見ていただくことにして、この記事ではプログラムを書いている間に気付いたことなどを適当にまとめてみます。

SDKの入手方法

まずはEDSDK(EOS Digital Software Development Kit)を入手します。

Canonは日本の企業のはずなんですが、なぜか日本のページからは入手できません。 Canon EuropeもしくはCanon USAのdeveloper programにアカウント登録することでダウンロードできるようです。 私はCanon Europeからダウンロードしました。

言語はC++C#VBに対応しているようです。

サンプルプログラム

EDSDKをダウンロードすると、中にCameraControlRawDevelopの2種類のサンプルプログラムが含まれています。 前者はGUIからカメラの露光時間やフォーカスを操作して写真を撮影することができるアプリです。 後者は撮影したRAWデータを使って、自由に色補正やホワイトバランス調整を施すためのアプリです。

今回はとりあえずPCからカメラを制御するのが目的ですので、前者を参考にしました。

とはいえ、このサンプルプログラム、相当古いのかWindows MFCで書かれてるんですよね…。 ロジックとビューの分離とかそういう原則ガン無視なのでかなり読みにくいです。

自前で実装する時は、SDKに同包されてるAPI Refferenceを参照する方が懸命かもしれません。

実装上気をつけた方が良さそうなこと

カメラ側で非同期処理が生じるケース

カメラのシャッターを押す処理やフォーカスを動かす処理など、EdsSendCommand()で指示した処理はカメラ側で非同期実行されます。 非同期処理の実行中は、カメラはビジー状態になり他の処理を受け付けなくなります。

カメラ側での処理を待つ場合は、事前にObjectEventHandlerPropertyEventHandlerCameraStateEventHandlerのうち対応するものに関数を登録した上で、EdsGetEvent()を繰り返しコールすればよいようです。 イベントはEdsGetEvent()をコールしたときにしかハンドルされないようなので注意しましょう。

画像の保存

撮影した画像の保存先はカメラに挿したSDカード・ホストPCから選択できます。 ホストPCに保存する場合は、カメラメモリからホストPCにデータを転送する処理を書かなければなりません。 サンプルコードではホストPC側に保存する処理の方を実装しています。

また、カメラ側での保存形式をJPEGに設定した上でメモリストリームを使ってデータ転送すれば、一時保存することなくOpenCVcv::Matの形式に直接変換することができます。 この辺りもサンプルコードを参照してください。

まずいコードを書いたとき

APIを呼び出す順序を間違えたり、変なパラメータを設定したりすると、カメラがエラーを起こすことがあります。 こうした場合は一度カメラの電源を切って再起動しましょう。


と、こんなところでしょうか?

使っていてまた気付いたことがあれば追記したいと思います。

OpenCVを使ってグレイコードパターン投影をする

よく忘れるので、自分用にOpenCVを使ったグレイコードパターン投影のテンプレートを作りました。 ついでなので解説記事を書いてみます。

github.com

目次

1. グレイコードパターン投影とは?

MRの分野でいうグレイコードパターン投影は空間コード化法の一種です。

空間コード化法とは、プロジェクタの投影像をカメラで撮影しているときに、あるカメラの画素にどのプロジェクタ画素が写り込んでいるかを分析するためのアルゴリズムです。 プロジェクタ・カメラのキャリブレーションプロジェクションマッピングをする際のキャリブレーション、3Dオブジェクトの形状計測などに利用されます。

グレイコードパターン投影はこれをグレイコード(gray code)を利用して行う手法を指します。

1.1. 基本的な原理

グレイコードパターン投影では、プロジェクタ画素(もしくは画素ブロック)の座標をグレイコードに変換して、それに応じた白黒パターンを画像として表示します。 これをカメラから撮影し、カメラの各画素にどのようなパターンが写り込んだかを分析することで、カメラ画素とプロジェクタ画素の対応関係を取得します。

例えば、横幅を10ブロックに分割するとき、X座標は4ビットのグレイコードで表現できます(24 = 16)。

 0: 0000     4: 0110     8: 1100    12: 1010
 1: 0001     5: 0111     9: 1101    13: 1011
 2: 0011     6: 0101    10: 1111    14: 1001
 3: 0010     7: 0100    11: 1110    15: 1000

つまりX座標を判別するためには、以下のような4枚のパターン画像を表示してやればよいです。

f:id:kamino-dev:20180921141751p:plain

これをカメラで撮影した時、ある画素に「黒黒白白」というようなパターンが写り込んだなら、0011 <-> 2より、投影した画像上のX座標2のブロックが写り込んでいるということが判別できます。

バイナリコード(2進数)ではなくグレイコードを使う理由は、パターン画像の縞模様の最小幅が2ブロックになり、カメラから撮影したときにモアレが生じにくくなるからです。

1.2. ネガポジパターン

上で述べた手法の課題として、どうやって白黒を判定するのかという問題があります。 例えば、白と黒のタイルが交互に貼ってあるような壁にパターンを投影したとき、白い部分に黒パターンを投影したものと、黒い部分に白パターンを投影したものはどちらも灰色になるので、白黒どちらのパターンが投影されているのかを区別できなくなります。

これを解決する方法として、投影の際に元のパターン画像(ポジティブ)と色を反転させたパターン画像(ネガティブ)の2つを投影するというものがあります。 こうすれば、ポジティブとネガティブどちらの方が明るいかを調べることで、もとの壁の色の影響を受けずに白黒を判別できるようになります。

1.3. 利点

  • 原理が単純
  • 実装がラク
  • セッティングに気をつければそこそこロバスト
    • 間違った対応関係を検出することがほとんどない

1.4. 欠点

  • プロジェクタ画素の座標をピクセル精度(整数精度)でしか取得できない
  • パターンの境目部分のコードの読み取りに失敗することが多い
  • カメラの焦点ぼけに弱い
    • ある位置に焦点を合わせると、他の位置のコードがうまく分析できなくなったりする

※この辺りの問題点が気になる人は位相シフト法(phase shifting method)などを調べましょう

2. グレイコードパターン投影の実装

2.1. OpenCVを用いた実装

OpenCV3系では、外部モジュールであるopencv_contrib内のstructured_light::GrayCodePatternにネガポジを利用したグレイコードアルゴリズムが実装されています。 三次元形状計測に利用することを前提に実装されているので取り回しが少し面倒ですが、これを流用すれば比較的ラクにプログラムを書くことができます。

※三次元形状計測については公式がサンプルコードを公開してくれています

opencv_contribのインストール方法については過去の記事を参照してください

2.2. サンプルプログラム

以上を踏まえてサンプルプログラムを作成しました。 ソースコードは記事冒頭のGitHubリンクからご覧ください。

処理の流れは次のようになっています。

  1. グレイコードパターン画像を生成
  2. カメラを初期化
  3. パターンの1つを投影してキーボード入力を待機
    • この間にカメラの位置やピントを調節してください
  4. パターンを順に投影・カメラから撮影
  5. カメラの終了処理
  6. カメラ画素ごとにグレイコードを分析
    1. 白色投影時と黒色投影時の輝度値の差がBLACKTHRESHOLDより大きいならグレイコードが写っていると判断する
    2. 各ビットについて、ポジティブ投影時とネガティブ投影時の輝度値の差がWHITETHRESHOLDより大きいなら白黒を判別する
    3. 全てのビットで白黒判定に成功した画素のみ対応関係を取得する
  7. 結果を可視化して画像として表示
  8. 取得できた対応関係をCSVに保存

2.3. サンプルプログラムの実装の欠点

OpenCVを使ったこの実装にはいくつか欠点があります。

本来、グレイコードは上位何ビットかの白黒判別に成功していれば、下位の何ビットかの判別に失敗しても座標の範囲を絞り込むことができます。 例えば、0101**のように下位2ビットが判別できなかったとしても、010100010111つまり20〜23の範囲のどれかであることはわかります。

この原理を利用すれば、カメラ画素に対してプロジェクタ画素の解像度が高くしすぎた場合、つまりカメラ画像上でパターン画像の1ブロックが画素より小さくなるほどパターンを細かくしてしまった場合でも、可能な範囲でプロジェクタ画素の座標を計算することができます。 要は「プロジェクタで表示できる限界まで細かさのパターン画像を投影しておいて、カメラ側で可能な限りの値を推定しよう」という作戦が通用するのです。

しかし、OpenCVの実装では全てのビットで白黒判別に成功した画素のみ成功と見なすので、上記のような範囲の絞り込みはしてくれません。 そのため、最も細かいパターンをカメラ側で判別できるよう(モアレが発生しないよう)にパターンの幅を調整する必要があります。

また、OpenCVで画像をフルスクリーン表示する場合、環境によっては画面の端にウィンドウの枠が表示されてしまい、思い通りの画素数で表示できないことがあります。 これはOpenCVの実装が原因なので根深い問題です。 stack overflowのこちらの記事などで議論されているので、どうしてもこの問題を修正したいという場合は確認してみてください。


以上、OpenCVを使ったグレイコードパターン投影の実装について紹介しました。

東工大 情報理工学院のA日程を外部受験した話

2019年入学の東工大 情報理工学院 情報工学系を受験したのでそのメモ。 誰かの参考になればということで残しておきます。

まず参考までに自分のスペックを。

  • 某旧帝出身
  • B4時に内部の院試を受けて爆死->院浪
  • GPA2.5とか
  • TOEIC700いかないくらい

4月

卒研関連の諸々が終わり、院浪生活開始。 とはいえ学部の研究室に引き続き通わせてもらっていたので、それまでとあまり環境は変わらず。

せっかく浪人するなら専攻変える選択肢もあるよなーと思い、周りの人に相談しつつ志望研究室の情報収集をする。 その中で以前から知っていた東工大の先生の研究室が候補に上がる。

5月中旬

学部の研究室の先生に顔つなぎをしてもらい、入試説明会の日程に合わせて研究室見学に行く。

東工大は4月から5月にかけて何度か入試説明会を開いているが、会場になるキャンパスにある研究室しか見学に行けない上、日によっては研究室を公開していないところもあるので注意が必要。 実際、私が行った研究室も当日配られた公開研究室のリストには載っていなかった。 事前にメールしておくのが吉。

2時間くらいお話を聞かせてもらい、晩ご飯までご馳走になってしまった()

他の大学院見学もこの時期に行った。 またこの頃から数学の勉強を始めた。

6月中旬

出願。

併願していた大学の小論文に手こずったこともあり、東工大の出願書類はかなりギリギリの日程で仕上げた。 消印有効ではなく必着だったし、提出直前に大阪府北部地震が来て交通網が荒れてたので結構冷汗かいた。 なんとか無事に届いたらしい。

志望動機書は指定文字数が少なかったので、素直に書いたら割とぴったり埋まった。 住所記入欄多すぎ。

6月下旬

先輩の伝手で東工大情報理工に在学中の方に「院試勉強に使ってた教科書とか教えてください」と聞いてもらったところ、「過去問で勉強したら十分だと思うよ」という返事をいただく。

情報工学系は2016年から出題傾向がガラッと変わっていたのでどう勉強しようか困った。 とりあえず論理回路情報理論オートマトン形式言語etc)は内部の院試では出題されなかった範囲だったので、その辺りから勉強し始めた。

7月中旬

受験票が届く。

A日程受験資格が降ってきたのでめちゃくちゃ驚いた。 内部生もしくは外部生の超成績優秀者しか受けられないと思っていた。

慌ててA日程の情報収集をしたところ、

  • 成績上位者から順に面接に呼ばれる
  • 各研究室にA日程合格枠の上限があり、同じ研究室を志望する他の受験者との枠の取り合いになる

らしいと知る。

7月下旬

A日程受験。

関西在住なので前日に東京入りした。

会場に入るとき、研究室見学のときに会った志望研究室のB4の人を見かけてちょっと絶望した(枠を取られるだろうなと思った)。 受験番号的に総受験者数は200人、A日程受験者は80人くらいだと思う(±20%)。 やはり大半が内部生という雰囲気だった。

面接は5~6部屋くらい並列でやっているよう(最初にまとめて呼ばれた人数がそれくらいだった)で、2~5分に1人のペースで呼ばれていった。 私が呼ばれたのは半分超えたか、というところ。

面接室では先生が5人、志望研究室の先生を含め、分野の近い方々が集まっている様子だった。 面接は合計30分くらい。 真ん中に座っている先生がタイムスケジュール表を見て進行役をやっていた。

以下うろ覚えだけど面接の内容。 外部生と内部生で内容違う可能性あり。

  1. 志望研究室の確認
    • 第一志望と第二志望を再確認された(おそらくA日程で合格可能性があるのが第二志望までなんでしょう)
  2. 卒研について
    • 「5分で内容を説明してください。ホワイトボードを使ってもかまいません」
    • 紹介した研究内容についての質問(「評価方法どうしたの?」とか「こういう問題出そうだけどどう解決したの?」とか)
  3. 情報工学の知識の確認
    • 基本質問
      • JPEGの圧縮方式を説明してください」
    • 専門分野関連の質問
      • 「卒研でCG使ってるんですよね?じゃあ3DデータをPC上でどう保持しているか説明してください」
      • 「卒研で使ってたこの機材には2つの方式がありますが、それを説明してください」etc
  4. その他
    • 志望理由
    • 学部時代の印象に残った授業
    • 課外活動などなんでもいいのでこれまでに成果を挙げたもの
    • どうして去年落ちたの?
  5. 意思確認
    • もし第一志望落ちたらB日程受験を希望するか、第二志望への合格を希望するか

研究分野についてはかなり勉強していたので答えに困った場面はほとんどなかった。 JPEGのとこで知らない知識問題が来てたらやばかった。

8月上旬

B日程の勉強をせねばと思いつつも、結果発表まで全く身が入らなかった。

発送予定日の午後に速達で出すとして、次の日くらいには届くだろうと予想していた。 実際には普通郵便で送られてきたので、さらに丸一日待たされた。

封筒を開くと第一志望研究室に内定したとの通知が入っていて無事院試終了。


出願書類上の成績はそんなによくなかったのにA日程受験資格者に選ばれたのが最大の謎。

それでも第一志望の研究室に入れたということは、内部生・外部生関係なく面接の内容がちゃんと評価されてるのだと思った。

何はともあれ来年はニート脱却。

OpenCV入門するならまずサンプルコードを動かしてみよう

以前のWindowsでOpenCV+contribをビルド・インストールするに引き続きOpenCVネタ。

OpenCVリポジトリに用意されているサンプルコードがなかなかバリエーション豊かで勉強になることに気付いたので、宣伝したいと思います。

OpenCVのサンプル集

https://github.com/opencv/opencv/tree/master/samplesを見てのとおりC++JavaPython向けのものからandroid、DNN、OpenGLなど特定の環境と組み合わせる用のものまで様々なサンプルが用意されています。

この記事ではとりあえず環境構築とサンプルコードの実行が簡単であるPython向けサンプルを試してみます。

インストールしておくもの

  • Python
    • 一応Python 2でも動くらしいですが、Python3を使うのが無難だと思います
    • 筆者の環境は3.7.0
  • Git

OpenCVライブラリのインストール

Pythonのパッケージマネージャーpipを使います。 いくつかのサンプルはOpenCVの外部モジュールであるopencv-contribを利用するのでそちらも一緒にインストールしておきます。

python -m pip install opencv-python opencv-contrib-python

サンプルのダウンロード

サンプルコードとかサンプル画像を取得するためにOpenCVリポジトリを丸ごとクローンします。 適当なディレクトリを作って次のコマンドを実行します。

git clone https://github.com/opencv/opencv.git

サンプルを実行してみる

サンプルのダウンロードが終わったらopencv/samples/pythonの中に大量のpythonファイルが入っていると思います。 その中の1つdemo.pyを実行します。

cd opencv/samples/python
python demo.py

すると次のような画面が出てきて、サンプルコードの一覧を確認することができます。

f:id:kamino-dev:20180904180746p:plain

とりあえずbrowseを選択してRunボタンを押してみます。 するとサンプルbrowse.pyが起動します。

f:id:kamino-dev:20180904181700p:plain

マウスカーソルを当てた場所が拡大して表示される、というだけの簡単なプログラムですが、きちんと動いていることがわかります。 プログラムを終了するときはESCキーを押します。

もしWindowsユーザーで'C:\Program' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。のようなエラーが出たら一旦画面を閉じて、demo.pyの170行目あたりのPopen(sys.executable + ' ' + cmd, shell=True)Popen('"' + sys.executable + '" ' + cmd, shell=True)に変更してもう一度実行してみてください。

ついでにもう一つdeconvolutionを実行してみます。

f:id:kamino-dev:20180904182628p:plain

今度はもう少し実践的なデモです。 左のような手ブレ写真にdeconvolution(畳み込み演算の逆の処理)を施すことで手ブレによるノイズを除去し、右のような画像を復元しています。 右上のスライダーを動かしてカーネルのパラメータを調節できるようになっています。


この他にもカメラキャリブレーションcalibrate)、Canny法によるエッジ検出(edge)、顔認識(facedetect)、Webカメラで物体追跡(camshift)など色々なサンプルがあるので、自分でプログラムを書くときの参考にしてみてください。

(時間ができたら全サンプルの解説記事とか書いてみたいなぁ)

AngularでHttpClientのgetのためにbase-hrefを取得する

どういう状況?

Angular 6.1.3でアプリを作っているときのこと。

「最終的にxxxx.example.comにデプロイするけど、とりあえずデモのためにxxxx.github.io/xxxx.example.comで公開したいな」と思いたちました。

そこでangular-cli-ghpagesを使って、

ng build --prod --base-href "https://xxxx.github.io/xxxx.example.com"
angular-cli-ghpages --repo=https://github.com/~~~~~~~

みたいな感じで公開しようとしました。

ところが、

//省略

export DataService {
  constructor(private http: HttpClient) { }

  getTopics() {
    return this.http.get('/data/topics.json');
  }
}

のように、静的なJSONファイルを取得しようとしたときhttps://xxxx.github.io/data/topics.jsonにアクセスしてしまい404が返ってきてしまいました。 実際にファイルが置かれているのはhttps://xxxx.github.io/xxxx.example.com/data/topics.jsonです。

どうやらHttpClientget()はホスト名を参照するのであって、base-hrefを参照してくれるわけではないようです。

解決方法

調べてみるとコードにbaseUrl: String = 'xxxx.github.io/xxxx.example.com';を埋め込む方法がよく紹介されていましたが、今回は最終的にxxxx.example.comにデプロイしたいので固定値を埋め込むのは遠慮したいところです。

かと言って、DOMを介して取得する、みたいな泥臭い方法は取りたくないです。

という感じで調べていくとこんな記述を見つけました。 どうやらLocationprepareExternalUrlが使えるらしい。

ということでコードを修正。

import { Location } from '@angular/common';

//省略

export DataService {
  constructor(private http: HttpClient, private location: Location) { }

  getTopics() {
    return this.http.get(this.location.prepareExternalUrl('/data/topics.json'));
  }
}

これで動きました!