AppleのAIMv2でマルチモーダル機能を活用編1「画像領域特徴量の抽出とテキストで画像領域の可視化」

## はじめに  

こんにちは、しゅんです！前回の記事では、AIMv2を使って類似画像検索を実現しました。今回は、その次のステップとして**マルチモーダル機能**に挑戦します。APPLEさん先程他の方のISSUESにすぐ対応して、おかけてプロジェクトを進むこともできました。AIMv2は画像とテキストの関係性を理解するのに強力なモデルですので、これを活用して画像領域ごとの関連性スコアを可視化してみました。  
この記事では、画像中の領域をスライディングウィンドウで区切り、指定したテキスト（例: "cat" や "dog"）に対してスコアを計算・可視化する方法を解説します。 

[このプロジェクトのGithubリンク](https://github.com/syun88/aimv2-project-for-qiita-article?tab=readme-ov-file#qitta-day2)

## AIMv2とマルチモーダルの可能性  

AIMv2の特徴は、単なる画像認識だけでなく、テキストと画像の関連性を深く理解する能力にあります。  
これにより以下のような応用が可能になります：  

- **特定の物体を画像内から探索する**  
- **特定テキストに関連する領域を特定する**  
- **画像キャプション生成や内容要約**  
---

### まず公式のサンプルコード
```python
import requests
from PIL import Image
from transformers import AutoProcessor, AutoModel

url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
text = ["Picture of a dog.", "Picture of a cat.", "Picture of a horse."]

processor = AutoProcessor.from_pretrained(
    "apple/aimv2-large-patch14-224-lit",
)
model = AutoModel.from_pretrained(
    "apple/aimv2-large-patch14-224-lit",
    trust_remote_code=True,
)

inputs = processor(
    images=image,
    text=text,
    add_special_tokens=True,
    truncation=True,
    padding=True,
    return_tensors="pt",
)
outputs = model(**inputs)
probs = outputs.logits_per_image.softmax(dim=-1)
print(outputs)
```
---

今回は、モデルを公式のサンプルコードと同じようにしました`aimv2-large-patch14-224-lit` 画像領域とテキストの関連性を測る機能を、前回のCOCOデータセットを活用して試してみます。

## システム設計  

本システムでは以下の手順：  
1. **スライディングウィンドウで領域ごとの特徴量を抽出**  
2. **テキストとの関連性スコアを計算**  
3. **画像上でスコアの高い領域を可視化** 

## コード解説  

以下は、スライディングウィンドウを用いて領域ごとの特徴量を取得し、テキストとの関連性を計算して画像に重ねるコードです。

### 実装コード
```python
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
from transformers import AutoProcessor, AutoModel
import torch
from tqdm import tqdm

# モデルとプロセッサのロード
processor = AutoProcessor.from_pretrained("apple/aimv2-large-patch14-224-lit")
model = AutoModel.from_pretrained("apple/aimv2-large-patch14-224-lit", trust_remote_code=True)

# 入力画像とテキスト
image = Image.open("/media/syun/ssd02/python_learning/apple/qiita_project_AIMv2/coco_image/val2017/000000010363.jpg")
text = ["cat", "dog", "bird"]

# スライディングウィンドウの設定
window_size = 100
stride = 50

# 類似度の結果を格納するリスト
high_score_regions = []
threshold = 0.85  # 類似度の閾値

# 画像サイズを取得
width, height = image.size

# 領域ごとの類似度計算
for y in tqdm(range(0, height - window_size + 1, stride)):
    for x in range(0, width - window_size + 1, stride):
        # 領域を切り出し
        region = image.crop((x, y, x + window_size, y + window_size))
        inputs = processor(images=region, text=text, return_tensors="pt", padding=True)
        
        # モデルで推論
        outputs = model(**inputs)
        probs = outputs.logits_per_image.softmax(dim=-1)

        # 閾値を超える領域を記録
        for i, score in enumerate(probs[0]):
            if score > threshold:
                high_score_regions.append((x, y, window_size, window_size, text[i], score.item()))

# 画像に枠を描画
draw_image = image.copy()
draw = ImageDraw.Draw(draw_image)

for region in high_score_regions:
    x, y, w, h, label, score = region
    draw.rectangle([(x, y), (x + w, y + h)], outline="red", width=1)  # 赤い枠を描画
    draw.text((x, y - 10), f"{label}: {score:.2f}", fill="red")  # ラベルとスコアを描画

# 結果を表示
plt.figure(figsize=(10, 10))
plt.imshow(draw_image)
plt.axis("off")
plt.show()

```


## 実行結果
- 指定したテキスト（例: "cat", "dog", "bird"）に関連する領域が赤枠で表示されます。  
- 関連性のスコアも枠の近くに表示され、画像中のどの領域がどのテキストに関連するのか一目でわかります。
 
## コード解説  
### 領域ごとのスコア計算  
`スライディングウィンドウ`を用いることで、画像を小さな領域に分割し、それぞれの領域の特徴量を計算します。その後、テキストとの関連性をモデルを通じて計算します。
### 可視化  
赤枠でスコアの高い領域を囲み、関連性スコアとテキストを表示します。
### このような感じの出力画像
想定以上に素晴らしいです。

![output_image_text.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506235/5d9addec-5504-2675-f6dc-12c5b6b5a939.jpeg)

![output_image_text-2.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506235/5e29915b-b89e-1120-d6aa-f665eebfaaf5.jpeg)


### 動画

<iframe width="560" height="315" src="https://www.youtube.com/embed/YZm9AMRe5x4?si=cMnK1lskcjUhIZ1w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## 応用例  

- **特定物体の領域特定**  
  例えば、画像内の猫や犬の領域を探索し、関連する領域だけを抽出可能。  
- **画像の重要領域ハイライト**  
  画像内の重要な部分（例: テキストに関連する領域）を抽出してハイライト表示。  
- **マルチモーダル検索の拡張**  
  前回の記事の類似画像検索と組み合わせて、マルチモーダル検索システムの実現。


## まとめ  

この記事では、AppleのAIMv2を使ってマルチモーダルな画像解析を行い、画像領域とテキスト関連性を可視化する方法を紹介しました。この手法は物体検出や画像キャプション生成にも応用可能です。ロボットでの応用を考え中  

### 次回予告
次回は、さらにAIMv2のGUIの作成です
今回も最後まで見てくれて、ありがとうございます。ぜひお試しください！布教してください。 
youtube 登録よろしくお願いします。

---
---

## おまけ
### **スライディングウィンドウとは？**

スライディングウィンドウ（Sliding Window）とは、**データの一部を一定サイズの窓（ウィンドウ）として切り出し、その窓をデータ全体にわたって動かして処理を行う方法**です。この方法は、画像解析やテキスト処理、時系列データ分析など、さまざまな分野で使用されます。

### **具体例：画像解析でのスライディングウィンドウ**

画像解析でのスライディングウィンドウでは、以下のように動作します：

1. **画像を一定サイズの矩形（ウィンドウ）で区切る**  
   - 例えば、100×100ピクセルのウィンドウを設定する。

2. **ウィンドウを画像の左上から順に移動させる**  
   - 水平方向と垂直方向に一定のステップ（**ストライド**）で動かします。

3. **各ウィンドウ領域に対して処理を行う**  
   - 例えば、物体検出や特徴量の抽出、テキストとの類似度計算などを実行。

4. **結果を記録**  
   - 各ウィンドウ領域で得られた結果を保持します。

### **利点**

- **領域ごとの詳細な解析が可能**: 大きな画像全体を一度に処理するのではなく、小さな領域ごとに解析することで、ローカルな特徴を捉えやすくなる。
- **カスタマイズ性**: ウィンドウサイズやストライドを調整することで、解析の粒度や効率を変更できる。
- **画像の任意の部分をターゲットにできる**: 領域の位置やスコアに基づいて特定部分だけに集中可能。

### **スライディングウィンドウの視覚的説明**

1. **画像全体**
   ```
   +-------------------+
   |                   |
   |                   |
   |                   |
   +-------------------+
   ```

2. **最初のウィンドウ**
   ```
   +-------------------+
   | +------+          |
   | |      |          |
   | +------+          |
   +-------------------+
   ```

3. **次のウィンドウ（右に移動）**
   ```
   +-------------------+
   |          +------+ |
   |          |      | |
   |          +------+ |
   +-------------------+
   ```

### **スライディングウィンドウの設定例**

以下のように設定できます：

- **ウィンドウサイズ（window_size）**: ウィンドウの幅と高さ。例えば、100×100ピクセル。
- **ストライド（stride）**: ウィンドウをどの程度ずつ動かすか。例えば、50ピクセルずつ移動。


### **Pythonでの実装例**

```python
from PIL import Image

# 画像の読み込み
image = Image.open("example.jpg")
width, height = image.size

# スライディングウィンドウの設定
window_size = 100
stride = 50

# ウィンドウをスライドして画像を分割
for y in range(0, height - window_size + 1, stride):
    for x in range(0, width - window_size + 1, stride):
        region = image.crop((x, y, x + window_size, y + window_size))
        region.show()  # 切り出された領域を表示
```


### **どんな場面で使われる？**

1. **物体検出**  
   - 画像全体から特定の物体を検出する際、ウィンドウ単位でスコアリング。

2. **特徴量の抽出**  
   - 各ウィンドウ領域の特徴量を計算し、データセットとして利用。

3. **異常検知**  
   - 時系列データや画像において、ウィンドウ内の値を解析して異常を検出。


### **注意点**

- **計算コストが高い**: ウィンドウサイズやストライドの設定によっては、処理回数が多くなり計算負荷が増大。
- **粒度の調整が必要**: ウィンドウサイズが小さすぎると詳細すぎる結果になり、大きすぎると全体がぼやけてしまう。

スライディングウィンドウは、特に画像解析や特徴量抽出で多用される基本的な手法であり、細かい領域分析に非常に有効です。