钢琴是人类创作音乐的经典乐器,程序是实现创意的工具之魂。明天我给大家分享用程序实现的桌上钢琴师我的项目。本我的项目基于飞桨实现一个虚构钢琴,让大家能够在任意立体上弹奏钢琴,实现弹奏自在。
该项目标原理是利用手部关键点检测模型辨认手的关键点,获取指尖点在画面的坐标地位。当指尖关键点跨过虚构钢琴键的黄色响应线,即播放该琴键的音。
我的项目计划
1)应用摄像头获取桌面上手指的实时画面;2)手指关键点模型辨认画面中手指指尖的x方向与y方向坐标;3)比拟以后每个手指指尖的y方向坐标与校准时的y方向坐标,断定某个手指尖是否做了敲击动作;4)依据敲击动作的指尖的x方向坐标来断定具体按了哪个键;
5)通过pygame的UI显示按键成果,并播放对应的琴键音。
我的项目特点
1)基于飞桨实现轻量化的单阶段关键点辨认模型。因为边缘设施算力缓和,为此,在运行实时手指关键点辨认模型时,将手指辨认简化为只辨认5个指尖,不辨别左右手。https://aistudio.baidu.com/aistudio/projectdetail/4915699
2)用pygame作为主UI框架。实时显示摄像头的画面及提醒UI,画面中拍到的手指在桌面上敲击,即用户敲击虚构琴键时,立刻播放对应钢琴音。
3)应用生产者-消费者模式,充分利用Python的多过程,实现高效实时的画面显示、模型推理及后果反馈,在端侧实现较好的体验。
01 模型推理
硬件筹备—摄像头安排
- 摄像头垂直立于桌面,拍摄角度平行于桌面
因本我的项目应用的是2D Hand Keypoint模型,通过辨认手指在画面中的y方向和x方向的地位来判断手指尖是否有敲击动作以及敲击哪个琴键,因而,摄像头须要垂直立于桌面上,这样能最好地拍摄到敲击桌面的状况。摄像头拍摄角度程度平行于桌面,点击地位能够通过初始化校准来主动调节,这样能确保较好的交互性。
- 摄像头可选用一般USB网络摄像头,最好清晰度高一点
本我的项目选用无畸变摄像头,益处是无需解决畸变问题,毛病是画面拍摄的画幅不够大,无奈对应钢琴的88键。而选用大广角的摄像头会有畸变,尽管能够通过OpenCV的四角纠正校准成简直无畸变的画面,但运算量会增大,响应延时会增大。感兴趣的同学也能够尝试。
算法选型
该工作能够思考应用3D Hand Keypoint Detection算法或2D Hand Keypoint Detection算法。如果思考摄像头空间地位及角度,可思考用3D Hand Keypoint Detection算法计算指尖三维空间地位,精度应该更高,受摄像头角度影响更小。应用2D Hand Keypoint Detection算法则须要通过固定摄像头地位及角度,能力实现同样的性能。
因本我的项目须要重点思考算力问题,因而应用了2D Hand Keypoint Detection算法。基于2D Hand Keypoint Detection的算法也有很多。PaddleHub中自身就已集成了手部姿势模型,AI Studio上也有大佬放出了基于ResNet50间接回归手部关键点的我的项目。在屡次尝试后,感觉速度与精确度还有晋升的空间,因而我应用飞桨框架,基于CenterNet魔改了一版手指关键点模型。
上面对上述3个模型进行简略介绍。
hand_pose_localization模型
模型源自CMU的OpenPose开源我的项目,目前曾经集成到PaddleHub中。
https://www.paddlepaddle.org.cn/hubdetail?name=hand_pose_loca…
在该项目标实际操作中,手指关键点辨认成果比拟个别,这与拍摄角度对应的训练数据比拟少无关,且无奈基于PaddleHub进行迁徙训练。
飞桨实现手部21个关键点检测模型
该模型是ID为“星尘局”的同学在AI Studio上开源的模型。我测试了一下,成果比上述计划好一些,且能够持续训练或进行迁徙训练。但其是基于ResNet50间接做回归,准确率和实时性还有待晋升。https://aistudio.baidu.com/aistudio/projectdetail/2235290
基于CenterNet的手部关键点模型
- 基于CenterNet模型的魔改
基于上述模型状况,本人应用飞桨框架魔改了一版CenterNet关键点模型,增加了基于heatmap辨认landmark的分支。本计划相似于DeepFashion2的冠军计划,如下图所示,DeepFashion2的计划基于CenterNet上增加了Keypoint辨认。本计划与之相似,因为工作绝对简略,并不需要求出bbox,因而删减了Object size的回归。具体代码实现将会公开在AI Studio我的项目。
https://aistudio.baidu.com/aistudio/projectdetail/4915699
- 手部5个指尖关键点
为了更好地在边缘端部署,把原来手部单手21个关键点简化为只训练或推理5个手指指尖点,缩减网络训练及推理的工夫。
- 不辨别左右手
因本我的项目利用于弹钢琴,左右手并不影响我的项目的后果,就没有辨别左手或右手。
训练数据
训练数据集来自于Eric.Lee的handpose_datasets_v2数据集,在handpose_datasets_v1的根底上减少了左右手属性”handType”: “Left” or “Right”,数据总量为38w+。
https://aistudio.baidu.com/aistudio/datasetdetail/162171/0
程序运行流程
程序整体应用生产者-消费者模式,分为三个模块:输出模块、手部关键点预测模块、主显示及UI解决按键响应模块。输出模块放在子过程是“生产者”,输出的图片退出到可跨过程读写的queue中,给到主过程的消费者。“消费者”蕴含关键点预测模块,预测手指关键点后果与画面及UI进行叠加,通过pygame来显示。程序采纳多过程解决,输出图片是一个过程,模型推理与UI响应是一个过程,能更高效运行,避免出现卡顿。
输出模块(生产者)
应用OpenCV的cv2.videoCapture读取视频流或摄像头画面或视频。取得的画面放入dataQueue中期待解决,取得画面frame,增加到dataQueue中。
手部关键点预测模块(消费者)
把pygame作为出现端,摄像头画面、叠加的UI或提醒、按键响应均通过pygame实现。
主UI模块
本我的项目应用pygame作为UI出现端。pygame播放声音更灵便,可同时播放多个声音。
import pygamefrom pygame.localsimport *from sys import exitimport sys
pathDict={}
pathDict['hand']='../HandKeypoints/'for path in pathDict.values():
sys.path.append(path)import cv2import timefrom collections import dequefrom PIL importImageimport tracebackfrom multiprocessing import Queue,Processfrom ModuleSound import effectDict# from ModuleHand import handKeypointsimport CVTools as CVTimport GameTools as GTfrom ModuleConsumer import FrameConsumer
from predict7 import CenterNetfrom ModuleInput import FrameProducerimport numpy as np
pygame.init()
defframeShow(frame,screen):
#
# timeStamp = cap.get(cv2.CAP_PROP_POS_MSEC)
# frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame=np.array(frame)[:,:,::-1]
#print('frame',frame.shape)
frame = cv2.transpose(frame)
frame = pygame.surfarray.make_surface(frame)
screen.blit(frame, (0, 0))
pygame.display.update()
# return timeStamp
defresetKeyboardPos(ftR,thresholdY):
print('key SPACE',ftR)
iflen(ftR)>0:
ftR=np.array(ftR)
avrR=np.average(ftR[:,1])
thresholdY=int(avrR)
print('reset thresholdY',thresholdY)
return thresholdY
defkeyboardResponse(prodecer,ftR,thresholdY):
for event in pygame.event.get():
if event.type == pygame.QUIT:
prodecer.runFlag = False
exit()
elif event.type == pygame.MOUSEBUTTONUP:
thresholdY=resetKeyboardPos(ftR,thresholdY)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
prodecer.runFlag = False
exit()
elif event.key == pygame.K_SPACE:
thresholdY=resetKeyboardPos(ftR,thresholdY)
return thresholdYdefloopRun(dataQueue,wSize,hSize,prodecer,consumer,thresholdY,movieDict,skipFrame):
# tip position of hand down
ftDown1={}
ftDown2={}
# tip position for now
ft1={}
ft2={}
# tip position of hand up
ftUp1={}
ftUp2={}
#
stageR=-1
stageL=-1
resXR=-1
resXL=-1
idsR=-1
idsL=-1
#
biasDict1={}
biasDict2={}
screen = pygame.display.set_mode((wSize,hSize))
# cap = cv2.VideoCapture(path)
num=-1
keyNums=12
biasy=20
result={}
whileTrue:
##
FPS=prodecer.fps/skipFrame
if FPS >0:
videoFlag = True
else:
videoFlag = False
##
##
# print('ppp', len(dataDeque), len(result))
if dataQueue.qsize()==0 :
time.sleep(0.1)
continue
# print('FPS',FPS)
elif dataQueue.qsize()>0:
##
image=dataQueue.get()
## flir left right:
image=image[:,::-1,:]
result=consumer.process(image,thresholdY)
resImage=result['image']
ftR=result['fringerTip1']
ftL=result['fringerTip2']
#print('resImage',resImage.size)
thresholdY=keyboardResponse(prodecer, ftR,thresholdY)
if videoFlag:
num += 1
if num == 0:
T0 = time.time()
print('T0',T0,num*(1./FPS))
try:
resImage = GT.uiProcess(resImage,ftR,ftL,biasy)
except Exception as e:
traceback.print_exc()
try:
fringerR,keyIndexR,stageR=GT.pressDetect(ftR,stageR,thresholdY,biasy,wSize,keyNums)
fringerL,keyIndexL,stageL=GT.pressDetect(ftL,stageL,thresholdY,biasy,wSize,keyNums)
#print('resR',idsR,resR,idsL,resL)
except Exception as e:
traceback.print_exc()
#
GT.soundPlay(effectDict,keyIndexR)
GT.soundPlay(effectDict,keyIndexL)
#
resImage=GT.moviePlay(movieDict,keyIndexR,resImage,thresholdY)
resImage=GT.moviePlay(movieDict,keyIndexL,resImage,thresholdY)
frameShow(resImage, screen)
#clear result
result={}
if __name__=='__main__':
link=0
wSize=640
hSize=480
skipFrmae=2
dataQueue = Queue(maxsize=2)
resultDeque = Queue()
thresholdY=250
producer = FrameProducer(dataQueue, link)
##
frontPIL=Image.open('pianoPic/pianobg.png')
handkeypoint=CenterNet(folderPath='/home/sig/sig_dir/program/HandKeypoints/')
consumer=FrameConsumer(dataQueue,resultDeque,handkeypoint,frontPIL)
producer.start()
#
moviePicPath='pianoPic/'
movieDict=GT.loadMovieDict(moviePicPath)
#
loopRun(dataQueue, wSize, hSize, producer,consumer,thresholdY,movieDict,skipFrmae)
02 部署
下载模型代码
下载本我的项目数据集的Piano.zip压缩包(或data/data181662/文件夹中的压缩包) 到本地并解压,能够依据不同具体情况抉择对应的版本开始部署。
X86 / X64零碎上运行
- 装置飞桨及pygame等所需的库;
- 解压data中的压缩包到目录中;
- 进入HandPiano文件夹,运行python main.py程序即可。
在ARM设施,如Jetson NX上运行
- 从0开始
如果从0开始部署飞桨到Jetson NX可参考“ゞ灰酱”的我的项目:https://aistudio.baidu.com/aistudio/projectdetail/969585?chan…
- 下载 Paddle Inference库并装置
https://www.paddlepaddle.org.cn/inference/v2.4/guides/install…
- 解压data中的压缩包到目录中
- 进入 HandPiano 文件夹,python main.py运行程序即可
03 运行
按硬件配置设置好,按C部署,启动程序,看到如下UI。
- 成果展现
https://www.bilibili.com/video/BV1wT411g7MU/
- 一只手的五指放于桌面上,当5个手指点的圆形都呈现后,点击鼠标左键进行“点击地位校准”。
- “点击地位校准”后,会调整琴键UI地位,黄线会在指尖所成的直线处。
- 当指尖的点越过黄线进入琴键地位后即触发该琴键的声音。
- 标示有C1的就示意C大调的do,之后的2、3、4、5、6、7 就是对应C大调的简谱的 2(re)、3(mi)、 4(fa)、 5(sol)、 6(la)、 7(si)。C1右边的是降一个调的简谱的5、6、7。
一起开始弹钢琴吧!
发表回复