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は面白いのではないかなと思いました。
しかし、毎度ファームウェアのアップロードが必要であったり、リサイズを行う必要があったりなど、実験には使えても展示での運用などを考えるとまだ難しい面もあると感じました。

builderscon tokyo 2017にボランティアスタッフ参加してきた話

こんにちわ、最近草野球にハマっているソリューション技術部 山口(@kinunori)です。

builderscon tokyo 2017にボランティアスタッフとして参加してきました。本エントリではスタッフ側の感想として書いていきます。

ボランティアスタッフになった理由

Builderscon 2017オーガナイザーの@lestrratさんが同じくオーガナイズしていたYAPC::Asia Tokyoに一般参加者として参加したことがあり、エンジニアとして良い刺激を多く受けたので、何か恩返しがしたいと考えていた為です。

たまたまツイッターで当日スタッフ募集の告知を見かけて応募してみました。 弊社では会社としてエンジニアコミュニティへの貢献を推奨していることもあり、同僚が参加を後押ししてくれた事も大きな理由の1つです。

もちろん通常業務はあるので、仕事の配分調整や、前倒し対応したり協力してくれた皆がいなければ参加出来ていなかったと思います。ありがとう。

担当したスタッフ作業

私は受付とトラックD会場スタッフを担当しましたが、コアスタッフの方が非常に手厚い事前準備をしてくれているので、難しいことはなくスタッフ作業を対応出来ました。 特にトラック会場スタッフはカンペや全体の流れも細かく説明されている資料がありとても分かりやすかった!

・受付作業: 入れるキューの整理(一般参加/個人スポンサー、スピーカー/スポンサー)と並列処理(QRコード読み取り担当)に回すこと ・トラックD会場スタッフ: スピーカーの方への事前説明、会場アナウンス、セッション進行

ボランティアスタッフのつらみ

つらみとしてあるのは1点だけ。当日のみの非公開セッションを聞けない可能性があることです。

それ以外はありません。

ボランティアスタッフで良かったこと

一般参加と比べ、参加者、スピーカー、スタッフと幅広くコミュニケーションを取ることが出来て繋がりが増える事だと思います。

特にスタッフ同士は「カンファレンスを成功させる」という強い目的を共有している仲間としてカンファレンス後も繋がりが継続する事も多いでしょう。(インフラエンジニア飲み会するぞ!)

しかも、作業をしながらの会話となるので、話しかけにくい、きっかけが分からない、など考える事もなくコミュニケーションがとれます。

カンファレンスはセッションを聞く以外に、参加者同士の繋がりを作っていく事も目的の1つだと考えているので、エンジニア同士(もちろんエンジニア以外とも)で繋がりを増やしたい人、エンジニアコミュニティに貢献したい人にはスタッフ参加がお勧めです。

カンファレンス会期中全てではなく、1日間もしくは2日間だけ参加されていたスタッフの方もいましたので、難しいと考えずに応募してみるのが良いのではないでしょうか!

個人的に良かったこと

スピーカーをはじめ、尊敬するエンジニアの方と会話出来て良い刺激を受けられたことです。

特に、@mamy1326さんとお話しできたことが嬉しかった!セッションは受付スタッフ中で聞けなかったので、後で動画が公開されたら見ます。

アウトプットとしてのボランティアスタッフ参加

エンジニアとして重要なことはインプットだけではなくアウトプットし、インプット -> アウトプットのサイクルを回し続ける事です。弊社のエンジニア育成もこのサイクルを重視しています。

アウトプットというと、ブログ投稿、登壇、寄稿などが最初に浮かびますが、私はボランティアスタッフ参加に関してアウトプットを強化する1つの方法だと考えています。

上述しましたが、ボランティアスタッフは、一般参加と比べ、参加者、スピーカー、スタッフと幅広くコミュニケーションを取ることが出来ます。つまり、会話の中でインプットから得た知識・経験を元に技術的対等に情報交換出来る事は自信になるし、話せなくてモヤモヤとした気持ちも持った場合は悔しさからくるモチベーションでインプットを増やすきっかけに繋がる事を期待しています。

最後に

builderscon最高でした!! 来年は同僚、後輩と一緒に参加するぞ!!

f:id:kinunori:20170809121606p:plain

UnityとHTC Viveで鏡を作る方法

はじめに

こんにちは!VR事業部の長谷川(id:waffle_maker)です。

Unity 5.4から、SteamVRでReflection(反射)とRefraction(屈折)が正しく表示されず、非常にハマりました。

どうやら、CameraのTarget EyeをBothにした場合に、シェーダー側がそれに対応していないとダメみたいです。

今回はUnityとSteamVRで水…ではなく、鏡を表示する方法について紹介します。

バージョン情報など

各種バージョン情報は以下の通りです。

HTC Viveの購入はこちら

実際に試してみる

  1. 新規プロジェクトを作成し、SteamVR PluginとThe Lab Rendererをインポートします。
  2. Main Cameraを削除し、[CameraRig]プレハブをシーンに配置します。
  3. [ここ](https://forum.unity3d.com/threads/reflection-rendering-wrong-in-openvr-htc-vive.398756/#post-2726226)からMirror.unitypackageをダウンロードし、インポートします。 thank you! dan-kroymann.
  4. Mirror.csでエラーが発生するので、GetProjectionMatrixメソッドの第3引数を削除します。
    Valve.VR.HmdMatrix44_t proj = SteamVR.instance.hmd.GetProjectionMatrix(eye, cam.nearClipPlane, cam.farClipPlane, SteamVR.instance.graphicsAPI);
    
    Valve.VR.HmdMatrix44_t proj = SteamVR.instance.hmd.GetProjectionMatrix(eye, cam.nearClipPlane, cam.farClipPlane);
    
  5. Mirrorプレハブをシーンに配置します。 やったー!できたー! f:id:waffle_maker:20170503042723p:plain ※VRで見ないと分からないですよね…

まとめ

自分自身のアバターを表示するVRコンテンツでは、鏡を表示することで自分自身がその世界に居ると認識させることが出来ると思います。

鏡は軽い処理ではありませんが、導入部分などで鏡を見せることで没入感を高めることが出来ると思います。

是非活用してみて下さい!