[設計師的AI自學之路]用圖像識別玩忍術

[設計師的AI自學之路]用圖像識別玩忍術

來自專欄人工智慧+區塊鏈+設計 修鍊指南9 人贊了文章

某日工作室學妹問我,看視頻學人工智慧好枯燥,有沒有實際項目可以實踐下?

正巧室友剛做了一個識別剪刀石頭布的圖像識別程序

於是腦洞大開

改造了一下,做了這個識別結印手勢來發動忍術的小遊戲。

演示視頻:

https://www.zhihu.com/video/994141709206446080

這裡我就把項目整理成教程,讓大家都能做腦洞大開的創作。

感謝室友陸玄青提供的簡單圖像識別源碼

改造後的識別手勢玩火影忍者忍術源碼在這裡

本項目不要求有人工智慧基礎,但要有python基礎

需要的環境

  • tensorflow1.1
  • keras
  • opencv
  • python3
  • ffmpeg
  • PIL
  • pathlib
  • shutil
  • imageio
  • numpy
  • pygame
  • 一個攝像頭

整體流程

  1. 下載源碼後,用jupyternotebook打開tutorial.ipynb文件,按照裡面的教程,一步一步運行,全部運行過後,就得到訓練好的能識別手勢的神經網路模型文件
  2. 運行model文件夾下的predict.py,即可開始試玩

注意事項

我用的vscode編輯器,把當前工作路徑設置為 NARUTO_game 這個主文件夾,並以此設置相關的相對路徑,若直接cd到model文件夾來運行predict.py文件,需要手動調整源碼中的相對路徑


其實教程具體操作已經全部寫在tutorial.ipynb里了,為讓大家更直觀了解整個操作過程,這裡就把tutorial.ipynb里的文字複製搬運到這裡來。


Step1 - 採集數據

  1. 用手機拍攝視頻記錄你想要識別的物體。每段視頻中只能包含一種物體,時長10~30秒,每個物體可以拍攝多段視頻。視頻盡量用4:3或1:1的長寬比,解析度越好(注意是低)。
  2. 進入data/video文件夾,為每種物體(手勢)新建一個文件夾,然後把相應的視頻導入進去。例如我拍攝了5段關於貓的視頻和3段關於狗的視頻,就在data/video文件夾下新建dog、cat兩個文件夾,然後把把貓的視頻全部放進cat文件夾,把狗的視頻全部放進dog文件夾,視頻的文件名無所謂。

識別結印手勢的話我分了14類,12個結印為一類,空白動作為1類,還增加了一個取消動作(雖然這次並沒有用到),一共14類。視頻文件太大我就沒傳到github源碼上了,大家可以自己用電腦攝像頭錄一下。

Step2 - 數據處理

在這一步,我們需要把視頻轉成圖片,然後按照60%、20%、20%的比例拆分成訓練集(training set)、驗證集(validation set)、和測試集(test set)。

為了節省大家時間,我事先已經寫好了相關的代碼(utils.py),大家只要按照提示進行調用即可完成這一步驟。

import utils#################### 以下是你可以修改的部分 ####################fps = 5 # fps是視頻的採樣率,即每秒中採集多少張圖片,建議設置為5~10# 每張圖片的大小,根據你原始視頻的比例進行縮放,建議不要超過300x300# 訓練所需時間會和圖像的像素數量成正比,建議設置得小一點,如160x120width = 160height = 90#################### 以上是你可以修改的部分 ####################utils.process_videos(fps, target_size=(width, height))

Step3 - 數據增強

把一張原始圖片經過拉伸、旋轉、斜切、反轉等操作,可以生產若干新的不同的圖片,用以擴充訓練集數據量,有助於提高模型的預測準確性

