2015/12/22

AppleWatchからTheta Sを操作して遊んでみた

この記事はRICOH THETA Advent Calendar 2015の22日目の記事です。




今回はAppleWatchからTHETA Sのリモートコントロール(静止画、動画撮影)を実装したので公開したいと思います。もちろん無音シャッターもありです。※用法、音量にはご注意を!
THETA SなのでAPIはv2です。
そもそもAppleWatchユーザでかつThetaSの方ってどのくらいいるのでしょうか。

※2016.2.7追記

この記事をベースにアプリを公開しました。詳細は以下
Remote Shutter for ThetaS v1.0を公開しました

THETA Sとは

もはや説明不要かと思いますが、
全天球の360度の写真、動画が取れるカメラデバイスです。
詳しくはRICOH THETA Advent Calendarの一日目の記事を参考にしてください。
http://panora.tokyo/3945/
2015/12/22日現在、コンパクトデジタルカメラのベストセラー一位となっています。すごい!
どんな写真をとっても360度で撮れるので、すごくおもしろいです。
写真を見たほうがわかりやすいので、京都の紅葉の写真と市内一望の写真をあげていますのでこちらを見てグリグリ触って確認してみてください。下の写真にフォーカスを当てて、キーボードの上下左右を押しても360度、見ることができますよ!
京都は今年最後の紅葉風景です。 また来年。 #theta360 - Spherical Image - RICOH THETA

京都市内一望 #theta360 - Spherical Image - RICOH THETA

写真をグリグリ触る体験て新しい!

なんでAppleWatchから?

  • THETA Sはボタンを押したらすぐ写真がとれるのですが、どうしても指が写ってしまう。
  • 上記の紅葉の写真みたいにスマホアプリからシャッター押せますがどうしてもうつむきがちになって顔が映らない
  • AppleWatchなら時計なので、そのまま腕を押すだけで、シャッターが押せる。アプリからより楽。それにwatchからの方がリモート感が増します!
  • 歩きながら、走りながらとか撮影可能(ウェラブルデバイスゆえに!)

AppleWatch単体でTHETA SとWifi接続できるの?

できます。
AppleWatchはiPhoneのペアリングして動作し、ネットワークに関してもiPhone経由でのアクセスでした。watchOS2からは単体でもWifiのネットワークに接続すること(iPhone側で接続したことがある既知のネットワーク)が可能です。

iPhoneが近くあって、Bluetoothオンの場合

消費電力を節約するため、iPhone経由で接続
グランス設定画面で 接続済み の表示
IMG_8028.PNG

BluetoothオフでiPhoneとの接続が切れている場合

単体でAppleWatchはWi-Fiを使います。たとえば、互換性のあるWi-Fiを利用できる環境下では、iPhoneがBluetoothの通信範囲内にない場合、AppleWatchはWi-Fiを使います。
iPhoneがAppleWatchと接続された状態で、以前にwifi接続された既知のネットワークに接続した場合
グランス設定画面で 緑の雲のアイコン の表示
IMG_8026.PNG
詳細についてはこちらを
Apple Watch の Bluetooth と Wi-Fi について

必要なもの、環境


実装 

IMG_7984 (2).PNG
ラベルテキスト、ボタン二種を用意(ひとつは撮影用、ひとつは音声あり、なし制御)

Getting Started

まずはSDKを確認。
https://developers.theta360.com/ja/docs/v2/api_reference/getting_started.html
THETA SはHTTP接続でやり取りが可能。比較的容易。
THETA Sのシャッターを押すために、HTTPネットワークライブラリと
レスポンスがJSONなので、その処理用ライブラリ
  • Alamofire
  • SwiftyJSON
を利用します。
Podfileにプロジェクト名はThetaDemo
Watchkit Extensionとのリンクを記述
shared_podsという名前で共通処理を定義
iOSとwatch extension両方にshared_podsを記述
そしてwatch extensionにplatform :watchos, '2.0'
これを記述しないと実機ビルドができなかった。
link_with "ThetaDemo", "ThetaWatch Watchkit Extension"

def shared_pods
        use_frameworks!
        pod 'Alamofire', '~> 2.0'
        pod 'SwiftyJSON'
