ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

PlayStation CameraをWindowsで使う

こんにちは。夏休みのインターンで参加しているけのじ

Tweets with replies by けのじ (@kxe2n4o0ji) | Twitter です。

PlayStation CameraをWindowsで使う

目次


PlayStation Cameraとは

f:id:izm_11:20170824170325j:plain PlayStation4PSVR等で位置のトラッキングのために使用されているステレオカメラです。
ラッキングを目的としたカメラであるため、下記のような高いフレームレートで動作することが特徴です。

  • 1280×800@60Hz
  • 640×400@120Hz
  • 320×192@240Hz

PlayStation Camera - Wikipedia


PlayStation Cameraの端子を付け替える

さて、このPlayStation Cameraですが、端子形状が異なるだけで信号はUSB3.0のものが流れているため、端子を付け替えるなどしてしまえば、PCに接続して利用することが可能です(注:USB2.0互換のための信号線は無いため、USB3.0専用です)
今回は、既存の端子部分を切り落とし、そこに直接USB3.0のプラグをはんだ付けすることで対応します。

準備するもの

端子交換の手順

  1. 既存の端子を切り落とし、皮膜を剥く

    f:id:izm_11:20170824171303j:plain 既存の端子をニッパーなどで切り落とし、USB3.0のカバーをケーブルに通した後 、皮膜を向いて中の線を引き出します。信号線は2本ずつ+GNDの計3本でツイスト&シールドされているので、シールドを剥がした後2本のGNDを撚り合わせておきます。

    線の色とその内容は写真左から以下のようになっています

    • 黒 - GND
    • 紫 - SSRX-
    • オレンジ - SSRX+
    • 撚り合わせたGND - GND_DRAIN
    • 黄 - STTX+
    • 青 - SSTX-
    • 赤 - 5V
  2. 端子を取り付ける

    このようなページの情報を参考にしつつUSB3.0コネクタにはんだ付けをしていきます。
    USB3.0の信号線しか来ていないので、USB2.0側は5VとGNDのみ接続します。

    USB 2.0 / 3.0 / 3.1 Connectors & Pinouts

    f:id:izm_11:20170829130158j:plain

    はんだ付けが終わったら端子をUSBの金具に押し込みます、ここで、はんだ付けを行った部分が金具に触れていないかなど、導通確認をしっかり行っておきましょう。

    カバーを被せてしまうと、外すことが非常に困難です

  3. 鉸めたらカバーをかぶせる

    USBの金具に蓋をして端を鉸めた後、カバーをプラグにかぶせます、パチッと音がすれば完了です。(結構力が必要でした)
    f:id:izm_11:20170824174611j:plain

以上で端子の付け替えは終了です、この段階で Windows機のUSB3.0ポートに差し込むと、デバイスマネージャー上では不明なデバイス(USB Boot)と認識されるはずです


Windows上でPlayStation Cameraを利用できるようにする

ここまでで、WindowsマシンにPlayStation Cameraが接続できるようになりました。しかし、そのままではカメラとして利用することができないため、ドライバとファームウェアの書き込みを行う必要があります。

使用したソフトウェア・ライブラリ

導入手順

  1. ドライバの書き換え

    ファームウェアのアップロードを行えるよう、ドライバを書き換えます。
    Zdiagを起動しUSB Bootを選択したのち、libusbKを選択してInstall (画像はインストール済みのものなので多少異なる部分があるかも) f:id:izm_11:20170831180622p:plain

  2. PS4-Eye-Driverのビルド

    PS4-Eye-Driverは、ファームウェアのアップロードに使用します。

    VisualStudio2015でC++の空のコンソールアプリケーションを作成し、PS4-Eye-Driverから

    • main.cpp
    • ps4eye.cpp
    • ps4eye.h
    • ps4epe_regs.h

    をソースファイルとヘッダーファイルに追加し、環境に合わせてlibusbxから

    • libusb-1.0.lib
    • libusb.h

    をヘッダーファイルに追加します。

    ソースコード内に表示部分も書かれているようですが、自分の環境では上手く動作しなかったため該当部分を削除し、ファームウェアのアップロードのみを担当させます。

    main.cpp を次のように書き換えます。

    #include <iostream>
    #include <string.h>
    #include "stdafx.h"
    #include "ps4eye.h"

    using namespace ps4eye;

    int main() {
        ps4eye::PS4EYECam::PS4EYERef eye;
        std::vector<PS4EYECam::PS4EYERef> devices(PS4EYECam::getDevices());

        if (devices.size() == 1) {
            eye = devices.at(0);
            eye->firmware_path = "firmware.bin";
            eye->firmware_upload();

            bool res = eye->init(1, 120);
            std::cout << res << std::endl;

            eye->start();
        }
        return 0;
    }