from keras.preprocessing.image import ImageDataGeneratorfrom pathlib import Path# 設置train,val,test的目錄base_dir = Path(data)train_dir = base_dir/trainval_dir = base_dir/valtest_dir = base_dir/test# 創建train和val圖像生成器,它們會不斷地產生出新的圖片#################### 以下是你可以修改的部分 ####################train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=10, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=False, vertical_flip=True, fill_mode=nearest)#################### 以上是你可以修改的部分 ####################train_generator = train_datagen.flow_from_directory(train_dir, target_size=(height,width))val_generator = train_datagen.flow_from_directory(val_dir, target_size=(height,width))# test的時候是模擬真實環境,所以要使用原始圖片,不要對圖片進行任何操作test_datagen = ImageDataGenerator(rescale=1./255)test_generator = test_datagen.flow_from_directory(test_dir, target_size=(height,width))

Step4 - 搭建卷積神經網路?

在這一步我們要搭建神經網路的架構。

圖像識別的常見方法是通過卷積操作提取圖片中的特徵,然後將特徵輸入到神經網路中,最後神經網路輸出結果。所以在這一階段,我們要分別準備卷積和神經網路兩個部分

4.1 - 卷積部分

遷移學習(transfer learning)

對圖像進行卷積操作需要耗費大量計算資源,並且訓練需要巨大的數據量,一般個人是搞不定這事的。

好消息是人們發現了一個有趣的現象:訓練出來用於識別A物體的卷積神經網路,它的卷積部分也能夠很好地被用於識別B物體。

所以我們可以把人家已經訓練好的NB的卷積神經網路借來用,這就是遷移學習。

載入VGG16

VGG16是一個非常經典的卷積神經網路,16代表有16個層,前13層是卷積層,後3層是全連階層。我們需要使用它的前13個卷積層,並且使用這些層的權值,用來從圖像中提取特徵。然後把提取後的特徵輸入到我們自己的神經網路中進行識別。

import keras as K# load pretrained model and weightsconv_layers = K.applications.VGG16(include_top=False, input_shape=(height,width,3))conv_layers.trainable = Falseprint(per-trained model has been loaded)

4.2 - 神經網路部分

model = K.models.Sequential()model.add(conv_layers)#載入VGG16的卷積部分model.add(K.layers.Flatten())#拉平成一維n_classes = len(utils.get_child_dir_names(base_dir/video))# 以下是你可以修改的部分model.add(K.layers.Dense(2048, activation=relu))model.add(K.layers.Dropout(0.5))model.add(K.layers.Dense(2048, activation=relu))model.add(K.layers.Dropout(0.5))# 以上是你可以修改的部分model.add(K.layers.Dense(n_classes, activation=softmax)) print(以下是神經網路的架構:)model.summary()

Step5 - 訓練及驗證

可以嘗試選擇不同的優化器和優化器參數(Keras文檔),好的優化器能讓訓練結果儘快收斂並獲得更高的準確率

model.compile(loss=categorical_crossentropy, optimizer=adam, metrics=[acc])print(優化器設置完畢)

下面開始訓練,為了節省時間只設置了迭代20次。你可以嘗試不同迭代次看看它數對最終結果的影響

n_epochs = 20n_train_samples = utils.count_jpgs(train_dir)#訓練集圖片總數n_val_samples = utils.count_jpgs(val_dir)#val集圖片總數batch_size = 32history = model.fit_generator(train_generator, steps_per_epoch=n_train_samples/batch_size, epochs=n_epochs, validation_data=val_generator, validation_steps=n_val_samples/batch_size, verbose=2)print(訓練完畢)

畫圖看一下訓練效果

from matplotlib import pyplot as pltfig = plt.figure(figsize=(8,4), dpi=100)plt.plot(range(n_epochs), history.history[acc], c, label=Training Accuracy, aa=True)plt.plot(range(n_epochs), history.history[val_acc], darkorange, label=Validation Accuracy, aa=True)plt.legend()plt.xlabel(epoch)plt.ylabel(Accuracy)plt.ylim(0,1)plt.grid()plt.show()

怎麼看訓練的結果好不好

好的情況

總體上來看,train和val的正確率都隨著迭代次數增加而上升,並且最後收斂於某一個比較高的數值。

兩種不好的情況

1.欠擬合(under-fitting)

train和val的正確率都比較低。 造成這種情況的原因有很多,常見的有:數據量不夠大、神經網路設計得不合理、優化器選擇不合理、迭代次數不夠

2.過擬合(over-fitting)

train的正確率很高,但是val正確率很低。 這種情況代表模型的泛化能力不好,它完全適應了訓練集的數據(可以接近100%的正確率),但是不適用於驗證集的數據。 解決方法是使用在Dense層後追加Dropout層或是在Densse層的選項中設置regularizer

Step6 - 測試

如果上面的驗證結果還不錯,那恭喜你就快要成功了! 最後我們用測試集的數據來測試一下

n_test_samples = utils.count_jpgs(test_dir)_, test_acc = model.evaluate_generator(test_generator, steps=n_test_samples/batch_size)print(測試正確率:{}.format(test_acc))

Step7 - 拍張照,讓程序來判斷它是什麼

拍一張照,上傳到 data/x 文件夾中,默認文件名是 myimage.jpg。如果你保存了其它文件名或是其它文件夾,需要修改下方代碼中的路徑。

先顯示一下圖片看看對不對

from PIL import Imagepath = data/test/辰/chen_0.jpgimg = Image.open(path)img.show()

讓程序來預測試試吧

x = utils.preprocess(img, (width, height))y = model.predict(x)[0]class_indices = train_generator.class_indices#獲得文件夾名的和類的序號對應的字典class_indices_reverse={v:k for k,v in class_indices.items()}#反轉字典的索引和內容值utils.show_pred(y,class_indices_reverse)

Optional - 用自己電腦的攝像頭做實時預測

先保存訓練好的模型文件

model.save(model/NARUTO.h5)utils.save_confg(class_indices_reverse,input_size=(160,90),fp=model/config.json)print(保存成功)

然後運行其中model文件夾下的的predict.py即可。

這裡有個注意事項:我用的vscode編輯器,把當前工作路徑設置為 NARUTO_game 這個主文件夾,並以此設置相關的相對路徑,若直接cd到model文件夾來運行predict.py文件,需要手動調整源碼中的相對路徑


然後predict.py文件里大部分是關於如何根據識別到的圖像結果,來做出放音效,放gif特效等操作,就不展開細講每一步在做什麼了,大家可以自己發揮想像力去改造。

用到的一些音效、gif圖、字體也都放在源碼倉庫里了。


