Java SDKを使ってImage Searchの類似検索を試す【登録編】

# はじめに
前回は、Image Searchのインポート機能を使って画像を登録しました。インポート機能では、V1の時のパラメータしか登録できずV2で新たに追加されたパラメータを登録する事は~~できません~~でした。**できました。すみません。**

ということで、Image Searchにデータを投入するにはAPIを使って登録する方が良いと思います。データを登録する際はいくつか気を付ける点があるので、そのあたりを記述していきたいと思います。

# データの登録方法
今回は、Java SDKを使って登録を行います。環境構築等は【検索編】を参照してください。

``` SimpleAdd.java
package imagesearch.sample;

import java.io.InputStream;
import java.util.Base64;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.imagesearch.model.v20190325.AddImageRequest;
import com.aliyuncs.imagesearch.model.v20190325.AddImageResponse;
import com.aliyuncs.imagesearch.model.v20190325.AddImageResponse.PicInfo;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;

public class SimpleAdd {
    /** AccessKey */
    private static final String ACCESS_KEY = "XXXXXXXXXXXXXxxx";
    /** SeacretKey */
    private static final String KEY_SEACRET = "YYYYYYYYYYYYYYY";

    public static void main(String[] args) throws Exception {
        // 初期化(Tokyoリージョンのケース
        DefaultProfile.addEndpoint("ap-northeast-1", "ImageSearch", "imagesearch.ap-northeast-1.aliyuncs.com");
        IClientProfile profile = DefaultProfile.getProfile("ap-northeast-1", ACCESS_KEY, KEY_SEACRET);
        IAcsClient client = new DefaultAcsClient(profile);

        AddImageRequest request = new AddImageRequest();
        request.setInstanceName("itemsearch");
        request.setProductId("id-0001");
        request.setPicName("item0001");
        request.setCategoryId(9);
        request.setCrop(true);
        request.setIntAttr(1000);
        request.setStrAttr("家具：椅子");
        request.setCustomContent("{'sample': 'さんぷる'}");
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        InputStream is = loader.getResourceAsStream("item01.jpg");
        String image = Base64.getEncoder().encodeToString(is.readAllBytes());
        request.setPicContent(image);

        AddImageResponse response = client.getAcsResponse(request);

        // Debug
        boolean checkShowJsonItemName = response.checkShowJsonItemName();
        Integer code = response.getCode();
        String message = response.getMessage();
        String requestId = response.getRequestId();
        Boolean success = response.getSuccess();
        PicInfo picInfo = response.getPicInfo();
        Integer categoryId = picInfo.getCategoryId();
        String region = picInfo.getRegion();

        System.out.printf("requestId:%s%nmessage:%s%ncheckShowJsonItemName:%s%ncode:%s%nsuccess:%s%n", requestId, message, checkShowJsonItemName, code, success);
        System.out.println("PicInfo");
        System.out.printf("\tcategoryId:%s%n\tregion:%s%n", categoryId, region);
    }
}
```

```
requestId:F009FFA3-D94C-4C95-A8D1-957F7EE06591
message:success
checkShowJsonItemName:false
code:0
success:true
PicInfo
        categoryId:9
        region:140,474,36,578
```

## パラメータ説明

### ProductId
```
request.setProductId(productId);
```
入力必須。商品ID、最大 512 文字をサポートします。1つの商品に複数の画像を含めることができます。

### PicName

```
request.setPicName(picName);
```

入力必須。画像名、最大 512 文字をサポートします。

1. ProductId + PicName で画像を特定します。
2. 複数回追加した画像に同じ ProductId + PicName がある場合、最後に追加された画像が上書きされます。

1つの商品に複数の画像を登録する事で、検索の精度を上げる事ができます。正面画像、斜め画像などいくつかのパターン画像を登録しておくと良いと思います。

### CategoryId

```
request.setCategoryId(9);
```

入力任意。画像カテゴリ。
1. 商品検索の場合：カテゴリが設定されている場合はその設定が優先され、設定されていない場合はシステムがカテゴリ予測を実行し、予測されたカテゴリの結果を Response で取得できます。
2. ユニバーサル検索の場合：カテゴリが設定されているかどうかに関わらず、システムによりカテゴリが 88888888 に設定されます。

設定可能なカテゴリ

| カテゴリー ID | 説明 |
|:-:|:-:|
| 0  | トップス  |
| 1  | ドレス  |
| 2  | ボトムズ  |
| 3  | バッグ  |
| 4  | シューズ  |
| 5  | アクセサリー  |
| 6  | スナック  |
| 7  | メイクアップ  |
| 8  | ボトルドリンク  |
| 9  | 家具  |
| 20  | おもちゃ  |
| 21  | 下着 |
| 22  | デジタル機器  |
| 88888888  | その他  |

