Laravel + Amazon Rekognitionで不適切な画像を検出する

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、前回に引き続きAWSのサービスをいろいろと試しているのですが、今回はその昔クライアントさんへ「完全には難しいですね…💦」と回答したことがある内容を試してみることにしました。

それは・・・・・・

ヌードや暴力などの不適切な画像の自動検出

です。

その昔、AIや機械学習がそれほど発展していなかったので、不適切な画像を検出するのは至難の業でしたが、現在はAWSを使うことで高い精度で検出ができるようになっています。

そこで❗

今回はLaravelを使って「アップロードされる前にアダルト画像を拒否する」という機能を実装していきたいと思います。

ぜひ、サイトコンテンツの質を向上させるためにお役に立てましたら嬉しいです😊✨

Amazon Rekognitionって、
わざと k をつかってるんですね!」

開発環境: Laravel 8.x、Amazon Rekognition

AWSの準備をする

アクセス情報(IAM)を取得する

では、まずLaravelからAWSへ接続するためのアクセス情報を取得していきます。

画面上部にある検索ボックスに「iam」と入力すると「IAM」サービスが表示されるのでこれをクリックします。

次にページ左側メニューに表示されている「ユーザー」をクリック

ページ移動後、「ユーザーを追加」ボタンをクリック。

詳細を入力する画面になるので、まずユーザー名をお好みで入力します。

また、今回はLaravelからのアクセスですので、「プログラムによるアクセス」にチェックを入れます。

そして、以下のボタンをクリック。

するとグループ作成コンテンツが表示されるので「グループの作成」をクリック。

今回はAmazon Rekognitionを使うので、グループ名の検索ボックスに「rekognition」と入力して検索。

すると、「Access to all Amazon Rekognition」という項目が表示されるのでこれにチェックをいれます。

そして、以下のボタンをクリック。

最後に「ユーザーの作成」ボタンをクリックしたら完了です。

表示が切り替わり、新規ユーザーが表示されるので、ここの「アクセスキー」と「シークレットアクセスキー」を取得しておきましょう。

2つのキーを取得したら、.envに登録しておきます。

.env

AWS_ACCESS_KEY_ID="(ここにアクセスキー ID)"
AWS_SECRET_ACCESS_KEY="(ここにシークレットアクセスキー)"
AWS_DEFAULT_REGION=ap-northeast-1

パッケージをインストールする

まずAWSにアクセスするためのパッケージをインストールします。

composer require aws/aws-sdk-php

また、今回は画像の操作もするのでinterventionもインストールしておきましょう。

composer require intervention/image

独自バリデーションをつくる

では、先に今回メインになる「不適切な画像を拒否する」独自バリデーションをつくりましょう。

以下のコマンドを実行してください。

php artisan make:rule SafeImage

すると、ファイルが作成されるので中身を以下のように変更します。

app/Rules/SafeImage.php

<?php

namespace App\Rules;

use Aws\Rekognition\RekognitionClient;
use Illuminate\Contracts\Validation\Rule;

class SafeImage implements Rule
{
    private $client, $unsafe_labels, $confidence;

    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct($unsafe_labels = ['Explicit Nudity'], $confidence = 50)
    {
        $this->client = new RekognitionClient([
            'version' => 'latest',
            'region' => 'ap-northeast-1'
        ]);
        $this->unsafe_labels = $unsafe_labels; // この数値以上なら不適切として処理
        $this->confidence = $confidence;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $file)
    {
        $image_path = $file->getPathName();

        // 画像サイズが大きいと送信データが大きくなってしまうので、最大横幅 500px へリサイズ
        $image = \Image::make($image_path);
        $image->resize(500, null, function ($constraint) {

            $constraint->aspectRatio();
            $constraint->upsize();

        });
        $bytes = $image->encode('png');

        $result = $this->client
            ->detectModerationLabels([
                'Image' => ['Bytes' => $bytes],
                'MinConfidence' => $this->confidence
            ]);
        $moderation_labels = $result['ModerationLabels'];

        foreach ($moderation_labels as $moderation_label) {

            if($this->hasUnsafeLabel($moderation_label)) {

                return false;

            }

        }

        return true;

    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'この画像はAIにより不適切な画像と判断されました。';
    }

    private function hasUnsafeLabel($moderation_label) {

        $moderation_label_name = $moderation_label['Name'];
        $moderation_label_parent_name = $moderation_label['ParentName'];

        return (
            in_array($moderation_label_name, $this->unsafe_labels) ||
            in_array($moderation_label_parent_name, $this->unsafe_labels)
        );

    }
}

この中でやっているのは以下3つです。

  1. 送信されてきた画像のサイズを変更
  2. Amazon Rekognition にその画像を送信
  3. 診断結果が返ってくるので、その内容によって{許可/拒否}を判別する

