RICOH THETA S API in several languages.

  • 45
    Like
  • 0
    Comment
More than 1 year has passed since last update.

At first

  • このページは「RICOH THETA Advent Calendar 2015」17日目のページです
  • RICOH THETA S ではカメラをプログラムから操作するAPIが提供されています
    • このモデルから、Open Spherical Camera準拠のAPIが提供されている
    • 以前のモデルより、簡単に任意のプログラミング言語から操作を行える
  • いろいろな言語でTHETA S の OSC APIを叩いてみた!という内容です
  • このページが THETA S のAPIを叩いてみようという人の参考になると幸いです

RICOH THETA API V2

開発者向けのページがあり、リファレンスフォーラムが公開されています。

本ページではGetting Startedを元にしつつ、下記のAPIを実際に叩いた例を示します

  1. カメラ本体の情報取得 GET /osc/info
  2. セッション開始 POST /osc/commands/execute camera.startSession
  3. 静止画撮影 POST /osc/commands/execute camera.takePicture
  4. 撮影状態の取得 POST /OSC/state
  5. ファイル取得 POST /osc/commands/execute camera.getImage
  6. セッション終了 POST /osc/commands/execute camera.closeSession

基本的にPOSTでJSON形式のパラメタ、レスポンスをやり取りします。

Prerequisite

  • OS X 10.10.5上で実行
  • THETA S の Wi-Fiに接続済みであること
    • インターネットにつなぎながら、THETA SのAPIを叩く方法については15日目の記事に詳しいです
  • なるべく各言語の標準環境で出来ることを目指します
  • THETA本体が Acceptヘッダや Content-Type の設定を行わなくても正常に動作したため、不要なものは省いています
  • エラー処理は基本的に行いません

Shell Script

まずは処理の流れのイメージを掴むために、レスポンスも確認しながらShellから叩いてみましょう。今回はMac OS Xに標準でインストールされている「curl」コマンドを用います。

# カメラ本体の情報取得
$ curl http://192.168.1.1:80/osc/info
{"manufacturer":"RICOH","model":"RICOH THETA S","serialNumber":"XXXXXXXX","firmwareVersion":"01.11","supportUrl":"https://theta360.com/en/support/","endpoints":{"httpPort":80,"httpUpdatesPort":80},"gps":false,"gyro":false,"uptime":583,"api":["/osc/info","/osc/state","/osc/checkForUpdates","/osc/commands/execute","/osc/commands/status"]}

カメラ本体の情報が取得できています。この値はこの後では特に用いません。

# セッション開始
$ curl -X POST http://192.168.1.1:80/osc/commands/execute -d '{"name": "camera.startSession"}'
{"name":"camera.startSession","state":"done","results":{ "sessionId":"SID_0001","timeout":180}}

ここで取得する「sessionId」を、この後利用します。手動で次の行の "SID_0001"に該当する箇所に入れる必要があります。

# 静止画撮影
$ curl -X POST http://192.168.1.1:80/osc/commands/execute -d '{"name": "camera.takePicture", "parameters": {"sessionId": "SID_0001"}}'
{"name":"camera.takePicture","state":"inProgress","id":"2","progress":{"completion":0.0}}

カメラがキュインといいます。画像の撮影終了まで、多少時間がかかります。

# 撮影状態の取得
$ curl -X POST http://192.168.1.1:80/osc/state
{"fingerprint":"FIG_0006","state":{"sessionId":"SID_0001","batteryLevel":1.0,"storageChanged":false,"_captureStatus":"idle","_recordedTime":0,"_recordableTime":0,"_latestFileUri":"100RICOH/R0010174.JPG","_batteryState":"charging"}}

ここで撮影処理が終わると取得できる「latestFileUri」を次のAPIで利用します。
「fingerprint」に変更があれば、画像処理が終わっています。
latestFileUri」が取得できるまで、何度か実行する必要があるかもしれません。

# ファイル取得
$ curl -X POST http://192.168.1.1:80/osc/commands/execute -d '{"name": "camera.getImage", "parameters": {"fileUri": "100RICOH/R0010174.JPG"}}' > image.jpg && open image.jpg

ファイルを引き取り、実行ディレクトリのimage.jpg に保存しています。引取後にビューワで開いています。

# セッション終了
$ curl -X POST http://192.168.1.1:80/osc/commands/execute -d '{"name": "camera.closeSession", "parameters": {"sessionId": "SID_0001"}}'
{"name":"camera.closeSession","state":"done"}

後始末完了です。

パラメタの指定などは多少手間がかかりますが、簡単にHTTPで撮影、画像の引取ができることが確認できたのではないでしょうか。

Ruby

  • ruby 2.3.0preview2 (2015-12-11 trunk 53028) [x86_64-darwin14]
  • _latestFileUri の取得を撮影終了の判定としています
  • irb/pryでの動作を確認しています
