(1時間4,000円の伴走型ビデオチャット指導も受付中!)
リアルタイム映像処理に挑戦!MediaPipeとTkinterを組み合わせて作る簡単ジェスチャーGUIアプリ
![]()
【Hiroki】
Yukiさん、こんにちは!
最近、Pythonを使ってデスクトップアプリを作ることに関心があるんです。
Python標準の「Tkinter」というライブラリを使うと、簡単に画面(GUI)が作れると知りました。
ただ、せっかくなら、カメラを使って手の動きを認識するような、AIっぽい機能も組み合わせてみたいんです。
たとえば、「MediaPipe」というライブラリと組み合わせることはできるんでしょうか?
![]()
【Yuki】
Hirokiくん、こんにちは。
TkinterとMediaPipeの組み合わせですね。とても素敵なアイデアだと思います...!
MediaPipeはGoogleが開発しているオープンソースのフレームワークで、カメラ映像から手や顔、体全体の動きを非常に軽い処理で検出できるんですよ。
これとTkinterを組み合わせれば、カメラ映像に写った手の動きに合わせてボタンが反応したり、画面上のオブジェクトを動かしたりする「ジェスチャー操作アプリ」が作れるかもしれません。
ただ、初心者の方が最初につまずきやすい「画像の形式変換」や、画面が固まってしまう「処理のループ問題」といったポイントがいくつかあります。
今日はそれらを1つずつ整理しながら、一緒にシンプルなカメラ連動型のGUIアプリを作っていきましょう。
MediaPipeとTkinterを組み合わせるメリット
![]()
【Hiroki】
ありがとうございます!
そもそも、どうしてこの2つの組み合わせが注目されるんでしょうか?
他にもWebカメラの映像を扱う方法はあるような気がするのですが。
![]()
【Yuki】
そうですね。確かにカメラ映像を表示するだけなら、OpenCVというライブラリだけでも十分可能です。
でも、実用的な「アプリ」としてボタンや設定画面、テキスト表示などのリッチなユーザーインターフェース(UI)を作りたいときは、OpenCVの機能だけだと少し物足りないことが多いんです...。
そこで、UIを作るのが得意なTkinterと、手の骨格を検出するのが得意なMediaPipeを組み合わせます。
これらを組み合わせることで、以下のようなメリットがあります。
- 直感的なUIの構築: ボタン、スライダー、テキストボックスなどを簡単に配置できます。
- 低負荷なAI処理: MediaPipeは非常に軽量に作られているため、専用のグラフィックボードがない一般的なパソコンでも、リアルタイムに動作させることができます。
- 完全なローカル動作: クラウドのAPIを呼び出さないため、インターネットに繋がっていなくても動きますし、通信遅延(レイテンシ)もほとんどありません。
このように、デスクトップ上で動くインタラクティブなAIツールを作るには、とても相性が良い組み合わせなんですよ。
事前準備と環境構築
![]()
【Hiroki】
なるほど、それぞれの得意分野を合体させるわけですね!
まずは、必要なライブラリのインストールから始めればいいですか?
![]()
【Yuki】
はい、その通りです。
今回は、Tkinterにカメラの映像を表示するために「Pillow(PIL)」という画像処理ライブラリも使います。
また、カメラから映像を取得するために「OpenCV(opencv-python)」も必要になりますね。
コマンドプロンプトやターミナルを開いて、以下のコマンドを実行してみてください。
pip install opencv-python mediapipe Pillow
Tkinter自体は、Pythonをインストールしたときに標準で一緒にシステムに入っていることが多いので、基本的には個別でインストールする必要はありません。
![]()
【Hiroki】
インストールできました!
必要なものが揃ったので、さっそくプログラムの全体像を知りたいです。
基本的なプログラムの設計図(仕組み)
![]()
【Yuki】
準備万端ですね。
プログラムを書く前に、どのようにして「カメラ映像」「AI(MediaPipe)」「画面(Tkinter)」が連携するのか、その仕組みを理解しておくと、コードがとても読みやすくなると思います...。
大まかな処理の流れは、以下の図のようになっています。
- カメラからフレーム(画像)を取得する(OpenCVの役割)
- 取得したフレームをMediaPipeに渡して、手の骨格を検出する(MediaPipeの役割)
- 検出された情報をフレームの上に描き込む
- 描画したフレームを、Tkinterが認識できる画像形式(PhotoImage)に変換する(Pillowの役割)
- Tkinterのラベル(Label)などの部品に画像を反映して画面を更新する
ここで一番大事なのは、「定期的に画面を更新し続ける」という点です。
普通、カメラ映像は1秒間に30回(30fps)など、高速で画像を切り替えることで動画に見せていますよね。
Tkinterには、一定時間後に指定した関数を実行する after というメソッドがあります。これを使って「10ミリ秒ごとにカメラ画像を読み込んで描画する」というループ処理を作ります。
![]()
【Hiroki】
なるほど。
通常のPythonプログラムだと、無限ループは while True: を使って書くことが多いですが、Tkinterでそれをやってしまうとどうなるんですか?
![]()
【Yuki】
とても鋭い質問ですね...!
実は、Tkinterは root.mainloop() というメソッドで、画面が閉じられるまで常に「ボタンが押されたか」「画面が動かされたか」といったイベントを監視する無限ループを実行しています。
もし、その中でわたしたちが while True: を使って別の無限ループを回してしまうと、Tkinter自身のループが止まってしまい、画面が全く反応しなくなってフリーズしてしまうんです。
ですから、Tkinterの画面を止めずに裏でカメラ処理を回し続けるために、after メソッドを使った「イベント駆動型のループ」を作る必要があります。これが最大のポイントになります。
サンプルコードの実装
![]()
【Hiroki】
while ループを使ってはいけないというのは、デスクトップアプリ開発ならではの落とし穴ですね。危うくやってしまうところでした。
それでは、実際のコードを教えていただけますか?
![]()
【Yuki】
はい。今回は、起動するとウィンドウが表示され、カメラから取得した自分の手に「青い骨格線」と「赤い関節点」がリアルタイムで描画される、シンプルなアプリを作成してみました。
以下のコードをコピーして、main.py などの名前で保存して実行してみてください。
import cv2
import mediapipe as mp
import tkinter as tk
from PIL import Image, ImageTk
class HandTrackerApp:
def __init__(self, root):
self.root = root
self.root.title("MediaPipe & Tkinter ジェスチャーアプリ")
self.root.geometry("800x650")
# UIの作成
self.title_label = tk.Label(
root,
text="手の骨格検出リアルタイムビューワー",
font=("Helvetica", 16, "bold")
)
self.title_label.pack(pady=10)
# 映像を表示する用のキャンバス(またはラベル)
self.video_label = tk.Label(root)
self.video_label.pack()
# 終了ボタン
self.quit_button = tk.Button(
root,
text="アプリを終了する",
command=self.on_closing,
font=("Helvetica", 12)
)
self.quit_button.pack(pady=10)
# OpenCVのカメラ初期化(引数の0は標準の内蔵カメラを示します)
self.cap = cv2.VideoCapture(0)
# MediaPipeの手検出モデルの初期化
self.mp_hands = mp.solutions.hands
self.hands = self.mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5
)
self.mp_draw = mp.solutions.drawing_utils
# ウィンドウが閉じられたときのイベントを設定
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# カメラ映像の更新ループを開始
self.update_frame()
def update_frame(self):
# 1. カメラからフレームを読み込む
ret, frame = self.cap.read()
if ret:
# カメラ映像は左右反転(鏡効果)させておくと扱いやすいです
frame = cv2.flip(frame, 1)
# OpenCVはBGR形式ですが、MediaPipeやPillowはRGB形式を求めます
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 2. MediaPipeで手検出を実行
results = self.hands.process(rgb_frame)
# 3. 検出された手を画像の上に描き込む
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
self.mp_draw.draw_landmarks(
rgb_frame,
hand_landmarks,
self.mp_hands.HAND_CONNECTIONS
)
# 4. Pillowを使ってTkinter用の画像形式に変換
image = Image.fromarray(rgb_frame)
# 画面サイズに合わせてリサイズ(例:横640px、縦480px)
image = image.resize((640, 480), Image.Resampling.LANCZOS)
self.photo_image = ImageTk.PhotoImage(image=image)
# 5. Tkinterのラベルに画像をセット
self.video_label.config(image=self.photo_image)
# 15ミリ秒後に、再度このupdate_frameメソッドを呼び出します
self.root.after(15, self.update_frame)
def on_closing(self):
# リソースを安全に解放して終了する処理
self.cap.release()
self.hands.close()
self.root.destroy()
# メイン処理の起動
if __name__ == "__main__":
root = tk.Tk()
app = HandTrackerApp(root)
root.mainloop()
サンプルコードの詳しい解説
![]()
【Hiroki】
おお、動きました!
画面の中にカメラ映像が映って、僕の手の動きに合わせて綺麗にラインが描画されています!
すごく不思議な感覚です。
コードの中で、いくつか気になる部分があるので教えてください。
まず、cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) という処理がありますが、これは何をしているのですか?
![]()
【Yuki】
無事に動いてよかったです...!とても嬉しいです。
その部分ですね。実は、画像の色の並び順に関する問題を解決しています。
OpenCVというライブラリは歴史が古いこともあって、画像を読み込むときに色のチャンネルを「BGR(青・緑・赤)」の順番で保持する仕様になっています。
しかし、一般的なライブラリであるMediaPipeや、画像の変換を行うPillow、そして画面を表示するTkinterなどは、すべて「RGB(赤・緑・青)」の順番で画像を扱います。
もし、このRGBへの変換(cvtColor)を行わずに処理を進めてしまうと、赤色と青色が入れ替わってしまって、カメラ映像の肌の色がゾンビのように青っぽくなってしまうんです。
ですので、一度RGBに並び替えてから、MediaPipeに渡したりPillowで変換したりしています。
![]()
【Hiroki】
なるほど、データの形式を揃えるための大事な儀式なんですね。
それから、先ほど教えてもらった after メソッドはどこに使われていますか?
![]()
【Yuki】
はい、update_frame メソッドの一番最後を見てみてください。
self.root.after(15, self.update_frame)
この部分ですね。
これは「15ミリ秒が経過したら、もう一度自分自身(self.update_frame)を実行してください」とTkinterにお願いしているコードになります。
15ミリ秒というのは秒に直すと約0.015秒ですので、1秒間に約60回以上のペースでこの関数が呼び出されるように予約している状態です。
このように、関数の中で自分自身を未来に予約することで、Tkinterのメインの管理ループ(mainloop)を邪魔することなく、何度も処理を繰り返す「擬似的な無限ループ」を実現しているんですよ。
![]()
【Hiroki】
だから画面がフリーズすることなく、カメラ映像がスムーズに動き続け、ボタンも問題なくクリックできるんですね!
とても効率的な仕組みですね。
![]()
【Yuki】
そう言っていただけると嬉しいです。
Tkinterでリアルタイムな処理(カメラ映像の描画や、定期的なセンサー値の取得など)を行う場合は、ほぼ必ずこの after メソッドが使われますので、ぜひ覚えておいてくださいね。
つまずきやすいポイントと解決策
![]()
【Hiroki】
よくわかりました!
ところで、今回作ったこのプログラムをベースに自分で改造していくときに、気をつけるべき「つまずきポイント」はありますか?
![]()
【Yuki】
そうですね...。いくつか初心者の皆さんが直面しやすい問題があります。
代表的なものを3つ、対策と一緒にご紹介しますね。
Q1. カメラの映像が表示されない、または非常にカクカクして重い
原因1: カメラのデバイス番号が違っている
パソコンに複数のカメラ(内蔵カメラと、外付けのUSB Webカメラなど)が接続されている場合、cv2.VideoCapture(0) の 0 という数値を変更する必要があります。
もし外付けカメラを使いたい場合は、0 から 1 や 2 に変更してみてください。
原因2: 解像度が高すぎる、またはパソコンのスペック不足
カメラの解像度がフルHD(1920x1080)など高解像度のままだと、MediaPipeの計算が追いつかずに処理が重くなってしまうことがあります。
その場合は、update_frame の中で画像をリサイズしてからMediaPipeに渡すか、カメラの解像度設定自体を事前に下げておくのが有効です。
例えば、カメラの初期化の直後に以下のコードを追加することで、読み込みサイズを抑えられます。
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
Q2. 画面を「✕」ボタンで閉じたときに、エラーが出たりプロセスが残ったりする
原因: リソースの未解放
Tkinterのウィンドウを閉じたとしても、カメラ(cv2.VideoCapture)やMediaPipeの内部プロセスが動きっぱなしになってしまうことがあります。
この状態だと、次回プログラムを起動したときに「カメラが既に使用されています」となって動かなくなったり、Pythonのプロセスがバックグラウンドで残り続けたりします。
サンプルコードでは、これを防ぐために self.root.protocol("WM_DELETE_WINDOW", self.on_closing) という記述をしています。
これは「ウィンドウが閉じられようとしたとき、on_closing メソッドを実行してね」という設定です。
その中で、以下のように安全にカメラやモデルを解放しています。
def on_closing(self):
self.cap.release() # カメラを解放
self.hands.close() # MediaPipeを終了
self.root.destroy() # ウィンドウを消滅させる
これを行っておくことで、予期せぬ不具合を防ぐことができます。
![]()
【Hiroki】
なるほど。後処理をしっかり行わないと、裏でカメラが動きっぱなしになってしまう可能性があるんですね。注意します!
さらに応用するためのアプローチ
![]()
【Hiroki】
この基本のアプリが作れたら、ここからどうやって肉付けしていけばいいでしょうか?
例えば、特定のジェスチャーをしたときにTkinterの画面に何か変化を起こす、といったことは可能ですか?
![]()
【Yuki】
もちろん可能です!
MediaPipeは、手の中にある「21個の関節の座標(Landmarks)」を画面の比率(0.0から1.0の値)で取得してくれます。
例えば、人差し指の先端の座標は 8 番、親指の先端は 4 番というように、すべての関節に番号が割り振られています。
これらの座標を比較することで、様々なジェスチャーを判定できますよ。
例:親指と人差し指が近づいたら「クリック」と判定する
# coordinates of landmark 4 (Thumb) and landmark 8 (Index Finger)
# 4番(親指先端)と8番(人差し指先端)の距離を計算して、一定以下なら「つまむ」動作と判定する
このような判定式を update_frame の中に記述し、条件を満たしたときにTkinterのラベルのテキストを「クリックされました!」と書き換えたり、キャンバス上に配置したボタンをプログラム側から強制的にクリック(.invoke() メソッドなど)させたりすれば、まさにSF映画のような「非接触型のUI」が完成します。
![]()
【Hiroki】
それは楽しそうですね!
人差し指の先端座標をそのままTkinterのキャンバスの座標に変換すれば、空中に絵を描く「ペイントアプリ」なんかも作れそうです。
![]()
【Yuki】
素晴らしいひらめきです...!
まさにその通りで、人差し指の位置(X座標、Y座標)を記録して、Tkinterの Canvas 上に線を引いていけば、簡単にエアペイントアプリが作れてしまいます。
アイデア次第で、リハビリ用のゲームや、料理中で手が汚れているときに画面をスクロールできるレシピビューワーなど、実用的なツールに進化させることができますよ。
![]()
【Hiroki】
MediaPipeとTkinterの組み合わせ、すごく可能性が広がっていますね。
まずはこの骨格検出のプログラムをベースにして、座標を取得して指先で画面を操作する簡単なゲームを作ってみたいと思います!
![]()
【Yuki】
ぜひ挑戦してみてください。
最初は座標の調整などが少し難しく感じるかもしれませんが、少しずつ試行錯誤していけばきっと素敵なものが作れると思います...。
もしまた分からない部分が出てきたら、いつでも聞いてくださいね。応援しています。
![]()
【Hiroki】
ありがとうございます、Yukiさん!頑張ってみます!
この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。
「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。
- 専門家の知見に基づいた、保守性の高いコード設計
- AIの専門家による、Gemini API等の最新AIを組み合わせた高度な自動化
- ChatGPT等が生成したコードのデバッグ・最適化
「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。
初心者から始められるPythonレッスン
● 月額4,000円で質問し放題!!
● 完全オンライン
● 翌日までには必ず返信
● 挫折しない独自の学習メソッド
● 圧倒的高評価!!
● テキストベースで時間を選ばない
● 4,000円/60分の伴走型ビデオチャット指導も可能
● 高品質なサンプルコード
詳細はこちら