end

target 'ThetaDemo' do
        shared_pods
end

target 'ThetaDemoTests' do

end

target 'ThetaDemoUITests' do

end

target 'ThetaWatch Extension' do
        platform :watchos, '2.0'
        shared_pods
end

THETA Sとのセッションの開始

デバイスとのセッションを開始し、sessionIdを取得します。
セッションを切らさないようにタイマー処理で1秒ごとにセッションを更新しています。
セッションの有効期間はデフォルトで180秒なので1秒じゃなくてもいいと思います。
https://developers.theta360.com/ja/docs/v2/api_reference/commands/camera.update_session.html
InterfaceController.swift
import WatchKit
import Foundation
import Alamofire
import SwiftyJSON

class InterfaceController: WKInterfaceController {

    @IBOutlet var cameraStateLb: WKInterfaceLabel!
    let commandExecURL:String = "http://192.168.1.1/osc/commands/execute"
    var ssid:String?

    override func awakeWithContext(context: AnyObject?) {

        cameraStateLb.setText("Connecting...")
        session_start()

        // 1秒ごとにタイマーでセッション更新
        NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateSession"), userInfo: nil, repeats: true)
    }

    // セッション更新
    private func updateSession(){

        if (self.ssid != nil) {

            let parameters = [
                "name": "camera.updateSession",
                "parameters": ["sessionId": self.ssid as String!]
            ]

            Alamofire.request(.POST, commandExecURL, parameters: parameters, encoding: .JSON)
                .responseJSON { _, _, result in

                    switch result {
                    case .Success(let data):
                        let data_dic = data as! NSDictionary

                        if let ssid = data_dic["results"]!["sessionId"] as? String{
                            self.shootBt.setEnabled(true)
                            self.volumeBt.setEnabled(true)
                            self.cameraStateLb.setText("Connected")
                            self.ssid = ssid
                            let ud = NSUserDefaults.standardUserDefaults()
                            ud.setValue(ssid, forKey: ssid)
                            ud.synchronize()
                        }

                    case .Failure(_, let error):
                        print("Request failed with error: \(error)")
                    }
            }
        }
        else{
            session_start()
        }
    }

    // セッション開始
    private func session_start() {

        let parameters = [
            "name": "camera.startSession",
            "parameters": []
        ]

        Alamofire.request(.POST, commandExecURL, parameters: parameters, encoding: .JSON)
            .responseJSON { _, _, result in

                switch result {
                case .Success(let data):
                    let data_dic = data as! NSDictionary
                    print(data_dic)

                    if let ssid = data_dic["results"]!["sessionId"] as? String{
                        self.ssid = ssid
                    }
                case .Failure(_, let error):
                    print("Request failed with error: \(error)")
                }
        }
    }
}

THETA Sで静止画撮影

静止画撮影するためのメソッドです。
セッション開始して、取得したsessionIdを利用します。
takePictureActionで撮影に成功すると、takePicture Success!!とアラートを出します。
InterfaceController.swift
    // 静止画撮影
    func takePicture(){

        let parameters = [
            "name": "camera.takePicture",
            "parameters": [
                "sessionId": self.ssid!
            ]
        ]

        Alamofire.request(.POST, commandExecURL, parameters: parameters, encoding: .JSON)
            .responseJSON { _, _, result in

                switch result {
                case .Success(let data):
                    let data_dic = data as! NSDictionary
                    print(data_dic)
                    let defaultAction = WKAlertAction(
                        title: "OK",
                        style: WKAlertActionStyle.Default) { () -> Void in
                    }

                    let actions = [defaultAction]
                    self.presentAlertControllerWithTitle(
                        "撮影完了",
                        message: "",
                        preferredStyle: WKAlertControllerStyle.Alert,
                        actions: actions)

                case .Failure(_, let error):
                    print("Request failed with error: \(error)")
                }
        }
    }

    @IBAction func takePictureAction() {
        self.takePicture()
    }

シャッターを無音にしたい場合