## PicContent

```
request.setPicContent(encodePicContent);
```

入力必須。Base64 でエンコードした画像コンテンツ。
最大 2 MB サイズの画像と 5 秒の送信待ち時間をサポートします。現在 jpg および png の画像形式のみサポートしています。
縦と横の画素数は共に 200 以上 1024 以下でなければならず、画像に回転情報を含めることはできません。

本番運用では、登録前に変換ロジックを入れるのが良いかもしれません。あと、ここで行っている「5秒の送信待ち時間をサポートします」というのは、どういう意味か分かっていません。。。なんでしょう。知っている人がいたら教えてください。（そのうちソースで確認してみます。。。）

## Crop
```
request.setCrop(true);
```
入力任意。被写体認識の要否、デフォルトは true です。
1. true の場合、システムにより被写体認識が行われ、認識された被写体による検索が実行されます。被写体認識結果を Response で取得できます。
2. false の場合、被写体は認識されず、画像全体として検索されます。

商品検索の場合、trueで良いと思います。例えば、部屋の雰囲気みたいなものを検索したいのであればfalseで登録しても良いかもしれません。

## Region
```
request.setRegion("280,486,232,351");
```
入力任意。x1、x2、y1、y2 の形式で表される画像の被写体範囲、x1、y1 は左上の点、x2、y2 は右下の点です。
ユーザーが Region を設定した場合、Crop パラメーターの値に関わらず、その Region で検索を実行します。

登録対象の画像に複数のオブジェクトがある場合（例えば椅子を登録しようとして、カーテンやテーブルなども写っている場合）に、検索の精度を上げるために対象のオブジェクトの位置情報を設定する事で、検索の精度を上げる事ができます。

## IntAttr

```
request.setIntAttr(intAttr);
```
入力任意。検索時のフィルタリングに使用できる整数型属性、検索された際にこのフィールドが返されます。
たとえば、サイトの画像 / ユーザーの画像ごとに IntAttr をそれぞれ設定することができ、検索された際にフィルタリングすることで分離できます。

V2から追加されたフィルタに使用できるパラメータです。後述のStrAttrと組み合わせてフィルタする事が可能になりました。フィルタを使う事で、Image Searchからフィルタ条件にマッチしたデータを取得する事ができます。以前はデータを取得した後フィルタするしかなかったので便利になりましたね。
要望を言えば、複数の値を設定できると助かるのですが。。。フィルタ条件は「>、>=、<、<=、=」です。商品の金額などを設定すると、ECサイトでよくある10,000円～30,000円の商品などで絞ることができるかなと思います。


## StrAttr
```
request.setStrAttr(strAttr);
```

入力任意。文字列型属性、最大 128 文字をサポートします。検索された際のフィルタリングに使用できます。検索された際にこのフィールドが返されます。

こちらは、文字列を設定してフィルタする事ができます。使用できるフィルタ条件は「=,!=」です。しかも、完全一致でのフィルタとなります。かなり使い勝手が悪いですね。。。現実的な使い方としては、パラメータをビット化して設定するとかですかね。。。それでも使いづらいけど。複数パラメータの設定と正規表現などが使えるとかなり助かります。

## CustomContent
```
request.setCustomContent("カスタム");
```

入力任意。ユーザーがカスタマイズした内容、最大 4096 文字をサポートします。
検索された際にこのフィールドが返されます。たとえば、画像の説明などのテキストを追加できます。

JSONなどを設定する事で使いやすくすることができます。

## システム制限
システム制限 - ユーザーガイド| Alibaba Cloud ドキュメントセンター
https://jp.alibabacloud.com/help/doc-detail/74408.htm

登録時に気を付ける点は以下の通りです。

1. 画像のサイズは 2 MB以下で、長さと幅のピクセルは 200 以上 1024 以下
2. JPG と PNG の 2 種類の画像をサポートしており、画像には回転情報を含むことはできません
3. 並行処理の使用制限
    4. 検索、追加の並行処理数 (インスタンス作成時に選択した クエリ/秒(QPS)) はコンソールで確認できます。現在 5 クエリ/秒、10 クエリ/秒を選択できます。つまり、1 秒あたり最大 5または10 の検索リクエストを並行処理できます。（無料版の場合は1秒当たり最大1リクエスト）
    5. 削除操作のデフォルトのクエリ/秒(QPS)は 20 です。つまり、1 秒あたり最大 20 つの削除リクエストを並行処理できます。


# まとめ
気を付ける点は以下の通り
1. 画像の種類
2. 画像のサイズ
3. 連続登録の場合は、APIの呼び出し制限をうける
4. フィルタ条件に使えるIntAttr、StrAttrの設計はむずかしい
5. 同じキーで登録すれば上書きされる（主キー制約などはないので注意）