Raspberry Pi 5 + SikuliX / ChatGPT Vision を活用したPC自動操作

---

```mermaid
flowchart LR
    subgraph 自動操縦の対象のPC
    A[PC<br>表示画面] -->|HDMI出力|B[HDMI<br>キャプチャ]
    end

    subgraph Raspberry Pi 5
    B[HDMI<br>キャプチャ] --> C[画面<br>キャプチャ<br>取得]
    C --> D[画像解析: <br>SikuliX<br>ChatGPT]
    D --> E[gRPC<br>クライアント<br>座標<br>クリック指示]
    end

    subgraph Raspberry Pi Zero 2 W
    E --> F[gRPCサーバー<br>マウス<br>キーボード<br>エミュレート]
    F --> G[USB HID<br>デバイスとして<br>PCを操作]
    end

    G --> |USB|自動操縦の対象のPC
```

上図のように、**Raspberry Pi 5** で HDMI キャプチャと画面解析を行い、**Raspberry Pi Zero 2 W** が USB HID デバイスをエミュレートして実際の **PC** を操作する構成です。

---

## **はじめに**

以前の記事で、Raspberry Pi 5 で動画をリアルタイムでOneDriveにアップロードする方法を紹介しました。
また、Raspberry Pi Zero 2 W でキーボードとマウスをエミュレートする方法も紹介しました。
本記事では、それらのハードウエア構成を応用し、**SikuliX** や **ChatGPT Vision** を使って画面上の様々な要素に対応する方法を解説します。  
また、**キャリブレーション**（HDMIキャプチャの座標とPC画面の座標を一致させる工程）や、**ChatGPT Visionに対してプロンプト（指示文）を与える**重要性などについても触れています。

---

## **この記事のゴール**

1. **SikuliXを使った「画像検索クリック」自動化の基本**  
2. **ChatGPT Visionを使い、画面のテキストやエラー情報を解析して動的な操作フローを実現**  
3. **HDMIキャプチャ画像を逐次（ループ的に）解析し、画面変化に応じてマウスやキーボード入力を行う方法**  
4. **マウス座標を正しく合わせるためのキャリブレーションの必要性と実装のポイント**  
5. **ChatGPT Visionに画像だけでなく、明確な“プロンプト”を与える意義**  
6. **前処理としてOpenCVを使うメリット**

---

## **ハードウェア構成の再掲**

- **Raspberry Pi 5**  
  - HDMIキャプチャデバイスを用いてPC画面を取得  
  - （本記事の主役）SikuliX / ChatGPT Visionで画像解析や指示生成  
  - gRPCクライアントとして動作し、操作指示をRaspberry Pi Zero 2 Wに送る

- **Raspberry Pi Zero 2 W**  
  - gRPCサーバー（別記事で解説）  
  - HID（キーボード・マウス）エミュレートしてPCを操作  

- **PC**  
  - Zero 2 Wが仮想キーボード/マウスとして接続され、実際に操作される対象

---

## **1. なぜループ的な画像解析が必要なのか？**

1. **画面状態の変化に追従**  
   - ログイン画面 → デスクトップ → エラー画面…など、PC画面が切り替わるたびに必要な操作が変わります。  
   - 毎回画面をキャプチャ＆解析して、適切な操作を続けるためには**ループ処理**で画面の変化を監視するアプローチが必須です。

2. **タイミングを管理**  
   - UIがまだ読み込み途中なのにクリックしても失敗します。  
   - 「特定のボタンが表示されたらクリック」というフローを作るには、繰り返し画面をチェックしながら進めるのが定石です。

3. **イレギュラー対応**  
   - 想定外のダイアログやエラー画面を検出して対処するためにも、**定期的な画像解析**が効果的です。

---

## **2. SikuliXを使った簡易自動化**

### **2-1. SikuliXの導入 (Raspberry Pi 5側)**

- **Java環境をインストール**  
  ```bash
  sudo apt-get update
  sudo apt-get install default-jre default-jdk
  java -version
  ```
- **SikuliX本体の導入**  
  - 公式サイトから `sikulixide-2.x.x.jar` をダウンロードして実行できるようにする。  
  - `java -jar sikulixide-2.x.x.jar` でSikuliX IDEが起動すればOK。

### **2-2. HDMIキャプチャとの連携**

- HDMIキャプチャデバイスを使い、Raspberry Pi 5上でPC画面を**連続的にキャプチャ**。  
- 通常はSikuliXがデスクトップ画面を検索しますが、Piでキャプチャした映像をウィンドウ表示し、そのウィンドウをSikuliXが認識するなどの工夫が必要です。

### **2-3. ループ的な画像検索クリックの例**

SikuliXのPythonモードで書く場合:

