「この人誰だっけ」をAIで解決する試み


### はじめに

2024年5月31日（Fri）20:00 ～ 2024年6月7日（Fri）22:00
#生成AI オンラインハッカソン4 by #protopedia
https://protopedia.net/event/102

このイベントで作った作品の解説をします。
ネタ作品ですが、Embeddingとベクター検索は生成AI関連でもよく使う技術となりますので、何かの役にたつと幸いです。

### 概要

- 以前どこかで会って名刺交換したのだが、名前を思い出せないという時に使用
- Raspberry Pi4 カメラで撮影した画像をベクター検索して、可能性の高い人を教えてくれるシステム

### 仕様

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710148/2e3ec730-e759-b091-6314-af3158e4d3bb.png)


### 使い方


1. パーティなどで名刺交換したあと、写真を撮影。

2. 家に帰ったら忘れないうちにEmbeddingし、MacのローカルベクターDBに登録しておく。
3. 人と会う機会にこのデバイスを持っていき、うっかり名前を思い出せない時に、「ちょっと失礼します！」と勢いよく鞄から人形を取り出し、目をあわせてもらう

4. MacからRaspberry Piに有線などで接続
5. Raspberry Pi カメラで撮影
6. ベクター化 → 類似検索
7. 可能性のある人物が提示される



### コード

https://github.com/sitopp/whoisthis/blob/main/whoisthis.ipynb

以下は ```「2. 家に帰ったら忘れないうちにEmbeddingし、MacのローカルのベクターDBに登録しておく」```の部分のコードとなります。
tensorflowのライブラリに含まれるResNet50を使っています。

```
# 画像からembeddingを抽出するモデルの設定
model = ResNet50(weights='imagenet', include_top=False, pooling='avg')

def get_image_embedding(img_path):
    """
    指定されたパスの画像からembeddingを取得します。
    """
    # 画像の読み込みと前処理
    img = image.load_img(img_path, target_size=(224, 224))
    img_array = image.img_to_array(img)
    img_array = np.expand_dims(img_array, axis=0)
    img_array = preprocess_input(img_array)

    # 画像からembeddingを取得
    embedding = model.predict(img_array)
    return embedding

def load_images_and_embeddings(directory):
    """
    指定されたディレクトリ内の全ての画像を読み込み、それぞれのembeddingを取得します。
    """
    image_paths = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith(('.jpg', '.jpeg', '.png'))]
    embeddings = []
    for img_path in image_paths:
        embedding = get_image_embedding(img_path)
        embeddings.append(embedding)
    return image_paths, np.vstack(embeddings)

def create_faiss_index(embeddings):
    """
    embeddingをFAISSインデックスに追加します。
    """
    dimension = embeddings.shape[1]
    index = faiss.IndexFlatL2(dimension)
    index.add(embeddings)
    return index
```

Raspberry Piのカメラを叩いて撮影し、ローカル上の```/content/target-image/target.jpeg```に保存するコードは割愛します。

次は、「6. ベクター化 → 類似検索」の部分のコードです。

```
  # クエリ画像のパス
    query_image_path = '/content/target-image/target.jpeg'

    # 類似画像を検索
    similar_images = search_similar_images(query_image_path, image_paths, index, top_k=5)

    # 結果を表示
    print("Query image:", query_image_path)
    print("Similar images:")
    for img_path in similar_images:
        print(img_path)
```

### 結果

自分の顔と芸能人3人の方の写真を、1人3枚づつ集めてこのシステムで判定したところ、残念ながら正確には判定できませんでした。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710148/1fdcadc2-d1d2-7248-2a7e-165c8116eff4.png)


1位 ロバート秋山さん 
2位 自分 
3位 ファーストサマーウイカさん 
4位 ロバート秋山さん 
5位 自分 

選外
ビートたけしさん
うる星やつらの「ラムちゃん」さん

（著作権の都合上、顔写真を出す事は控えます）

### 結果その2

結果が納得いかないので、他のモデルを試そうと思い、ResNet50 -> VGG16に変えて試したところ、信頼性が落ちてしまいました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710148/61b9a00e-d1e8-9026-89b2-0766f0ad1e0b.png)

1位 ファーストサマーウイカさん 
2位 自分 
3位 ロバート秋山さん 
4位 自分 
5位 うる星やつらの「ラムちゃん」さん

選外
ビートたけしさん

### Tips

VGG16はResNet50より古く、ベクターの次元数も少ないので、多少重かったとしてもResNet50を使った方が良さそうです。


|名前|特徴|ベクトルの次元数|
|---|---|---|
|VGG16|深さが 16 層の畳み込みニューラル ネットワーク|512次元|
|ResNet50|深さが 50 層の畳み込みニューラル ネットワーク|2048次元|

### 振り返り

様々なハードルがあり、実用には適しておりませんでした。
とくに「勢いよく人形を取り出し、目をあわせてもらう」運用は無理でした。残念。
ですが将来的に「Ray-Ban Meta」のような民間のwearable デバイスがネット越しにAIと繋がり、このような機能を持つ事は容易に想像できます。
その時を楽しみに待ちたいと思います。


以上となります。