2015年4月11日土曜日

[android]画像認識API(docomo developer support様ご提供)をvolleyを使ってリクエストする

読書日記にdocomo developer support様ご提供の画像認識APIを使って、カメラで撮った書籍画像から本の情報を取得するようにしました。
いやあ、こんなにすばらしいものを提供していただいて、本当にありがたいです。
画像認識用のSDKがありましたが、遅ればせながら最近導入したvolleyで実装することにしました。
volleyの基本的な使い方はTransmitting Network Data Using Volley等が詳しいです。

final byte[] imageBytes = ……
final String url = "https://api.apigw.smt.docomo.ne.jp/imageRecognition/v1/recognize?" +
                "APIKEY=YOUR_API_KEY" +
                "&recog=book" +
                "&numOfCandidates=1";

JsonObjectRequest jsObjRequest = new JsonObjectRequest
                (Request.Method.POST, url, null, new Response.Listener() {
                    @Override
                    public void onResponse(JSONObject jsonObject) {
                        Log.d("TAG", jsonObject.toString());
                        success(jsonObject);
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d("TAG", error.toString());
                        failed();
                    }
                }) {

            @Override
            protected Map getParams() {
                Map params = new HashMap<>();
                params.put("Content-Type", "application/octet-stream");
                return params;
            }

            @Override
            public byte[] getBody() {
                return imageBytes;
            }

        };

// あとはjsObjRequestをcom.android.volley.RequestQueueインスタンスにadd()すると通信が始まります。

通信結果はJSONで得られるので、JsonObjectRequestを使いました。
ポイントはPOSTであることと、JsonObjectRequestのgetParamsをOverrideしてContent-Typeの設定をすることと、getBodyをOverrideしてbyte[]つまりここでは画像データを返すようにすることです。
上記はnew JsonObjectRequestしたときにOverrideしていますが、JsonObjectRequestを継承して新たにクラスを作ったほうがよいのかもしれません。
通信結果はonResponse(JSONObject)でコールバックされます。
上記のsuccess()、failed()は私が作った適当なメソッドです。
byte[] imageBytesについては下記で補足します。

その他です。
・カメラ画像の取得はImage capture intentを参考にminimal codingしました。下記のようなintentを作ってActivity#startActivityForResult()する感じです。結果はもちろん、onActivityResultで得られます。
・getOutputMediaFileUri()はここに説明があります
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name

・撮影された画像はfileUri(android.net.Uriのインスタンス)に格納されているので、
File file = new File(fileUri.getPath());
FileInputStream inputStream = new FileInputStream(file);
byte[] imageBytes = ByteStreams.toByteArray(inputStream);
とでもすれば、byte[]が得られます。
com.google.common.io.ByteStreamsをつかっています。android studioへのguavaの追加方法はDependenciesあたりでしょうか。

2015年4月10日金曜日

[Ruby][Java]reduce(inject)を使ってみる

#ruby 2.0.0p643 (2015-02-25 revision 49749) [x86_64-darwin13.4.0]
1から100までの足し算は、テンポラリーの変数を使ってこんなふうにかける。
sum = 0
(1..100).each {|n| sum += n}
puts "sum = #{sum}"

Enumerable#reduceを使うと、こんなふうにかける。
sum = (1..100).reduce(0) {|memo, n| memo + n}
puts "sum = #{sum}"

1回目のループでは、memoにはreduce(0)で指定した0が入る。nには1が入っている。
memo + nの結果は次のループではmemoに格納される。
つまり2回目のループではmemo=1, n=2で、3回目にはmemo=3, n=3、4回目にはmemo=6, n=4…… といった具合。
reduceのかわりにinjectでもよい。

単純な足し算だけじゃつまらないので、下記のようなUserというクラスがあったとして、
class User
  attr_accessor :name, :age
  def initialize(options={})
    @name = options[:name]
    @age = options[:age]
  end
end

Userインスタンスの配列の中から21歳以上のUserのnameだけを取り出したい場合には下記のようにすればよい。

users = [User.new(name: 'john', age: 20), User.new(name: 'hanako', age: 21),User.new(name: 'taro', age: 22)]
names = users.reduce([]) {|memo, u| u.age >= 21 ? memo << u.name : memo}
p names
# => ["hanako", "taro"]


JavaではJava8でinjectが使える。
 class User {

  private final String name;
  private final int age;

  private User(final String nameArg, final int ageArg) {
    name = nameArg;
    age = ageArg;
  }

  static User newInstance(String name, int age) {
    return new User(name, age);
  }

  String getName() {
    return name;
  }

  int getAge() {
    return age;
  }
}

List list = new ArrayList<>();
list.add(User.newInstance("john", 20));
list.add(User.newInstance("hanako", 21));
list.add(User.newInstance("taro", 22));
List result = list.stream().reduce(new ArrayList(),
            new BiFunction, User, List>() {
              @Override
              public List apply(List t, User u) {
                if (u.getAge() >= 21) {
                  t.add(u.getName());
                }
                return t;
              }
            },
            new BinaryOperator>() {
              @Override
              public List apply(List t, List u) {
                t.addAll(u);
                return t;
              }
            }
    );

長い……
クラス定義やlistを作っているところはしようがないとしても、reduceのところが長い……
ラムダ式を使うと
result = list.stream().reduce(new ArrayList(), (memo, user) -> {
      if (user.getAge() >= 21) {
        memo.add(user.getName());
      }
      return memo;
    }, (t, u) -> {
      t.addAll(u);
      return t;
    });

第3引数の存在がなぞ。
この例だと{}の中をthrow new NotImplementedException();と書いても動作する。
要素数が多くなったりすると活躍するのかなあ?

けれど、結局
result = new ArrayList<>();
for (User u : list) {
  if (u.getAge() >= 21) {
    result.add(u.getName());
  }
}
のほうがわかりやすい?

2015年4月1日水曜日

[android][Ruby]strings.xmlを一覧化する

すでにこことかここにありますが、Rubyの練習でnokogiriを使わずに書いてみました。