ビルド後、実行ファイルと同じディレクトリ内に、PS4-Eye-Driver内のfirmware.binをコピーし、ビルドしたexeを実行し

    Found ov580 boot mode Camera
    Uploading firmware to ov580 camera...
    Firmware uploaded...

と表示されればファームウェアのアップロード作業は完了です。
バイスマネージャで確認すると、USB Bootが消え、USB Camera-OV580が表示されているはずです。
f:id:izm_11:20170831180638p:plain

ここまででWindows上からカメラとして認識できるようになりました、Skypeやカメラアプリなどで確認してみましょう。
尚、カメラを抜くと再度ファームウェアのアップロードを行う必要があります(作成したexeを叩けばOK)


PlayStation Cameraを接続したら自動でファームウェアがアップロードされるようにする

このままでは、接続するたびにアップロードの作業を行わなければいけなく、手間です。

そこで、特定のデバイスが接続された際に指定したプログラムを実行するアプリケーションをC#で作成します。
本来は、DLL化やラッパーライブラリの作成などを行うべきですが、制作時間の関係上exeをProcess 関数で直接叩く形としました。

Windowsでは、デバイスの接続時にすべてのトップレベルウィンドウに対して、WM_DEVICECHANGEというメッセージが送られます、今回はこれを用いてデバイスの接続タイミングを取得します。

private uint WM_DEVICECHANGE = 0x0219;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_DEVICECHANGE)
        DeviceChange();//ここで目的の作業を行わせる
    base.WndProc(ref m);
}

これだけでは、PlayStation Cameraが接続されたのかの判別が不可能であるため、Win32_PnPEntity を利用してデバイスの一覧を取得し、合致するデバイス名(今回はUSB Boot)が存在するかを判断させます。

//デバイス名がDeviceNameのデバイスが接続されているか
public bool IsDeviceExists(string DeviceName)
{
    var pnpEntity = new ManagementClass("Win32_PnPEntity");
    var devices = pnpEntity.GetInstances();
    return devices.Cast<ManagementObject>().Select(o => o.GetPropertyValue("Name") as string).Any(s=>s==DeviceName);
    
}

最終的に下記のようなコードでPlayStationCameraの接続時に自動的にファームウェアのアップロードを行うことができました。(汚くてすみません)
Form1.cs

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace GetDeviceConnect
{
    public partial class Form1 : Form
    {
        private bool isGotEvent = false;

        public Form1()
        {
            InitializeComponent();
            TargetDevice.Text = ExecuteProgram.Default.DeviceName;
            ExecutePath.Text = ExecuteProgram.Default.ExecutePath;
            
        }

        private uint WM_DEVICECHANGE = 0x0219;

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE)
                DeviceChange();
            base.WndProc(ref m);
        }

        public void DeviceChange()
        {
            isGotEvent = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (!isGotEvent) return;
            isGotEvent = false;
            GetDeviceFromName gdfn = new GetDeviceFromName();
            if (gdfn.IsDeviceExists(ExecuteProgram.Default.DeviceName))
            {
                ProcessStart();
            }
        }

        private void ProcessStart()
        {
            ProcessStart(ExecuteProgram.Default.ExecutePath);
        }

        private void ProcessStart(string path)
        {
            Console.WriteLine(path);
            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = path;
            psInfo.CreateNoWindow = true; 
            psInfo.UseShellExecute = false;

            Process.Start(psInfo);
        }

        private void ChangeVisible()
        {
            if (Visible)
            {
                Hide();
            }
            else
            {
                Show();
            }
        }
        private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
        {
            ChangeVisible();
        }

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            ChangeVisible();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            Hide();
        }
    }
}

GetDevivceFromName.cs

using System.Linq;
using System.Management;
namespace GetDeviceConnect
{
    class GetDeviceFromName
    {
        public bool IsDeviceExists(string DeviceName)
        {
            var pnpEntity = new ManagementClass("Win32_PnPEntity");
            var devices = pnpEntity.GetInstances();
            return devices.Cast<ManagementObject>().Select(o => o.GetPropertyValue("Name") as string).Any(s=>s==DeviceName);
            
        }
    }
}

自動ファームウェア焼き部分の実行ファイルは以下にあります。(firmware.binは含んでいないため、PS4-Eye-Driver よりダウンロードしてください)

GetDeviceConnect.zip - Google ドライブ


BM法でステレオマッチングを試す

