画像の綺麗さを判定するNIMAスコアを試してみた

このエントリーは [一休.comのカレンダー | Advent Calendar 2023 - Qiita](https://qiita.com/advent-calendar/2023/ikyu) の23日目の記事です。

# はじめに
[一休.com](https://www.ikyu.com/)では、施設を検索したリスト画面で画像を大きく見せるつくりをしています。施設が登録した画像を表示するのですが、その選定には任意性があります。
中には画質が悪いせいで、実際の施設は良いのに予約してくれないといったこともあるかもしれません。
というわけで、施設画像の画質が良いことはある程度大切になってきますが、定量的に計算するのは難しいです。
今回はその計算が行える、googleが発表した[ResNetベースの画質評価モデル](https://shiropen.com/2017/12/19/30631/)を利用しました。

# コード

主要ライブラリのバージョン
- tensorflow,keras: 2.10.0
- numpy: 1.24.4

[neural-image-assessment](https://github.com/titu1994/neural-image-assessment)を参考に、バッチ処理できるようにしました。
weights/inception_resnet_weights.h5とutilsの内容はgithub上でダウンロードできます。

pythonのインポート、モデルの定義は以下で行います。
```python
import numpy as np
import pandas as pd
import os
from PIL import Image
import requests
from io import BytesIO

os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

from keras.models import Model
from keras.layers import Dense, Dropout
from keras.applications.inception_resnet_v2 import InceptionResNetV2
from keras.applications.inception_resnet_v2 import preprocess_input
from tensorflow.keras.preprocessing.image import load_img
from tensorflow.keras.utils import img_to_array
import tensorflow as tf
from utils.score_utils import mean_score, std_score

base_model = InceptionResNetV2(input_shape=(None, None, 3), include_top=False, pooling='avg', weights=None)
x = Dropout(0.75)(base_model.output)
x = Dense(10, activation='softmax')(x)

model = Model(base_model.input, x)
model.load_weights('weights/inception_resnet_weights.h5')
```

```python
def download_image(url):
    try:
        response = requests.get(url, timeout=2)
    except Exception as e:
        print(e)
        return None

    # レスポンスが正常かどうかをチェック
    if response.status_code == 200:
        image_bytes = BytesIO(response.content)
        return image_bytes
    else:
        print(f"Error: Unable to download image. HTTP status code {response.status_code}")
        return None
        
def get_nima_score(model, image_bytes):
    target_size = (224, 224)

    with tf.device('/CPU:0'):
        xs = []
        for image_byte in image_bytes:
            img = load_img(image_byte, target_size=target_size)
            x = img_to_array(img)
            xs.append(x)
            
        x = np.stack(xs, axis=0)
        x = preprocess_input(x)
        scores = model.predict(x, batch_size=len(image_bytes), verbose=0)
        
        results = []
        for score in scores:
            mean = mean_score(score)
            std = std_score(score)
            results.append([mean, std])
            
    return results
```

下のように、複数画像URLに対して計算を実行できます。
```python
for i, image_url in enumerate(image_urls):
    image_byte = download_image(image_url)
    if image_byte is not None:
        image_bytes.append(image_byte)
        target_image_urls.append(image_url)
result = get_nima_score(image_bytes)
```


# スコア
用意した画像だとこんな感じのスコアになりました。
全部Stable diffusionの[Juggernaut XL](https://civitai.com/models/133005/juggernaut-xl)を使って生成しています。施設ページにありそうな画像をイメージしています。
NIMAは1(最低)~10(最高)でスコアリングされます。大体綺麗なのでそこまで差は出ていません。
壮大な自然系の画像だとスコアが高くなり、背景がボケてる画像だと低めに出る傾向にあります。

|スコア: 6.52  |スコア: 5.87  |スコア: 5.23  |
|---|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/0d2efa3a-e757-5767-6edf-7f7fec0d2e21.png)  |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/75d48c5a-54c8-e7b8-df02-16dd2b54b50f.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/b43285a8-ce14-56f5-8e29-20a0b9700a33.png)

|スコア: 6.13  |スコア: 5.54  |スコア: 6.08  |
|---|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/656e6491-949d-34e6-83e8-c0724390fa7f.png)  |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/0baff3b7-97e8-bcc2-d19a-f0c016fbeb63.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/bb5912a9-bc9e-90dc-6966-67ace042304a.png)


画質を悪くしてみます。拡大してみると分かりやすいです。
スコア4台辺りから厳しくなってきますね。

|スコア: 5.16  |スコア: 4.65  |スコア: 4.43  |
|---|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/fc3c336d-4672-7535-4a7c-7ecc9f01726f.png) |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/be2fe26a-493e-0909-7185-a2e025b6f530.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154105/610d91bb-d093-171e-19da-544ec59bb275.png)

[一休.com](https://www.ikyu.com/)の施設画像70万枚について実際に計算してみたところ、良い画像でも意外とスコアが上がらないケースが散見されましたが、スコア4.5以下は概ね悪いことが分かりました。
検索時に提示する画像を選定する一つの基準になりそうです。