# coding=utf-8from keras import modelsimport numpy as npimport cv2 import jsonimport osfrom PIL import Image, ImageDraw, ImageFont import pygame,timedef load_config(fp): with open(fp,encoding=UTF-8) as f: config = json.load(f, encoding=UTF-8) indices = config[indices] input_size = config[input_size] return indices, input_sizedef decode(preds, indices): results = [] for pred in preds: index = pred.argmax() result = indices[str(index)] results.append(result) result = results[0] return resultdef preprocess(arr, input_size): input_size = tuple(input_size) # resize x = cv2.resize(arr, input_size) # BGR 2 RGB x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB) x = np.expand_dims(x, 0).astype(float32) x /= 255 return xdef put_text_on_img(img, text=文字信息, font_size = 50, start_location = (100, 0), font_color = (255, 255, 255), fontfile = model/font.ttf): 讀取opencv的圖片,並把中文字放到圖片上 font_size = 100 #字體大小 start_location = (0, 0) #字體起始位置 font_color = (0, 0, 0) #字體顏色 fontfile = model/font.ttf #字體文件 # cv2讀取圖片 cv2img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # cv2和PIL中顏色的hex碼的儲存順序不同 pilimg = Image.fromarray(cv2img) # PIL圖片上列印漢字 draw = ImageDraw.Draw(pilimg) # 圖片上列印 font = ImageFont.truetype(fontfile, font_size, encoding="utf-8") # 參數1:字體文件路徑,參數2:字體大小 draw.text(start_location, text, font_color, font=font) # 參數1:列印坐標,參數2:文本,參數3:字體顏色,參數4:字體 # PIL圖片轉cv2 圖片 convert_img = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR) # cv2.imshow("圖片", cv2charimg) # 漢字窗口標題顯示亂碼 return convert_imgdef playBGM(): bgm_path = raudio/BGM.mp3 pygame.mixer.init() pygame.mixer.music.load(bgm_path) pygame.mixer.music.set_volume(0.2) pygame.mixer.music.play(loops=-1)def playsound(action): sound_path1 = audio/test1.wav sound_path2 = audio/test2.wav sound_path3 = audio/huituzhuansheng.wav sound_path4 = audio/yingfenshen.wav if action == "寅": sound1 = pygame.mixer.Sound(sound_path2) sound1.set_volume(0.3) sound1.play() elif action == "申": sound1 = pygame.mixer.Sound(sound_path1) sound1.set_volume(0.5) sound1.play() elif action == 酉: sound1 = pygame.mixer.Sound(sound_path3) sound1.set_volume(1) sound1.play() elif action == "丑": sound1 = pygame.mixer.Sound(sound_path4) sound1.set_volume(1) sound1.play() else: pass def add_gif2cap(cap, pngimg): # I want to put logo on top-left corner, So I create a ROI rows1,cols1,channels1 = cap.shape rows,cols,channels = pngimg.shape roi = cap[(rows1-rows)//2:(rows1-rows)//2+rows, (cols1-cols)//2:(cols1-cols)//2+cols ] # Now create a mask of logo and create its inverse mask also img2gray = cv2.cvtColor(pngimg,cv2.COLOR_BGR2GRAY) ret, mask = cv2.threshold(img2gray, 180, 255, cv2.THRESH_BINARY) mask_inv = cv2.bitwise_not(mask) # Now black-out the area of logo in ROI img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv) # Take only region of logo from logo image. img2_fg = cv2.bitwise_and(pngimg,pngimg,mask = mask) # Put logo in ROI and modify the main image dst = cv2.add(img1_bg,img2_fg) cap[(rows1-rows)//2:(rows1-rows)//2+rows, (cols1-cols)//2:(cols1-cols)//2+cols] = dst return capdef add_gif2cap_with_action(action, cap, png_num): if action == "寅": pngpath = image/shuilongdan/png/action-%02d.png%(png_num) pngimg = cv2.imread(pngpath) pngimg = cv2.resize(pngimg,None,fx=0.8, fy=0.8, interpolation = cv2.INTER_CUBIC) cap = add_gif2cap(cap, pngimg) return cap else: return capdef main(): indices, input_size = load_config(model/config.json) model = models.load_model(model/NARUTO.h5) cap = cv2.VideoCapture(0) counter = 0 counter_temp = 0 #計數器 action = "子" playBGM() png_num = 1 #用於計數動畫圖片序號的變數 while True: _, frame_img = cap.read() # predict x = preprocess(frame_img,input_size) y = model.predict(x) action = decode(y,indices) #播放音效,且每次播放間隔50個幀 counter+=1 if counter == 2: #觸發音效 playsound(action) counter += 1 if counter == 50: counter = 0 #顯示動作名 frame_img = put_text_on_img( img= frame_img, text= "當前動作:"+action, font_size = 50, start_location = (0, 100), font_color = (255, 150, 0) ) #觸發動畫 if action == "寅": frame_img = add_gif2cap_with_action(action, frame_img, png_num) png_num += 1 if png_num >=37:#水龍彈動畫有37幀 png_num=0 #show image cv2.imshow(webcam, frame_img) #按Q關閉窗口 if cv2.waitKey(1) & 0xFF == ord(q): break cv2.destroyAllWindows() cap.release()if __name__ == __main__: main() # playBGM()


寫在最後

我自己代碼水平不高,可能引起知乎讀者不適

因為編程和AI只是上學期才開始自學的 ???

真正的專業是工業設計(〃′-ω?)

跟知乎人工智慧大神沒法比

正在努力學習python和AI中

推薦閱讀:

Tensorflow object detection API之InvalidArgumentError: image_size must contain 3 elements[4]
GANimation讓圖片變GIF表情包,秒殺StarGAN
吊炸天的CNNs,這是我見過最詳盡的圖解!(上)
從人臉識別到情感分析,這有50個機器學習實用API!
DenseNet:比ResNet更優的CNN模型

TAG:機器學習 | 圖像識別 | 人工智慧 |