目录
所需工具:
硬件部分:数字舵机5个 ,摄像头 ,stm32f103rbt6 。
软件部分:Python 3.7 ,Mediapipe 0.8.9.1 ,Numpy 1.21.6 ,Opencv -python 4.5.5.64
Opencv -contrib -python 4.5.5.64 ,Pyserial 3.4
开发工具:PyCharm Community Edition 2021.2.3
Mediapipe技术:
MediaPipe 的核心框架由 C++ 实现,并提供 Java 以及 Objective C 等编程语 言的支持。MediaPipe 有如下几个重要的模块:图 Graph,子图 Subgraph ,数据流 Stream,数据包 Packet。
官方也提供了可以将解决方案可视化的工具:
viz.mediapipe.dev/
MediaPipe 框架的主要组成部分包括:媒体处理管道(media processing pipeline), 处理单元(processing units),工具包(tooling)。媒体处理管道用于数据采集、预处理、 特征提取等功能,支持流式处理。处理单元将不同算法、模型集成到管道中,用于对 运动捕捉、面部分析、虚拟化增强现实等任务进行处理。工具包包括一系列的 sdk 工 具,可以用于模型构建、模型转换、性能测试等操作,以满足各种需求。
MediaPipe 实现手部跟踪
为了检测手部的初始位置,本次设计使用了类似 BlazeFace 组件在移动端实时优 化的单一检测模型。检测手是一个十分艰难的任务。有下面几种不同的原因:
1.人的手,有各种各样的差别,大小差别,亦或者手指长度的差别;
2.检测时手部有遮挡或者自遮挡的问题;
3.手部和身体其他部位的检测有一定的差距,手部其中缺乏高对比度;
手部检测流程如下图:
总体硬件电路的框架设计
上位机软件设计程序流程
上位机代码参考:
import cv2 #opencv 头文件
import mediapipe as mp #mediapipe 头文件
import math
import time
import serial #串口头文件
odder_mode = 0
#手指的初始角
yi = er = san = si = wu = 0
#串口定义
ser = serial.Serial()
def port_open_recv():#对串口的参数进行配置
ser.port='com9'
ser.baudrate=115200
ser.bytesize=8
ser.stopbits=1
ser.parity="N"#奇偶校验位
ser.open()
if ser.isOpen():
print("串口打开成功!")
else:
print("串口打开失败!")
def vector_2d_angle(v1,v2):
v1_x=v1[0]
v1_y=v1[1]
v2_x=v2[0]
v2_y=v2[1]
try:
angle_=
math.degrees(math.acos((v1_x*v2_x+v1_y*v2_y)/(((v1_x**2+v1_y**2)**0.5)*((v2_x**2+v2_y**2)*
*0.5))))
#degrees:方法将角度 x 从弧度转换为度数
#acos:返回 x 的反余弦弧度值
except:
angle_ =65535.
if angle_ > 180.:
angle_ = 65535.
return angle_
def hand_angle(hand_):
global yi,er,san,si,wu
angle_list = []
#---------------------------- thumb 大拇指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[2][0])),(int(hand_[0][1])-int(hand_[2][1]))),
((int(hand_[3][0])- int(hand_[4][0])),(int(hand_[3][1])- int(hand_[4][1])))
)
yi = int(angle_)
angle_list.append(angle_)
#---------------------------- index 食指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])-int(hand_[6][0])),(int(hand_[0][1])- int(hand_[6][1]))),
((int(hand_[7][0])- int(hand_[8][0])),(int(hand_[7][1])- int(hand_[8][1])))
)
er = int(angle_)
angle_list.append(angle_)
#---------------------------- middle 中指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[10][0])),(int(hand_[0][1])- int(hand_[10][1]))),
((int(hand_[11][0])- int(hand_[12][0])),(int(hand_[11][1])- int(hand_[12][1])))
)
san = int(angle_)
angle_list.append(angle_)
#---------------------------- ring 无名指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[14][0])),(int(hand_[0][1])- int(hand_[14][1]))),
((int(hand_[15][0])- int(hand_[16][0])),(int(hand_[15][1])- int(hand_[16][1])))
)
si = int(angle_)
angle_list.append(angle_)
#---------------------------- pink 小拇指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[18][0])),(int(hand_[0][1])- int(hand_[18][1]))),
((int(hand_[19][0])- int(hand_[20][0])),(int(hand_[19][1])- int(hand_[20][1])))
)
wu = int(angle_)
angle_list.append(angle_)
return angle_list
def h_gesture(angle_list):
thr_angle = 65.
thr_angle_thumb = 53.
thr_angle_s = 49.
gesture_str = "0"
if 65535. not in angle_list:
if (angle_list[0]>thr_angle_thumb) and (angle_list[1]>thr_angle) and
(angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "0"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and
(angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]<thr_angle_s):
gesture_str = "5"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and
(angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "8"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and
(angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
gesture_str = "9"
elif (angle_list[0]>5) and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and
(angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "1"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle)
and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
gesture_str = "6"
elif (angle_list[0]>thr_angle_thumb) and (angle_list[1]<thr_angle_s) and
(angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]>thr_angle):
gesture_str = "3"
elif (angle_list[0]>thr_angle_thumb) and (angle_list[1]<thr_angle_s) and
(angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]<thr_angle):
gesture_str = "4"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle)
and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "7"
elif (angle_list[0]>thr_angle_thumb) and (angle_list[1]<thr_angle_s) and
(angle_list[2]<thr_angle_s) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "2"
return gesture_str
def detect():
pTime = 0
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1,
min_detection_confidence=0.75,
min_tracking_confidence=0.75)
cap = cv2.VideoCapture(0)
while True:
ret,frame = cap.read()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame= cv2.flip(frame,1)
results = hands.process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(frame, hand_landmarks,
mp_hands.HAND_CONNECTIONS)
hand_local = []
for i in range(21):
x = hand_landmarks.landmark[i].x*frame.shape[1]
y = hand_landmarks.landmark[i].y*frame.shape[0]
hand_local.append((x,y))
if hand_local:
angle_list = hand_angle(hand_local)
gesture_str = h_gesture(angle_list)
cv2.putText(frame, gesture_str, (30, 100), 0, 1, (0, 0, 255), 2)
ser.write(gesture_str.encode('utf-8'))
ser.write("\r\n".encode('utf-8'))
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(frame, f"FPS : {int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,
0, 0), 2)
cv2.imshow('MediaPipe Hands', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
if __name__ == '__main__':
port_open_recv()
#odder_mode = input("选择你想要执行的模式(0:数字 1:角度) :")
detect()
下位机软件设计程序流程
下位机控制代码参考:
#include "include.h"
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大 USART_REC_LEN 个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到 0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
void uart_init(u32 bound)
{
//GPIO 端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,
ENABLE); //使能 USART1,GPIOA 时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA.9
//USART1_RX GPIOA.10 初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//
无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口 1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口 1
}
void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据
必须是 0x0d 0x0a 结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;// 接收数
据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntExit();
#endif
}
int main(void)
{
SystemInit(); //系统时钟初始化为 72M SYSCLK_FREQ_72MHz
InitDelay(72); //延时初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2:2 位抢占优先级,
2 位响应优先级
InitPWM();
InitTimer2();//用于产生 100us 的定时中中断
uart_init(115200);
InitUart3();//外接模块的串口
InitADC();
InitLED();
InitKey();
int odder;//接收到的数据
u8 len,i;
int pwm_odder[6]={500,500,500,500,500,500};
while(1)
{
TaskRun();
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
for(int t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口 1 发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结
束
}
// printf("\r\n\r\n");//插入换行
odder = atoi((const char *)USART_RX_BUF);
USART_RX_STA=0;
}
if(odder == 0)
{
pwm_odder[1] = 2500;pwm_odder[2] = 500;pwm_odder[3] = 500;pwm_odder[4] =
500;pwm_odder[5] = 500;
}