AI Car 第三次展示報告
實現(xiàn)的功能
- 基于攝像頭的雙目測距
- 基于卷積神經(jīng)網(wǎng)絡(luò)的圖像識別
- 基于長短期記憶網(wǎng)絡(luò)的音頻識別
- 實時學(xué)習(xí)
- 聽從人命令
- 跟隨主人速度行走
- 為主人導(dǎo)航
功能介紹
室內(nèi)模式
- 啟動小車,人在小車側(cè)面晃動,小車學(xué)習(xí)主人身體特征——實時學(xué)習(xí)
- 小車識別出主人之后,帶領(lǐng)主人在室內(nèi)隨機游走
- 時刻緊跟主人腳步,主人停下就停下,主人放慢腳步就減慢速度——實時學(xué)習(xí)
- 距離障礙物半米時向右拐彎——雙目測距
跑道模式
- 主人喊出口令“Hey AI”,小車識別出來之后學(xué)習(xí)主人身體特征——音頻識別
- 認(rèn)識主人后,啟動小車馬達(dá)——實時學(xué)習(xí)
- 識別跑道白線,順著跑道線外延行走——圖像識別
- 同時跟著主人速度走——實時學(xué)習(xí)
詳細(xì)算法介紹
雙目測距
雙目測距原理圖如下:
[圖片上傳失敗...(image-c1e316-1514170339592)]
雙目測距主要是利用了目標(biāo)點在左右兩幅視圖上成像的橫向坐標(biāo)直接存在的差異,也就是視差d,由相似三角形原理得知其與目標(biāo)點到成像平面的距離Z存在著反比例的關(guān)系 Z = fT/d。
相機標(biāo)定: 攝像頭由于光學(xué)透鏡的特性使得成像存在著徑向畸變,可由三個參數(shù)k1,k2,k3確定;由于裝配方面的誤差,傳感器與光學(xué)鏡頭之間并非完全平行,因此成像存在切向畸變,可由兩個參數(shù)p1,p2確定。單個攝像頭的定標(biāo)主要是計算出攝像頭的內(nèi)參(焦距f和成像原點cx,cy、五個畸變參數(shù)(一般只需要計算出k1,k2,p1,p2,對于魚眼鏡頭等徑向畸變特別大的才需要計算k3))以及外參(標(biāo)定物的世界坐標(biāo))。而雙目攝像頭定標(biāo)不僅要得出每個攝像頭的內(nèi)部參數(shù),還需要通過標(biāo)定來測量兩個攝像頭之間的相對位置(即右攝像頭相對于左攝像頭的旋轉(zhuǎn)矩陣R、平移向量t)。
[圖片上傳失敗...(image-5e36b5-1514170339592)])
[圖片上傳失敗...(image-cf4a4c-1514170339592)])
[圖片上傳失敗...(image-8b31b0-1514170339592)]\right])
[圖片上傳失敗...(image-8039ae-1514170339593)]+2p_2x\right])
雙目校正: 雙目校正是根據(jù)攝像頭定標(biāo)后獲得的單目內(nèi)參數(shù)據(jù)(焦距、成像原點、畸變系數(shù))和雙目相對位置關(guān)系(旋轉(zhuǎn)矩陣和平移向量),分別對左右視圖進(jìn)行消除畸變和行對準(zhǔn),使得左右視圖的成像原點坐標(biāo)一致、兩攝像頭光軸平行、左右成像平面共面、對極線行對齊。這樣一幅圖像上任意一點與其在另一幅圖像上的對應(yīng)點就必然具有相同的行號,只需在該行進(jìn)行一維搜索即可匹配到對應(yīng)點。
雙目匹配: 雙目匹配的作用是把同一場景在左右視圖上對應(yīng)的像點匹配起來,這樣做的目的是為了得到視差圖。雙目匹配被普遍認(rèn)為是立體視覺中最困難也是最關(guān)鍵的問題。得到視差數(shù)據(jù),通過上述原理中的公式就可以很容易的計算出深度信息。
實際注意事項:
- SIFT特征提取算法對左右圖像點提取特征
- knnMatch取k=2找到左右圖片最佳匹配
- 再過濾去除壞的匹配點
- 對于剩下的點使用相似三角形計算公式得到圖片各點景深標(biāo)在圖上
- 最終小車避障可根據(jù)其中少數(shù)點進(jìn)行判斷,或者取均值。
標(biāo)定結(jié)果如下:
以下圖片均為右轉(zhuǎn)90度的結(jié)果,因為小車拍攝到的視頻原狀是右偏的




由標(biāo)定結(jié)果可以看出,其測距效果還是比較接近真實值的。
圖像識別
CNN算法介紹