なお、使い方としては次のようになります。

$unsafe_labels = [ // 拒否する「ラベル」を指定
    'Explicit Nudity' // 明らかなヌードの場合
];
$confidence = 50; // この数値以上の「確実性」なら不適切として処理 min: 0, max: 100

$request->validate([
    'image' => [

        // 省略

        new SafeImage($unsafe_labels, $confidence)
    ]
]);

なお、拒否できるラベルは以下のようになっています。(今回の実装では、Top-Level CategorySecond-Level Categoryどちらのラベルも使えるようにしています)

📝 参考URL: Using the image and video moderation APIs

コントローラーをつくる

では、ここからはテスト用に画像の送信フォームをつくっていきます。

以下のコマンドを実行してください。

php artisan make:controller ImageUploadController

コントローラーが作成されるので、中身を以下のように変更します。

app/Http/Controllers/ImageUploadController.php

<?php

namespace App\Http\Controllers;

use App\Rules\SafeImage;
use Illuminate\Http\Request;

class ImageUploadController extends Controller
{
    public function create() {

        return view('image_upload.create');

    }

    public function store(Request $request) {

        $unsafe_labels = [ // 拒否する「ラベル」を指定
            'Explicit Nudity' // 明らかなヌードの場合
        ];
        $confidence = 50; // この数値以上の「確実性」なら不適切として処理 min: 0, max: 100

        $request->validate([
            'image' => [
                'required',
                'image',
                'max:5000', // 最大 5MB まで
                'mimetypes:image/jpeg,image/png',
                new SafeImage($unsafe_labels, $confidence)
            ]
        ]);

    }
}

ビューをつくる

続いて先ほどのコントローラーで指定したビューをつくります。

resources/views/image_upload/create.blade.php

<html>
<head>
    <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
    <div class="p-4">
        <h1 class="text-xl mb-5">不適切な画像を検出するバリデーション実装</h1>
        <form method="post" action="{{ route('image_upload.store') }}" enctype="multipart/form-data">
            @csrf
            <div class="pb-3">
                <input type="file" name="image" accept="image/jpeg,image/png">
            </div>
            @error('image')
            <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-3" role="alert">
                {{ $message }}
            </div>
            @enderror
            <button type="submit" class="bg-blue-500 text-blue-50 p-2 rounded">送信する</button>
        </form>
    </div>
</body>
</html>

ルートをつくる

最後にルートです。
今回は省コード化しやすいresource()を使って実装します。

routes/web.php

use App\Http\Controllers\ImageUploadController;

// 省略

Route::resource('image_upload', ImageUploadController::class)->only([
    'create',
    'store'
]);

これで準備は全て完了です!

テストしてみる

では、実際に不適切な画像(今回の場合はヌード画像)をインターネット上から探してきて送信してみましょう❗

まず、「http://******/image_upload/create」へアクセスします。

そして、先ほどダウンロードしてきた「不適切な画像」を選択し、送信ボタンをクリックします。

すると・・・・・・

はい❗
きちんと不適切な画像として処理されました。

成功です😊👍✨

ちなみに、実は今回どうなるか試してみたい画像がありました。
それが、「砂漠の画像」です。

というのも、どこかの記事で「砂漠の画像をAIがヌードとして処理してしまった・・・」というのを見たことがあったからです。(確かに風紋が裸に見えなくもないですね)

ということで、pixabayから以下3つの砂漠の画像をダウンロードして実際に試してみることにしました。



結果はというと・・・・・・

なんと全て「適切な画像」として処理されました❗
つまり、AIはきちんとヌードではないと判断してくれたということになります。

もしかすると、私が読んだ記事から時間が経ち、より正確になったのかもしれません。

とにかく、こちらも成功でした😊✨

企業様へのご提案

今回の技術を使うと以下のような画像を検出しアップロードを事前に拒否することができます。ぜひお気軽にご相談ください。

  • ヌードなどのアダルト画像
  • 飲酒、薬物、ドラッグ、暴力、ギャンブル
  • 中指を立てるジェスチャー
  • ハーケンクロイツなどのマーク
  • 遺体など

※ なお、「裸はアウトだけど、水着や下着ならOK」というような条件にすることも可能です。

おわりに

ということで、今回はAmazon Rekognitionを使って不適切な画像をAIで検出してみました。

そこまで多くの画像で試してはいませんが、さすがAmazonです。
私が試した画像には、ひとつもおかしな判断はありませんでした。

また、拒否したい内容を選べるというのも汎用性が高くていいですよね。
ぜひ皆さんもAmazon Rekognitionを試してみてくださいね。

ではでは〜❗

「AWSには面白い機能が
いっぱいあります👍」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly