5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「マイノリティ・リポート」を自作する。Python×MediaPipeで叶える、マウス不要の空間UI

Last updated at Posted at 2026-01-28

はじめに

マウスやキーボードを使わずに、手の動きだけでコンピュータを操作できたら便利だと思いませんか?映画「マイノリティ・リポート」のような近未来的なインターフェースを、たった3つのPythonライブラリで実現できます。

この記事では、OpenCVMediaPipeNumPyを使って、Webカメラベースのハンドジェスチャー制御デスクトップUIを構築する方法を解説します。

読了時間:約5分

screenshot_0016.jpg

このプロジェクトで実現できること

  • 手の動きでカーソル操作
  • 👌 ピンチジェスチャーでクリック
  • ⌨️ 仮想キーボードでテキスト入力
  • 🌐 ブラウザ起動とGoogle検索
  • 🎮 両手を使ったボールバウンスゲーム

必要なライブラリ

pip install opencv-python mediapipe numpy

たったこれだけ!それぞれの役割:

  • OpenCV: カメラ映像の取得と画像処理
  • MediaPipe: Googleが開発した高精度なハンドトラッキング
  • NumPy: 数値計算とデータ処理

技術的な仕組み

1. ハンドトラッキングの基礎

MediaPipeは、1つの手につき 21個のランドマーク(特徴点) を検出します。

image.pngimage.png

import mediapipe as mp

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    max_num_hands=2,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.5
)

重要なランドマーク:

  • ランドマーク4: 親指の先端
  • ランドマーク8: 人差し指の先端
  • ランドマーク0: 手首

2. カーソル制御のロジック

人差し指の先端(ランドマーク8)をカーソル位置として使用します。

def map_to_ui(x, y, cam_width, cam_height):
    """カメラ座標をUI座標に変換"""
    ui_x = int(x * UI_WIDTH)
    ui_y = int(y * UI_HEIGHT)
    return np.clip(ui_x, 0, UI_WIDTH - 1), np.clip(ui_y, 0, UI_HEIGHT - 1)

# 人差し指の先端を取得
index_tip = hand_landmarks.landmark[8]
cursor_pos = map_to_ui(index_tip.x, index_tip.y, cam_width, cam_height)

screenshot_0009.jpg

3. クリック検出:ピンチジェスチャー

親指と人差し指の距離を計算し、閾値以下ならクリックと判定します。

def detect_pinch(hand_landmarks, frame_width, frame_height):
    """ピンチジェスチャーを検出"""
    thumb_tip = hand_landmarks.landmark[4]
    index_tip = hand_landmarks.landmark[8]
    
    # 2点間の距離を計算
    distance = np.sqrt(
        (thumb_tip.x - index_tip.x)**2 + 
        (thumb_tip.y - index_tip.y)**2
    ) * frame_width
    
    return distance < PINCH_THRESHOLD  # 例: 40ピクセル

image.png  image.png

4. インタラクティブなボタンUI

ボタンをクラスとして定義し、ROI(Region of Interest・関心領域)を管理します。

class Button:
    def __init__(self, x, y, w, h, label, action):
        self.x, self.y, self.w, self.h = x, y, w, h
        self.label = label
        self.action = action  # クリック時に実行する関数
        self.is_hovered = False
    
    def contains(self, px, py):
        """カーソルがボタン内にあるか判定"""
        return self.x <= px <= self.x + self.w and \
               self.y <= py <= self.y + self.h
    
    def click(self):
        """ボタンがクリックされた時の処理"""
        if self.action:
            self.action()

ホバーとクリックで色を変えることで、視覚的なフィードバックを提供します。

ホバー時赤になる         クリック時緑になる 
image.png image.png

5. 仮想キーボードの実装

QWERTYレイアウトを2次元配列で定義し、動的にボタンを生成します。

keyboard_keys = [
    ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
    ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
    ['Z', 'X', 'C', 'V', 'B', 'N', 'M'],
    ['SPACE', 'BACK', 'ENTER']
]