使用CNN神經(jīng)網(wǎng)絡(luò)對六百多張圖片進(jìn)行學(xué)習(xí),判斷小車應(yīng)當(dāng)直走、左轉(zhuǎn)、還是右轉(zhuǎn)。如左圖所示,白線斜率過大,小車距離白線過近,因此小車應(yīng)該左轉(zhuǎn),如中間圖片所示,小車應(yīng)該直走,如右圖所示,視野內(nèi)并沒有白線,此時默認(rèn)小車直走。
CNN神經(jīng)網(wǎng)絡(luò)的基本結(jié)構(gòu)

可以看出最左邊的圖像是輸入層,計算機理解為輸入若干個矩陣,接著是卷積層(Convolution Layer),在卷積層后面是池化層(Pooling layer),卷積層+池化層的組合可以在隱藏層出現(xiàn)很多次,在若干卷積層+池化層后面是全連接層(Fully Connected Layer, 簡稱FC),最后是輸出層。
- 卷積層
卷積層是CNN神經(jīng)網(wǎng)絡(luò)中最重要的一層,我們通過如下的一個例子來理解它的原理。圖中的輸入是一個二維的3x4的矩陣,而卷積核是一個2x2的矩陣。這里我們假設(shè)卷積是一次移動一個像素來卷積的,那么首先我們對輸入的左上角2x2局部和卷積核卷積,即各個位置的元素相乘再相加,得到的輸出矩陣S的S00S00的元素,值為aw+bx+ey+fzaw+bx+ey+fz。接著我們將輸入的局部向右平移一個像素,現(xiàn)在是(b,c,f,g)四個元素構(gòu)成的矩陣和卷積核來卷積,這樣我們得到了輸出矩陣S的S01S01的元素,同樣的方法,我們可以得到輸出矩陣S的S02,S10,S11,S12S02,S10,S11,S12的元素。

- 池化層
池化層的作用是對輸入張量的各個子矩陣進(jìn)行壓縮。假如是2x2的池化,那么就將子矩陣的每2x2個元素變成一個元素,如果是3x3的池化,那么就將子矩陣的每3x3個元素變成一個元素,這樣輸入矩陣的維度就變小了。
要想將輸入子矩陣的每nxn個元素變成一個元素,那么需要一個池化標(biāo)準(zhǔn)。常見的池化標(biāo)準(zhǔn)有2個,MAX或者是Average。即取對應(yīng)區(qū)域的最大值或者平均值作為池化后的元素值。
下面這個例子采用取最大值的池化方法。同時采用的是2x2的池化。步幅為2。

首先對紅色2x2區(qū)域進(jìn)行池化,由于此2x2區(qū)域的最大值為6.那么對應(yīng)的池化輸出位置的值為6,由于步幅為2,此時移動到綠色的位置去進(jìn)行池化,輸出的最大值為8.同樣的方法,可以得到黃色區(qū)域和藍(lán)色區(qū)域的輸出值。最終,我們的輸入4x4的矩陣在池化后變成了2x2的矩陣。進(jìn)行了壓縮。
- 損失層
dropout是指在深度學(xué)習(xí)網(wǎng)絡(luò)的訓(xùn)練過程中,對于神經(jīng)網(wǎng)絡(luò)單元,按照一定的概率將其暫時從網(wǎng)絡(luò)中丟棄。注意是暫時,對于隨機梯度下降來說,由于是隨機丟棄,故而每一個mini-batch都在訓(xùn)練不同的網(wǎng)絡(luò)。

dropout最重要的功能就是防止數(shù)據(jù)出現(xiàn)過擬合。
算法具體實現(xiàn)
- CNN結(jié)構(gòu)圖
使用keras搭建卷積神經(jīng)網(wǎng)絡(luò)

- CNN各層介紹
- 卷積層2:33小核計算,降低復(fù)雜度同時不損失精度
- 激活層:Relu,f(x)=max(0,x),收斂速度快
- 池化層:區(qū)域壓縮為1/4,降低復(fù)雜度并減少特征損失
- 全連接層*2:將分布式特征表示映射到樣本標(biāo)記空間
- Dropout層:Dropout設(shè)為0.5,防止過擬合,減少神經(jīng)元之間相互依賴
- 激活層:softmax,平衡多分類問題
- 效果分析

上圖是我們各個類別的準(zhǔn)確率和召回率??梢钥闯?,除了類別1,也就是左轉(zhuǎn)類的召回率較低以外,其他類的準(zhǔn)確率和召回率都較高。

