PaPeRo iのカメラでリアルタイムモニタ Python編

 PaPeRo iのカメラの画像をWebブラウザでリアルタイムモニタする方法です。今回はPythonを使用します。

構成

 PaPeRo iでWebサーバ/WebSocketサーバを動かしローカルLAN接続のPCのWebブラウザでモニタする構成とします。Webページを開くと同時にWebSocketでも接続し、カメラデータの転送に使用します。
 PaPeRo i上でPython3アプリを実行しますのでPaPeRo iにPython3をインストールし、PaPeRo i 制御用WebSocket通信アドオンシナリオを動作させる必要があります。

使用するAPI

 PaPeRo iでカメラ画像を取得するAPIには2種類あります。

(1) send_take_picutre() : 画像データは/tmp以下のファイルに書かれる
(2) send_start_capturing() + send_get_capture_data() : 画像データは共有メモリに書かれる

(1)は指定のJPEGまたはBMPファイルで取得できて簡単なのですが遅いので今回の目的には適しません。今回は共有メモリを使用するため面倒ですが高レートを期待できる(2)を利用します。

カメラの種類と特徴

 PaPeRo iの左右の目には異なるタイプのカメラが搭載されており、以下の様な特徴があります。

搭載位置 解像度 データ形式 1画像のデータサイズ フレームレート
左目   VGA(640×480) yuyv422 614.4kB 30fps
右目 SXGA(1280×1024) JPEG 200kB弱~300kB強? 15fps

VGAカメラのデータ形式yuyv422は直接ブラウザで表示できず、JPEGなどへの変換が必要です。
SXGAカメラのJPEG形式はブラウザでは表示可能なのですが、ハフマンテーブルという情報が欠落しており、そのままの形でファイルに保存すると画像として開けない場合が多い様です。これを回避するためにはハフマンテーブルを挿入する必要がありますが、今回はブラウザで見ることが目的なのでそこまでやっていません。
 今回は両方のカメラを試してみることにします。

Pythonの共有メモリ操作用パッケージ

 Pythonで共有メモリを操作するにはsysv-ipcパッケージを使用します。PePeRo i用がこちらにありますのでダウンロードし、/Extension/pkgroot下に展開して下さい。
 使い方としてはVGAの場合、

import sysv_ipc
camera_shm = sysv_ipc.SharedMemory(None, sysv_ipc.IPC_CREX, 0o666, 640*480*2)
papero.send_start_capturing(camera_shm.id, 640*480*2, "VGA")

としてキャプチャーを開始し、

papero.send_get_capture_data(camera_shm.id, "VGA")

でキャプチャを指示、getCaptureDataResメッセージを受信したら

data = camera_shm.read()

とすればデータを取得できます。
 SXGAの場合、

import sysv_ipc
camera_shm = sysv_ipc.SharedMemory(None, sysv_ipc.IPC_CREX, 0o666, 128*1024*21)  # >= 1280*1024*2.1
papero.send_start_capturing(camera_shm.id, 128*1024*21, "SXGA")

としてキャプチャーを開始し、

papero.send_get_capture_data(camera_shm.id, "SXGA")

でキャプチャを指示、getCaptureDataResメッセージを受信したら受信メッセージmsgから

siz = msg.get('CaptureDataSize')

でデータサイズを取得してから

data = camera_shm.read(siz)

とします。

VGAカメラのデータをJPEGに変換する

 VGAカメラのデータ変換は2つの方法を試しました。

(1) PaPeRo i上でffmpegを利用してJPEGに変換
(2) ブラウザ上(JavaScript)でRGBA形式に変換

ffmpegはopkgでインストールできます。VGAカメラのデータを同サイズのJPEGに変換するには、プログラム内部で標準入力からデータを入力し、標準出力からデータを取り出すとして、

ffmpeg -f rawvideo -s 640x480 -pix_fmt yuyv422 -i - -s 640x480 -f mjpeg -

とします。データ量が問題になるようであれば2番目の-s指定を変えることで出力サイズを任意の大きさに減らす事もできます。

ブラウザ上でVGAカメラのデータを変換する

 VGAカメラのデータを以下の様に処理する事でRGBA形式に変換して表示することができます。

var ctx = getElementById(画像を表示するcanvasのid).getContext('2d');