```python
# sikuli_main.py
from sikuli import *
import time

MAX_RETRY = 10
retry_count = 0

while retry_count < MAX_RETRY:
    try:
        # "login_button.png" を探してクリック
        button_region = find("login_button.png")
        click(button_region)
        print("Login button found and clicked!")
        break
    except FindFailed:
        print("Login button not found yet, retrying...")
        time.sleep(1)
        retry_count += 1

if retry_count == MAX_RETRY:
    print("Failed to find login button after maximum retries.")
```

- `find()` が失敗すると `FindFailed` が飛ぶので、例外ハンドリングしながらリトライできます。

### **2-4. gRPCでRaspberry Pi Zero 2 Wと連携**

- ここでの想定は「**Raspberry Pi Zero 2 W が実際のマウス操作を行う**」ため、SikuliXからは座標情報だけを取得して gRPC でZero 2 W に伝える流れ。  
- 例:  
  ```python
  from sikuli import *
  import grpc
  import hid_pb2
  import hid_pb2_grpc
  import time

  channel = grpc.insecure_channel("raspberrypi-zero2w.local:50051")
  stub = hid_pb2_grpc.HIDServiceStub(channel)

  try:
      m = find("login_button.png")
      center = m.getTarget()  # 画像中心座標
      x, y = center.x, center.y

      # gRPCで移動
      cmd = hid_pb2.ControlCommand(action="move_mouse", x=x, y=y)
      stub.SendCommand(cmd)
      time.sleep(0.2)

      # クリック
      cmd = hid_pb2.ControlCommand(action="click", x=x, y=y)
      stub.SendCommand(cmd)

      print("Login button found & clicked via Zero 2 W!")
  except FindFailed:
      print("Button not found.")
  ```

---

## **3. ChatGPT Visionを使った高度な解析**

### **3-1. ChatGPT Visionの強み**

- 画面上の文字列や構造を**自然言語で解析**し、エラーメッセージや多言語UIへの柔軟な対応が可能。  
- たとえば「ログイン画面かどうか？」を文字解析で判断したり、「エラーが出たらキャンセルボタンを押す」といった**柔軟なロジック**を自然言語ベースで記述することが期待できます。

### **3-2. 画面をChatGPT Visionに送るフロー**

1. **Raspberry Pi 5**でHDMIキャプチャ → 画像ファイル（`screen.png` など）  
2. **ChatGPT VisionのAPI**にアップロードして解析  
3. **自然言語の応答**（例: *"This is a Windows login screen..."* など）を得る  
4. 応答から「ログインボタンが右下にある」「今はエラー画面」といった情報を抽出  
5. 抽出した情報をgRPC経由でZero 2 Wに伝え、マウスやキーボード操作を行う

### **3-3. ループ＋ChatGPT Visionの例**

```python
import requests
import grpc
import hid_pb2
import hid_pb2_grpc
import time

API_ENDPOINT = "https://api.openai.com/v1/images/analyze"
API_KEY = "YOUR_API_KEY"

def analyze_screen(image_path):
    headers = {"Authorization": f"Bearer {API_KEY}"}
    files = {"file": open(image_path, "rb")}
    response = requests.post(API_ENDPOINT, headers=headers, files=files)
    return response.json()

def main():
    channel = grpc.insecure_channel("raspberrypi-zero2w.local:50051")
    stub = hid_pb2_grpc.HIDServiceStub(channel)
    
    MAX_RETRY = 10
    retry_count = 0

    while retry_count < MAX_RETRY:
        # 1) HDMIキャプチャ → "screen.png"
        # 2) ChatGPT Visionで解析
        result = analyze_screen("screen.png")
        print("ChatGPT Vision result:", result)

        # 例: resultからボタン座標を取り出す
        if "button_position" in result:
            x = result["button_position"]["x"]
            y = result["button_position"]["y"]

            cmd_move = hid_pb2.ControlCommand(action="move_mouse", x=x, y=y)
            stub.SendCommand(cmd_move)
            time.sleep(0.2)

            cmd_click = hid_pb2.ControlCommand(action="click", x=x, y=y)
            stub.SendCommand(cmd_click)

            print("Clicked the login button via ChatGPT Vision data!")
            break
        else:
            print("Button not found, retrying...")
            time.sleep(1)
            retry_count += 1

    if retry_count == MAX_RETRY:
        print("Failed to find button after max retries.")

if __name__ == "__main__":
    main()
```

---

### **3-4. ChatGPT Visionに“プロンプト”が必要**

ChatGPT Visionのような**大規模言語モデル**を利用する際には、**画像を送るだけ**では不十分な場合が多く、**「どう解析してほしいのか」を示すテキストプロンプト**が必要です。