require 'net/http'
require 'json'

http = Net::HTTP.new('192.168.1.1', 80)
JSON.parse(http.get("/osc/info").body)

res = http.post('/osc/commands/execute', {name: "camera.startSession"}.to_json)
sessionId = JSON.parse(res.body)["results"]["sessionId"]

params = {name: "camera.takePicture", parameters: {sessionId: sessionId}}.to_json
http.post('/osc/commands/execute', params)

fileUri = JSON.parse(http.post('/osc/state',"").body)["state"]["_latestFileUri"] while fileUri.empty?

params = {name: "camera.getImage", parameters: {fileUri: fileUri}}.to_json
res = http.post('/osc/commands/execute', params)

open("image.jpg", "wb") {|f| f.write(res.body)}
`open image.jpg`

params = {name: "camera.closeSession", parameters: {sessionId: sessionId}}.to_json
http.post('/osc/commands/execute', params)

大分直感的に読めるのではないでしょうか。ぼくはRubyが好きです。

Python

  • Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 12:54:16)
  • _latestFileUri の取得を撮影終了の判定としています
  • pythonインタプリタ、REPL環境で動作を確認しています
import urllib
import json

urllib.urlopen("http://192.168.1.1/osc/info").read()

data = json.dumps({"name":"camera.startSession"})
res = urllib.urlopen('http://192.168.1.1/osc/commands/execute', data)
sessionId = json.loads(res.read())["results"]["sessionId"]

data = json.dumps({"name":"camera.takePicture", "parameters": {"sessionId": sessionId}})
urllib.urlopen('http://192.168.1.1/osc/commands/execute', data)

fileUri = ""
while not fileUri:
    res = urllib.urlopen('http://192.168.1.1/osc/state', urllib.urlencode({}))
    fileUri = json.loads(res.read())["state"]["_latestFileUri"]

data = json.dumps({"name":"camera.getImage", "parameters": {"fileUri": fileUri}})
res = urllib.urlopen('http://192.168.1.1/osc/commands/execute', data)
with open("image.jpg", "wb") as file:
    file.write(res.read())

data = json.dumps({"name":"camera.closeSession", "parameters": {"sessionId": sessionId}})
urllib.urlopen('http://192.168.1.1/osc/commands/execute', data)

こちらも直感的に読めると思います。

画像を開くところまでやりたかったのですが、上記のコードに続けて、下記のコードもまとめてペースト実行すると、Popenの挙動が安定しませんでした。随時実行であればREPL環境からでも、下記コードにて画像を開くことができました。

import subprocess
subprocess.Popen(['open', 'image.jpg'])

Swift

  • Apple Swift version 2.1.1 (swiftlang-700.1.101.15 clang-700.1.81)
  • Xcode 7.2
  • Playground環境で実行
import UIKit

// for asynchronous use in Playground
import XCPlayground 
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 

let session = NSURLSession.sharedSession()

// osc/info
var url = NSURL(string: "http://192.168.1.1/osc/info")
var task = session.dataTaskWithURL(url!) {
    (data, response, error) in
    print(NSString(data: data!, encoding: NSUTF8StringEncoding))
}
task.resume()

// camera.startSession
var sessionId = "";
var jsonDic = NSDictionary()
url = NSURL(string: "http://192.168.1.1/osc/commands/execute")
var req = NSMutableURLRequest(URL: url!)
var params: [String: AnyObject] = ["name": "camera.startSession"]
req.HTTPMethod = "POST"
req.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions.PrettyPrinted)
task = session.dataTaskWithRequest(req) {
    (data, response, error) in
    print(NSString(data: data!, encoding: NSUTF8StringEncoding))
    do {
        jsonDic = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
    } catch {}
    let result = jsonDic["results"]  as! NSDictionary
    sessionId = result["sessionId"] as! String
}
task.resume()
while sessionId.isEmpty { sleep(1) }

// camera.takePicture
url = NSURL(string: "http://192.168.1.1/osc/commands/execute")
req = NSMutableURLRequest(URL: url!)
params = ["name": "camera.takePicture", "parameters": ["sessionId": sessionId]]
req.HTTPMethod = "POST"
req.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params,
    options: NSJSONWritingOptions.PrettyPrinted)
task = session.dataTaskWithRequest(req) {
    (data, response, error) in
    print(NSString(data: data!, encoding: NSUTF8StringEncoding))
}
task.resume()

