エロ画像検出APIを用いてエロ画像のアップロードを阻止してみる【前編】

こんにちは！
バックエンドエンジニアからフルスタックエンジニアに進化したいすぎちゃんです！

キャリアアップのため転職先にアピールする成果物のためポートフォリオを作成中 :muscle: 
今までに培ったサーバーサイドの知識だけでなく新しく学んだAWSの知識もフルに活用して邁進しております！

皆さんも素晴らしいポートフォリオは作りたいですよね？
また見てくれる人から前向きなFBはもらいたいですよね？

しかし見てもらおうとポートフォリオを公開すると、どうしても民度の低い方が1人2人は出てきてしまうもの・・・ :crying_cat_face: ([実際に被害を受けた人の声](https://qiita.com/nasuB7373/items/10bebd8e9f0b3331f348)）

そんな人たちのために時間を割かれてしまうのは実に勿体ないことですよね :thermometer_face: 

なので今回は「エロ画像検出API」を用いて、
エロ画像をアップロードしようとしても未然にブロックされてしまう仕掛けを作ろうと思います :clap:  

↓の記事も是非よろしくお願いします！

https://qiita.com/sugichaaaan/items/826bf81a853ef951a541

https://qiita.com/sugichaaaan/items/0a184a557928da0e3620



## 完成系
完成系はこちら！
猫ちゃんの画像をアップロードしたらプロフィール画像に設定できますが、
反対にエロ画像ははじかれる。

純粋な方に優しい機能を作っていきましょう:muscle: 

成果物のURLはこちら↓（なんか上に検閲されたっぽいのでGithubに隔離済み）
https://github.com/sugizakikun/docker-laravel/issues/21#issuecomment-2677396037


## 準備

今回使用するのは[nsqf_api](https://github.com/EugenCepoi/nsfw_api)というもの。

yahooが開発した[open_nsfw](https://github.com/yahoo/open_nsfw)に依存しする、画像がアダルトコンテンツを含むか予測するREST APIです。

README.mdには以下２つの方法が記載されています。

- ローカル環境内でAPIリクエストを投げる方法
- Herokuにコンテナをデプロイし、立ち上げたAPIサーバーにAPIリクエストを送る方法

しかしherokuは2022年の夏頃に無料枠が完全に撤廃したため、
２つ目の方法を実現するためには他のサービスを使う必要があることがわかりました :cry: 

> Herokuは2022年8月25日、Heroku DynosやHeroku Postgresなどの無料プランの廃止をアナウンスしました。このニュースには、個人開発者だけでなく、Web開発者であればほとんどの人が衝撃を受けたのではないでしょうか？ 

[参照元：Heroku無料枠廃止で路頭に迷う開発者に捧ぐ、次世代PaaS「Render.com」の使い方](https://qiita.com/mycndev/items/fc7a8fecd7d0b0d09828)

そのため、今回はAWSのECRとECS Fargateを用いて、エロ画像検出APIのサーバー構築をクラウド上で実現したいと思います！

## ECRの設定

:::note info
下記のコマンドを打つ前に[Docker](https://sukkiri.jp/technologies/virtualizers/docker/docker-win_install.html)をインストール&起動しておきましょう。
:::

### 1.nsfw_apiのdockerイメージをpullする

```
$ docker pull eugencepoi/nsfw_api:latest
```

### 2.ECRの作成

下記の手順でリポジトリを作成して下さい

> 1. AWSマネージメントコンソールにアクセス
> 2. 右上のリージョンをUS EAST(N. Virginia)に変更
> 2. 右上のリージョンを選択
> 3. ECSサービスを選択
> 4. 左サイドバーの Repositories を選択
> 5. Create repositoryボタン押下

リポジトリ名はわかりやすいように`detect-noodie-image`に設定しておきましょう。

### 3. リモートリポジトリに取得したイメージをpushする

#### 3-1. ECRにログイン

<アカウントID>は各ユーザーに紐づいている12文字の文字列に置き換えて下さい。

```
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.us-east-1.amazonaws.com
```

#### 3-2. 取得したイメージのタグ付け

タグ名は`<アカウントID>.dkr.ecr.us-east-1.amazonaws.com/detect-noodie-image:latest`を設定して下さい。

```
docker tag eugencepoi/nsfw_api:latest <アカウントID>.dkr.ecr.us-east-1.amazonaws.com/detect-noodie-image:latest
```

#### 3-3. イメージをpush

ECRのリポジトリにpushして下さい。

```
 docker push <アカウントID>.dkr.ecr.us-east-1.amazonaws.com/detect-noodie-image:latest
```

上記のコマンドを入力すると、ECRのリポジトリ詳細ページのイメージ一覧画面が以下の画像のにようになっているはずです。

![スクリーンショット 2024-09-01 16.25.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/3f303bb3-fa9a-ce62-e7f7-141fd8a1dcb0.png)


## ECSの設定

### 1. タスク定義画面の表示

サイドメニューの「[タスク定義](https://us-east-1.console.aws.amazon.com/ecs/v2/task-definitions?region=us-east-1)」を選択して下さい。

![スクリーンショット 2024-09-01 16.32.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/58837f33-8235-f306-e1b2-d7d919ba8e3e.png)

そうすると[タスク一覧](https://us-east-1.console.aws.amazon.com/ecs/v2/task-definitions?region=us-east-1)画面に遷移します。右上の「新しいタスク定義の作成」をクリックして下さい。
![スクリーンショット 2024-09-01 16.33.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/dab42dc0-1116-bf2f-bfff-f347df523130.png)

クリックすればドロップダウンメニューが表示されます。
今回は「新しいタスク定義の作成」を選択して下さい。

![スクリーンショット 2024-09-01 16.37.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/1a53ae45-9806-21e8-fd82-56ae2a5ebca8.png)

### 2. タスク定義の設定

#### 2-1. タスク定義ファミリー名の設定

今回は`nsfw-api-task`と命名します。

![ECSTaskDefinition Name_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/d1a43267-275d-9ae7-a1e5-25a98386749c.png)

#### 2−2. インフラストラクチャの要件の設定

![ECSTaskDefinition InfrastructureRequirement_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/56e30077-6988-0bc2-9166-f1e6c4646d4f.png)

起動タイプは**AWS Fargate**を選択、CPUサイズは一番最小のものに設定しておきましょう。

また条件付きのタスクロールでは、タスクロール、タスク実行ロールのどちらにも**ecsTaskExectuionRole**(AWSマネージドロール)を設定してください。

最小のものに設定しておくと月に2000円もかかりません。

:::note info
[ざっくりAWS Fargate](https://aws-rough.cc/fargate/)
:::

#### 2-3. コンテナ設定

名前は**nsfw-api-container**、イメージURLにはECRを作成した時に生成されたイメージURI(**<アカウントID>.dkr.ecr.us-east-1.amazonaws.com/detect-noodie-image:latest**)を設定して下さい。

![ECSTaskDefinition container_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/d829ffc5-baf8-6945-58d1-2eff1f3a4245.png)

コンテナとの通信を可能にするため、ポートマッピングも忘れずに設定しておきましょう。

コンテナポートは**5000**、プロトコルは**TCP**、ポート名は**5000**、アプリケーションプロトコルは**HTTP**を選択して下さい。


#### 2-4. 環境変数の設定

キーに**PORT**、値を**5000**に設定しましょう。

![ECSTaskDefinition Container EnvVal_2.5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/3d51c87f-fbb5-a775-47d5-973fffd973e5.png)

以上の手順を踏み右下の「作成ボタン」を押すと、コンテナ詳細画面に遷移し画面上部に「正常に作成されたタスク定義」と表示されます。
![ECSTaskDefinition Complete_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/c6666a61-89a9-6da7-a67b-b258ac9fca89.png)

### 3. コンテナサービスの設定

#### 3-1. クラスターの作成

クラスター名は `nsfw-cluster`、インフラストラクチャでは**AWS Fargate(サーバレス)**を選択して下さい。
設定が終わったら画面右下の「作成ボタン」をクリックして下さい。

![ECSCluster .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/0870f016-94d5-219d-88e9-05bd5430ff49.png)

そうするとクラスター一覧画面に詳細画面へのリンクが表示されます。

![スクリーンショット 2024-08-31 2.03.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/85004c64-d844-2584-56ba-48d428eb29bb.png)


#### 3-2. クラスター詳細画面へ遷移

作成された詳細画面に移動しましょう。
詳細画面の下部は以下のように表示されているはずです。

![スクリーンショット 2024-09-01 17.09.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/6de04ff3-0e36-1954-6b20-64265a2d5c99.png)

真ん中に表示されている作成ボタンをクリックし、サービス作成画面に遷移してください。

#### 3-3. 環境の設定

コンピューティングオプションは**起動タイプ**、起動タイプは**FARGATE**を選択して下さい。

プラットフォームバージョンはデフォルトの**LATEST**のままで大丈夫です。

![ECSService Environment_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/20e503ec-64f5-354e-c258-884e382c3a2e.png)

#### 3-4. デプロイ設定

アプリケーションタイプは**サービス**、ファミリーはタスク定義で作成した**nsfw-api-task**を選択してください。

サービス名は`nsfw-api-service`に設定しておきましょう。

![ECSService DeploymentSetting_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/2c78f4ba-9460-e8fa-0d8b-517a4d348857.png)

#### 3-5. ネットワーキング

VPCはご自身が作成したもの、サブネットはパブリックのものだけを選択して下さい。

コンテナとの通信を可能にするため、セキュリティグループのインバウンドルールは以下のように設定します。

- タイプ：カスタムTCP
- ポート範囲： **5000**
- ソース：　カスタム
- 値：　自身のマシンのIP, もしくはアプリケーションがホスティングされているEC2のAPI

外部からのアクセスができるようパブリックIPの設定もONにしておきましょう :muscle: 

![ECSService Networking_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/c99648e2-2a7b-e40c-0010-b4d4278f8d3e.png)


以上の設定が終われば画面下部の「作成」ボタンをタップして下さい。

そうするとクラスター詳細画面に遷移し、画面下部の「タスク」を選択するとタスクも作成されているはずです。

![スクリーンショット 2024-09-01 17.27.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/dc4f7220-e9d8-96bd-6347-de9e2c27bfc7.png)


## 動作確認

作成したサービスに紐づいているタスクの詳細画面に移動します。

![ECSService TaskDetail .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/c1320492-7d66-0daf-0895-a3abc64a4847.png)

設定のセクションにパブリックIPがされています。
APIのURLの形式は以下の通りです。

```
http://<タスクに紐づいたパブリックIP>:5000/?url=<調べたい画像のURL>
```

[猫の画像](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA0XNyhytw0thfWMu4X6yK89mQ4lwuqPWmOTy4-UQ4NCOLFiYMJfetMdfCze2aGIZra6_SSqLtHvl-zXFmlFVqfu5RySPBi7gEXg8sP-QFPePD88dSmb0EQUU9nRZneIm36zSbUMT60nE/s400/animal_stand_neko.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401561/be869913-05ec-350d-8c0b-cdddd7e287b1.png)

試しにいらすとや猫ちゃんの画像 :cat:  がエロいかどうかを調べてみましょう。

```
{"score":0.026141654700040817,"url":"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgA0XNyhytw0thfWMu4X6yK89mQ4lwuqPWmOTy4-UQ4NCOLFiYMJfetMdfCze2aGIZra6_SSqLtHvl-zXFmlFVqfu5RySPBi7gEXg8sP-QFPePD88dSmb0EQUU9nRZneIm36zSbUMT60nE/s400/animal_stand_neko.png"}
```

結果は約0.02。全くエロ画像っぽくないことがわかりました。
業務でも問題なく使用してもいいことがわかります。

他の画像でも色々試してみたところ、やはりGoogleで「●● エロ画像」と画像検索して最上位に来るものについては0.9以上のscoreを叩き出すことがわかりました。

しかし私は疑り深い性質なのか「ほんまかいな？どうせ肌色成分が多いからちゃうん？ :rolling_eyes: 」と思い、念のため[肩ロースの画像](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSrgnoeiWSTDfAGdECsqMJqrVfs0CBF3r2-sA&s)でも検証してみました。

```
{"score":0.092671982944011688,"url":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSrgnoeiWSTDfAGdECsqMJqrVfs0CBF3r2-sA"}
```

結果は0.09とエロ画像と認識されていないことが判明しました。
ある程度の精度が保証されているようです :smile: 

ここまでお読みいただき、誠にありがとうございます！
次週はlaravelアプリケーションと連携をして、アップロードされた画像がもしエロ画像だったら警告モーダルを表示する仕掛けを作っていこうと思います！

↓の記事も是非よろしくお願いします！

https://qiita.com/sugichaaaan/items/826bf81a853ef951a541


## 参考記事

https://qiita.com/MOSO1409/items/8f02613c41e18b1b6df8