1. **指示文を与える理由**  
   - 画像だけ送っても、「ボタンの位置が知りたいのか？ エラー文を解析したいのか？」などがモデルに伝わりません。  
   - 例: 「この画像がログイン画面なら、ユーザー名フィールド・パスワードフィールド・ログインボタンの座標をJSON形式で返してください」など、目的を明示する必要があります。

2. **プロンプト付きのリクエスト例**  
   ```python
   import requests

   API_ENDPOINT = "https://api.openai.com/v1/images/analyze"
   API_KEY = "YOUR_API_KEY"

   def analyze_with_prompt(image_path, prompt_text):
       headers = { "Authorization": f"Bearer {API_KEY}" }
       files = { "file": open(image_path, "rb") }
       data = { "prompt": prompt_text }  # プロンプトを一緒に送る

       response = requests.post(API_ENDPOINT, headers=headers, files=files, data=data)
       return response.json()

   # 使い方
   prompt = (
       "Please analyze the attached screenshot. If it is a login screen, "
       "give me the coordinates of the username field, the password field, "
       "and the login button in JSON format. If any error message is visible, "
       "provide a short summary of that error."
   )
   result = analyze_with_prompt("screen.png", prompt)
   print(result)
   ```
   - `data["prompt"]` に「何をしてほしいか」を自然言語で書く。  
   - たとえば「ログイン画面ならボタン座標を返して」「エラーが出ていればエラー内容をまとめる」など。

3. **実際の運用上の注意**  
   - クラウドAPIだと応答時間やコストがかかるので、**毎フレーム送るのは非現実的**。  
   - 秘密情報が画面に映っている場合はプライバシー面で要注意。  
   - モデルの回答が100%正しい保証はないので、**SikuliXとの併用や再試行**を組み込むのがおすすめ。

---

## **4. マウス座標のキャリブレーションが必要な理由**

### **4-1. 座標がずれる背景**

1. **HDMIキャプチャ解像度 vs. 実際のPC画面解像度**  
   - PC側は 1920×1080、キャプチャは 1280×720 等に縮小されることがある。  
   - 「キャプチャ上の (100,100)」と「実際の画面の (100,100)」が一致しないリスク。

2. **HIDモード（相対／絶対）**  
   - OSのマウス加速などにより、同じdx, dyでも実際の移動量が変わるケース。  
   - 絶対座標モードでもディスプレイ解像度が変われば計算をやり直す必要あり。

3. **画面が想定外の場所をクリックする恐れ**  
   - キャリブレーションをしないと、画面上の意図しない位置をクリックし、大きなトラブルを招く可能性が高い。

---

### **4-2. キャリブレーションの手順**

1. **ディスプレイ解像度を確認**  
   - キャプチャとディスプレイの解像度を合わせられるなら簡単。  
   - 合わない場合はスケールやオフセットを計算する必要がある。

2. **テストポイントでクリックして確認**  
   - PC画面の左上、右上など複数点を実際にクリックさせ、HDMIキャプチャ上どう見えるかチェックする。  
   - `(capturedX, capturedY) → (actualX, actualY)` という対応を複数点集めて誤差を最小にする。

3. **補正パラメータを保存・適用**  
   - スケール (`scaleX`, `scaleY`) やオフセット (`offsetX`, `offsetY`) を算出し、以降の座標指定に適用。  
   - OSが再起動されたり解像度が変わったら再キャリブレーションをする。

---

## **5. 全体フローまとめ**

1. **(キャリブレーション)**  
   - システム起動時に、キャプチャ座標とPC画面座標が一致するように調整。  
   - スケールやオフセットを把握しておけば「(capturedX, capturedY)」→「(actualX, actualY)」の変換が可能。

2. **(ループで画面解析 + 操作)**  
   - HDMIキャプチャ → SikuliX/ChatGPT Visionで解析 → ボタンなどを見つける → Zero 2 Wへ操作指示 → 次の画面を再解析  
   - 必要な処理が終わるか、エラーなどの条件に達するまでループする。

3. **(ChatGPT Vision時はプロンプトが必要)**  
   - 画像だけでなく、「この画面がログイン画面かどうか？ もしそうなら座標を出力して」など、**テキストによる指示**を同時に送ると精度が上がる。  
   - 出力フォーマット（JSON形式など）も明確に指定すると後段の処理が楽になる。

4. **(エラー対応や多言語UI)**  
   - ChatGPT Visionのような高度なモデルを使うと、画面のテキストからエラー内容を抽出したり、多言語UIにも柔軟に対応できる。

---

## **6. メリットと注意点**

### **メリット**

- **高度な自動化**: 画面遷移やエラーを拾って柔軟に操作できる。  
- **保守の容易化**:  
  - SikuliXなら画像ベースでUI部品を管理。変更があれば差し替えや追加しやすい。  
  - ChatGPT Visionなら多言語や多彩な画面変化にも対応しやすい。  