// osc/state
var fileUri = "";
url = NSURL(string: "http://192.168.1.1/osc/state")
req = NSMutableURLRequest(URL: url!)
req.HTTPMethod = "POST"
repeat {
    sleep(1)
    print("try osc/state")
    task = session.dataTaskWithRequest(req) {
        (data, response, error) in
        print(NSString(data: data!, encoding: NSUTF8StringEncoding))
        do {
            jsonDic = try NSJSONSerialization.JSONObjectWithData(data!,
                options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
        } catch {}
        let result = jsonDic["state"]  as! NSDictionary
        fileUri = result["_latestFileUri"] as! String
    }
    task.resume()
} while fileUri.isEmpty
print(fileUri)

// camera.getImage
var img :UIImage?
url = NSURL(string: "http://192.168.1.1/osc/commands/execute")
req = NSMutableURLRequest(URL: url!)
params = ["name": "camera.getImage", "parameters": ["fileUri": fileUri]]
req.HTTPMethod = "POST"
req.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params,
    options: NSJSONWritingOptions.PrettyPrinted)
task = session.dataTaskWithRequest(req) {
    (data, response, error) in
//    NSData.init(data: data!)
    img = UIImage(data: data!)
}
task.resume()
while (img == nil) { sleep(1) }

// camera.closeSession
url = NSURL(string: "http://192.168.1.1/osc/commands/execute")
req = NSMutableURLRequest(URL: url!)
params = ["name": "camera.closeSession", "parameters": ["sessionId": sessionId]]
req.HTTPMethod = "POST"
req.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params,
    options: NSJSONWritingOptions.PrettyPrinted)
task = session.dataTaskWithRequest(req) {
    (data, response, error) in
    print(NSString(data: data!, encoding: NSUTF8StringEncoding))
}
task.resume()

NSURLSessionが非同期で実行するAPIなので、ところどころで待ちの処理を入れています。
imgに値が入った時点で、Playground環境のResults Sidebarで変数をプレビューすると画像が確認できます。
JSONを処理するところ、もう少し綺麗に書きたいところです。

Unity

  • 5.3.0f4

C#

  • RawImageオブジェクトに下記のスクリプトを追加して動作確認
  • MiniJsonJsonNodeを使用
using UnityEngine;
using UnityEngine.UI;
using System.Text;
using System.Collections;
using System.Collections.Generic;

public class THETAOSC : MonoBehaviour {

    void Start () {
        StartCoroutine(StartThetaS());
    }

    IEnumerator StartThetaS () {
        string url = "http://192.168.1.1/osc/info";
        WWW www = new WWW(url);
        yield return www;
        Debug.Log(www.text);

        Dictionary<string, string> header = new Dictionary<string, string>();
        url = "http://192.168.1.1/osc/commands/execute";
        string jsonStr = "{\"name\": \"camera.startSession\"}";
        byte[] postBytes = Encoding.Default.GetBytes (jsonStr);
        www = new WWW (url, postBytes, header);
        yield return www;
        JsonNode json = JsonNode.Parse(www.text);
        string sessionId = json["results"]["sessionId"].Get<string>();
        Debug.Log(sessionId);

        jsonStr = "{\"name\": \"camera.takePicture\", \"parameters\": {\"sessionId\": \"" + sessionId + "\"}}";
        postBytes = Encoding.Default.GetBytes (jsonStr);
        www = new WWW (url, postBytes, header);
        yield return www;
        Debug.Log(www.text);

        string fileUri = "";
        url = "http://192.168.1.1/osc/state";
        jsonStr = "{}";
        postBytes = Encoding.Default.GetBytes (jsonStr);
        while(fileUri == "") {
            www = new WWW (url, postBytes, header);
            yield return www;
            Debug.Log(www.text);
            json = JsonNode.Parse(www.text);
            fileUri = json["state"]["_latestFileUri"].Get<string>();
        }
        Debug.Log(fileUri);

        // fileUriが取れても処理が終わっていない場合があったので少し待つ
        yield return new WaitForSeconds(3); 

        url = "http://192.168.1.1/osc/commands/execute";
        jsonStr = "{\"name\": \"camera.getImage\", \"parameters\": {\"fileUri\": \"" + fileUri + "\"}}";
        postBytes = Encoding.Default.GetBytes (jsonStr);
        www = new WWW (url, postBytes, header);
        yield return www;

        // 確認のためにRawImageに表示
        RawImage rawImage = GetComponent<RawImage>();
        rawImage.texture = www.textureNonReadable;
        rawImage.SetNativeSize();

        jsonStr = "{\"name\": \"camera.closeSession\", \"parameters\": {\"sessionId\": \"" + sessionId + "\"}}";
        postBytes = Encoding.Default.GetBytes (jsonStr);
        www = new WWW (url, postBytes, header);
        yield return www;
        Debug.Log(www.text);
    }
}

Unreal Engine 4(予定)

Summary

THETA S の OSC準拠APIを利用して、撮影・画像引取を行う流れとAPIの叩き方について、各環境で実際に動かしてみました。同じようなやり方でカメラの撮影オプションの設定などもできるので、色々遊べるのではないかと思います。なにかの参考、きっかけになると幸いです!

This post is the No.17 article of RICOH THETA Advent Calendar 2015