Google Mediapipe
Google Mediapipe 針對即時串流媒體提供了開放原始碼且跨平台的機器學習解決方案,不需要深度攝影機就能偵測臉部、臉部格點、姿勢、2D物件體積以及手指等等,非常厲害。支援的平台包含 Android, iOS, C++ python、JavaScript 與自家的 Coral 運算裝置等平台,另外經過 CAVEDU 實際測試,在 Jetson nano 與 Raspberry Pi 上都有很不錯的執行速度。
Google Mediapipe 提供了非常多立即可用的應用,根據首頁說明,目前即可用的方案約有16種,請注意,目前並非所有平台都支援所有方案,請根據所要的應用來看看是否有更快上手的方法。但基礎應用如臉部偵測、臉部網格、手勢、姿勢等則各平台都有。
迫不急待想要先玩一下的朋友,mediapipe 提供了 colab 的版本,直接點開來就可以玩啦,如以下就是 hand 範例 的 colab 執行畫面,自行上傳一張有手的照片就可以辨識手部的細微動作與各關節位置。對於各個方案所使用的神經網路模型請點我
Mediapipe 姿態偵測結合 Arduino 首次接觸就上手
這次要做一個自虐的專題,偵測深蹲是否到位,其實是檢測大腿與小腿的夾角是否小於指定角度,看看怎樣使用 mediapipe 來做到喔!分成兩個版本:[Mediapipe 結合 Arduino 首次接觸就上手] 以及 [純 Mediapipe (超純)]。兩者差異在於 python 端會根據辨識結果發送訊號給 Arduino 首次接觸就上手,來看影片吧 (大家都要減肥了QQ)
以下是 mediapipe POSE api 的人體關節點定義,可看到兩腿的髖、膝與踝關節分別為 24 26 28 與 23 25 27。
請用以下指令安裝 mediapipe (python)
pip install mediapipe
如果執行本範例程式出現錯誤,請根據本文操作到圖 21。本文執行環境使用 Anaconda (python 3.7)
Python
使用 pose API 偵測兩腿夾角,想挑戰更高難度的話可以把角度調小一點(先不要謝謝)
import cv2
import mediapipe as mp
import numpy as np
import time
import json
import serial
cam = cv2.VideoCapture(0)
mppose = mp.solutions.pose
mpdraw = mp.solutions.drawing_utils
poses = mppose.Pose()
h = 0
w = 0
ser = serial.Serial("COM3", 9600)
start_time = 0
status = False
sport = {
"name": "Squat",
"count": 0,
"calories": 0
}
def logger(count, cals):
f = open("log.txt", 'a')
fs = f"{time.ctime()} count: {count} cals: {cals}\n"
f.write(fs)
f.close()
def calc_angles(a, b, c):
a = np.array(a)
b = np.array(b)
c = np.array(c)
radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - \
np.arctan2(a[1] - b[1], a[0] - b[0])
angle = np.abs(radians * 180.0 / np.pi)
if angle > 180:
angle = 360 - angle
return angle
def get_landmark(landmarks, part_name):
return [
landmarks[mppose.PoseLandmark[part_name].value].x,
landmarks[mppose.PoseLandmark[part_name].value].y,
landmarks[mppose.PoseLandmark[part_name].value].z,
]
def get_visibility(landmarks):
if landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].visibility < 0.8 or \
landmarks[mppose.PoseLandmark["LEFT_HIP"].value].visibility < 0.8:
return False
else:
return True
def get_body_ratio(landmarks):
r_body = abs(landmarks[mppose.PoseLandmark["RIGHT_SHOULDER"].value].y
- landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y)
l_body = abs(landmarks[mppose.PoseLandmark["LEFT_SHOULDER"].value].y
- landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y)
avg_body = (r_body + l_body) / 2
r_leg = abs(landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y
- landmarks[mppose.PoseLandmark["RIGHT_ANKLE"].value].y)
l_leg = abs(landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y
- landmarks[mppose.PoseLandmark["LEFT_ANKLE"].value].y)
if r_leg > l_leg:
return r_leg / avg_body
else:
return l_leg / avg_body
def get_knee_angle(landmarks):
r_hip = get_landmark(landmarks, "RIGHT_HIP")
l_hip = get_landmark(landmarks, "LEFT_HIP")
r_knee = get_landmark(landmarks, "RIGHT_KNEE")
l_knee = get_landmark(landmarks, "LEFT_KNEE")
r_ankle = get_landmark(landmarks, "RIGHT_ANKLE")
l_ankle = get_landmark(landmarks, "LEFT_ANKLE")
r_angle = calc_angles(r_hip, r_knee, r_ankle)
l_angle = calc_angles(l_hip, l_knee, l_ankle)
m_hip = (r_hip + l_hip)
m_hip = [x / 2 for x in m_hip]
m_knee = (r_knee + l_knee)
m_knee = [x / 2 for x in m_knee]
m_ankle = (r_ankle + l_ankle)
m_ankle = [x / 2 for x in m_ankle]
mid_angle = calc_angles(m_hip, m_knee, m_ankle)
return [r_angle, l_angle, mid_angle]
def main():
global h, w, start_time, status
flag = False
if not cam.isOpened():
print("Camera not open")
exit()
try:
f = open("sport_recorder.json", "r")
prevdata = json.load(f)
if sport['name'] == prevdata['name']:
sport['count'] = prevdata['count']
sport['calories'] = prevdata['calories']
print("Read Success!")
f.close()
except:
print("Read Error...")
pass
tmp = f"a{sport['count']}\n"
ser.write(str.encode(tmp))
tmp = f"b{sport['calories']}\n"
ser.write(str.encode(tmp))
cv2.namedWindow('frame', cv2.WINDOW_FREERATIO)
while not flag:
ret, frame = cam.read()
if not ret:
print("Read Error")
break
frame = cv2.flip(frame, 1)
rgbframe = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
poseoutput = poses.process(rgbframe)
h, w, _ = frame.shape
preview = frame.copy()
if poseoutput.pose_landmarks:
mpdraw.draw_landmarks(preview, poseoutput.pose_landmarks, mppose.POSE_CONNECTIONS)
knee_angles = get_knee_angle(poseoutput.pose_landmarks.landmark)
body_ratio = get_body_ratio(poseoutput.pose_landmarks.landmark)
if knee_angles[0] < 120:
cv2.putText(preview, "Left: Down {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
elif knee_angles[0] < 130:
cv2.putText(preview, "Left: ??? {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA
)
else:
cv2.putText(preview, "Left: Up {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
if knee_angles[1] < 120:
cv2.putText(preview, "Right: Down {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
elif knee_angles[1] < 130:
cv2.putText(preview, "Right: ??? {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA
)
else:
cv2.putText(preview, "Right: Up {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
avg_angle = (knee_angles[0] + knee_angles[1]) // 2
# determine the status
if status:
if avg_angle > 160:
status = False
pass_time = time.time() - start_time
start_time = 0
if 3000 > pass_time > 3:
sport['count'] = sport['count'] + 1
sport['calories'] = sport['calories'] + int(0.66 * pass_time)
logger(sport['count'], sport['calories'])
tmp = f"a{sport['count']}\n"
ser.write(str.encode(tmp))
tmp = f"b{sport['calories']}\n"
ser.write(str.encode(tmp))
else:
if avg_angle < 120 and body_ratio < 1.2:
start_time = time.time()
status = True
# print(f"status:{status} {start_time}")
if status:
cv2.putText(preview, f"{status} : {avg_angle:.1f} {body_ratio:.3f}", (10, 120)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
if time.time() - start_time > 3:
ser.write(b'command_2\n')
else:
ser.write(b'command_1\n')
else:
cv2.putText(preview, f"{status} : {avg_angle:.1f} {body_ratio:.3f}", (10, 120)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
ser.write(b'command_4\n')
else:
ser.write(b'command_4\n')
start_time = 0
cv2.imshow('frame', preview)
if cv2.waitKey(1) & 0xFF == ord('q'):
flag = True
f = open("sport_recorder.json", "w+")
f.write(json.dumps(sport))
f.close()
# release camera
cam.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
Arduino ino
接收來自於PC端的 python程式,並執行對應的LED、蜂鳴器動作
//Arduino首次接觸就上手
//Google Mediapipe - POSE api
//example: https://cavedu.gitbook.io/cavedu/hangeekduino
#define LED_1_pin 4 //板子上有
#define LED_2_pin 9 //需另外接
#define AUDIO_pin 5 //板子上有
int counter = 0;
int calories = 0;
bool stat = false;
bool breaker = false;
String str;
void setup(void)
{
Serial.begin(9600);
// init pin states
pinMode(LED_1_pin, OUTPUT);
digitalWrite(LED_1_pin, LOW);
pinMode(LED_2_pin, OUTPUT);
digitalWrite(LED_2_pin, LOW);
}
void loop(void)
{
int i;
if (Serial.available()) {
// 讀取傳入的字串直到"\n"結尾
str = Serial.readStringUntil('\n');
if (str.startsWith("a")) {
str.remove(0, 1);
counter = str.toInt();
}
else if (str.startsWith("b")) {
str.remove(0, 1);
calories = str.toInt();
}
else if (str == "command_1") {
if (!stat) {
tone(AUDIO_pin, 110, 100);
delay(100);
tone(AUDIO_pin, 165, 100);
}
stat = true;
digitalWrite(LED_1_pin, HIGH);
digitalWrite(LED_2_pin, LOW);
}
else if (str == "command_2") {
digitalWrite(LED_1_pin, HIGH);
digitalWrite(LED_2_pin, HIGH);
if (!breaker) {
tone(AUDIO_pin, 200, 100);
delay(100);
tone(AUDIO_pin, 200, 100);
breaker = true;
}
}
else if (str == "command_4" ) {
if (stat) {
tone(AUDIO_pin, 165, 100);
delay(100);
tone(AUDIO_pin, 110, 100);
}
stat = false;
breaker = false;
digitalWrite(LED_1_pin, LOW);
digitalWrite(LED_2_pin, LOW);
}
}
}
純 Mediapipe 版本
可以看出就是取消 serial 相關的程式碼而已,歡迎您也一起來做做看喔!
import cv2
import mediapipe as mp
import numpy as np
import time
import json
#import serial
cam = cv2.VideoCapture(0)
mppose = mp.solutions.pose
mpdraw = mp.solutions.drawing_utils
poses = mppose.Pose()
h = 0
w = 0
#ser = serial.Serial("COM3", 9600)
start_time = 0
status = False
sport = {
"name": "Squat",
"count": 0,
"calories": 0
}
def logger(count, cals):
f = open("log.txt", 'a')
fs = f"{time.ctime()} count: {count} cals: {cals}\n"
f.write(fs)
f.close()
def calc_angles(a, b, c):
a = np.array(a)
b = np.array(b)
c = np.array(c)
radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - \
np.arctan2(a[1] - b[1], a[0] - b[0])
angle = np.abs(radians * 180.0 / np.pi)
if angle > 180:
angle = 360 - angle
return angle
def get_landmark(landmarks, part_name):
return [
landmarks[mppose.PoseLandmark[part_name].value].x,
landmarks[mppose.PoseLandmark[part_name].value].y,
landmarks[mppose.PoseLandmark[part_name].value].z,
]
def get_visibility(landmarks):
if landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].visibility < 0.8 or \
landmarks[mppose.PoseLandmark["LEFT_HIP"].value].visibility < 0.8:
return False
else:
return True
def get_body_ratio(landmarks):
r_body = abs(landmarks[mppose.PoseLandmark["RIGHT_SHOULDER"].value].y
- landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y)
l_body = abs(landmarks[mppose.PoseLandmark["LEFT_SHOULDER"].value].y
- landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y)
avg_body = (r_body + l_body) / 2
r_leg = abs(landmarks[mppose.PoseLandmark["RIGHT_HIP"].value].y
- landmarks[mppose.PoseLandmark["RIGHT_ANKLE"].value].y)
l_leg = abs(landmarks[mppose.PoseLandmark["LEFT_HIP"].value].y
- landmarks[mppose.PoseLandmark["LEFT_ANKLE"].value].y)
if r_leg > l_leg:
return r_leg / avg_body
else:
return l_leg / avg_body
def get_knee_angle(landmarks):
r_hip = get_landmark(landmarks, "RIGHT_HIP")
l_hip = get_landmark(landmarks, "LEFT_HIP")
r_knee = get_landmark(landmarks, "RIGHT_KNEE")
l_knee = get_landmark(landmarks, "LEFT_KNEE")
r_ankle = get_landmark(landmarks, "RIGHT_ANKLE")
l_ankle = get_landmark(landmarks, "LEFT_ANKLE")
r_angle = calc_angles(r_hip, r_knee, r_ankle)
l_angle = calc_angles(l_hip, l_knee, l_ankle)
m_hip = (r_hip + l_hip)
m_hip = [x / 2 for x in m_hip]
m_knee = (r_knee + l_knee)
m_knee = [x / 2 for x in m_knee]
m_ankle = (r_ankle + l_ankle)
m_ankle = [x / 2 for x in m_ankle]
mid_angle = calc_angles(m_hip, m_knee, m_ankle)
return [r_angle, l_angle, mid_angle]
def main():
global h, w, start_time, status
flag = False
if not cam.isOpened():
print("Camera not open")
exit()
try:
f = open("sport_recorder.json", "r")
prevdata = json.load(f)
if sport['name'] == prevdata['name']:
sport['count'] = prevdata['count']
sport['calories'] = prevdata['calories']
print("Read Success!")
f.close()
except:
print("Read Error...")
pass
tmp = f"a{sport['count']}\n"
#ser.write(str.encode(tmp))
tmp = f"b{sport['calories']}\n"
#ser.write(str.encode(tmp))
cv2.namedWindow('frame', cv2.WINDOW_FREERATIO)
while not flag:
ret, frame = cam.read()
if not ret:
print("Read Error")
break
frame = cv2.flip(frame, 1)
rgbframe = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
poseoutput = poses.process(rgbframe)
h, w, _ = frame.shape
preview = frame.copy()
if poseoutput.pose_landmarks:
mpdraw.draw_landmarks(preview, poseoutput.pose_landmarks, mppose.POSE_CONNECTIONS)
knee_angles = get_knee_angle(poseoutput.pose_landmarks.landmark)
body_ratio = get_body_ratio(poseoutput.pose_landmarks.landmark)
if knee_angles[0] < 120:
cv2.putText(preview, "Left: Down {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
elif knee_angles[0] < 130:
cv2.putText(preview, "Left: ??? {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA
)
else:
cv2.putText(preview, "Left: Up {:.1f}".format(knee_angles[0]), (10, 40)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
if knee_angles[1] < 120:
cv2.putText(preview, "Right: Down {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
elif knee_angles[1] < 130:
cv2.putText(preview, "Right: ??? {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 1, cv2.LINE_AA
)
else:
cv2.putText(preview, "Right: Up {:.1f}".format(knee_angles[1]), (10, 80)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
avg_angle = (knee_angles[0] + knee_angles[1]) // 2
# determine the status
if status:
if avg_angle > 160:
status = False
pass_time = time.time() - start_time
start_time = 0
if 3000 > pass_time > 3:
sport['count'] = sport['count'] + 1
sport['calories'] = sport['calories'] + int(0.66 * pass_time)
logger(sport['count'], sport['calories'])
tmp = f"a{sport['count']}\n"
#ser.write(str.encode(tmp))
tmp = f"b{sport['calories']}\n"
#ser.write(str.encode(tmp))
else:
if avg_angle < 120 and body_ratio < 1.2:
start_time = time.time()
status = True
# print(f"status:{status} {start_time}")
if status:
cv2.putText(preview, f"{status} : {avg_angle:.1f} {body_ratio:.3f}", (10, 120)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA
)
#if time.time() - start_time > 3:
#ser.write(b'command_2\n')
#else:
#ser.write(b'command_1\n')
else:
cv2.putText(preview, f"{status} : {avg_angle:.1f} {body_ratio:.3f}", (10, 120)
, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA
)
#ser.write(b'command_4\n')
else:
#ser.write(b'command_4\n')
start_time = 0
cv2.imshow('frame', preview)
if cv2.waitKey(1) & 0xFF == ord('q'):
flag = True
f = open("sport_recorder.json", "w+")
f.write(json.dumps(sport))
f.close()
# release camera
cam.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()