// yuyv422イメージを表示する yuyv:受信データ width=640 height=320 ctx=上記 dw=表示ヨコ dh=表示タテ
var drawYuyv = function(yuyv, width, height, ctx, dw, dh) {
  var output = ctx.createImageData(width, height);
  var od = output.data;
  var opos = 0;
  for (var h=0; h < height; h++) {
    for (var w = 0; w < width; w+=2) {
      var ipos = 2 * (width * h + w);
      var c = yuyv[ipos + 0] - 16;
      var d = yuyv[ipos + 1] - 128;
      var e = yuyv[ipos + 3] - 128;
      od[opos + 0] = (298 * c + 409 * e + 128) >> 8;
      od[opos + 1] = (298 * c - 100 + d - 208 * e + 128) >> 8;
      od[opos + 2] = (298 * c + 516 * d + 128) >> 8;
      od[opos + 3] = 255;
      opos += 4;

      c = yuyv[ipos + 2] - 16;
      od[opos + 0] = (298 * c + 409 * e + 128) >> 8;
      od[opos + 1] = (298 * c - 100 + d - 208 * e + 128) >> 8;
      od[opos + 2] = (298 * c + 516 * d + 128) >> 8;
      od[opos + 3] = 255;
      opos += 4;
    }
  }
  if ((width == dw)&&(height == dh)) {
    ctx.putImageData(output, 0, 0);
  }
  else {
    createImageBitmap(output, 0, 0, width, height).then(function(bm) {
      ctx.drawImage(bm, 0, 0, width, height, 0, 0, dw, dh);
    });
  }
};

今回表示は320×240に縮小しています。

WebサーバとPaPeRo i制御

 Web/WebSocketサーバにはtornadoを使用しています。パペロの制御としては今回座布団右ボタンでカメラ画像転送開始、真ん中ボタンでカメラ画像転送停止という制御しかしていませんが、こちらで作成したpypapero.pyのラッパークラスstpypapero.pyを使用しています。
 アプリのソース全体はこちらからダウンロードできます。ダウンロードファイルをパペロに転送して展開し、

# python3 paperocamera.py -wssvr ws://localhost:8088/papero

として起動してください。WebブラウザでパペロのIPアドレス:8860/cameraを開き、座布団向かって右ボタンでカメラ画像が表示される様になります。
 なおカメラと変換の種類を変更する場合、paperocamera.pyとtemplate/cam.tmpl.jsの冒頭の定数CONV_ON_PAPERO/CAMERA_SIZEおよびorgSize/CONV_ON_PAPEROを同時に変更する必要があります。また、カメラの種類を変更する場合にはパペロを一度再起動する必要がある様です。

実行結果

 実行結果は以下の様になりました。PCはCore i7の比較的高性能なWindows10のPC、スマホは古いNexus4を使用しました。PC欄とスマホ欄は表示レート/表示遅延です。

No カメラ データ変換 1画像の転送データサイズ 挿入スリープ時間 パペロCPU使用率 PC スマホ 備考
1 VGA ffmpeg 約40kB? 1msec 約40% 約3fps/0.6sec 約3fps/0.8sec
2 VGA ブラウザ 614.4kB 100msec 約24% 約5fps/0.5sec ×(遅延大) これ以上スリープ減らすと遅延による廃棄発生
3 VGA ブラウザ 614.4kB 250msec 約20% 約3fps/0.5sec 約3fps/0.8sec スマホは数秒停止することあり
4 SXGA なし 200kB弱~300kB強? 30msec 約35% 約12fps/0.3sec ×(数秒でエラー停止) スリープ0.5secでもスマホNG

 カメラ自体のフレームレートが高く、情報量は少ないVGAの方が、変換コストのために表示レートが低くなっています。SXGAは元データがJPEGで変換不要のため高レートの表示が可能ですが、データサイズが大きいせいか表示するホストの負担が大きく、Nexus4では安定して表示できませんでした。
 結果としては、スマホもサポートする場合安定して動作するのはNo1のみ、PCのみサポートで良い場合No4、パペロのCPU使用率を重視する場合No2(PCのみ)またはNo3(両方サポート)が良さそうという事になるかと思います(但しNo2、No3は画像が数秒間停止する場合があるほか10秒に一回程度少し画像が乱れてしまう様でした)。
 なおこれはカメラ画像転送のみを行った場合なので、あくまでも参考程度にお考え下さい。