1. 程式人生 > >基於卷積神經網路和tensorflow實現的人臉識別

基於卷積神經網路和tensorflow實現的人臉識別

以前在學習卷積神經網路的時候,發現了很多很有趣的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的一些大佬幫助下還是解決了問題(雖然我沒有提問,但是上面之前的一些回答幫助了我很多)。總而言之,自己動手擼一遍程式碼還是比只是看看學到的多啊。