宏平均(Macro-averaging),是先對每一個類統(tǒng)計指標(biāo)值,然后在對所有類求算術(shù)平均值。
微平均(Micro-averaging),是對數(shù)據(jù)集中的每一個實例不分類別進(jìn)行統(tǒng)計建立全局混淆矩陣,然后計算相應(yīng)指標(biāo)。
- 具體代碼實現(xiàn)
model = Sequential()
model.add(Convolution2D(nb_filters, (kernel_size[0], kernel_size[1]),
padding='same',
input_shape=(200,480,1))) # 卷積層
model.add(Activation('relu')) #激活層
model.add(Convolution2D(nb_filters, (kernel_size[0], kernel_size[1]))) #卷積層2
model.add(Activation('relu')) #激活層
model.add(MaxPooling2D(pool_size=pool_size)) #池化層
model.add(Dropout(0.25)) #神經(jīng)元隨機失活
model.add(Flatten()) #拉成一維數(shù)據(jù)
model.add(Dense(128)) #全連接層1
model.add(Activation('relu')) #激活層
model.add(Dropout(0.5)) #經(jīng)過交叉驗證
model.add(Dense(nb_classes)) #全連接層2
model.add(Activation('softmax')) #評分函數(shù)
#編譯模型
model.compile(loss='categorical_crossentropy',
optimizer='adadelta',
metrics=['accuracy'])
#訓(xùn)練模型
model.fit(train, y, batch_size=32, epochs=3,
verbose=1)
實驗結(jié)果
| Class | Precision | Recall | F1-Score |
|---|---|---|---|
| 0 | 0.73 | 1.00 | 0.85 |
| 1 | 1.00 | 0.18 | 0.31 |
| 2 | 0.89 | 0.94 | 0.91 |
| Avg/Total | 0.81 | 0.80 | 0.75 |
下圖為本實驗的ROC曲線,由此可見,除了左轉(zhuǎn)之外,剩下的曲線AUC值均達(dá)到了0.97,最差的AUC值也達(dá)到了0.84,效果還算不錯。

實驗的分類報告如下:
| Class | Precision | Recall | F1-Score |
|---|---|---|---|
| 0 | 0.73 | 1.00 | 0.85 |
| 1 | 1.00 | 0.18 | 0.31 |
| 2 | 0.89 | 0.94 | 0.91 |
| Avg/Total | 0.81 | 0.80 | 0.75 |
音頻識別
音頻識別的目標(biāo)是識別出主人的口令“Hey AI”,識別出來口令中是否包含“Hey AI”,為一個二分類問題,使用的工具為RNN的一個變種LSTM。
LSTM介紹
Long Short Term 網(wǎng)絡(luò)—— 一般就叫做 LSTM ——是一種 RNN 特殊的類型,可以學(xué)習(xí)長期依賴信息。LSTM 由Hochreiter & Schmidhuber (1997)提出,并在近期被Alex Graves進(jìn)行了改良和推廣。在很多問題,LSTM 都取得相當(dāng)巨大的成功,并得到了廣泛的使用。
LSTM 通過刻意的設(shè)計來避免長期依賴問題。記住長期的信息在實踐中是 LSTM 的默認(rèn)行為,而非需要付出很大代價才能獲得的能力!
所有 RNN 都具有一種重復(fù)神經(jīng)網(wǎng)絡(luò)模塊的鏈?zhǔn)降男问?。在?biāo)準(zhǔn)的 RNN 中,這個重復(fù)的模塊只有一個非常簡單的結(jié)構(gòu),例如一個 tanh 層。

LSTM 同樣是這樣的結(jié)構(gòu),但是重復(fù)的模塊擁有一個不同的結(jié)構(gòu)。不同于 單一神經(jīng)網(wǎng)絡(luò)層,這里是有四個,以一種非常特殊的方式進(jìn)行交互。

LSTM 的關(guān)鍵就是細(xì)胞狀態(tài),水平線在圖上方貫穿運行。
細(xì)胞狀態(tài)類似于傳送帶。直接在整個鏈上運行,只有一些少量的線性交互。信息在上面流傳保持不變會很容易。

LSTM 有通過精心設(shè)計的稱作為“門”的結(jié)構(gòu)來去除或者增加信息到細(xì)胞狀態(tài)的能力。門是一種讓信息選擇式通過的方法。他們包含一個 sigmoid 神經(jīng)網(wǎng)絡(luò)層和一個 pointwise 乘法操作。
Sigmoid 層輸出 0 到 1 之間的數(shù)值,描述每個部分有多少量可以通過。0 代表“不許任何量通過”,1 就指“允許任意量通過”!
LSTM 擁有三個門,來保護(hù)和控制細(xì)胞狀態(tài)。
LSTM具體實現(xiàn)
LSTM的框架圖如下

