前回の記事では、opencv に基づくジェスチャ認識を紹介しましたが、コードを実行すると、コード内で手の輪郭を検出する効果が理想的ではないことがわかります。当時、インターネットで解決策を探していたところ、mediapip ライブラリを見つけたので、opencv と mediapipe の 2 つのライブラリを使用してジェスチャ認識用のコードを書き直しました。効果は悪くないので記録のために記事を書きます。
1.メディアパイプの概要
Mediapipe は、オープンソースのクロスプラットフォームの共通機械学習 (機械学習) ソリューションを提供できる Google のオープンソース プロジェクトです。Mediapipe は実際には、顔検出、顔のキー ポイント、ジェスチャ認識、アバター セグメンテーション、ポーズ認識などのさまざまなモデルを含む、統合された機械学習ビジョン アルゴリズムのツール ライブラリです。
私は主にジェスチャ認識を行っているため、最終的なジェスチャ認識のソース コードを誰もがよりよく理解できるように、ライブラリの手検出モジュールについて簡単に説明します。(他のモジュールについて知りたい場合は、ここをクリックして詳細を確認できます: mediapipe 公式紹介
まず、手検出モジュールを初期化します
mphand = mp.solutions.hands
hands = mphand.Hands()
mpHand.Hands パラメーター | 詳細パラメータ |
---|---|
static_image_mode=False | False に設定すると、遅延が減少し、ビデオ フレームの処理に最適です。True に設定すると、無関係である可能性のある静的画像のバッチを処理するのに適しています。デフォルトは False です。 |
max_num_hands=2 | 検出する最大ロットサイズ。デフォルトは2です |
モデルの複雑さ=1 | 手のランドマーク モデルの複雑さ: 0 または 1。ランドマークの精度と推論の遅延は、通常、モデルの複雑さとともに増加します。デフォルトは1です |
min_detection_confidence=0.5 | 検出が成功したとみなされる、手検出モデルにおける最小信頼値。デフォルトは0.5です |
min_tracking_confidence=0.5 | ランドマーク追跡モデルの最小信頼値。手のランドマークが正常に追跡されたとみなされることを示します。そうでない場合は、次の入力画像で手の検出が自動的に呼び出されます。より高い値に設定すると、待ち時間が長くなりますが、ソリューションの堅牢性が向上します。static_image_mode が True の場合は無視され、手の検出は各画像に対して単純に実行されます。デフォルトは0.5です |
上記のパラメータの意味がよく理解できなくても、デフォルトのままで問題ありません。max_num_hands と min_detection_confidence という 2 つのパラメーターの意味を知っていればいいと思います
次に、ジェスチャ検出プロセスがあります
hand = hands.process(img)
パラメータimgは検出対象の画像ですが、opencvで読み込んだ画像はBGRモードなので、ここでのimgをRGBモードに変換する必要があります。
最後に、テスト結果を読んでください
finger = []
for handlms in hand.multi_hand_landmarks:
for lm in handlms.landmark:
img_height,img_width,_ = img.shape
#这里检测返回的x,y的坐标是基于原图像大小的比例坐标,
#所以要与原图像相乘得到真实的坐标
x, y = int(lm.x * img_width), int(lm.y * img_height)
finger.append([x, y])
指リストを印刷すると、21 個の座標値が表示されます。この 21 個の座標は、元の画像上の人間の手の 21 個のキーポイントの位置座標に基づいて、midiapipe が検出したものです。これら 21 個の座標値に対応する手検出のポイントは次のとおりです。
2. ジェスチャー認識のアイデア
誰もがメディアパイプの基本的な使い方をすでに理解しているので、ここでアイデアと重要なスキルを説明します。
一連の考え
まず、opencv を使用してカメラを呼び出し、カメラから画像を読み取り、画像を反転して (カメラが読み取った画像が実際とは逆になります)、画像を RGB モードに変換します。次に、メディアパイプを使用して手を検出し、リストに保存します。次に、指によって形成される角度を使用して、指が曲がっているかどうかが判断されます。
重要なポイントを説明
プログラム全体はそれほど難しくないようですが、主な鍵は指が曲がっているかどうかを検出する方法です。
指の付け根から指先を引いて、その観察結果のプラスとマイナスで指の曲がりを判断すると本で読みました。たとえば、上の図では、8 つの座標点の y 値から 6 つの座標点の y 値を引きます (ここで y 値が理解できない場合は、ここをクリックしてください)。結果が正であれば、それは指がが曲がっていて、マイナスの場合は指が伸びていることを意味します。
しかし、ここには 2 つの問題があります。
まず、親指の曲がりは他の 4 本の指の曲がりとは異なります (ここでは、手を伸ばして観察すると理解できます)。たとえば、私は 4 つの座標を使用します。上の図 1 座標から y 値を減算します (ここでの y 値は上と同じです) が、全員の親指を曲げたときに、全員の 4 座標の y 値が 1 座標の y 値より大きくなければならないわけではありません。親指が曲がっているかどうかを判断することはできません。
この問題を解決するためにしきい値を設定したいと考えたこともありましたが、このしきい値のサイズを常にデバッグする必要があり、指のサイズは人それぞれ異なります。このしきい値は自分にとっては適しているかもしれませんが、他の人にとっては適していない可能性があります。
次に、上記の例はすべて、手の指先が上を向いている場合、手の指先が下、左、右を向いている場合に基づいていることがわかったかどうかはわかりません。このような状況ではどうすればよいでしょうか? もっと判定条件を書いて一つ一つ分析する必要があるのでしょうか。これは膨大な量のコードなので、あまりお勧めしません。
角度を導入して問題を解く
三角関数を導入し、指で作る角度で判断する
たとえば、ここでは座標 5、6、7 を頂点として三角形を形成し、
座標 5、6 を辺として使用します。 、
座標 6 と 7 によって形成される辺は b、
座標 5 と 7 によって形成される辺は c で、
コサイン関数を使用して頂点 6 の角度を計算します。式は次のとおりです:
a 2 + b 2 − c 2 / 2 aba^ 2+b^2-c^2/2abある2+b2−c2 /2ab
次に、指が曲がっているかどうかを判断するためのしきい値を 155 に設定します。
3. 完全なコード
ここでは、プログラムを読みやすくするためにコードにコメントを付けました。
import cv2
import numpy
import mediapipe as mp
import math
#创建手部检测的对象
mphand = mp.solutions.hands
hands = mphand.Hands()
mpdraw = mp.solutions.drawing_utils
cap = cv2.VideoCapture(0)
while cap.isOpened():
#对读取的图像进行反转,转换为RGB模式,读出图片大小
ret,frame = cap.read()
frame = cv2.flip(frame,1)
img =cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
h,w,_ = frame.shape
#进行手部的检测
hand = hands.process(img)
#判断是否检测到了手部
if hand.multi_hand_landmarks:
count = 0 #统计手指的伸出个数
for handlms in hand.multi_hand_landmarks:
finger = []
finger_point = []
#这里我将每一根手指的四个坐标整合到一个列表中,大家可以打印finger,进行详细的查看
for id,lm in enumerate(handlms.landmark):
x,y = int(lm.x*w),int(lm.y*h)
if id == 0:
pass
elif id % 4 == 0:
finger_point.append([x,y])
finger.append(finger_point)
finger_point = []
else:
finger_point.append([x,y])
#遍历每一根手指列表,计算其构成的三角形的三边长,这里使用2,6,10,14,18所对应的角进行判断
for id,point in enumerate(finger):
a = math.hypot((point[0][0]-point[1][0]),(point[0][1]-point[1][1]))
b = math.hypot((point[1][0]-point[2][0]),(point[1][1]-point[2][1]))
c = math.hypot((point[0][0]-point[2][0]),(point[0][1]-point[2][1]))
#在计算value值的时候,除数可能为零,以及当三点在一点直线上,都会抛出异常,所以要捕获异常
try :
value = (a**2+b**2-c**2)/(2*a*b)
#这里的value为弧度制,乘上57转换为角度制,当然你也可以选择直接使用弧度制
angle = math.acos(value)*57
except ValueError:
angle = 180
except ZeroDivisionError:
angle = 0
print(angle)
#当角度大于155的时候记为手指伸出
if angle >= 155:
count += 1
else:
pass
#在手部绘制关键点位置
mpdraw.draw_landmarks(frame,handlms,mphand.HAND_CONNECTIONS)
#将手指检测的结果显示在图像上
cv2.putText(frame,str(count),(int((1/9)*w),int((1/9)*h)),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),1)
#展示图片
cv2.imshow('img',frame)
#按下Esc退出循环
c = cv2.waitKey(25)
if c == 27:
break
cap.release()
cv2.destroyAllWindows()
4. エンディング
Mediapipe ライブラリを使用して手を検出すると、ジェスチャを通じてコンピューターのマウスを制御したり、ジェスチャを使用して描画したりするなど、ジェスチャを通じて多くの興味深いことを行うことができます。私もこれらに挑戦していますが、それが終わった後に私のアイデアや遭遇した困難を共有します。
最終的な作品を作るのは簡単ではありませんが、皆さんにサポートしていただき、気に入っていただければ幸いです。皆さんと共有し、学び、共に進歩できることを楽しみにしています。