now i will show you my code
# ################################################################
# 手話のメイン処理
#
# ################################################################
# ----------------------------------------------------------------
# import
# ----------------------------------------------------------------
import os
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" # カメラの立ち上がりが遅い問題への対策
import cv2 # pip install opencv-python
import numpy as np
import threading
from collections import deque
from logging import NullHandler
import SignLanguageDebug as sld
import SignLanguageGUI as slg
import SignLanguageClassifier as slc
import SignLanguagePreview as slp
import SignLanguageSetting as sls
from SignLanguageCommon import Pose
from SignLanguageCommon import Hand
from SignLanguageCommon import Train
from SignLanguageCommon import Face
from SignLanguageCommon import Mouth
WinParam = slg.GetWindowParam()
play_flg:bool = False # メイン処理スレッド起動の有無
hol_thread:threading.Thread = None
is_shot:bool = False # 撮影可否(キューに積む判定)
tm = NullHandler
# ----------------------------------------------------------------
# function
# ----------------------------------------------------------------
##
# メイン処理スレッド
# 入力映像を取り込んで手話判定し、結果を表示する
def HolisticThread(error_func):
global WinParam
global play_flg
global is_shot
global tm
WinParam = slg.GetWindowParam()
rec_frame_num = sls.GetRecordFrameNum() # 記録するフレーム数(約1秒分)
cap_width, cap_height = sls.GetCameraResolution() # 入力 (横幅, 縦幅)
disp_width, disp_height = sls.GetDisplayResolution() # 出力 (横幅, 縦幅)
LoadModel:str = "" # 学習済みモデルの有無
confidence:float = sls.GetSignConfidence() # 手話判定の閾値
pre_que_count = sls.GetPreQueCount() # 手話開始直前に溜めるフレーム数
lips_close_count = sls.GetLipsCloseCount() # 口が閉じていると判断するフレーム数
cap = cv2.VideoCapture(WinParam["CAM_ID"])
if not cap.isOpened():
print("Failed to open camera")
error_func()
else:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, cap_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, cap_height)
pos_que = deque(maxlen=rec_frame_num) # 手話データキュー
sign = ""
per = 0.0
pre_que = deque(maxlen=pre_que_count) # 手話開始直前のフレームキュー
lips_close_range = deque([True]*lips_close_count, maxlen=lips_close_count) # 口が閉じていると判断するまでの期間
# FPS開始
sld.FpsStart()
# ショットタイマー開始
tm = threading.Timer(1/rec_frame_num, ShotCb)
tm.start()
# 学習済みモデルの読み込み
LoadModel = slc.LoadSignLanguageModel()
# メインループ
while play_flg == True:
success, img = cap.read()
if not success:
print("Failed to capture camera")
error_func()
break
cap_height, cap_width, _ = img.shape # キャプチャサイズを取り直す
w_offset = int((cap_width-cap_height)/2)
img = img[0:cap_height, w_offset:w_offset+cap_height] # 縦幅に合わせて正方形クロッピング
img = cv2.flip(img, 1) # カメラ画像の場合は左右反転
results = sls.holistic.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
# 手話判定対象となる点の取得
ret, pos, area_width, upp_l, low_r = GetTargetPosition(results)
# 手話の区切りの目安となる点の取得
lips_pos, lips_close, lips_dist, mouth_ratio = GetMouthPosition(results)
# 手話判定領域が取得できたときに処理をする
if ret == "OK":
# デバッグ用情報描画
if WinParam["DBG_MODE"] == "全表示":
sld.DrawAllInfo(img, results)
elif WinParam["DBG_MODE"] == "対象のみ表示":
sld.DrawTargetInfo(img, pos, lips_pos, cap_height)
# 手話モード
if WinParam["CAM_MODE"] == "Sign":
if len(pos_que) > 0:
cv2.putText(img, "{}".format(len(pos_que)), np.uint16((upp_l * cap_height - 10).tolist()), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 2)
cv2.rectangle(img, np.uint16((upp_l * cap_height).tolist()), np.uint16((low_r * cap_height).tolist()), (0, 0, 255), 2)
else:
cv2.rectangle(img, np.uint16((upp_l * cap_height).tolist()), np.uint16((low_r * cap_height).tolist()), (0, 255, 0), 1)
# 学習モード
else:
if WinParam["REC_START"] == "Recording":
cv2.rectangle(img, np.uint16((upp_l * cap_height).tolist()), np.uint16((low_r * cap_height).tolist()), (0, 0, 255), 2)
else:
cv2.rectangle(img, np.uint16((upp_l * cap_height).tolist()), np.uint16((low_r * cap_height).tolist()), (0, 255, 0), 1)
if is_shot == True:
is_shot = False
# データ正規化
pos = pos - upp_l # 領域の左上を(0,0)にする
pos = pos / area_width # 領域幅で割る
li = (pos.flatten()).tolist() # 一次元配列に変換
li = [0.0 if i < 0.0 else i for i in li] # 0.0以下を0.0にする
li = [1.0 if i > 1.0 else i for i in li] # 1.0以上を1.0にする
pos_que.append(li) # キューに登録
if WinParam["CAM_MODE"] == "Sign":
# --------------------------------------------
# 手話モード
# --------------------------------------------
lips_close_range.append(lips_close)
if lips_close_range.count(True) == lips_close_count:
# 一定フレーム 口が閉じていたら手話の対象としない
pos_que.clear()
pre_que.append(li) # 閉じてる間のフレームを保持
else:
# 口が開いたら直前の数フレームを挿入する
pre_que_num = len(pre_que)
if pre_que_num > 0:
for pi in range(pre_que_num):
pos_que.appendleft(pre_que[pre_que_num - 1 - pi])
pre_que.clear()
if LoadModel == "OK" and len(pos_que) == rec_frame_num:
in_data = np.array([list(pos_que)], dtype=np.float32)
result = slc.PredictSignLanguage(in_data)
slg.SetPredictResult(result)
if np.argmax(result) != 0 and np.amax(result) >= confidence:
sign = slg.GetSign(np.argmax(result))
per = np.amax(result)*100
if sls.GetPreviewEnableSign() is True:
slp.StartPreviewByList(list(pos_que), True)
pos_que.clear()
else:
# --------------------------------------------
# 学習モード
# --------------------------------------------
sign = ""
per = 0.0
if WinParam["REC_START"] == "Start":
pos_que.clear()
WinParam["REC_START"] = "Recording"
elif WinParam["REC_START"] == "Recording" and len(pos_que) == rec_frame_num:
filename_list = slc.SaveTrainingDataFile(WinParam["LABEL_ID"], list(pos_que), rec_frame_num)
if sls.GetPreviewEnableTrain() is True:
# 学習データのプレビュー
slp.StartPreviewByFile(filename_list[0], True)
# 学習データの保存確認
slg.ConfirmFileSave(filename_list)
# 学習データの保存完了通知
slg.NotifyFileSaveComp(WinParam["LABEL_ID"])
WinParam["REC_START"] = "Stop"
slg.SetStatusText("")
WinParam["REC_NUM"] -= 1
if WinParam["REC_NUM"] > 0:
slg.CallCountDown()
# 手話判定領域が取得できない場合に表示をクリアする
else:
sign = ""
per = 0.0
if sign != "":
sld.DrawText(
img, sign + " {:.2f}%".format(per), org_y=cap_height-30,
color=(255,255,255), bg = True, bgcolor=(0,0,0)
)
elif ret == "ToCenter":
# 画面中央へ移動することを誘導
sld.DrawText(img, "画面中央に移動してください")
elif ret == "StepBack":
# 後ろに下がることを誘導
sld.DrawText(img, "後ろに下がってください")
# FPS表示
sld.FpsOutput(img)
# lips_dist 表示
sld.mouthDistOutput(img, lips_dist, mouth_ratio, lips_close)
# 画像の表示
img = cv2.resize(img, (disp_width, disp_height)) # 出力サイズにリサイズ
cv2.imshow("Sign Language", img)
cv2.waitKey(1)
cap.release()
cv2.destroyAllWindows()
# ショットタイマー破棄
tm.cancel()
del tm
tm = NullHandler
##
# 手話判定対象となる点の取得
# ポーズ、左右手の情報から手話判定対象となる点を取得し、リスト化する
def GetTargetPosition(results:type):
pos:np.ndarray = np.empty((0,2)) # 手話判定対象となる点群
upp_l:np.ndarray = np.empty((0,2)) # 手話判定領域の左上座標
low_r:np.ndarray = np.empty((0,2)) # 手話判定領域の右下座標
area_width:np.float32 = 1.0
w_times:np.float32 = 3.0 # 肩幅の何倍を手話判定領域にするか
# ----------------
# ポーズ
if results.pose_landmarks:
p_lm = results.pose_landmarks.landmark
# 鼻、両目、両肩、両腕 を取得
pose_list = [
Pose.NOSE,
Pose.L_EYE,
Pose.R_EYE,
Pose.L_SHOULDER,
Pose.R_SHOULDER,
Pose.L_ELBOW,
Pose.R_ELBOW,
]
for i in pose_list:
pos = np.append(pos, [[p_lm[i].x, p_lm[i].y]], axis=0)
# 両肩の中心を追加
pos = np.append(pos, [(pos[Train.L_SHOULDER] + pos[Train.R_SHOULDER])/2], axis=0)
# 手話判定領域を求める
area_width = np.linalg.norm(pos[Train.L_SHOULDER] - pos[Train.R_SHOULDER]) * w_times
upp_l = pos[Train.C_SHOULDER] - area_width/2
low_r = pos[Train.C_SHOULDER] + area_width/2
# 領域がキャプチャ領域内に収まっているか
if upp_l[0] < 0.0 or low_r[0] > 1.0: # 左寄りor右寄り
return "ToCenter", pos, area_width, upp_l, low_r
elif upp_l[1] < 0.0 or low_r[1] > 1.0: # 上寄りor下寄り
return "StepBack", pos, area_width, upp_l, low_r
else:
# 取得できなかったときは左右寄りとする
return "ToCenter", pos, area_width, upp_l, low_r
# ----------------
# 左手
if results.left_hand_landmarks:
# 左手位置情報を全て取得
for h_id, h_lm in enumerate(results.left_hand_landmarks.landmark):
pos = np.append(pos, [[h_lm.x, h_lm.y]], axis=0)
else:
# 取得できなかったときは -1にする
for index in range(len(Hand)):
pos = np.append(pos, [[-1.0, -1.0]], axis=0)
# ----------------
# 右手
if results.right_hand_landmarks:
# 右手位置情報を全て取得
for h_id, h_lm in enumerate(results.right_hand_landmarks.landmark):
pos = np.append(pos, [[h_lm.x, h_lm.y]], axis=0)
else:
# 取得できなかったときは -1にする
for index in range(len(Hand)):
pos = np.append(pos, [[-1.0, -1.0]], axis=0)
return "OK", pos, area_width, upp_l, low_r
def GetMouthPosition(results:type):
pos:np.ndarray = np.empty((0,2)) # 手話区切り判定に使用する点群
lips_dist:np.float32 = 0.0 # 上唇と下唇の距離
lips_side_dist:np.float32 = 0.0 # 左右唇の距離
mouth_ratio :np.float32 = 0.0 # 口の縦横割合(横幅÷縦幅)
lips_close:bool = True # 口が閉じてるかどうか
# ----------------
# 唇
if results.face_landmarks:
f_lm = results.face_landmarks.landmark
face_list = [
Face.UPPER_LIP,
Face.UNDER_LIP,
Face.L_SIDE_LIP,
Face.R_SIDE_LIP,
]
for i in face_list:
pos = np.append(pos, [[f_lm[i].x, f_lm[i].y]], axis=0)
# 上唇と下唇の距離を求める
lips_dist = np.linalg.norm(pos[Mouth.UPPER_LIP] - pos[Mouth.UNDER_LIP])
# 左右唇の距離を求める
lips_side_dist = np.linalg.norm(pos[Mouth.L_SIDE_LIP] - pos[Mouth.R_SIDE_LIP])
if lips_dist <= 0.0: lips_dist = sls.GetMouthOpenThd()
mouth_ratio = lips_side_dist / lips_dist
# 口の開閉判定
if lips_dist <= sls.GetMouthOpenThd() and mouth_ratio >= sls.GetMouthOpenRatio():
lips_close = True
else:
lips_close = False
else:
# 取得できなかったときは 口を閉じてるとする
lips_dist = 0.0
mouth_ratio = 0.0
lips_close = True
# 取得できなかったときは -1にする
for index in range(len(Mouth)):
pos = np.append(pos, [[-1.0, -1.0]], axis=0)
return pos, lips_close, lips_dist, mouth_ratio
##
# メイン処理スレッド開始
def StartSignLanguage(error_func):
global hol_thread
global play_flg
hol_thread = threading.Thread(target=HolisticThread, args=[error_func,], daemon=True)
play_flg = True
hol_thread.start()
##
# メイン処理スレッド終了
def StopSignLanguage():
global hol_thread
global play_flg
play_flg = False
hol_thread.join()
##
# パラメータ更新
def UpdateParam(param):
global WinParam
print(param)
WinParam = param
##
# ショットタイマー
# 記録するフレーム数(fps)調整タイマー
def ShotCb():
global is_shot
global tm
tm.cancel()
del tm
tm = NullHandler
is_shot = True
rec_frame_num = sls.GetRecordFrameNum()
tm = threading.Timer(1/rec_frame_num, ShotCb)
tm.start()
##
# メイン処理
# 必要なスレッドの立ち上げとGUI作成
if __name__ == "__main__":
# プロセスの開始
slp.StartPreviewProcess()
slg.CreateMainWindow(
StartFunc = StartSignLanguage,
StopFunc = StopSignLanguage,
UpdateParam = UpdateParam
)
# プロセスの終了
slp.StopPreviewProcess()