以下の記事で紹介した THETA プラグインで QR コードを読む方法ですが、歪み補正をしておらず、数バイト程度の QR コードを片方のレンズでしか読むことができませんでした。
THETA プラグインで QR コードを読む - Qiita
今回は、魚眼レンズの歪みを補正することで数100バイト程度の QR コードを両方のレンズで同時に読めるようにできたので紹介します。
この記事の内容はライブラリとして簡単に使えるようにまとめています。ライブラリを利用したい方は以下を参照してください。
GitHub - theta4j/theta-code-reader
QR コード読み取りの流れ
QR コードは以下の流れで読みます。
- Dual-fisheye 画像 (魚眼画像が横に2枚ならんだ画像) のキャプチャ
- 魚眼画像の歪み補正
- ZXing (QR コードをデコードするライブラリ) への入力
Dual-fisheye 画像のキャプチャ
THETA のカメラからの画像のキャプチャは、Android の Camera API v1 を使います。
基本的な処理は THETA プラグインで QR コードを読む - Qiita と同じですが、一部のパラメータ設定が異なります。
前の記事では、Equirectangular 形式でキャプチャしていましたが、今回は Dual-fisheye 形式の最大解像度である 4K でキャプチャします。また、あまり高フレームレートでもさばききれないので、8fps で取得します。コードにすると以下のようになります。
mCamera = Camera.open();
final Camera.Parameters params = mCamera.getParameters();
params.set("RIC_SHOOTING_MODE", "RicMoviePreview3840");
params.set("RIC_PROC_STITCHING", "RicNonStitching"); // Dual-fisheye
params.setPreviewSize(3840, 1920); // 4K
params.setPreviewFpsRange(8000, 8000); // 8fps
mCamera.setParameters(params);
mCamera.setPreviewCallback(...) // コールバックを設定 (内容は後述)
これで、mCamera.setPreviewCallback
に設定したコールバックに Dual-fisheye かつ 4K 解像度の画像が 8fps で届くようになります。
魚眼画像の歪み補正
Camera API からのコールバックで得られた画像は魚眼レンズで撮影したそのままの画像なので、中心が膨らむように歪んでいます。
そのまま ZXing に入力するとうまく読めないため、歪みを補正してやる必要があります。
魚眼レンズの画像が歪んで見えるのは、通常のレンズとは射影方式が異なるからです。通常のレンズは中心射影ですが、魚眼レンズでは等距離射影や立体射影が使われます。射影方式を中心射影に変換することで歪みのない画像が得られます。
射影方式とその変換方法については以下のページがわかりやすいです。
今回、THETA のレンズの射影方式と焦点距離がわからなかったため、射影方式は等距離射影と仮定して、焦点距離は実際の画像を変換して補正具合を確認しながら決めました。
変換式は以下のようになります。f は焦点距離、x', y' はキャプチャした画像のレンズ中心からの座標、x, y は変換後の座標です。
r' = \sqrt{ x'^2 + y'^2 }\\
r = f \arctan {\frac{r'}{f}}\\
\alpha = \frac{r}{r'}\\
x = \alpha x'\\
y = \alpha y'
歪み補正の実装
上にあげた変換式は 4K 画像の各ピクセルに対して実行する必要があるのですが、普通に実装すると時間がかかりすぎて 8fps では処理できません。こういった各ピクセルで並列化可能な計算は GPU を使って処理します。今回は RenderScript を使って並列処理を実装しました。
Dual-fisheye の等距離射影の画像を中心射影に変換する RenderScript は以下のようになります。関数の仮引数 x, y が変換前の座標です。変換前の座標が入力されたときに変換後の座標の輝度値を返しています。
uchar __attribute__((kernel)) correctDistortion(uchar in, uint32_t x, uint32_t y)
{
int width = rsAllocationGetDimX(allocIn) / 2;
int height = rsAllocationGetDimY(allocIn);
// Dual-fisheye 画像の左半分か右半分かでオフセットを切り替え
int offset_x = width / 2 + (x < width ? 0 : width);
int offset_y = height / 2;
float x1 = (int)x - offset_x;
float y1 = (int)y - offset_y;
float f = 600.0;
float r1 = hypot(x1, y1);
float r2 = f * atan(r1 / f);
float alpha = r2 / r1;
float x2 = alpha * x1;
float y2 = alpha * y1;
uint32_t new_x = clamp((int)x2, -width/2, width/2) + offset_x;
uint32_t new_y = clamp((int)y2, -height/2, height/2) + offset_y;
return rsGetElementAt_uchar(allocIn, new_x, new_y);
}
コード全体は filters.rs を参照してください。
また、RenderScript を Java から呼び出す部分は ImageConverter.java を参照してください。
ZXing への入力
ZXing への入力方法は以下の記事を参考にしてください。
THETA プラグインで QR コードを読む - Qiita
まとめ
THETA プラグインで QR コードを読む方法を紹介しました。
今回の改善版で数100バイト程度の QR コードを現実的なフレームレートで処理できるようになりました。普通の URL くらいの長さなら問題なく読めるようになったので、色々な応用ができると思われます。
興味を持たれた方は Twitter のフォローと THETAプラグイン開発コミュニティ (Slack) への参加もよろしくお願いします。