※くれぐれもご使用にはご注意を
デフォルトで 「キュ〜イン 」とかわいらしい音が鳴りますが、今回は消しちゃいます。
pushVolumeButtonアクションで実行可能です。
_shutterVolumeを0にすることで、シャッター音なしも可能となります。
デフォルトで音量は100になっています。
InterfaceController.swift
    func setShutterVolume(volume:Int) {

        if let _ = self.ssid as String? {

            let parameters = [
                "name": "camera.setOptions",
                "parameters": [
                    "sessionId": self.ssid!,
                    "options": [
                        "_shutterVolume":volume
                    ]
                ]
            ]

            Alamofire.request(.POST, commandExecURL, parameters: parameters, encoding: .JSON)
                .responseJSON { _, _, result in

                    switch result {
                    case .Success(let data):
                        let data_dic = data as! NSDictionary
                        print(data_dic)
                    case .Failure( _, let error):
                        print("Request failed with error: \(error)")
                    }
            }

        }
    }


    @IBAction func pushVolumeButton() {

        if volumeZero {
            volumeZero = false
            setShutterVolume(100)
            volumeBt.setBackgroundImageNamed("VolumeOn")
        }
        else{
            volumeZero = true
            setShutterVolume(0)
            volumeBt.setBackgroundImageNamed("VolumeOff")
        }
    }

ビルド実行

THETA Sの電源を入れ、iPhoneのWifi設定でTHETAのWifiに接続します。
AppleWatchの実機にインストールが完了できれば、撮影ボタンが押下可能となります。
ラベルを表示して、セッション取得前はConnecting...から取得後Connectedに変更されるとしています。

動画は?

もちろんあります。
撮影モード(captureMode)の設定を_videoに設定してPOSTリクエストを送ればOK
静止画撮影モードに戻す際は"captureMode": "image"
      let parameters = [
              "name": "camera.setOptions",
              "parameters": [
                  "sessionId": self.ssid!,
                  "options": [
                      "captureMode": "_video"
                  ]
              ]
          ]
撮影スタートのパラメータはこちら
        let parameters = [
            "name": "camera._startCapture",
            "parameters": [
                "sessionId": self.ssid!
            ]
        ]
撮影ストップのパラメータはこちら
        let parameters = [
            "name": "camera._stopCapture",
            "parameters": [
                "sessionId": self.ssid!
            ]
        ]
撮影モードは維持されるので、動画撮影ストップしたら、
静止画撮影モードに戻した方がベターです。
"captureMode": "image"
今回はサンプル実装ですので、エラー処理は未実装、API共通処理など、リファクタリングされていませんがご了承ください。

今回のサンプルに使用しているAPIの詳細はこちらから
Getting Started · API Reference · v2 · API & SDK | RICOH THETA Developers

番外編 JINS MEMEからTHETA Sを操作しました

JinsMEME ESの3点眼位センサーをインプットとして使って、アウトプット先をTHETA Sにしてシャッターを押すという記事を書きました。

というかAppleWatch×ThetaSユーザっているの?

AppleWatchユーザでかつThetaSを使っているという方がどれくらいいるかわかりませんが、
アプリにして欲しいなどありましたら是非ご連絡ください。喜んでさぶみっとします!

おわりに

以上でAppleWatchからTheta Sをリモート操作は終わりです。
Theta Sは比較的容易にAPI操作でコントロールできることがわかったかと思います。
今回やったことはThetaでできることの序盤です。もっとおもしろいことはできると思います。
暫くスリープさせずに撮影するオプションなどあるので屋外にずっと置いて定点ストリーミングとか、撮影する画像のサイズを変更したり、ホワイトバランス、
露出補正、画像加工フィルター、シャッタースピードを変更したり、GPS情報を付与する、360度リアルタイムストリーミングとか。リアルウォーリーを探せゲームをつくるとか:)
やっぱりVRデバイスでリアルタイムストリーミングliveとか見れたら一番おもしろいな〜なんて思います。
今後こういった全天球360度撮影できるカメラはこれから続々と出てくる中、
また一般市場にVRデバイスが発売され始め、2016年はVR元年と言われているくらいなので、続々とおもしろい実写コンテンツがででくるのではないかというのが来年の楽しみです。
ではまた。