Alibaba Cloud OSS 内にある同一画像を除去する

この記事は [Alibaba Cloud Advent Calendar 2020]() 18日目の記事となります。（ちょいと遅れました）

## この記事の背景
類似画像検索サービス [Image Search](https://www.alibabacloud.com/ja/product/imagesearch) への画像登録は、ストレージサービス [OSS](https://www.alibabacloud.com/ja/product/oss) からのエクスポートでできます。
しかし、OSS 内にファイル名が異なる同一画像がある場合、同一画像が重複して登録されます。
その結果、画像検索結果に同一画像が返され、同一画像の重複を間引く必要が出てきます。
そこで、OSS 内にある同一画像を削除するべく、仮想サーバー [ECS](https://www.alibabacloud.com/ja/product/ecs) に OSS から画像をダウンロードし、OpenCV で同一画像判定をして、同一画像を削除したのち、OSS に画像を再アップロードしたいと思います。
※エクスポートについては[公式ドキュメント](https://www.alibabacloud.com/help/doc-detail/66580.htm)をご参照ください。


## ECS に OSS から画像をダウンロード
OSS には Image Search の[カテゴリ](https://www.alibabacloud.com/help/doc-detail/179184.htm)に従ってフォルダがあり、各フォルダ内に約700枚 ~ 10000枚の画像があります。
これらの画像を [ossutil](https://www.alibabacloud.com/help/doc-detail/50452.htm) を使って、ECS に一括ダウンロードします。
インスタンスのスペックは 2vCPU 8GiB です。
使用したコマンドは [cp](https://www.alibabacloud.com/help/doc-detail/120057.htm) で、`--jobs` オプションで同時実行数を 100 に指定した結果、2分程度でダウンロードが完了しました。

### OSS フォルダ構成
```bash
products
├── 0
├── 1
├── 2
├── 20
├── 21
├── 22
├── 3
├── 4
├── 5
├── 6
├── 7
├── 8
├── 88888888
└── 9
```

### cp コマンド発行
ECS の `images` ディレクトリに上記 OSS フォルダ構成で画像がダウンロードされます。

```bash
./ossutil64 cp --jobs=100 -f -r oss://バケット名/products/ images/
```

### 実行ログ
```bash
Scanned num: 11500, size: 937,891,951. Dealed num: 7673(download 7673 objects), OK size: 541,480,436, Speed: 1053
Scanned num: 20400, size: 2,033,138,975. Dealed num: 13068(download 13068 objects), OK size: 1,173,929,335, Speed
Scanned num: 39600, size: 4,868,303,380. Dealed num: 17742(download 17742 objects), OK size: 1,784,801,542, Speed
...
FinishWithError: Total num: 98039, size: 9,244,025,341. Error num: 1. OK num: 98038(download 98038 objects), Transfer size: 9,244,007,415.

average speed 100570000(byte/s)

91.921215(s) elapsed
```

### 実行結果
```bash
[root@ホスト名 ~]# ll images
total 4564
drwxr-xr-x 2 root root 458752 Dec 20 17:43 0
drwxr-xr-x 2 root root 634880 Dec 18 16:34 1
drwxr-xr-x 2 root root 446464 Dec 18 16:34 2
drwxr-xr-x 2 root root 417792 Dec 18 16:34 20
drwxr-xr-x 2 root root 180224 Dec 18 16:34 21
drwxr-xr-x 2 root root 352256 Dec 18 16:35 22
drwxr-xr-x 2 root root 438272 Dec 18 16:35 3
drwxr-xr-x 2 root root 819200 Dec 18 16:35 4
drwxr-xr-x 2 root root 589824 Dec 18 16:35 5
drwxr-xr-x 2 root root  69632 Dec 18 16:35 6
drwxr-xr-x 2 root root 122880 Dec 18 16:35 7
drwxr-xr-x 2 root root  65536 Dec 20 16:06 8
drwxr-xr-x 2 root root  40960 Dec 18 16:35 88888888
drwxr-xr-x 4 root root   4096 Dec 18 16:35 9
```

## OpenCV で同一画像判定と同一画像削除
OpenCV で同一画像を判定し、同一画像を削除する Python プログラムを作りました。


### 処理内容
1. 実行引数で指定されたカテゴリIDディレクトリ内の画像ファイル一覧を取得する
2. 画像ファイル一覧の先頭画像から1枚ずつ、次の画像ファイルと同一判定をする
3. 同一であれば次の画像ファイルを同一画像リストに追加する
4. 最後の画像ファイルとの比較が完了したら、画像ファイル一覧及びディレクトリ内から同一画像リストの画像を削除する
5. 2. に戻り、画像ファイル一覧を最後まで比較する

つまり、10000枚画像があれば、*n = 10000* となり、最高で

```math
\begin{eqnarray*}
{}_n C _2 = 49,995,000
\end{eqnarray*}
```
回の処理を行うことになります。
14カテゴリあるため、ECS のスペックを 16vCPU 32GiB にして全ディレクトリを並行して処理していますが、17:40 から開始しても 22:40 現在、3カテゴリしか処理が完了していません。
CPU 使用率は 88% 前後なので、期待通りです。


### Python プログラム
```search_same_image.py
import os
import sys
import cv2
import numpy as np

path = './' + sys.argv[1] + '/'
files = os.listdir(path)

same_images = []
for file in files:
    for comparison_file in files:
        print('DIR:' + sys.argv[1] + ':FILE:' + str(files.index(file)) + ':COMPFILE:' + str(files.index(comparison_file)))

        if files.index(file) >= files.index(comparison_file):
            continue

        img = cv2.imread(path + file)
        comparison_img = cv2.imread(path + comparison_file)
        if np.array_equal(img, comparison_img):
            print('========================================FOUND SAME IMAGE :' + comparison_file + '========================================')
            same_images.append(comparison_file)

        if files.index(comparison_file) == len(files) - 1:
            for delete_file in same_images:
                os.remove(path + delete_file)
                files.remove(delete_file)
            same_images = []
```

### 実行コマンド
```bash
nohup python search_same_image.py 8 &
```

### 実行ログ
```bash
...
DIR:8:FILE:714:COMPFILE:711
DIR:8:FILE:714:COMPFILE:712
DIR:8:FILE:714:COMPFILE:713
DIR:8:FILE:714:COMPFILE:714
```
### 実行結果は如何程に
処理が完了した3カテゴリの処理前後の画像枚数を比較してみます。
100枚程度削除されていると、プログラムを組んだ甲斐があります。
処理途中の11カテゴリが楽しみです。

| カテゴリID | 処理前枚数 | 処理後枚数 |
|:-----------:|:------------:|:------------:|
| 0 | 9442 | **8899** |
| 1 | 9320 | **9262** |
| 2 | 8937 | **7535** |
| 20 | 5951 | **5739** |
| 21 | 3462 | **2978** |
| 22 | 6935 | **6803** |
| 3 | 7991 | **5931** |
| 4 | 15755 | **12342** |
| 5 | 12417 | **8741** |
| 6 | 817 | **767** |
| 7 | 1432 | **1237** |
| 8 | 848 | **715** |
| 9 | 6414 | **6091** |
| 88888888 | 740 | 740 |

### OpenCV で Warning が出ている
処理途中でちょくちょく以下の Warning が出ています。
調査すると、PNG のメタデータが不正なため、ImageMagick による画像変換が必要なようですが、そのまま処理し続けています。
参考にしたページはこちら。 [OpenCVで「libpng warning: iCCP: known incorrect sRGB profile」というエラーの対処法](https://www.henjins-toolbox.tech/entry/opencv_error1)

```bash
libpng warning: iCCP: known incorrect sRGB profile
```

## OSS に画像を再アップロード
ダウンロードと同じく、cp コマンドを使用します。

### cp コマンド発行
ECS の `images` ディレクトリから別の OSS バケットの `products` フォルダに同構成で画像がアップロードされます。
ECS のスペックが上がっているので、同時実行数は 800 くらいでもいいかもしれません。
※同一画像判定 & 画像削除処理が終わっていないのでまだ実施していません。

```bash
./ossutil64 cp --jobs=800 -f -r images/ oss://バケット名/products/
```

## まとめ
まだ何も終わっていないですが、今回のポイントは ossutil cp コマンドの同時実行数オプションです。かなり優秀です。
あっという間に画像がダウンロードされました。
ossutil はオプションが豊富なので、プログラムを組む必要がないのがありがたいです。