-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
初期追加
- Loading branch information
Showing
7 changed files
with
375 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import pyaudio | ||
import numpy as np | ||
import pyautogui | ||
|
||
# 音声キャプチャの設定 | ||
CHUNK = 1024 | ||
FORMAT = pyaudio.paInt16 | ||
CHANNELS = 1 | ||
RATE = 44100 | ||
THRESHOLD = 2000 # 拍手の音量を検知するための閾値 | ||
CLAP_DURATION = 0.1 # 拍手の持続時間(秒) | ||
|
||
def detect_clap(data): | ||
"""音声データから拍手を検出する関数""" | ||
audio_data = np.frombuffer(data, dtype=np.int16) | ||
peak = np.max(np.abs(audio_data)) | ||
|
||
if peak > THRESHOLD: | ||
return True | ||
return False | ||
|
||
# PyAudioの初期化 | ||
p = pyaudio.PyAudio() | ||
|
||
# ストリームの開始 | ||
stream = p.open(format=FORMAT, | ||
channels=CHANNELS, | ||
rate=RATE, | ||
input=True, | ||
frames_per_buffer=CHUNK) | ||
|
||
print("拍手を検知しています...") | ||
|
||
try: | ||
while True: | ||
data = stream.read(CHUNK) | ||
if detect_clap(data): | ||
print("拍手を検知しました!") | ||
pyautogui.click() | ||
except KeyboardInterrupt: | ||
print("終了します...") | ||
|
||
# ストリームの停止と解放 | ||
stream.stop_stream() | ||
stream.close() | ||
p.terminate() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import numpy as np | ||
import dlib | ||
import cv2 | ||
from cv2 import Mat | ||
#顔の特徴点検出モデルの読み込み | ||
face_detector = dlib.get_frontal_face_detector() | ||
# Dlibの顔ランドマーク検出器モデルの読み込み | ||
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') | ||
|
||
#カメラの設定 | ||
cap = cv2.VideoCapture(0) # 0はデフォルトカメラ | ||
ret, frame = cap.read() | ||
|
||
#マスク色 | ||
bgr_black = (0, 0, 0) # BGR黒色 | ||
bgr_white = (255, 255, 255) # BGR白色 | ||
|
||
# 検出点の色定義 | ||
bgr_blue = (255, 0, 0) | ||
|
||
# 点の座標を表すためのPointクラス | ||
class Point: | ||
def __init__(self, x, y): | ||
self.x = x | ||
self.y = y | ||
|
||
@classmethod | ||
def from_contour(cls, cnt): | ||
# cnt が空の場合は None を返す | ||
if len(cnt) == 0: | ||
return None | ||
|
||
# 輪郭の最小外接円の中心座標を取得 | ||
(x, y), radius = cv2.minEnclosingCircle(cnt) | ||
center = cls(int(x), int(y)) | ||
return center | ||
|
||
# 前回の目の座標を保持する (左目、右目) | ||
get_contouring.prev_points = [None, None] | ||
|
||
# 評価関数 (前回の座標からの距離が小さいほど高スコア) | ||
def eval_contour(cnt, prev): | ||
if prev is None: | ||
return 0 | ||
return 1.0 / (cv2.pointPolygonTest(cnt, (prev.x, prev.y), True) + 1) | ||
|
||
def get_one_face(gray_image: Mat) -> Mat | None: | ||
faces = face_detector(gray_image) | ||
if len(faces) == 0: | ||
return None | ||
face = faces[0] | ||
|
||
return face | ||
|
||
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) | ||
face = get_one_face(gray_frame) | ||
|
||
display = frame.copy() | ||
|
||
if face is not None: | ||
landmarks = predictor(gray_frame, face) # 特徴点を表す座標のlist(len=68) | ||
|
||
def extract_eyes(frame: Mat, lps: list[Point], rps: list[Point]) -> Mat: | ||
# make mask with eyes white and other black | ||
mask = np.zeros(frame.shape[:2], dtype=np.uint8) | ||
mask = make_hole_on_mask(mask, lps) | ||
mask = make_hole_on_mask(mask, rps) | ||
mask = cv2.dilate(mask, np.ones((9, 9), np.uint8), 5) | ||
|
||
# attach mask on frame | ||
masked_frame = cv2.bitwise_and(frame, frame, mask=mask) | ||
masked_area = (masked_frame == bgr_black).all(axis=2) | ||
masked_frame[masked_area] = bgr_white | ||
return masked_frame # 目以外が白塗りされた画像 | ||
|
||
|
||
# 目の輪郭を検知して円を描画する関数 | ||
def get_contouring( | ||
thresh, face_mid_y, frame=None, is_right: bool = False | ||
) -> Point | None: | ||
index = int(is_right) | ||
cnts, _ = cv2.findContours( | ||
thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE | ||
) | ||
try: | ||
cnt = max( | ||
cnts, | ||
key=lambda cnt: eval_contour( | ||
cnt, get_contouring.prev_points[index] | ||
), | ||
) | ||
c = Point.from_contour(cnt) | ||
if c is None: | ||
raise ValueError("point is Null.") | ||
if is_right: | ||
c.x += face_mid_y | ||
get_contouring.prev_points[index] = c | ||
if frame is not None: | ||
cv2.circle(frame, (c.x, c.y), 2, bgr_blue, 2) | ||
return Point(c.x, c.y) | ||
except ValueError: | ||
pass | ||
except ZeroDivisionError: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import cv2 | ||
threshold = 30 | ||
_, thresh = cv2.threshold( | ||
eyes_frame_gray, threshold, 255, cv2.THRESH_BINARY | ||
) | ||
thresh_erode = cv2.erode(thresh, None, iterations=2) | ||
thresh_dilate = cv2.dilate(thresh_erode, None, iterations=4) | ||
thresh_blur = cv2.medianBlur(thresh_dilate, 3) | ||
thresh_inv = cv2.bitwise_not(thresh_blur) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import cv2 | ||
import numpy as np | ||
import dlib | ||
|
||
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") | ||
|
||
def extract_eyes(frame, lps, rps): | ||
mask = np.zeros(frame.shape[:2], dtype=np.uint8) | ||
if lps: | ||
mask = make_hole_on_mask(mask, lps) | ||
if rps: | ||
mask = make_hole_on_mask(mask, rps) | ||
mask = cv2.dilate(mask, np.ones((9, 9), np.uint8), 5) | ||
masked_frame = cv2.bitwise_and(frame, frame, mask=mask) | ||
masked_area = (masked_frame == [0, 0, 0]).all(axis=2) | ||
masked_frame[masked_area] = [255, 255, 255] | ||
return masked_frame | ||
|
||
def make_hole_on_mask(mask, points): | ||
points = np.array(points, dtype=np.int32) | ||
cv2.fillPoly(mask, [points], 255) | ||
return mask | ||
|
||
def get_eye_center(points): | ||
n = len(points) | ||
if n == 0: | ||
raise ValueError("List has no item.") | ||
sum_point = np.sum(points, axis=0) | ||
return sum_point // n | ||
|
||
def get_eye_contours(thresh, face_mid_y, frame=None, is_right=False): | ||
cnts, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) | ||
try: | ||
cnt = max(cnts, key=cv2.contourArea) | ||
M = cv2.moments(cnt) | ||
cX = int(M['m10'] / M['m00']) | ||
cY = int(M['m01'] / M['m00']) | ||
if is_right: | ||
cX += face_mid_y | ||
if frame is not None: | ||
cv2.circle(frame, (cX, cY), 2, (255, 0, 0), 2) | ||
return (cX, cY) | ||
except ValueError: | ||
pass | ||
except ZeroDivisionError: | ||
pass | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import dlib | ||
|
||
face_detector = dlib.get_frontal_face_detector() | ||
|
||
def get_one_face(gray_image): | ||
faces = face_detector(gray_image) | ||
if len(faces) == 0: | ||
return None | ||
return faces[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import cv2 | ||
import numpy as np | ||
from pynput.mouse import Controller | ||
from face_detect import get_one_face | ||
from eye_detect import extract_eyes, get_eye_center, get_eye_contours | ||
import dlib | ||
import logging | ||
|
||
# ログ設定 | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
# dlibの顔特徴点検出器を初期化 | ||
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") | ||
|
||
# マウスコントローラーの初期化 | ||
mouse = Controller() | ||
|
||
def scroll_based_on_eye_position(eye_center, frame_center): | ||
""" | ||
黒目の中心位置に基づいてスクロールを実行する関数 | ||
""" | ||
diff_x = eye_center[0] - frame_center[0] | ||
if diff_x > 15: | ||
mouse.scroll(0, -1) # 下方向にスクロール | ||
elif diff_x < -15: | ||
mouse.scroll(0, 1) # 上方向にスクロール | ||
|
||
def main(): | ||
""" | ||
メイン関数:カメラから映像を取得し、黒目の位置を検出してスクロール操作を行う | ||
""" | ||
cap = cv2.VideoCapture(0) # カメラのキャプチャを開始 | ||
while True: | ||
ret, frame = cap.read() # フレームをキャプチャ | ||
if not ret: | ||
break | ||
|
||
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # フレームをグレースケールに変換 | ||
face = get_one_face(gray_frame) # 顔を検出 | ||
if face is not None: | ||
# 顔のランドマーク(特徴点)を取得 | ||
landmarks = predictor(gray_frame, face) | ||
left_eye_points = [(landmarks.part(i).x, landmarks.part(i).y) for i in range(36, 42)] | ||
|
||
# 左目の部分を抽出 | ||
masked_frame = extract_eyes(frame, left_eye_points, None) | ||
eyes_gray = cv2.cvtColor(masked_frame, cv2.COLOR_BGR2GRAY) # 再度グレースケールに変換 | ||
|
||
# 閾値処理 | ||
threshold = 50 # ここを調整して最適な値を見つける | ||
_, thresh = cv2.threshold(eyes_gray, threshold, 255, cv2.THRESH_BINARY) | ||
thresh = cv2.erode(thresh, None, iterations=2) | ||
thresh = cv2.dilate(thresh, None, iterations=4) | ||
thresh = cv2.medianBlur(thresh, 3) | ||
thresh_inv = cv2.bitwise_not(thresh) | ||
|
||
# 顔の中央のy座標を計算 | ||
face_mid_y = (face.left() + face.right()) // 2 | ||
|
||
# 左目の黒目の中心を検出 | ||
left_eye_center = get_eye_contours(thresh_inv, face_mid_y, frame, is_right=False) | ||
|
||
if left_eye_center: | ||
logging.info(f"Left eye center detected at: {left_eye_center}") | ||
# 左目の黒目の中心を赤色で表示 | ||
cv2.circle(frame, left_eye_center, 2, (0, 0, 255), 2) | ||
|
||
# フレームの中心を計算 | ||
frame_center = (frame.shape[1] // 2, frame.shape[0] // 2) | ||
# スクロール操作を実行 | ||
scroll_based_on_eye_position(left_eye_center, frame_center) | ||
|
||
cv2.imshow("Eye Tracking", frame) # フレームを表示 | ||
if cv2.waitKey(1) & 0xFF == ord('q'): # 'q'キーが押されたらループを終了 | ||
break | ||
cap.release() # カメラのキャプチャを解放 | ||
cv2.destroyAllWindows() # すべてのOpenCVウィンドウを閉じる | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import cv2 | ||
import numpy as np | ||
from pynput.mouse import Controller | ||
from face_detect import get_one_face | ||
from eye_detect import extract_eyes, get_eye_center, get_eye_contours | ||
import dlib | ||
import logging | ||
|
||
# ログ設定 | ||
logging.basicConfig(level=logging.INFO) | ||
|
||
# dlibの顔特徴点検出器を初期化 | ||
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") | ||
|
||
# マウスコントローラーの初期化 | ||
mouse = Controller() | ||
|
||
def scroll_based_on_eye_position(eye_center, frame_center): | ||
""" | ||
黒目の中心位置に基づいてスクロールを実行する関数 | ||
""" | ||
diff_x = eye_center[0] - frame_center[0] | ||
if diff_x > 15: | ||
mouse.scroll(0, -1) # 下方向にスクロール | ||
elif diff_x < -15: | ||
mouse.scroll(0, 1) # 上方向にスクロール | ||
|
||
def main(): | ||
""" | ||
メイン関数:カメラから映像を取得し、黒目の位置を検出してスクロール操作を行う | ||
""" | ||
cap = cv2.VideoCapture(0) # カメラのキャプチャを開始 | ||
while True: | ||
ret, frame = cap.read() # フレームをキャプチャ | ||
if not ret: | ||
break | ||
|
||
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # フレームをグレースケールに変換 | ||
face = get_one_face(gray_frame) # 顔を検出 | ||
if face is not None: | ||
# 顔のランドマーク(特徴点)を取得 | ||
landmarks = predictor(gray_frame, face) | ||
right_eye_points = [(landmarks.part(i).x, landmarks.part(i).y) for i in range(42, 48)] | ||
|
||
# 右目の部分を抽出 | ||
masked_frame = extract_eyes(frame, [], right_eye_points) | ||
eyes_gray = cv2.cvtColor(masked_frame, cv2.COLOR_BGR2GRAY) # 再度グレースケールに変換 | ||
|
||
# 閾値処理 | ||
threshold = 50 # ここを調整して最適な値を見つける | ||
_, thresh = cv2.threshold(eyes_gray, threshold, 255, cv2.THRESH_BINARY) | ||
thresh = cv2.erode(thresh, None, iterations=2) | ||
thresh = cv2.dilate(thresh, None, iterations=4) | ||
thresh = cv2.medianBlur(thresh, 3) | ||
thresh_inv = cv2.bitwise_not(thresh) | ||
|
||
# 顔の中央のy座標を計算 | ||
face_mid_y = (face.left() + face.right()) // 2 | ||
|
||
# 右目の黒目の中心を検出 | ||
right_eye_center = get_eye_contours(thresh_inv, face_mid_y, frame, is_right=True) | ||
|
||
if right_eye_center: | ||
logging.info(f"Right eye center detected at: {right_eye_center}") | ||
# 右目の黒目の中心を緑色で表示 | ||
cv2.circle(frame, right_eye_center, 2, (0, 255, 0), 2) | ||
|
||
# フレームの中心を計算 | ||
frame_center = (frame.shape[1] // 2, frame.shape[0] // 2) | ||
# スクロール操作を実行 | ||
scroll_based_on_eye_position(right_eye_center, frame_center) | ||
|
||
cv2.imshow("Eye Tracking", frame) # フレームを表示 | ||
if cv2.waitKey(1) & 0xFF == ord('q'): # 'q'キーが押されたらループを終了 | ||
break | ||
cap.release() # カメラのキャプチャを解放 | ||
cv2.destroyAllWindows() # すべてのOpenCVウィンドウを閉じる | ||
|
||
if __name__ == "__main__": | ||
main() |