6. 基于手势识别的智能家居控制系统
器材:
JetsonNano
USB摄像头
便捷式HDMI显示屏
控制器:开元
模块:可编程全彩LED;电机风扇;舵机云台
6.1. 程序(JetsonNano 端)
检测识别手势
'''
@20221110
手势识别智能家居控制
风扇开关控制
风扇方向角度控制
灯开关控制
'TX' + 设备控制1 + 设备控制2
0x01 获取控制
0x10 开/关灯
0x11 开/关风扇
0x12 风扇摇头
'''
import cv2
import time
import math
import mediapipe as mp
#import pygame
import serial
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
finger_landmark_point = ((0,1,2,3,4),
(0,5,6,7,8),
(0,9,10,11,12),
(0,13,14,15,16),
(0,17,18,19,20))
'''
手势定义
'''
gesture_def = [
{'stat':[0,1,0,0,0], 'label':'one'},
{'stat':[0,1,1,0,0], 'label':'two'},
{'stat':[0,1,1,1,0], 'label':'three'},
{'stat':[0,1,1,1,1], 'label':'four'},
{'stat':[1,1,1,1,1], 'label':'five'},
{'stat':[0,0,1,1,1], 'label':'ok'},
{'stat':[1,1,0,0,1], 'label':'love you'},
{'stat':[0,0,0,0,0], 'label':'fist'},
]
gesture_ctrl_def = [
{'stat':[0,1,0,0,0], 'label':'one'},
{'stat':[0,1,1,0,0], 'label':'moror_fan_angle'}, # 风扇角度控制
{'stat':[0,1,1,1,0], 'label':'three'},
{'stat':[0,1,1,1,1], 'label':'four'},
{'stat':[1,1,1,1,1], 'label':'five'},
{'stat':[0,0,1,1,1], 'label':'ok'},
{'stat':[1,1,0,0,1], 'label':'love you'},
{'stat':[0,0,0,0,0], 'label':'fist'},
]
'''
计算矢量夹角
@v1 [x0,y0,x1,y1]
@v2 [x0,y0,x1,y1]
@return 矢量夹角(°)
'''
def vector_included_angle(v1, v2):
dx1 = v1[2] - v1[0]
dy1 = v1[3] - v1[1]
dx2 = v2[2] - v2[0]
dy2 = v2[3] - v2[1]
v1_angle = math.atan2(dy1, dx1)/math.pi*180 # 弧度转度
v2_angle = math.atan2(dy2, dx2)/math.pi*180
if v1_angle*v2_angle >= 0:
angle = abs(v1_angle-v2_angle)
else:
angle = abs(v1_angle) + abs(v2_angle)
if angle > 180:
angle = 360 - angle
return angle
'''
创建手检测器
'''
hands = mp_hands.Hands(
#model_complexity=0,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
'''
打开并设置摄像头
'''
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("open camera failed!")
exit(-1)
# 设置分辨率 720P:1280x720; 1080P:1920x1080
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 设置帧率
cap.set(cv2.CAP_PROP_FPS, 30)
# 设置曝光
cap.set(cv2.CAP_PROP_EXPOSURE, 2)
last_replay_time = 0
last_sw_time = 0
last_res = 0
#pygame.mixer.init()
err_cnt = 0
'''
打开串口
'''
com = serial.Serial('/dev/ttyTHS1', 115200, timeout=10)
tx_buf = bytearray(5)
tx_buf[:2] = 'TX'.encode() # 帧头
light_on = False
flag_stable = False
while True:
# 读取视频帧
success, image = cap.read()
if not success:
err_cnt += 1
if err_cnt > 3:
break
print("frame read error!")
continue
image_h, image_w = image.shape[0:2]
raw_image = image.copy()
# 获取检测结果
results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
# 如果检测结果不为空
if results.multi_hand_landmarks:
handedness = results.multi_handedness
for hand_index, hand_landmarks in enumerate(results.multi_hand_landmarks):
# 绘制 landmarks
mp_drawing.draw_landmarks(
image,
hand_landmarks,
mp_hands.HAND_CONNECTIONS)
# 获取手掌属性 左或右
palm_type = handedness[hand_index].classification[0].label
palm_type = 'Left' if (palm_type == 'Right') else 'Right' # 镜像反转
# 获取手掌各关键点坐标
landmark_x = []
landmark_y = []
for p in hand_landmarks.landmark:
landmark_x.append(math.ceil(p.x*image_w))
landmark_y.append(math.ceil(p.y*image_h))
# 手掌边界框
x1 = min(landmark_x)
x2 = max(landmark_x)
y1 = min(landmark_y)
y2 = max(landmark_y)
palm_bbox = x1,y1,x2,y2
c = (0xe7,0xed,0x3e) if palm_type == "Left" else (0x83,0x79,0xf4)
cv2.rectangle(image, (x1,y1), (x2,y2), c, 2)
#cv2.putText(image, "{}".format(palm_type), (x1, y1-10), cv2.FONT_HERSHEY_COMPLEX, 1.0, c, 2)
# 手指状态(弯曲或伸直)计算
finger_stat = []
for i in range(5):
p = finger_landmark_point[i]
angle_sum = 0
for i in range(3):
v1 = [landmark_x[p[i]], landmark_y[p[i]], landmark_x[p[i+1]], landmark_y[p[i+1]]]
v2 = [landmark_x[p[i+1]], landmark_y[p[i+1]],landmark_x[p[i+2]], landmark_y[p[i+2]]]
a = vector_included_angle(v1, v2)
angle_sum += a
if angle_sum > 45: # 根基累加角度判断手指状态,数值自己测试确定
finger_stat.append(0)
else:
finger_stat.append(1)
# 搜索匹配手势
for i in range(len(gesture_def)):
if finger_stat == gesture_def[i]['stat']:
if i != last_res: # 前后两次识别结果不同
last_sw_time = time.time() # 记录当前时间
last_replay_time = 0 # 重置语音重播时间
# 防止在手势动作变换时播放(保持同一手势超过 1S 判断为手势稳定);以及防止同一结果短时间内重复播放
if time.time() - last_sw_time > 0.5 and time.time() - last_replay_time > 2:
last_replay_time = time.time() # 记录语音重播时间
#sound = pygame.mixer.Sound("media/%d.mp3"%(i+1)) # 播放语音
#sound.play()
flag_stable = True
else:
flag_stable = False
last_res = i
cv2.putText(image, gesture_def[i]['label'], (x1, y1-10), cv2.FONT_HERSHEY_COMPLEX, 1.0, c, 2)
try:
# 获取控制权 love you
if finger_stat == gesture_ctrl_def[6]['stat']:
cv2.putText(image, "control", (20, 20), cv2.FONT_HERSHEY_COMPLEX, 1.0, (0xFF,0x00,0x00), 2)
tx_buf[2] = 0x01
if flag_stable:
com.write(tx_buf)
# 开/关灯 ok
elif finger_stat == gesture_ctrl_def[5]['stat']:
tx_buf[2] = 0x10
if flag_stable:
com.write(tx_buf)
# 风扇控制 two
elif finger_stat == gesture_ctrl_def[1]['stat']:
# 当(无名指,尾指弯曲,食指和中指伸直且并拢)时 控制风扇的摆动角度
if abs(landmark_x[8] - landmark_x[12]) < 100 and abs(landmark_y[8] - landmark_y[12]) < 50:
tx_buf[2] = 0x12
tx_buf[3] = int(landmark_x[8]/10)
cv2.putText(image, str(landmark_x[8]), (x1, y1-50), cv2.FONT_HERSHEY_COMPLEX, 1.0, (0xFF,0x00,0x00), 2)
cv2.putText(image, str(landmark_y[8]), (x1 + 100, y1-50), cv2.FONT_HERSHEY_COMPLEX, 1.0, (0xFF,0x00,0x00), 2)
com.write(tx_buf)
else:
tx_buf[2] = 0x11
if flag_stable:
com.write(tx_buf)
except:
pass
# 显示
image = cv2.flip(image, 1)
cv2.imshow('Hands', image)
if cv2.waitKey(1) & 0xFF == ord('q'): # 按 ESC 键退出
break
cap.release()
cv2.destroyAllWindows()
6.2. 程序(开元主控端)
根据识别到的手势控制执行
'''
@20221110
手势识别智能家居控制
风扇开关控制
风扇方向角度控制
灯开关控制
'TX' + 设备控制1 + 设备控制2
0x01 获取控制
0x10 开/关灯
0x11 开/关风扇
0x12 风扇摇头
'''
import time
import lcd
from machine import UART, Pin
from openaie import math_map, servo, rgb_led, motor_fan, buzzer
light = rgb_led(5) # 全彩LED -- 端口5
m = motor_fan(8) # 电机风扇 -- 端口8
m.set(0)
s = servo(6) # 舵机 -- 端口6
s.write(90)
serial = UART(0, baudrate=115200, tx=Pin(16), rx=Pin(17))
flag_ctrl = False
flag_light = False
flag_fan = False
last_time_ctrl_on = 0
lcd.clear(color=0)
lcd.rotation(0)
lcd.draw_string(40, 10, "手势识别智能家居控制", fc=(0,0,255), bc=0)
lcd.draw_line(0, 36, 219, 36, color=(0,0,255), thickness=5)
lcd.draw_string(10, 50, "灯: 关", fc=(255,0,0), bc=0)
lcd.draw_string(130, 50, "风扇: 关", fc=(255,0,0), bc=0)
lcd.draw_string(88, 182, "停止控制", fc=(255,0,0), bc=0)
lcd.display()
while True:
if serial.any() > 0:
rx_buf = serial.read()
try:
if rx_buf[:2].decode('utf-8') == 'TX': # 帧头判断
if rx_buf[2] == 0x01: # 获取控制
flag_ctrl = True
lcd.draw_circle(119, 190, 80, thickness=8, color=(0,255,0), fill=False)
lcd.draw_string(88, 182, "开始控制", fc=(0,255,0), bc=0)
buzzer.tone(345)
time.sleep_ms(200)
buzzer.no_tone()
last_time_ctrl_on = time.ticks_ms()
if flag_ctrl: # 当获取控制时
if rx_buf[2] == 0x10: # 开/关灯
flag_ctrl = False
lcd.draw_circle(119, 190, 80, thickness=8, color=(0,0,0), fill=False)
lcd.draw_string(88, 182, "停止控制", fc=(255,0,0), bc=0)
flag_light = not flag_light
if flag_light:
light.set(0, (100,100,100))
lcd.draw_string(10, 50, "灯: 开", fc=(0,255,0), bc=0)
else:
lcd.draw_string(10, 50, "灯: 关", fc=(255,0,0), bc=0)
light.set(0, (0,0,0))
light.display()
elif rx_buf[2] == 0x11: # 开/关风扇
flag_ctrl = False
lcd.draw_circle(119, 190, 80, thickness=8, color=(0,0,0), fill=False)
lcd.draw_string(88, 182, "停止控制", fc=(255,0,0), bc=0)
flag_fan = not flag_fan
print(flag_fan)
if flag_fan:
m.set(50)
lcd.draw_string(130, 50, "风扇: 开", fc=(0,255,0), bc=0)
else:
m.set(0)
lcd.draw_string(130, 50, "风扇: 关", fc=(255,0,0), bc=0)
elif rx_buf[2] == 0x12: # 风扇摇头
last_time_ctrl_on = time.ticks_ms()
angle = math_map(rx_buf[3], 20, 120, 150, 30)
s.write(angle)
lcd.display()
except:
pass
if flag_ctrl:
if time.ticks_diff(time.ticks_ms(), last_time_ctrl_on) > 6000: # 超时自动关闭控制
flag_ctrl = False
lcd.draw_circle(119, 190, 80, thickness=8, color=(0,0,0), fill=False)
lcd.draw_string(88, 182, "停止控制", fc=(255,0,0), bc=0)
lcd.display()
time.sleep_ms(50)