for row_idx, row in enumerate(keyboard_keys):
    for col_idx, key in enumerate(row):
        x = start_x + col_idx * (KEY_SIZE + KEY_MARGIN)
        y = start_y + row_idx * (KEY_SIZE + KEY_MARGIN)
        buttons.append(Button(x, y, KEY_SIZE, KEY_SIZE, key, 
                             lambda k=key: state.type_char(k)))

screenshot_0025.jpg

応用例:ボールバウンスゲーム

両手を検出し、手のひらの中心同士を結んでバーを作成します。

def get_palm_center(hand_landmarks):
    """手のひらの中心を計算"""
    palm_points = [0, 5, 9, 13, 17]  # 手首と各指の付け根
    x_avg = sum(hand_landmarks.landmark[i].x for i in palm_points) / 5
    y_avg = sum(hand_landmarks.landmark[i].y for i in palm_points) / 5
    return map_to_ui(x_avg, y_avg, cam_width, cam_height)

# 2つの手を検出した場合
if len(results.multi_hand_landmarks) == 2:
    palm1 = get_palm_center(results.multi_hand_landmarks[0])
    palm2 = get_palm_center(results.multi_hand_landmarks[1])
    # バーを描画
    cv2.line(frame, palm1, palm2, (0, 255, 255), BAR_THICKNESS)

ボールとバーの衝突判定を行い、スコアとコンボを計算します。
screenshot_0043.jpgscreenshot_0052.jpg

パフォーマンス最適化のポイント

1. クリックのデバウンス処理

連続クリックを防ぐため、クールダウン時間を設定します。

def can_click(self):
    current_time = time.time()
    if current_time - self.last_click_time > 0.3:  # 300ms
        self.last_click_time = current_time
        return True
    return False

2. FPSの計算と表示

リアルタイムパフォーマンスをモニタリングします。

prev_time = time.time()
current_time = time.time()
fps = 1 / (current_time - prev_time)
prev_time = current_time

3. MediaPipeの設定調整

hands = mp_hands.Hands(
    static_image_mode=False,      # 動画モード
    max_num_hands=2,               # 最大検出数
    min_detection_confidence=0.7,  # 検出信頼度
    min_tracking_confidence=0.5    # トラッキング信頼度
)

実装時の注意点

ミラー効果

カメラ映像は左右反転させることで、鏡のような直感的な操作感を実現します。

# カメラ映像を反転
bg = cv2.flip(cam_frame, 1)

照明条件

良好な手の検出には適切な照明が必要です:

  • 明るすぎず暗すぎない環境
  • 背景とのコントラストを確保
  • 逆光を避ける

カメラ解像度

cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

高解像度は精度向上につながりますが、処理負荷も増加します。

拡張アイデア

このプロトタイプをベースに、さらに発展させることができます:

  1. スワイプジェスチャー: 手全体の動きでページ切り替え
  2. ピンチズーム: 2本指の距離で拡大縮小
  3. 回転ジェスチャー: 手首の回転でオブジェクト回転
  4. 音声コマンド統合: ジェスチャーと音声のマルチモーダル操作
  5. 機械学習: カスタムジェスチャーの学習と認識

まとめ

Pythonと3つのライブラリだけで、マウス不要のハンドジェスチャーUIを実装できました。

ポイント

  • MediaPipeで21個の手のランドマークを高精度検出
  • 人差し指の位置でカーソル制御
  • ピンチジェスチャーでクリック判定
  • ROIベースのボタン管理で拡張性を確保
  • 両手の協調動作でより複雑なインタラクション

このプロジェクトは、HCI(ヒューマンコンピュータインタラクション)の入門として最適です。アクセシビリティの向上、VR/ARインターフェース、非接触操作など、様々な応用可能性があります。

サンプルコード

:cat: 完全なコードはGitHubで公開しています:

https://github.com/Rikiza89/Mirror_Screen_prototype

参考資料

ぜひ試してみて、あなた独自のジェスチャーUIを作ってみてください!質問やフィードバックはコメント欄でお待ちしています。

最後に・・・

この記事が指に馴染んだら、画面の前で『親指(ランドマーク4)』を立てて、ついでに下の『♥️』もピンチしてみてね!🤏✨

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?