程序员深夜用Python跑神经网络,只为用中二动作关掉台灯
導(dǎo)讀:對(duì)于上了床就再也不想下來(lái)的人來(lái)說(shuō),關(guān)燈成為睡覺(jué)前面臨的最大挑戰(zhàn)!
然而,一個(gè)來(lái)自意大利拉不勒斯的小哥哥,決定利用“舞步”(身體姿勢(shì))來(lái)控制自己家的燈,整個(gè)過(guò)程利用一個(gè)神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)。
此前,關(guān)于關(guān)燈這件事,這一屆網(wǎng)友永遠(yuǎn)不會(huì)讓人失望,他們開(kāi)發(fā)出了各種關(guān)燈大法:
當(dāng)然少不了憨豆先生最簡(jiǎn)單粗暴的關(guān)燈方式:
而小哥哥的“舞步”是這樣的:
下面是小哥哥寫(xiě)的教程,我們?cè)诓桓淖冊(cè)獾幕A(chǔ)上進(jìn)行了編譯。
在今天的文章里,我將手把手教大家訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)模型,用來(lái)識(shí)別攝像頭拍下的“舞步”,從而控制燈的開(kāi)關(guān)。
我們將在已有的OpenPose深度學(xué)習(xí)模型之上建立我們自己的模型來(lái)識(shí)別身體的位置,然后,我們會(huì)建立一些樣本來(lái)代表各種身體的各種姿態(tài)。
當(dāng)我們建立好舞步(包括嘻哈超人舞步、T-Pose舞步)和其他身體姿態(tài)的樣本后,我們會(huì)清理數(shù)據(jù)集,然后利用這些樣例訓(xùn)練我們的神經(jīng)網(wǎng)絡(luò)。
當(dāng)神經(jīng)網(wǎng)絡(luò)訓(xùn)練好之后,我們會(huì)用它來(lái)控制燈光。
今天的文章包括很多步驟,不過(guò),所有的代碼都在Github上,上面還包括了我已經(jīng)搜集到的原始數(shù)據(jù)樣例。
GitHub鏈接:
https://github.com/burningion/dab-and-tpose-controlled-lights
01 編寫(xiě)“編寫(xiě)軟件”的軟件:怎樣訓(xùn)練你的神經(jīng)網(wǎng)絡(luò)?
首先就是數(shù)據(jù)——大量數(shù)據(jù)。
我們今天即將采用的神經(jīng)網(wǎng)絡(luò)模型卡內(nèi)基梅隆大學(xué)的團(tuán)隊(duì)也曾經(jīng)使用過(guò),他們用自己的全景數(shù)據(jù)集來(lái)訓(xùn)練該模型。該數(shù)據(jù)集包括五個(gè)半小時(shí)的視頻,視頻中包含了150萬(wàn)個(gè)手動(dòng)添加的代表人體骨骼位置的標(biāo)簽。
整個(gè)全景工作室的圓屋頂上裝有500個(gè)攝像頭,所有攝像頭都對(duì)準(zhǔn)人,從不同角度記錄他們的動(dòng)作。
這個(gè)全景工作室用構(gòu)造訓(xùn)練數(shù)據(jù)集幾乎是完美的,很方便進(jìn)行計(jì)算機(jī)視覺(jué)的實(shí)驗(yàn)。
今天,我們將在他們的工作基礎(chǔ)之上開(kāi)始我們的工作。
首先我們會(huì)用他們的工具來(lái)創(chuàng)建一個(gè)很小的數(shù)據(jù)集。我們最終的神經(jīng)網(wǎng)絡(luò)只會(huì)使用171個(gè)姿態(tài),包括記錄的嘻哈超人舞步、T-Pose舞步和其他姿態(tài)。每一個(gè)姿態(tài)樣例都是從卡耐基梅隆大學(xué)已有的工作中提取出來(lái)的。
神經(jīng)網(wǎng)絡(luò)的一個(gè)好處就是你可以使用別人已經(jīng)建成的模型,然后加入一些新的神經(jīng)網(wǎng)絡(luò)層,以此來(lái)擴(kuò)展該模型。這個(gè)過(guò)程被稱(chēng)之為遷移學(xué)習(xí),因此我們可以用有限的資源來(lái)進(jìn)行遷移學(xué)習(xí)。
從技術(shù)上來(lái)說(shuō),我們不會(huì)在這個(gè)項(xiàng)目中使用遷移學(xué)習(xí),因?yàn)槲覀儠?huì)對(duì)OpenPose的工作做一些細(xì)微的修改,然后創(chuàng)建一個(gè)獨(dú)立的神經(jīng)網(wǎng)絡(luò)。
那么問(wèn)題來(lái)了,我們?cè)撊绾潍@取數(shù)據(jù)呢?
02 寫(xiě)一個(gè)程序并利用OpenCV來(lái)收集帶標(biāo)簽的數(shù)據(jù)
使用OpenPose的成果,我們得到了25個(gè)代表人體骨骼架構(gòu)的標(biāo)簽。我們可以寫(xiě)一個(gè)程序來(lái)控制網(wǎng)絡(luò)攝像頭,在圖像上運(yùn)行OpenPose,然后將動(dòng)作與鍵盤(pán)上的按鍵相對(duì)應(yīng)。
也就是說(shuō),我們做出一個(gè)T-Pose的動(dòng)作,然后在鍵盤(pán)上點(diǎn)擊m鍵,那么這個(gè)動(dòng)作就被歸到T-Pose那一類(lèi)里。我們按照這個(gè)方法去添加171個(gè)不同的姿勢(shì),這樣一來(lái),我們就有數(shù)據(jù)訓(xùn)練神經(jīng)網(wǎng)絡(luò)了。以下是用于數(shù)據(jù)收集的代碼的示例:
然后用NumPy的數(shù)組來(lái)儲(chǔ)存特征,并用np.save函數(shù)把特征保存為二進(jìn)制文件以便后續(xù)使用。我個(gè)人傾向于使用Jupyter notebook來(lái)觀(guān)察和處理數(shù)據(jù)。
當(dāng)數(shù)據(jù)收集好之后,我們可以觀(guān)察并清理數(shù)據(jù)以便更好地去訓(xùn)練模型。
03 觀(guān)察數(shù)據(jù)、清理數(shù)據(jù)以及使用數(shù)據(jù)訓(xùn)練模型
這部分看上去很復(fù)雜,但是通過(guò)使用Jupyter notebook、NumPy和Keras,我們就可以很直觀(guān)地去觀(guān)察數(shù)據(jù)、清理數(shù)據(jù),并且使用數(shù)據(jù)來(lái)訓(xùn)練神經(jīng)網(wǎng)絡(luò)。
根據(jù)我們的截圖,我們可以發(fā)現(xiàn)npy文件中保存的數(shù)據(jù)和OpenPose模型本身都有三個(gè)維度,25個(gè)已知的身體位置坐標(biāo)點(diǎn),X、Y、以及Confidence。
我們的模型訓(xùn)練工作不需要用到confidence。如果某個(gè)身體位置坐標(biāo)點(diǎn)被命名了,我們就保留它,否則,我們就直接讓它為0。
我們已經(jīng)把(絕大部分)數(shù)據(jù)梳理好了,現(xiàn)在我們需要把數(shù)據(jù)特征和標(biāo)簽結(jié)合起來(lái)。
我們用0代表其他姿勢(shì),1代表嘻哈超人舞步、2代表T-Pose舞步。
labels = np.zeros(len(otherDataset)) labels = np.append(labels, np.full((len(dabDataset)), 1)) labels = np.append(labels, np.full((len(tposeDataset)), 2)) print(labels) print("%i total examples for training." % len(labels))接下來(lái),我們可以使用獨(dú)熱編碼處理我們的數(shù)字標(biāo)簽。也就是說(shuō),我們將標(biāo)簽0、1、2轉(zhuǎn)換成[1,0,0]、[0,1,0]、[0,0,1]。之后,我們可以使用sklearn的shuffle函數(shù)將數(shù)據(jù)標(biāo)簽和特征打亂(數(shù)據(jù)標(biāo)簽和特征仍保持原有的對(duì)應(yīng)關(guān)系)
# now, let's shuffle labels and the array, the same way from sklearn.utils import shuffle X1, y1 = shuffle(dataset, labels) # now let's label them for 'one hot' from keras.utils.np_utils import to_categorical y1 = to_categorical(y1, 3) # we have 3 categories, dab, tpose, other print(y1.shape[1)]我們的輸入數(shù)據(jù)代表著鼻子、手等等的位置,而它們的是介于0到720和0到1280之間的像素值,所以我們需要把數(shù)據(jù)歸一化。這樣一來(lái),我們可以重復(fù)使用我們的模型而不用考慮輸入圖片數(shù)據(jù)的分辨率。
X1[:,:,0] = X1[:,:,0] / 720 # I think the dimensions are 1280 x 720 ? X1[:,:,1] = X1[:,:,1] / 1280 # let's see? X1 = X1[:,:,1:] print(X1.shape) X1 = X1.reshape(len(X1), 50) # we got rid of confidence percentage在最后一步中,我們將把我們的多維數(shù)據(jù)變成一維。我們會(huì)分批向模型輸入50個(gè)位置信息(25個(gè)部位,每個(gè)部位的X和Y值)。
04 構(gòu)建并訓(xùn)練我們的模型
在Jupyter notebook中使用Keras可以把訓(xùn)練和測(cè)試神經(jīng)網(wǎng)絡(luò)模型的工作變得十分簡(jiǎn)單,這也是我最喜歡Keras的地方。
現(xiàn)在我們的數(shù)據(jù)已經(jīng)貼上標(biāo)簽準(zhǔn)備就緒了,我們可以開(kāi)始訓(xùn)練一個(gè)簡(jiǎn)單的模型了,只需要幾行代碼。
現(xiàn)在我們導(dǎo)入Keras庫(kù)然后訓(xùn)練一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)模型。
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴,互幫互助, 群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書(shū)! ''' from keras.models import Sequential from keras.layers import Dense, Dropout, Activation, Flatten from keras.optimizers import SGDmodel = Sequential() model.add(Dense(128, activation='relu', input_shape=(50,))) model.add(Dropout(0.5)) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(y1.shape[1], activation='softmax')) model.compile(optimizer='Adam',loss='categorical_crossentropy',metrics=['accuracy']) model.fit(X1, y1, epochs=2000,batch_size=25)搞定!
這里有個(gè)稍微需要注意的地方,輸入層的大小為50,提醒大家一下,這個(gè)數(shù)字是OpenPose模型中位置點(diǎn)的X坐標(biāo)和Y坐標(biāo)數(shù)量之和。
最后我們用到了Softmax層,它是用來(lái)分類(lèi)的。我們將y.shape[1]傳入該層,這樣我們的模型就知道不同類(lèi)別的數(shù)量了。
最后的最后,我們使用輸入數(shù)據(jù),用model.fit()的方法去訓(xùn)練模型。這里,我已經(jīng)做了2000次迭代(全部樣本訓(xùn)練一次為一次迭代)。2000次迭代貌似有點(diǎn)多了,500次左右的迭代可能更好,因?yàn)榈螖?shù)過(guò)多可能使我們的模型出現(xiàn)一些過(guò)擬合問(wèn)題。但是不論是哪一種情況,你都需要經(jīng)過(guò)多次嘗試來(lái)確定迭代次數(shù)。
當(dāng)我們運(yùn)行這段代碼時(shí),我們會(huì)看到準(zhǔn)確度在提高。如果你看不到,請(qǐng)?jiān)俅未_認(rèn)當(dāng)你打亂數(shù)據(jù)時(shí),數(shù)據(jù)標(biāo)簽和數(shù)據(jù)特征的對(duì)應(yīng)關(guān)系是不變的。此外,也要確認(rèn)數(shù)據(jù)里的數(shù)值是不是在0到1之間。
最后,我們可以保存訓(xùn)練后的模型,也可以使用樣本數(shù)據(jù)集來(lái)測(cè)試該模型,保存模型的代碼很簡(jiǎn)單:
model.save('data/dab-tpose-other.h5') # save our model as h5# in our other code, or inline, load the model and test against sample dab dataset import keras modello = keras.models.load_model('data/dab-tpose-other.h5') dabDataset = np.load('data/test-dabs.npy') dabDataset[:,:,0] = dabDataset[:,:,0] / 720 # I think the dimensions are 1280 x 720 ? dabDataset[:,:,1] = dabDataset[:,:,1] / 1280 # let's see? dabDataset = dabDataset[:,:,1:] dabDataset = dabDataset.reshape(len(dabDataset), 50) modello.predict_classes(dabDataset) # returns array([1, 1, 1, 1, 1, 1])05 用模型來(lái)控制燈光
我們現(xiàn)在已經(jīng)有了可以識(shí)別姿勢(shì)的模型,接下來(lái)要做的只是把這個(gè)模型和無(wú)線(xiàn)燈光控制關(guān)聯(lián)起來(lái)就行了。
在我的這個(gè)例子中,我使用Aeotec Z-Stick來(lái)發(fā)送Z-Wave指令,并配有兩個(gè)GE Z-Wave的室外開(kāi)關(guān)。USB接口接入到NVIDIA TX2人工智能模塊,其實(shí)NVIDIA的Jestson Nano也能勝任,盡管Jetson Nano所能提供的分辨率要低于我樣例中1280x720的分辨率。當(dāng)Z-Stick插入到ARM設(shè)備后,你首先需要把開(kāi)關(guān)調(diào)到Z-Wave模式,可能需要多按幾下USB Stick上的按鈕和燈的開(kāi)關(guān)。
代碼并不復(fù)雜,基本上就是訓(xùn)練環(huán)境再加上一個(gè)額外的步驟。現(xiàn)在,我們導(dǎo)入Keras,然后使用清理過(guò)的數(shù)據(jù)訓(xùn)練模型。
import cv2 import pyopenpose as op from imutils import translate, rotate, resizeimport openzwave from openzwave.option import ZWaveOption from openzwave.network import ZWaveNetwork# make sure these commands get flushed by doing them first, then loading tensorflow... # tensorflow should take enough time to start for these commands to flush options = ZWaveOption('/dev/ttyACM0') options.lock()network = ZWaveNetwork(options)import time import numpy as np np.random.seed(1337)import tensorflow as tf# make sure tensorflow doesn't take up all the gpu memory conf = tf.ConfigProto() conf.gpu_options.allow_growth=True session = tf.Session(config=conf)import keras# Custom Params (refer to include/openpose/flags.hpp for more parameters) params = dict() params["model_folder"] = "../../models/"# built in TX2 video capture source vs = cv2.VideoCapture("nvarguscamerasrc ! video/x-raw(memory:NVMM), width=(int)1280, height=(int)720,format=(string)NV12, framerate=(fraction)24/1 ! nvvidconv flip-method=0 ! video/x-raw, format=(string)BGRx ! videoconvert ! video/x-raw, format=(string)BGR ! appsink")tposer = keras.models.load_model('dab-tpose-other.h5')# Starting OpenPose opWrapper = op.WrapperPython() opWrapper.configure(params) opWrapper.start()datum = op.Datum() np.set_printoptions(precision=4)fps_time = 0DAB = 1 TPOSE = 2 OTHER = 0LIGHTS = 0bounced = time.time() debounce = 3 # wait 3 seconds before allowing another commandwhile True:ret_val, frame = vs.read()datum.cvInputData = frameopWrapper.emplaceAndPop([datum])# need to be able to see what's going onimage = datum.cvOutputDatacv2.putText(image,"FPS: %f" % (1.0 / (time.time() - fps_time)),(10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(0, 255, 0), 2)cv2.imshow("Openpose", image)if datum.poseKeypoints.any():first_input = datum.poseKeypointstry:first_input[:,:,0] = first_input[:,:,0] / 720first_input[:,:,1] = first_input[:,:,1] / 1280first_input = first_input[:,:,1:]first_input = first_input.reshape(len(datum.poseKeypoints), 50)except:continueoutput = tposer.predict_classes(first_input)for j in output:if j == 1:print("dab detected")if LIGHTS == 0 or (time.time() - bounced) < debounce:continuefor node in network.nodes:for val in network.nodes[node].get_switches():network.nodes[node].set_switch(val, False)LIGHTS = 0bounced = time.time()elif j == 2:print("tpose detected")if LIGHTS == 1 or (time.time() - bounced) < debounce:continuefor node in network.nodes:for val in network.nodes[node].get_switches():network.nodes[node].set_switch(val, True)LIGHTS = 1bounced = time.time()fps_time = time.time()# quit with a q keypress, b or m to save datakey = cv2.waitKey(1) & 0xFFif key == ord("q"):break# clean up after yourself vs.release() cv2.destroyAllWindows()到了這一步,工作基本上就算完成了!
我們成功地訓(xùn)練了一個(gè)用于識(shí)別嘻哈超人舞步、T-Pose舞步的神經(jīng)網(wǎng)絡(luò)模型,然后我們可以讓它根據(jù)我們的舞步來(lái)制造可互動(dòng)的燈。
太棒了,給自己點(diǎn)個(gè)贊!
06 后記
所有代碼、模型以及訓(xùn)練數(shù)據(jù)都免費(fèi)公布在Github上。
我建議你們?cè)贘upyter notebook上試試這個(gè)項(xiàng)目。我的代碼中有個(gè)漏洞,我一直無(wú)法從自己的工作簿中找出來(lái)。這個(gè)漏洞導(dǎo)致我的原始的X和Y標(biāo)簽并沒(méi)有被正確地標(biāo)記。如果你找到了解決這個(gè)漏洞的方法,記得在Github上創(chuàng)建一個(gè)Pull Request(PR)。
另外,我們今天構(gòu)建的基礎(chǔ)模型可以用來(lái)訓(xùn)練很多類(lèi)型的舞蹈動(dòng)作。盡管我的模型每秒只能捕捉很少的畫(huà)面,但我們可以開(kāi)始建立一個(gè)有關(guān)舞步的數(shù)據(jù)集,然后再構(gòu)建一個(gè)能識(shí)別這些不同舞步的神經(jīng)網(wǎng)絡(luò)模型。
相關(guān)報(bào)道:
https://www.makeartwithpython.com/blog/dab-and-tpose-controlled-lights/
總結(jié)
以上是生活随笔為你收集整理的程序员深夜用Python跑神经网络,只为用中二动作关掉台灯的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python中有用的字符串方法
- 下一篇: Python中的一些“小坑”