基於卷積神經網路和tensorflow實現的人臉識別
阿新 • • 發佈:2019-01-08
以前在學習卷積神經網路的時候,發現了很多很有趣的demo,有一次發現了上面這個人臉識別的例子,不過當時還看不懂,經過一段時間之後決定試試能不能將上面的例子改一下,調以調參什麼的,於是就有了這篇文章。本以為我的程式碼和原文沒有什麼太大的區別,應該不會出現什麼錯誤,但是實際自己上手之後才會發現很多的問題。具體的程式安裝,我這裡就不再贅述了,大家可以參考原文,講的很詳細。下面我把我的程式碼貼出來。和原來的程式碼沒有相差很多,就是改了一下中間的卷積層。自己實際操作還是學到了很多東西的。和原文相比,收集人臉和處理其他人臉的程式碼我都沒有做太大的改變,只是去掉了原文程式碼中修改圖片亮度和對比度的部分,所以我收集的圖片都是一樣的亮度和一樣的對比度,這裡就不貼這兩部分的程式碼了。這裡直接貼訓練部分的程式碼和最後使用模型的程式碼。
import tensorflow as tf import cv2 import numpy as np import os import random import sys from sklearn.model_selection import train_test_split my_faces_path = './gray_my_faces' other_faces_path = './other_faces' #圖片的大小採集的64*64 size = 64 imgs = [] labs = []#定義兩個陣列用來存放圖片和標籤 #這裡是得到圖片的大小,進行統一處理,將圖片改成統一大小 def getPaddingSize(img): h,w,_ = img.shape#獲得圖片的寬和高還有深度 top,bottom,left,right = (0,0,0,0) longest = max(h,w) if w < longest: tmp = longest - w left = tmp // 2 right = tmp - left elif h < longest: tmp = longest - h top = tmp // 2 bottom = tmp - top else: pass return top,bottom,left,right def readData(path,h=size,w=size): for filename in os.listdir(path): if filename.endswith('.jpg'): filename = path + '/' + filename img = cv2.imread(filename) top,bottom,left,right = getPaddingSize(img) #將圖片放大,擴充圖片邊緣部分,這裡不是很理解為什麼要擴充邊緣部分 #可能是為了實現像padding的作用 img = cv2.copyMakeBorder(img,top,bottom,left,right,cv2.BORDER_CONSTANT,value=[0,0,0]) img = cv2.resize(img,(h,w)) # 將對應的圖片和標籤存進數組裡面 imgs.append(img) labs.append(path) readData(my_faces_path) readData(other_faces_path) #將圖片陣列與標籤轉換成陣列,並給圖片做上標籤 imgs = np.array(imgs) labs = np.array([[0,1] if lab == my_faces_path else [1,0] for lab in labs]) #隨機劃分測試集和訓練集,規定測試集的大小,這裡是可以自己調的 train_x,test_x,train_y,test_y = train_test_split(imgs,labs,test_size=0.05,random_state=random.randint(0,100)) #引數:圖片資料的總數,圖片的高、寬、通道 train_x = train_x.reshape(train_x.shape[0], size, size,3) test_x = test_x.reshape(test_x.shape[0], size, size, 3) #將資料轉換為小於1的數 train_x = train_x.astype('float32') / 255.0 test_x = test_x.astype('float32') / 255.0 #輸出一下獲取的訓練圖片和測試圖片的長度,也就是大小 print('train size:%s,test size:%s' % (len(train_x),len(test_x))) #圖片塊,每次取100張圖片 batch_size = 100 #計算有多少個batch num_batch = (len(train_x)) // batch_size input = tf.placeholder(tf.float32,[None,size,size,3]) output = tf.placeholder(tf.float32,[None,2]) #這裡將input在進行處理一下 images = tf.reshape(input,[-1,size,size,3]) keep_prob_5 = tf.placeholder(tf.float32) keep_prob_75 = tf.placeholder(tf.float32) #下面開始進行卷積層的處理 #第一層卷積,首先輸入的圖片大小是64*64 def cnnlayer(): conv1 = tf.layers.conv2d(inputs=images, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#輸出大小是(64*64*32) #第一層池化 pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2,2], strides=2)#輸出大小是(32*32*32) #第二層卷積 conv2 = tf.layers.conv2d(inputs=pool1, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#輸出大小是(32*32*32) #第二層池化 pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2,2], strides=2)#輸出大小是(16*16*32) #第三層卷積 conv3 = tf.layers.conv2d(inputs=pool2, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#(變成16*16*32) #第三層池化 pool3 = tf.layers.max_pooling2d(inputs=conv3, pool_size=[2,2], strides=2)#輸出大小是(8*8*32) #第四層卷積 conv4 = tf.layers.conv2d(inputs=pool3, filters=64, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#輸出大小是(變成8*8*64) # pool3 = tf.layers.max_pooling2d(inputs=conv4, # pool_size=[2,2], # strides=2)#輸出大小是(變成4*4*64) #卷積網路在計算每一層的網路個數的時候要細心一些,不然容易出錯 #要注意下一層的輸入是上一層的輸出 #平坦化 flat = tf.reshape(conv4,[-1,8*8*64]) #經過全連線層 dense = tf.layers.dense(inputs=flat, units=4096, activation=tf.nn.relu) #drop_out處理 drop_out = tf.layers.dropout(inputs=dense,rate=0.5) #輸出層 logits = tf.layers.dense(drop_out,units=2) return logits # yield logits def cnntrain(): logits = cnnlayer() # logits = next(cnnlayer()) #交叉熵損失函式 cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=output)) #將訓練優化方法改成GradientDescentOptimizer發現並沒有加快收斂所以又改回AdamOptimizer #train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) train_step = tf.train.AdamOptimizer(0.01).minimize(cross_entropy) # 比較標籤是否相等,再求的所有數的平均值,tf.cast(強制轉換型別) accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits,1),tf.argmax(output,1)),tf.float32)) #將loss與accuracy儲存以供tensorboard使用 tf.summary.scalar('loss',cross_entropy) tf.summary.scalar('accuracy',accuracy) #合併所有的Op為一個Op merged_summary_op = tf.summary.merge_all() #資料儲存器的初始化 saver = tf.train.Saver() with tf.Session() as sess: sess.run(tf.global_variables_initializer()) #把summary Op返回的資料寫到磁盤裡 summary_writer = tf.summary.FileWriter('./tmp',graph=tf.get_default_graph()) for n in range(10): #每次取100(batch_size)張圖片 for i in range(num_batch): batch_x = train_x[i*batch_size : (i + 1) * batch_size] batch_y = train_y[i*batch_size : (i + 1) * batch_size] #開始訓練資料,同時訓練三個變數,返回三個資料, _,loss,summary = sess.run([train_step,cross_entropy,merged_summary_op], feed_dict={input: batch_x, output: batch_y}) summary_writer.add_summary(summary, n * num_batch + i) # 列印損失 print(n * num_batch + i, loss) if (n * num_batch + i) % 100 == 0: # 獲取測試資料的準確率 acc = accuracy.eval({input: test_x, output: test_y,keep_prob_5:1.0,keep_prob_75:1.0}) print("第%f個batch,準確率%f" % (n*num_batch+i, acc)) # 準確率大於0.98時儲存並退出 if acc > 0.98 and n > 2: saver.save(sess, './train_faces.model', global_step=n * num_batch + i) sys.exit(0) print('accuracy less 0.98, exited!') cnntrain()
再加上最後使用模型的程式碼。
import tensorflow as tf import cv2 import numpy as np import os import random import sys from sklearn.model_selection import train_test_split import dlib my_faces_path = './my_faces' other_faces_path = './other_faces' #將得到的自己的圖片和其他圖片進行處理 size = 64 imgs = [] labs = []#定義兩個陣列用來存放圖片和標籤 def getPaddingSize(img): h,w,_ = img.shape#獲得圖片的寬和高還有深度 top,bottom,left,right = (0,0,0,0) longest = max(h,w) if w < longest: tmp = longest - w left = tmp // 2 right = tmp - left elif h < longest: tmp = longest - h top = tmp // 2 bottom = tmp - top else: pass return top,bottom,left,right def readData(path,h=size,w=size): for filename in os.listdir(path): if filename.endswith('.jpg'): filename = path + '/' + filename img = cv2.imread(filename) top,bottom,left,right = getPaddingSize(img) #將圖片放大,擴充圖片邊緣部分,這裡不是很理解為什麼要擴充邊緣部分 img = cv2.copyMakeBorder(img,top,bottom,left,right,cv2.BORDER_CONSTANT,value=[0,0,0]) img = cv2.resize(img,(h,w)) imgs.append(img) labs.append(path)#將對應的圖片和標籤存進數組裡面 readData(my_faces_path) readData(other_faces_path) #將圖片陣列與標籤轉換成陣列 imgs = np.array(imgs) labs = np.array([[0,1] if lab == my_faces_path else [1,0] for lab in labs]) #隨機劃分測試集和訓練集 train_x,test_x,train_y,test_y = train_test_split(imgs,labs,test_size=0.05,random_state=random.randint(0,100)) #引數:圖片資料的總數,圖片的高、寬、通道 train_x = train_x.reshape(train_x.shape[0], size, size,3) test_x = test_x.reshape(test_x.shape[0], size, size, 3) #將資料轉換為小於1的數,二值化使處理變得更簡單 train_x = train_x.astype('float32') / 255.0 test_x = test_x.astype('float32') / 255.0 #獲取訓練圖片和測試圖片的長度,也就是大小 print('train size:%s,test size:%s' % (len(train_x),len(test_x))) #圖片塊,每次取100張圖片 batch_size = 100 num_batch = (len(train_x)) // batch_size#計算總共多少輪 input = tf.placeholder(tf.float32,[None,size,size,3]) output = tf.placeholder(tf.float32,[None,2])#輸出加兩個,true or false #這裡注意的是tf.reshape不是np.reshape # images = tf.reshape(input,[-1,size,size,3]) keep_prob_5 = tf.placeholder(tf.float32) keep_prob_75 = tf.placeholder(tf.float32) #下面開始進行卷積層的處理 #第一層卷積,首先輸入的圖片大小是64*64 def cnnlayer(): #第一層卷積 conv1 = tf.layers.conv2d(inputs=input, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#(64*64*32) #第一層池化 pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2,2], strides=2)#(32*32*32) #第二層卷積 conv2 = tf.layers.conv2d(inputs=pool1, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#(32*32*32) #第二層池化 pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2,2], strides=2)#(16*16*32) #第三層卷積 conv3 = tf.layers.conv2d(inputs=pool2, filters=32, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#(變成16*16*32) #第三層池化 pool3 = tf.layers.max_pooling2d(inputs=conv3, pool_size=[2,2], strides=2)#(8*8*32) #第四層卷積 conv4 = tf.layers.conv2d(inputs=pool3, filters=64, kernel_size=[5,5], strides=1, padding='same', activation=tf.nn.relu)#(變成8*8*64) # pool3 = tf.layers.max_pooling2d(inputs=conv4, # pool_size=[2,2], # strides=2)#(變成4*4*6) #卷積網路在計算每一層的網路個數的時候要細心一些 #卷積層加的padding為same是不會改變卷積層的大小的 #要注意下一層的輸入是上一層的輸出 #平坦化 flat = tf.reshape(conv4,[-1,8*8*64]) #經過全連線層 dense = tf.layers.dense(inputs=flat, units=4096, activation=tf.nn.relu) #drop_out,flat打錯一次 drop_out = tf.layers.dropout(inputs=dense,rate=0.2) #輸出層 logits = tf.layers.dense(drop_out,units=2) return logits # yield logits out = cnnlayer() # out = next(cnnlayer()) predict = tf.argmax(out,1) saver = tf.train.Saver() sess = tf.Session() saver.restore(sess,tf.train.latest_checkpoint('.')) def is_my_face(image): res = sess.run(predict, feed_dict={input: [image / 255.0]}) if res[0] == 1: return True else: return False # 使用dlib自帶的frontal_face_detector作為我們的特徵提取器 detector = dlib.get_frontal_face_detector() cam = cv2.VideoCapture(0) while True: _, img = cam.read() gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) dets = detector(gray_image, 1) if not len(dets): # print('Can`t get face.') cv2.imshow('img', img) key = cv2.waitKey(30) & 0xff if key == 27: sys.exit(0) for i, d in enumerate(dets): x1 = d.top() if d.top() > 0 else 0 y1 = d.bottom() if d.bottom() > 0 else 0 x2 = d.left() if d.left() > 0 else 0 y2 = d.right() if d.right() > 0 else 0 face = img[x1:y1, x2:y2] # 調整圖片的尺寸 face = cv2.resize(face, (size, size)) print('Is this my face? %s' % is_my_face(face)) cv2.rectangle(img, (x2, x1), (y2, y1), (255, 0, 0), 3) cv2.imshow('image', img) key = cv2.waitKey(30) & 0xff if key == 27: sys.exit(0) sess.close()
總結:經過這一次的學習,對卷積神經網路有了更深的理解,在我修改程式碼的時候,雖然只是修改中間的卷積層,但還是出現了很多的問題,感覺自己不是寫的程式碼而是寫的bug。期間被卡住了很久,不過在Stack Overflow的一些大佬幫助下還是解決了問題(雖然我沒有提問,但是上面之前的一些回答幫助了我很多)。總而言之,自己動手擼一遍程式碼還是比只是看看學到的多啊。