【Rails】Cloud Vision APIを用いて画像解析し、過激な画像を検知する方法

# 目標

`refile`でユーザーのプロフィール画像を編集した場合、
アップロードする画像を`Cloud Vision API`で画像解析し、
過激な画像の場合はDBに保存出来なくする。

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)
・[投稿機能実装](https://qiita.com/matsubishi5/items/1a5aeab651b5822d1f2c)

# `Cloud Vision API`を有効化

### 1.下記記事の「Geocoding APIを有効化」と同様の手順で、Cloud Vision APIを有効化して下さい。
[Geolocation APIを用いて緯度経度を算出する方法](https://qiita.com/matsubishi5/items/0afa6845ea024c081d76)

### 2.ホーム画面に遷移し、`IAMと管理`をクリック
![スクリーンショット 2020-06-08 9.47.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/cfccab9e-f6cc-e6f8-a0b0-821110edcd66.png)

### 3.`サービスアカウント`をクリック
![スクリーンショット 2020-06-08 9.48.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/ef0fbfec-2c33-7d4c-da80-2d11392c5330.png)

### 4.`サービスアカウントを追加`をクリック
![スクリーンショット 2020-06-08 9.48.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/43ea2f34-4383-0c65-0aad-3281ae6e307a.png)

### 5.`サービスアカウント名(適当で良い)`を入力し、`作成`をクリック
![スクリーンショット 2020-06-08 9.49.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/ee2b2b85-498e-d484-37fe-7a6134aac45c.png)

### 6.`続行`をクリック
![スクリーンショット 2020-06-08 9.49.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/a45cf2c5-6b3d-3af2-74e0-3172c578b7fa.png)

### 7.`キーを作成`をクリック
![スクリーンショット 2020-06-08 9.55.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/f087164b-657d-6630-4064-7c61dc4832f4.png)

### 8.キーのタイプを`JSON`に設定し、`作成`をクリック
![スクリーンショット 2020-06-08 9.50.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/8d641d77-e70b-d5aa-7d3b-68db6d7b0ec9.png)

### 9.キーが作成された事を確認し、`閉じる`をクリック
![スクリーンショット 2020-06-08 9.55.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/45eb01cd-b8a9-3fb1-f8b9-7439e0a4bf0f.png)

### 10.`完了`をクリック
![スクリーンショット 2020-06-08 9.55.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/6e0d4852-47c4-7c7a-d633-8f5fa50f43f0.png)

### 11.キーをアプリケーション直下に移動
![スクリーンショット 2020-06-08 10.10.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/48d42421-b1d8-7e65-785c-5502c0a43538.png)

# `Cloud Vision API`の評価について

### 評価カテゴリー

|カテゴリー|内容|
|---|---|
|adult|アダルト画像かどうか|
|spoof|インターネットミームかどうか|
|medical|インターネットミームかどうか|
|violence|暴力的な画像かどうか|
|racy|際どい画像かどうか|

### 評価値

|評価値|度合い|
|---|---|
|UNKNOWN|解析不可|
|VERY_UNLIKELY|非常に低い|
|UNLIKELY|低い|
|POSSIBLE|可能性あり|
|LIKELY|高い|
|VERY_LIKELY|非常に高い|

# 実装

### 1.Gemを導入

評価カテゴリーを問わず、評価値が`LIKELY`又は`VERY_LIKELY`を
含む場合はDBに保存しない様に実装をしていきます。

```ruby:Gemfile
gem 'google-cloud-vision'
```

```terminal:ターミナル
$ bundle
```

### 2.Cloud Vision APIの設定ファイルを作成し、編集

```terminal:ターミナル
$ touch lib/vision.rb
```

```ruby:lib/vision.rb
require 'base64'
require 'json'
require 'net/https'
module Vision
  class << self
    def image_analysis(profile_image)
      image_annotator = Google::Cloud::Vision::ImageAnnotator.new(
          version: :v1,
          credentials: JSON.parse(File.open('キーのファイル名') do |f| f.read end)
      )

      # リクエストパラメータ作成
      image = profile_image # 解析させたい画像(引数)
      requests_content = { image: { content: image }, features: [{ type: :SAFE_SEARCH_DETECTION }] }
      requests =   [requests_content]

      # Cloud Vision APIに画像を送信
      response = image_annotator.batch_annotate_images(requests)
      result = response.responses[0].safe_search_annotation.to_h

      # 解析結果が「LIKELY」又は「VERY_LIKELY」を含む場合はfalse、それ以外はtrueを返す
      if result.values.include?(:LIKELY) || result.values.include?(:VERY_LIKELY)
        return false
      else
        return true
      end
    end
  end
end
```

### 3.コントローラーを編集

```ruby:users_controller.rb
def update
  # 画像が編集された場合
  if params[:user][:profile_image].present?
    # パラメーター(画像)を「tempfile」として開いて変数に代入
    profile_image = File.open(params[:user][:profile_image].tempfile)
    # Cloud Vision APIで画像分析して、分析結果を変数に代入
    result = Vision.image_analysis(profile_image)
  else
    # 画像が編集されてない場合は「true」を代入
    result = true
  end
  # 解析結果によって条件分岐
  if result == true
    @user.update(user_params)
    redirect_to user_user_path(@user)
  elsif result == false
    flash[:notice] = '画像が不適切です'
    render 'edit'
  end
end
```