- **プロンプト次第で多様なタスク**: 「この画面の文字を読み取ってSlack通知」など、さらに拡張可能。

### **注意点**

- **クラウド依存（ChatGPT Vision）**: API呼び出しの遅延やコストが発生。  
- **SikuliX環境構築**: JavaやGUI環境を整える必要があり、Piの性能要件に注意。  
- **キャリブレーション必須**: 座標ずれが起きると誤クリックリスクが高い。  
- **プロンプト設計**: 大規模モデルに合わせて、何をどう出力してほしいか明示する。

---

## **7. よくある質問（FAQ）**

**Q1. キャリブレーションは毎回実行しないといけない？**  
A1. 基本的には一度環境をセットアップした段階で行えば大丈夫です。ただし、**OSの解像度変更やPCを再起動**した際、あるいは**別モニタに切り替えた**などの状況では再キャリブレーションをするのがおすすめです。

**Q2. ChatGPT Visionの出力が誤っていた場合はどうする？**  
A2. モデルの出力が常に正しいとは限らないため、**再試行**や**SikuliXでのダブルチェック**を挟むのが実運用では安心です。また、プロンプトを工夫し、「エラーがあれば必ず座標を返さず、メッセージを返すだけ」など明確化するとよいでしょう。

**Q3. マウス操作がズレる原因は他に何がある？**  
A3. 代表的なのは **マウス加速設定** と **相対/絶対モード** の違いです。相対モードの場合、OSのマウス加速が有効だと `(dx, dy)` が理想通り動かない場合があります。絶対モードでも OS による座標変換が入ることがあるので注意が必要です。

**Q4. クラウドの API 読み込みが遅い場合はどうすれば？**  
A4. ChatGPT Visionのような大規模モデルはネットワーク経由で呼び出すため、どうしても **遅延** が生じがちです。リアルタイム性がそこまで必要でない場合は問題ありませんが、**処理間隔**を適切に設定する、あるいは**ローカル環境に近い形で推論を行うオプション**（将来のローカル版モデルなど）を検討するのも手です。

**Q5. SikuliXとChatGPT Visionは併用できる？**  
A5. もちろん可能です。たとえば **定型UI操作や座標取得は SikuliX**、**画面上の文章理解や多言語対応は ChatGPT Vision** といった役割分担をすれば、それぞれの得意分野を活かせます。

---

## **8. まとめ**

1. **ループ的画像解析**: HDMIキャプチャ→(SikuliX/ChatGPT Visionで解析)→(必要な操作) の繰り返しで、画面状態の変化に追従。  
2. **SikuliX**: 画像パターンマッチング中心で手軽に自動化。  
3. **ChatGPT Vision**: テキストやUI構造を自然言語で理解し、エラー画面などを高度に解析可能。  
4. **キャリブレーション**: HDMIキャプチャ座標とPC実座標の差を埋めるために必須。  
5. **プロンプト**: ChatGPT Visionなどのモデルには、何をどう解析すべきかテキストで指示し、出力形式を明示すると精度が上がる。  

---

## **9. OpenCVで前処理を入れると有効か？**

結論から言えば、**SikuliX や ChatGPT Vision の解析前に、OpenCVで画像をある程度整形しておくのは効果的な場合が多い**です。  

- **ノイズ除去やコントラスト補正**  
  - HDMIキャプチャ画像が暗すぎたりノイズが多い場合、軽いぼかしやヒストグラム平坦化で解析精度が上がることがあります。  
- **リサイズ・トリミング**  
  - 必要ない領域を切り出してしまえば、ChatGPT Visionに送るデータ量が減り、速度とコストを削減できます。  
- **二値化**  
  - OCR的な文字認識をしたい場合は、グレースケール化＋二値化が有効な場合があります。  
  - ただしカラー情報が必要なボタン認識には向かないので、処理を切り分ける工夫が必要です。

### **前処理の注意点**

- **やりすぎには注意**  
  - 過度にぼかすと肝心の文字やボタンが潰れる恐れあり。  
- **使うシーンに合わせて**  
  - 文字認識が主目的なら二値化、ボタン検出が主目的ならカラー情報を残す、など柔軟に選択。  

このように、**「とりあえず生データを送る」よりは、OpenCVなどで必要最小限の前処理を行ってからSikuliX/ChatGPT Visionに渡すほうが精度や処理効率が向上する**可能性が高いです。

---

## **参考リンク**

- **Raspberry Pi Zero 2 W でマウス&キーボードをエミュレートする方法**（別記事）  
  - gRPCサーバー構築例やHIDデバイスの詳細  
- **SikuliX公式サイト**  
  - <https://sikulix.com/>  
- **ChatGPT Vision (OpenAI公式)**  
  - <https://openai.com/blog/chatgpt-vision/>  

---

以上