首先使用了兩層雙向LSTM,接著利用Flatten實現(xiàn)到兩層全連接層的過渡。雖然單向LSTM已經(jīng)足夠進(jìn)行分類,但為了獲得更高的準(zhǔn)確度,是用了更強的雙向LSTM。
最后實驗結(jié)果在測試集上的各項指標(biāo)均接近于1,也就是全部分類正確,就不進(jìn)行圖表繪制。
實時學(xué)習(xí)
算法介紹
- 識別出運動的像素點

通過對比相鄰的兩幀圖像之間像素點的移動,標(biāo)注出移動的像素點。得到效果圖如下圖所示。

代碼如下所示:
def draw_flow(old, new, step=4):
flow = cv.calcOpticalFlowFarneback(
cv.cvtColor(old, cv.COLOR_BGR2GRAY),
cv.cvtColor(new, cv.COLOR_BGR2GRAY),
None, 0.5, 3, 15, 3, 5, 1.2, 0)
h, w = new.shape[:2]
y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2, -1)
fx, fy = flow[np.int32(y), np.int32(x)].T
lines = np.int32(np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2))
for (x1, y1), (x2, y2) in lines:
if sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) < 15:
continue
cv.line(old, (x1, y1), (x2, y2), (0, 128, 0), 2)
cv.circle(old, (x1, y1), 3, (0, 255, 0), -1)
# x1 y1是old的運動點坐標(biāo),x2y2是new運動點的坐標(biāo)
return old
- 畫出目標(biāo)區(qū)域
kmeans 算法接受參數(shù) k ;然后將事先輸入的n個數(shù)據(jù)對象劃分為 k個聚類以便使得所獲得的聚類滿足:同一聚類中的對象相似度較高;而不同聚類中的對象相似度較小。聚類相似度是利用各中對象的均值所獲得一個“中心對象”(引力中心)來進(jìn)行計算的。
Kmeans算法是最為經(jīng)典的基于劃分的聚類方法,是十大經(jīng)典數(shù)據(jù)挖掘算法之一。Kmeans算法的基本思想是:以空間中k個點為中心進(jìn)行聚類,對最靠近他們的對象歸類。通過迭代的方法,逐次更新各聚類中心的值,直至得到最好的聚類結(jié)果。

我們使用Kmeans聚類分析的算法,將運動的像素點劃分為三個類別,分別用矩形框?qū)^(qū)域框出。

- 特征提取
我們使用顏色作為人的主要特征,找出上步標(biāo)注出的三個矩形框中面積最大的一個,進(jìn)行主顏色的提取。
def get_dominant_color(image):
#顏色模式轉(zhuǎn)換,以便輸出rgb顏色值
image = image.convert('RGBA')
#生成縮略圖,減少計算量,減小cpu壓力
image.thumbnail((200, 200))
max_score = 0
dominant_color = 0
for count, (r, g, b, a) in image.getcolors(image.size[0] * image.size[1]):
# 跳過純黑色
if a == 0:
continue
saturation = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)[1]
y = min(abs(r * 2104 + g * 4130 + b * 802 + 4096 + 131072) >> 13, 235)
y = (y - 16.0) / (235 - 16)
# 忽略高亮色
if y > 0.9:
continue
score = (saturation + 0.1) * count
if score > max_score:
max_score = score
dominant_color = (r, g, b)
return dominant_color
- 實時識別
我們根據(jù)顏色特征來識別出實時圖像中人的位置。在RGB顏色空間中,以主顏色+-20作為判斷的顏色區(qū)域,找出符合的像素點。通過erode和dilate來平滑像素點,得到一個區(qū)域,然后通過opencv的輪廓尋找功能找到區(qū)域輪廓的像素點,用矩形框標(biāo)出這個區(qū)域。

mask = cv2.inRange(image, lower, upper)
mask = cv2.erode(mask, None, iterations=2)
mask = cv2.dilate(mask, None, iterations=2)
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
if len(cnts) > 0:
#找到面積最大的輪廓
c = max(cnts, key = cv2.contourArea)
x1,y1 = 1000,1000
x2,y2 = 0,0
for i in range(0,len(c)):
if c[i][0][0] < x1:
x1 = c[i][0][0]
if c[i][0][0] > x2:
x2 = c[i][0][0]
if c[i][0][1] < y1:
y1 = c[i][0][1]
if c[i][0][1] > y2:
y2 = c[i][0][1]
cv2.rectangle(image,(x1,y1),(x2,y2),(55,255,155),5)
小組分工
| 組員 | 參與工作 |
|---|---|
| 楊昆霖 | 圖像識別、音頻識別 |
| 劉玨 | 圖像識別、實時學(xué)習(xí) |
| 施暢 | 雙目測距、數(shù)據(jù)采集 |
| 侯尚文 | 數(shù)據(jù)標(biāo)注、 |