BM法(ブロックマッチング法)という方法でキャリブレーションなしにステレオマッチングが可能らしく、こちらを参考に試してみました。 カメラのフレームレートが最速値以外選択できなかったため、暗い画しか取れなかったことや、後記するPlayStation Cameraが持つ問題などが原因なのか、BM法を使ってのステレオマッチングは失敗でした。

f:id:izm_11:20170831180656p:plain


PlayStation Cameraのステレオキャリブレーション

ステレオマッチングの種類によっては、カメラの内部パラメータ、外部パラメータが必要になります。
こちらの記事 にある cv2stereoCalibrate.py をお借りして、PlayStation Cameraのパラメータをはかってみました。

ステレオ画像の保存はProcessingで行いました。以下コードです

import gab.opencv.*;

PImage src;
ArrayList<PVector> cornerPoints;
OpenCV opencv;
import processing.video.*;
Capture camera;

PGraphics pg;

void setup() {
  size(1280, 400);
  pg = createGraphics(1280,800);
  
  camera = new Capture(this, 3448, 808, 60);
  camera.start();
  opencv = new OpenCV(this,1280,800);
}
PGraphics checkerBoard(PImage sourceImage){
    opencv.loadImage(sourceImage);
    opencv.gray();
    cornerPoints = opencv.findChessboardCorners(6, 9);
    pg.clear();
    pg.beginDraw();
    pg.image( opencv.getOutput(), 0, 0);
    pg.fill(255, 0, 0);
    pg.noStroke();
    for (PVector p : cornerPoints) {
      pg.ellipse(p.x, p.y, 20, 20);
    }
    pg.endDraw();
    return pg;
}
void draw() {  
  if (camera.available()) {
    camera.read();
    src = camera.get(48,0,1280,800);
    image(checkerBoard(src),640,0,640,400);
    src = camera.get(1328,0,1280,800);
    image(checkerBoard(src),0,0,640,400);
  }
}
void mouseClicked(){
  String time = hour() + "_" + minute() + "_" + second();
  src = camera.get(48,0,1280,800);
  src.save(time+"_L.png");
  src = camera.get(1328,0,1280,800);
  src.save(time+"_R.png");
  println(time);
}

テストに用いた画像はこちら、解像度は1280×800、サンプル数は18セットで、パターンの正方形サイズは32ミリです。
計測の結果のデータはこちらに置いておきます

この導出したカメラパラメータを見ると、外部行列上左右のカメラ間の回転成分はほぼゼロ、左右のカメラ間の距離は約80mmであることがわかり、どうやら正しそうな値になっています。 しかし後述する右目画像のオフセット(1200x808)などの影響のせいか、左右のカメラの内部行列の値は微妙に差異が出ています。これは今後追試したほうが良いかもしれません。


Playstation Cameraの絵が取得できた環境

  • Processing2.2.1 (processing.videoで開けました)
  • Unity5.5.3(WebcamTextureで3448,808,60を指定して開けました)
  • Windows10標準のカメラアプリ

PlayStation Cameraを使う際に気を付けるべきこと

  • 現在のファームウェアで取得できるカメラ画像のオフセット問題

    PlayStation Cameraから取得できる画像は左右のf:id:izm_11:20170831133135j:plain画像と高速処理用と思われる縮小しインタレースにパックした画像とがまとまったものですが、左右の画像を取り出すとき、右目にあたる画像(最も左側のもの)の解像度が1280×808になってしまっているので、1280×800にリサイズする必要があります。
    これが要因となっているのか、前述の通り計測結果のTのZ軸成分に誤差と思われる-2mmの値が入っていました(PlayStation Cameraは真横に並んでいるためほぼ0となるべき)
    このため、計測値にも誤差が含まれる可能性があります、正確さが求められる用途には現状あまり向いていないかもしれません。

  • 一部アプリやライブラリで、PlaystationCameraを開いて終了したあと、再度開いても画像取得ができなくなる

    自分のソフトウェアの実装が悪いのか判断できかねますが、プログラム上からPlayStation Cameraに接続し、画像を取得した後、プログラムを終了した後、再度同一のプログラムを立ち上げて接続をしようとすると接続を拒否されてしまいました。
    USB抜き差しなどでデバイスの再接続を行うと接続できるようになりました。

    Windows10標準のカメラアプリでは起きません。ProcessingやUnityのWebcamTextureで起きます。


まとめ

安くて高性能なカメラとして、PlayStation Cameraは面白いのではないかなと思いました。
しかし、毎度ファームウェアのアップロードが必要であったり、リサイズを行う必要があったりなど、実験には使えても展示での運用などを考えるとまだ難しい面もあると感じました。