Ordner erstellt
This commit is contained in:
parent
77ee05338f
commit
24a6f5f239
1103
Bilderkennung/WerWolfYolo_MediapipePose.py
Normal file
1103
Bilderkennung/WerWolfYolo_MediapipePose.py
Normal file
File diff suppressed because it is too large
Load Diff
558
Bilderkennung/gesture_runtime.py
Normal file
558
Bilderkennung/gesture_runtime.py
Normal file
@ -0,0 +1,558 @@
|
|||||||
|
import pickle
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import mediapipe as mp
|
||||||
|
import numpy as np
|
||||||
|
from mediapipe.tasks.python import vision
|
||||||
|
from sklearn.ensemble import RandomForestClassifier
|
||||||
|
from sklearn.linear_model import LogisticRegression
|
||||||
|
from sklearn.neighbors import KNeighborsClassifier
|
||||||
|
from sklearn.preprocessing import LabelEncoder, StandardScaler
|
||||||
|
from sklearn.svm import SVC
|
||||||
|
|
||||||
|
|
||||||
|
WRIST = 0
|
||||||
|
|
||||||
|
THUMB_CMC = 1
|
||||||
|
THUMB_MCP = 2
|
||||||
|
THUMB_IP = 3
|
||||||
|
THUMB_TIP = 4
|
||||||
|
|
||||||
|
INDEX_MCP = 5
|
||||||
|
INDEX_PIP = 6
|
||||||
|
INDEX_DIP = 7
|
||||||
|
INDEX_TIP = 8
|
||||||
|
|
||||||
|
MIDDLE_MCP = 9
|
||||||
|
MIDDLE_PIP = 10
|
||||||
|
MIDDLE_DIP = 11
|
||||||
|
MIDDLE_TIP = 12
|
||||||
|
|
||||||
|
RING_MCP = 13
|
||||||
|
RING_PIP = 14
|
||||||
|
RING_DIP = 15
|
||||||
|
RING_TIP = 16
|
||||||
|
|
||||||
|
PINKY_MCP = 17
|
||||||
|
PINKY_PIP = 18
|
||||||
|
PINKY_DIP = 19
|
||||||
|
PINKY_TIP = 20
|
||||||
|
|
||||||
|
HAND_CONNECTIONS = [
|
||||||
|
(0, 1), (1, 2), (2, 3), (3, 4),
|
||||||
|
(0, 5), (5, 6), (6, 7), (7, 8),
|
||||||
|
(0, 9), (9, 10), (10, 11), (11, 12),
|
||||||
|
(0, 13), (13, 14), (14, 15), (15, 16),
|
||||||
|
(0, 17), (17, 18), (18, 19), (19, 20),
|
||||||
|
(5, 9), (9, 13), (13, 17),
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_UNKNOWN_THRESHOLD = 0.7
|
||||||
|
DEFAULT_KNN_K = 3
|
||||||
|
DEFAULT_RF_TREES = 200
|
||||||
|
DEFAULT_NUM_HANDS = 2
|
||||||
|
|
||||||
|
|
||||||
|
def landmark_to_pixel(lm, width, height):
|
||||||
|
return int(lm.x * width), int(lm.y * height)
|
||||||
|
|
||||||
|
|
||||||
|
def vec2_from_landmarks(hand_landmarks):
|
||||||
|
return np.array([[lm.x, lm.y] for lm in hand_landmarks], dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def vec3_from_world_landmarks(world_landmarks):
|
||||||
|
return np.array([[lm.x, lm.y, lm.z] for lm in world_landmarks], dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def hand_scale_2d(points2d):
|
||||||
|
s = np.linalg.norm(points2d[MIDDLE_MCP] - points2d[WRIST])
|
||||||
|
return max(float(s), 1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def hand_scale_3d(points3d):
|
||||||
|
s = np.linalg.norm(points3d[MIDDLE_MCP] - points3d[WRIST])
|
||||||
|
return max(float(s), 1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_2d_features(hand_landmarks):
|
||||||
|
pts = vec2_from_landmarks(hand_landmarks)
|
||||||
|
origin = pts[WRIST].copy()
|
||||||
|
pts = pts - origin
|
||||||
|
scale = hand_scale_2d(pts + origin)
|
||||||
|
pts = pts / scale
|
||||||
|
return pts.flatten().astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_3d_features(world_landmarks):
|
||||||
|
pts = vec3_from_world_landmarks(world_landmarks)
|
||||||
|
origin = pts[WRIST].copy()
|
||||||
|
pts = pts - origin
|
||||||
|
scale = hand_scale_3d(pts + origin)
|
||||||
|
pts = pts / scale
|
||||||
|
return pts.flatten().astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def angle_between(v1, v2):
|
||||||
|
n1 = np.linalg.norm(v1)
|
||||||
|
n2 = np.linalg.norm(v2)
|
||||||
|
if n1 < 1e-6 or n2 < 1e-6:
|
||||||
|
return 0.0
|
||||||
|
c = np.dot(v1, v2) / (n1 * n2)
|
||||||
|
c = np.clip(c, -1.0, 1.0)
|
||||||
|
return float(np.arccos(c))
|
||||||
|
|
||||||
|
|
||||||
|
def joint_angle(points, a, b, c):
|
||||||
|
ba = points[a] - points[b]
|
||||||
|
bc = points[c] - points[b]
|
||||||
|
return angle_between(ba, bc)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_angle_features(world_landmarks):
|
||||||
|
pts = vec3_from_world_landmarks(world_landmarks)
|
||||||
|
|
||||||
|
thumb_angle_1 = joint_angle(pts, THUMB_CMC, THUMB_MCP, THUMB_IP)
|
||||||
|
thumb_angle_2 = joint_angle(pts, THUMB_MCP, THUMB_IP, THUMB_TIP)
|
||||||
|
|
||||||
|
index_angle_1 = joint_angle(pts, INDEX_MCP, INDEX_PIP, INDEX_DIP)
|
||||||
|
index_angle_2 = joint_angle(pts, INDEX_PIP, INDEX_DIP, INDEX_TIP)
|
||||||
|
|
||||||
|
middle_angle_1 = joint_angle(pts, MIDDLE_MCP, MIDDLE_PIP, MIDDLE_DIP)
|
||||||
|
middle_angle_2 = joint_angle(pts, MIDDLE_PIP, MIDDLE_DIP, MIDDLE_TIP)
|
||||||
|
|
||||||
|
ring_angle_1 = joint_angle(pts, RING_MCP, RING_PIP, RING_DIP)
|
||||||
|
ring_angle_2 = joint_angle(pts, RING_PIP, RING_DIP, RING_TIP)
|
||||||
|
|
||||||
|
pinky_angle_1 = joint_angle(pts, PINKY_MCP, PINKY_PIP, PINKY_DIP)
|
||||||
|
pinky_angle_2 = joint_angle(pts, PINKY_PIP, PINKY_DIP, PINKY_TIP)
|
||||||
|
|
||||||
|
wrist_to_thumb = pts[THUMB_TIP] - pts[WRIST]
|
||||||
|
wrist_to_index = pts[INDEX_TIP] - pts[WRIST]
|
||||||
|
wrist_to_middle = pts[MIDDLE_TIP] - pts[WRIST]
|
||||||
|
wrist_to_ring = pts[RING_TIP] - pts[WRIST]
|
||||||
|
wrist_to_pinky = pts[PINKY_TIP] - pts[WRIST]
|
||||||
|
|
||||||
|
spread_thumb_index = angle_between(wrist_to_thumb, wrist_to_index)
|
||||||
|
spread_index_middle = angle_between(wrist_to_index, wrist_to_middle)
|
||||||
|
spread_middle_ring = angle_between(wrist_to_middle, wrist_to_ring)
|
||||||
|
spread_ring_pinky = angle_between(wrist_to_ring, wrist_to_pinky)
|
||||||
|
|
||||||
|
feats = np.array([
|
||||||
|
thumb_angle_1, thumb_angle_2,
|
||||||
|
index_angle_1, index_angle_2,
|
||||||
|
middle_angle_1, middle_angle_2,
|
||||||
|
ring_angle_1, ring_angle_2,
|
||||||
|
pinky_angle_1, pinky_angle_2,
|
||||||
|
spread_thumb_index,
|
||||||
|
spread_index_middle,
|
||||||
|
spread_middle_ring,
|
||||||
|
spread_ring_pinky,
|
||||||
|
], dtype=np.float32)
|
||||||
|
|
||||||
|
return feats
|
||||||
|
|
||||||
|
|
||||||
|
def extract_feature_family(hand_landmarks, world_landmarks, family_name):
|
||||||
|
family_name = family_name.upper()
|
||||||
|
|
||||||
|
if family_name == "2D":
|
||||||
|
return normalize_2d_features(hand_landmarks)
|
||||||
|
if family_name == "3D":
|
||||||
|
return normalize_3d_features(world_landmarks)
|
||||||
|
if family_name == "ANGLES":
|
||||||
|
return extract_angle_features(world_landmarks)
|
||||||
|
|
||||||
|
raise ValueError(f"Unknown feature family: {family_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def load_pickle(path):
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
return pickle.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def family_key_from_name(name):
|
||||||
|
name = name.upper()
|
||||||
|
if name == "2D":
|
||||||
|
return "features_2d"
|
||||||
|
if name == "3D":
|
||||||
|
return "features_3d"
|
||||||
|
if name == "ANGLES":
|
||||||
|
return "features_angles"
|
||||||
|
raise ValueError(f"Unknown family name: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def ranking_key_from_method(method):
|
||||||
|
method = method.lower()
|
||||||
|
if method == "f":
|
||||||
|
return "rank_f"
|
||||||
|
if method == "mi":
|
||||||
|
return "rank_mi"
|
||||||
|
if method == "fisher":
|
||||||
|
return "rank_fisher"
|
||||||
|
raise ValueError(f"Unknown ranking method: {method}")
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_training_data(data, family_key):
|
||||||
|
X = []
|
||||||
|
y = []
|
||||||
|
|
||||||
|
for label in sorted(data.keys()):
|
||||||
|
for sample in data[label]:
|
||||||
|
X.append(np.asarray(sample[family_key], dtype=np.float32))
|
||||||
|
y.append(label)
|
||||||
|
|
||||||
|
X = np.vstack([x.reshape(1, -1) for x in X])
|
||||||
|
y = np.asarray(y)
|
||||||
|
return X, y
|
||||||
|
|
||||||
|
|
||||||
|
def build_classifier(classifier_type):
|
||||||
|
classifier_type = classifier_type.lower()
|
||||||
|
|
||||||
|
if classifier_type == "knn":
|
||||||
|
return KNeighborsClassifier(n_neighbors=DEFAULT_KNN_K)
|
||||||
|
|
||||||
|
if classifier_type == "svm":
|
||||||
|
return SVC(kernel="rbf", probability=True)
|
||||||
|
|
||||||
|
if classifier_type == "rf":
|
||||||
|
return RandomForestClassifier(
|
||||||
|
n_estimators=DEFAULT_RF_TREES,
|
||||||
|
random_state=42,
|
||||||
|
class_weight=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if classifier_type == "logreg":
|
||||||
|
return LogisticRegression(
|
||||||
|
max_iter=2000,
|
||||||
|
solver="lbfgs",
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ValueError(f"Unknown classifier type: {classifier_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_feature_indices(analysis, family_key, ranking_method, num_features):
|
||||||
|
rank_key = ranking_key_from_method(ranking_method)
|
||||||
|
rank = analysis[family_key][rank_key]
|
||||||
|
return np.asarray(rank[:num_features], dtype=np.int32)
|
||||||
|
|
||||||
|
|
||||||
|
def train_model(data, analysis, feature_family, classifier_type, ranking_method, num_features):
|
||||||
|
family_key = family_key_from_name(feature_family)
|
||||||
|
|
||||||
|
X, y = flatten_training_data(data, family_key)
|
||||||
|
selected_idx = get_selected_feature_indices(analysis, family_key, ranking_method, num_features)
|
||||||
|
|
||||||
|
X_sel = X[:, selected_idx]
|
||||||
|
|
||||||
|
scaler = StandardScaler()
|
||||||
|
X_scaled = scaler.fit_transform(X_sel)
|
||||||
|
|
||||||
|
label_encoder = LabelEncoder()
|
||||||
|
y_enc = label_encoder.fit_transform(y)
|
||||||
|
|
||||||
|
clf = build_classifier(classifier_type)
|
||||||
|
clf.fit(X_scaled, y_enc)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"classifier": clf,
|
||||||
|
"scaler": scaler,
|
||||||
|
"label_encoder": label_encoder,
|
||||||
|
"selected_idx": selected_idx,
|
||||||
|
"family_key": family_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def predict_with_confidence(model_bundle, feature_vector, unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD):
|
||||||
|
selected_idx = model_bundle["selected_idx"]
|
||||||
|
clf = model_bundle["classifier"]
|
||||||
|
scaler = model_bundle["scaler"]
|
||||||
|
label_encoder = model_bundle["label_encoder"]
|
||||||
|
|
||||||
|
x = feature_vector[selected_idx].reshape(1, -1)
|
||||||
|
x = scaler.transform(x)
|
||||||
|
|
||||||
|
pred_id = clf.predict(x)[0]
|
||||||
|
pred_label = label_encoder.inverse_transform([pred_id])[0]
|
||||||
|
|
||||||
|
if hasattr(clf, "predict_proba"):
|
||||||
|
probs = clf.predict_proba(x)[0]
|
||||||
|
conf = float(np.max(probs))
|
||||||
|
else:
|
||||||
|
conf = 1.0
|
||||||
|
|
||||||
|
if conf < unknown_threshold:
|
||||||
|
return "UNKNOWN", conf
|
||||||
|
|
||||||
|
return pred_label, conf
|
||||||
|
|
||||||
|
|
||||||
|
def create_hand_landmarker(
|
||||||
|
model_path="hand_landmarker.task",
|
||||||
|
num_hands=DEFAULT_NUM_HANDS,
|
||||||
|
min_hand_detection_confidence=0.5,
|
||||||
|
min_hand_presence_confidence=0.5,
|
||||||
|
min_tracking_confidence=0.5,
|
||||||
|
):
|
||||||
|
BaseOptions = mp.tasks.BaseOptions
|
||||||
|
HandLandmarkerOptions = vision.HandLandmarkerOptions
|
||||||
|
VisionRunningMode = vision.RunningMode
|
||||||
|
|
||||||
|
options = HandLandmarkerOptions(
|
||||||
|
base_options=BaseOptions(model_asset_path=model_path),
|
||||||
|
running_mode=VisionRunningMode.IMAGE,
|
||||||
|
num_hands=num_hands,
|
||||||
|
min_hand_detection_confidence=min_hand_detection_confidence,
|
||||||
|
min_hand_presence_confidence=min_hand_presence_confidence,
|
||||||
|
min_tracking_confidence=min_tracking_confidence,
|
||||||
|
)
|
||||||
|
|
||||||
|
return vision.HandLandmarker.create_from_options(options)
|
||||||
|
|
||||||
|
|
||||||
|
def load_gesture_model_bundle(
|
||||||
|
data_pickle="gesture_samples.pkl",
|
||||||
|
analysis_pickle="gesture_feature_analysis.pkl",
|
||||||
|
feature_family="ANGLES",
|
||||||
|
classifier_type="knn",
|
||||||
|
ranking_method="fisher",
|
||||||
|
num_features=10,
|
||||||
|
unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD,
|
||||||
|
hand_model_path="hand_landmarker.task",
|
||||||
|
):
|
||||||
|
if not Path(data_pickle).exists():
|
||||||
|
raise FileNotFoundError(f"Missing {data_pickle}")
|
||||||
|
if not Path(analysis_pickle).exists():
|
||||||
|
raise FileNotFoundError(f"Missing {analysis_pickle}")
|
||||||
|
|
||||||
|
data = load_pickle(data_pickle)
|
||||||
|
analysis = load_pickle(analysis_pickle)
|
||||||
|
|
||||||
|
model_bundle = train_model(
|
||||||
|
data=data,
|
||||||
|
analysis=analysis,
|
||||||
|
feature_family=feature_family,
|
||||||
|
classifier_type=classifier_type,
|
||||||
|
ranking_method=ranking_method,
|
||||||
|
num_features=num_features,
|
||||||
|
)
|
||||||
|
|
||||||
|
hand_landmarker = create_hand_landmarker(model_path=hand_model_path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"model_bundle": model_bundle,
|
||||||
|
"hand_landmarker": hand_landmarker,
|
||||||
|
"feature_family": feature_family,
|
||||||
|
"classifier_type": classifier_type,
|
||||||
|
"ranking_method": ranking_method,
|
||||||
|
"num_features": num_features,
|
||||||
|
"unknown_threshold": unknown_threshold,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _put_text_with_background(frame, text, origin, font_scale=0.6, text_color=(255, 255, 255), bg_color=(0, 0, 0)):
|
||||||
|
x, y = origin
|
||||||
|
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||||
|
thickness = 2
|
||||||
|
(text_w, text_h), baseline = cv2.getTextSize(text, font, font_scale, thickness)
|
||||||
|
top_left = (x, max(0, y - text_h - baseline - 6))
|
||||||
|
bottom_right = (x + text_w + 6, max(0, y + 4))
|
||||||
|
cv2.rectangle(frame, top_left, bottom_right, bg_color, -1)
|
||||||
|
cv2.putText(
|
||||||
|
frame,
|
||||||
|
text,
|
||||||
|
(x + 3, y - 3),
|
||||||
|
font,
|
||||||
|
font_scale,
|
||||||
|
text_color,
|
||||||
|
thickness,
|
||||||
|
cv2.LINE_AA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _ray_to_frame_bounds(start_point, direction_vector, frame_w, frame_h):
|
||||||
|
x0, y0 = start_point
|
||||||
|
dx, dy = direction_vector
|
||||||
|
|
||||||
|
norm = float(np.hypot(dx, dy))
|
||||||
|
if norm < 1e-6:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dx /= norm
|
||||||
|
dy /= norm
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
if abs(dx) > 1e-6:
|
||||||
|
for x_bound in (0, frame_w - 1):
|
||||||
|
t = (x_bound - x0) / dx
|
||||||
|
if t <= 0:
|
||||||
|
continue
|
||||||
|
y = y0 + t * dy
|
||||||
|
if 0 <= y <= frame_h - 1:
|
||||||
|
candidates.append((t, (int(x_bound), int(round(y)))))
|
||||||
|
|
||||||
|
if abs(dy) > 1e-6:
|
||||||
|
for y_bound in (0, frame_h - 1):
|
||||||
|
t = (y_bound - y0) / dy
|
||||||
|
if t <= 0:
|
||||||
|
continue
|
||||||
|
x = x0 + t * dx
|
||||||
|
if 0 <= x <= frame_w - 1:
|
||||||
|
candidates.append((t, (int(round(x)), int(y_bound))))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
return None
|
||||||
|
|
||||||
|
candidates.sort(key=lambda item: item[0])
|
||||||
|
return candidates[0][1]
|
||||||
|
|
||||||
|
|
||||||
|
def draw_pointing_ray(frame, hand_landmarks, crop_x, crop_y, crop_w, crop_h, color=(255, 255, 0), thickness=3):
|
||||||
|
index_tip_x, index_tip_y = landmark_to_pixel(hand_landmarks[INDEX_TIP], crop_w, crop_h)
|
||||||
|
index_mcp_x, index_mcp_y = landmark_to_pixel(hand_landmarks[INDEX_MCP], crop_w, crop_h)
|
||||||
|
|
||||||
|
start_point = (index_tip_x + crop_x, index_tip_y + crop_y)
|
||||||
|
direction = (
|
||||||
|
(index_tip_x - index_mcp_x),
|
||||||
|
(index_tip_y - index_mcp_y),
|
||||||
|
)
|
||||||
|
|
||||||
|
end_point = _ray_to_frame_bounds(start_point, direction, frame.shape[1], frame.shape[0])
|
||||||
|
if end_point is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
cv2.line(frame, start_point, end_point, color, thickness)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_hand_on_frame(frame, hand_landmarks, crop_x, crop_y, crop_w, crop_h, label_text=""):
|
||||||
|
points = []
|
||||||
|
|
||||||
|
for a, b in HAND_CONNECTIONS:
|
||||||
|
x1, y1 = landmark_to_pixel(hand_landmarks[a], crop_w, crop_h)
|
||||||
|
x2, y2 = landmark_to_pixel(hand_landmarks[b], crop_w, crop_h)
|
||||||
|
x1 += crop_x
|
||||||
|
y1 += crop_y
|
||||||
|
x2 += crop_x
|
||||||
|
y2 += crop_y
|
||||||
|
cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
||||||
|
|
||||||
|
for i, lm in enumerate(hand_landmarks):
|
||||||
|
x, y = landmark_to_pixel(lm, crop_w, crop_h)
|
||||||
|
x += crop_x
|
||||||
|
y += crop_y
|
||||||
|
points.append((x, y))
|
||||||
|
color = (0, 0, 255) if i in [THUMB_TIP, INDEX_TIP, MIDDLE_TIP, RING_TIP, PINKY_TIP] else (255, 255, 0)
|
||||||
|
radius = 5 if i in [THUMB_TIP, INDEX_TIP, MIDDLE_TIP, RING_TIP, PINKY_TIP] else 3
|
||||||
|
cv2.circle(frame, (x, y), radius, color, -1)
|
||||||
|
|
||||||
|
if label_text and points:
|
||||||
|
label_x = max(0, min(x for x, _ in points))
|
||||||
|
label_y = max(20, min(y for _, y in points) - 10)
|
||||||
|
_put_text_with_background(frame, label_text, (label_x, label_y), font_scale=0.55)
|
||||||
|
|
||||||
|
|
||||||
|
class GestureRecognizer:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data_pickle="gesture_samples.pkl",
|
||||||
|
analysis_pickle="gesture_feature_analysis.pkl",
|
||||||
|
feature_family="ANGLES",
|
||||||
|
classifier_type="knn",
|
||||||
|
ranking_method="fisher",
|
||||||
|
num_features=10,
|
||||||
|
unknown_threshold=DEFAULT_UNKNOWN_THRESHOLD,
|
||||||
|
hand_model_path="hand_landmarker.task",
|
||||||
|
):
|
||||||
|
bundle = load_gesture_model_bundle(
|
||||||
|
data_pickle=data_pickle,
|
||||||
|
analysis_pickle=analysis_pickle,
|
||||||
|
feature_family=feature_family,
|
||||||
|
classifier_type=classifier_type,
|
||||||
|
ranking_method=ranking_method,
|
||||||
|
num_features=num_features,
|
||||||
|
unknown_threshold=unknown_threshold,
|
||||||
|
hand_model_path=hand_model_path,
|
||||||
|
)
|
||||||
|
self.model_bundle = bundle["model_bundle"]
|
||||||
|
self.hand_landmarker = bundle["hand_landmarker"]
|
||||||
|
self.feature_family = bundle["feature_family"]
|
||||||
|
self.classifier_type = bundle["classifier_type"]
|
||||||
|
self.ranking_method = bundle["ranking_method"]
|
||||||
|
self.num_features = bundle["num_features"]
|
||||||
|
self.unknown_threshold = bundle["unknown_threshold"]
|
||||||
|
|
||||||
|
def detect_gestures(self, crop_bgr):
|
||||||
|
crop_rgb = cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB)
|
||||||
|
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=crop_rgb)
|
||||||
|
result = self.hand_landmarker.detect(mp_image)
|
||||||
|
|
||||||
|
detections = []
|
||||||
|
if not result.hand_landmarks or not result.hand_world_landmarks:
|
||||||
|
return detections
|
||||||
|
|
||||||
|
hand_count = min(len(result.hand_landmarks), len(result.hand_world_landmarks))
|
||||||
|
for hand_index in range(hand_count):
|
||||||
|
hand_landmarks = result.hand_landmarks[hand_index]
|
||||||
|
world_landmarks = result.hand_world_landmarks[hand_index]
|
||||||
|
|
||||||
|
feature_vector = extract_feature_family(
|
||||||
|
hand_landmarks=hand_landmarks,
|
||||||
|
world_landmarks=world_landmarks,
|
||||||
|
family_name=self.feature_family,
|
||||||
|
)
|
||||||
|
pred_label, conf = predict_with_confidence(
|
||||||
|
self.model_bundle,
|
||||||
|
feature_vector,
|
||||||
|
unknown_threshold=self.unknown_threshold,
|
||||||
|
)
|
||||||
|
|
||||||
|
handedness_text = ""
|
||||||
|
if result.handedness and len(result.handedness) > hand_index and len(result.handedness[hand_index]) > 0:
|
||||||
|
handedness_text = result.handedness[hand_index][0].category_name
|
||||||
|
|
||||||
|
detections.append(
|
||||||
|
{
|
||||||
|
"hand_landmarks": hand_landmarks,
|
||||||
|
"label": pred_label,
|
||||||
|
"confidence": conf,
|
||||||
|
"handedness": handedness_text,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return detections
|
||||||
|
|
||||||
|
def draw_gesture(self, frame, detection, crop_x, crop_y, crop_w, crop_h):
|
||||||
|
label = detection["label"]
|
||||||
|
confidence = detection["confidence"]
|
||||||
|
handedness_text = detection["handedness"]
|
||||||
|
|
||||||
|
if handedness_text:
|
||||||
|
label_text = f"{handedness_text}: {label} {confidence:.2f}"
|
||||||
|
else:
|
||||||
|
label_text = f"{label} {confidence:.2f}"
|
||||||
|
|
||||||
|
draw_hand_on_frame(
|
||||||
|
frame,
|
||||||
|
detection["hand_landmarks"],
|
||||||
|
crop_x,
|
||||||
|
crop_y,
|
||||||
|
crop_w,
|
||||||
|
crop_h,
|
||||||
|
label_text=label_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
if label.upper() == "A":
|
||||||
|
draw_pointing_ray(
|
||||||
|
frame,
|
||||||
|
detection["hand_landmarks"],
|
||||||
|
crop_x,
|
||||||
|
crop_y,
|
||||||
|
crop_w,
|
||||||
|
crop_h,
|
||||||
|
)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.hand_landmarker.close()
|
||||||
@ -11,4 +11,8 @@
|
|||||||
<PackageReference Include="CoreOSC" Version="1.0.0" />
|
<PackageReference Include="CoreOSC" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Sprachausgabe\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user