1. 程式人生 > >KNN也能進行人臉識別(Python實現加權歐氏距離KNN演算法)

KNN也能進行人臉識別(Python實現加權歐氏距離KNN演算法)

程式碼地址: https://github.com/youaresherlock/IntelligentInformationProcessing

 

前沿: 本實踐是純屬小白練手入門小專案,希望未來可以手動自己用神經網路來識別人臉。共勉,加油!

 

話不多說, 我們用加權歐氏距離KNN演算法來實現人臉識別(Python實現)

題目內容:

針對標準人臉樣本庫,選擇訓練和測試樣本,對基本的knn分類演算法設計智慧演算法進行改進,能夠對測試樣本識別出身份。 題目要求:

1) 選擇合適的編碼方法;

2) 構造目標函式,設計演算法進行求解;

3) 記錄目標函式進化曲線,記錄分配結果

4) 分析引數設定對演算法結果的影響

5) 統計識別正確率

 

人臉資料庫的選擇: ORL人臉庫

ORL人臉庫(Olivetti Research Laboratory人臉資料庫),誕生於英國劍橋Olivetti實驗室。

開源下載地址:   https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html

ORL人臉資料庫由該實驗室從1992年4月到1994年4月期間拍攝的一系列人臉影象組成,共有40個不同年齡、不同性別和不同種族的物件。每個人10幅影象共計400幅灰度影象組成,影象尺寸是92×112,影象背景為黑色。其中人臉部分表情和細節均有變化,例如笑與不笑、眼睛睜著或閉著,戴或不戴眼鏡等,人臉姿態也有變化,其深度旋轉和平面旋轉可達20度,人臉尺寸也有最多10%的變化。該庫是目前使用最廣泛的標準人臉資料庫,特別是剛從事人臉識別研究的學生和初學者,研究ORL人臉庫是個很好的開始。

 

使用加權KNN演算法來分類人臉:

目標函式:

通過結合KNN本身的分類演算法以及對前k個距離加權,來達到分類的目的 wk-nnc演算法是對經典knn演算法的改進,這種方法是對k個近鄰的樣本按照他們距離待分類樣本的遠近給一個權值w w(i) = (h(k) - h(i)) / (h(k) - h(1)) w(i)是第i個近鄰的權值,其中1<i<k,h(i)是待測樣本距離第i個近鄰的距離 

 

 

看程式碼:

影象特徵提取三大法寶:HOG特徵,LBP特徵,Haar特徵,Hog_descriptor這個類不是我寫的,本來還想弄特徵提取,結果分類結果很差,等待以後系統學習吧。

import os
import cv2
import shutil
import math
import random
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

class Hog_descriptor():
    def __init__(self, img, cell_size=16, bin_size=8):
        self.img = img
        self.img = np.sqrt(img / np.max(img))
        self.img = img * 255
        self.cell_size = cell_size
        self.bin_size = bin_size
        self.angle_unit = 360 / self.bin_size
        # assert type(self.bin_size) == int, "bin_size should be integer,"
        # assert type(self.cell_size) == int, "cell_size should be integer,"
        # assert type(self.angle_unit) == int, "bin_size should be divisible by 360"

    def extract(self):
        height, width = self.img.shape
        gradient_magnitude, gradient_angle = self.global_gradient()
        gradient_magnitude = abs(gradient_magnitude)

        cell_gradient_vector = np.zeros((int(height / self.cell_size), int(width / self.cell_size), self.bin_size))
        for i in range(cell_gradient_vector.shape[0]):
            for j in range(cell_gradient_vector.shape[1]):
                cell_magnitude = gradient_magnitude[i * self.cell_size:(i + 1) * self.cell_size,
                                 j * self.cell_size:(j + 1) * self.cell_size]
                cell_angle = gradient_angle[i * self.cell_size:(i + 1) * self.cell_size,
                             j * self.cell_size:(j + 1) * self.cell_size]
                cell_gradient_vector[i][j] = self.cell_gradient(cell_magnitude, cell_angle)

        hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)
        hog_vector = []
        for i in range(cell_gradient_vector.shape[0] - 1):
            for j in range(cell_gradient_vector.shape[1] - 1):
                block_vector = []
                block_vector.extend(cell_gradient_vector[i][j])
                block_vector.extend(cell_gradient_vector[i][j + 1])
                block_vector.extend(cell_gradient_vector[i + 1][j])
                block_vector.extend(cell_gradient_vector[i + 1][j + 1])
                mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))
                magnitude = mag(block_vector)
                if magnitude != 0:
                    normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
                    block_vector = normalize(block_vector, magnitude)
                hog_vector.append(block_vector)
        return hog_vector, hog_image

    def global_gradient(self):
        gradient_values_x = cv2.Sobel(self.img, cv2.CV_64F, 1, 0, ksize=5)
        gradient_values_y = cv2.Sobel(self.img, cv2.CV_64F, 0, 1, ksize=5)
        gradient_magnitude = cv2.addWeighted(gradient_values_x, 0.5, gradient_values_y, 0.5, 0)
        gradient_angle = cv2.phase(gradient_values_x, gradient_values_y, angleInDegrees=True)
        return gradient_magnitude, gradient_angle

    def cell_gradient(self, cell_magnitude, cell_angle):
        orientation_centers = [0] * self.bin_size
        for i in range(cell_magnitude.shape[0]):
            for j in range(cell_magnitude.shape[1]):
                gradient_strength = cell_magnitude[i][j]
                gradient_angle = cell_angle[i][j]
                min_angle, max_angle, mod = self.get_closest_bins(gradient_angle)
                orientation_centers[min_angle] += (gradient_strength * (1 - (mod / self.angle_unit)))
                orientation_centers[max_angle] += (gradient_strength * (mod / self.angle_unit))
        return orientation_centers

    def get_closest_bins(self, gradient_angle):
        idx = int(gradient_angle / self.angle_unit)
        mod = gradient_angle % self.angle_unit
        return idx, (idx + 1) % self.bin_size, mod

    def render_gradient(self, image, cell_gradient):
        cell_width = self.cell_size / 2
        max_mag = np.array(cell_gradient).max()
        for x in range(cell_gradient.shape[0]):
            for y in range(cell_gradient.shape[1]):
                cell_grad = cell_gradient[x][y]
                cell_grad /= max_mag
                angle = 0
                angle_gap = self.angle_unit
                for magnitude in cell_grad:
                    angle_radian = math.radians(angle)
                    x1 = int(x * self.cell_size + magnitude * cell_width * math.cos(angle_radian))
                    y1 = int(y * self.cell_size + magnitude * cell_width * math.sin(angle_radian))
                    x2 = int(x * self.cell_size - magnitude * cell_width * math.cos(angle_radian))
                    y2 = int(y * self.cell_size - magnitude * cell_width * math.sin(angle_radian))
                    cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
                    angle += angle_gap
        return image

# move file to trainSet or testSet
def moveFile(srcFilePath, dstPath):
    if not os.path.isfile(srcFilePath):
        print("%s not exist!" %(srcFilePath))
    else:
        if not os.path.exists(dstPath):
            os.makedirs(dstPath)
        shutil.move(srcFilePath, dstPath)


#  transform bmp to jpg
def bmpToJpg(filePath):
    if not os.path.isdir(filePath):
        raise ValueError("filePath Error!")
    for each in os.listdir(filePath):
        filename = each.split('.')[0] + ".jpg"
        im = Image.open(os.path.join(filePath, each))
        im.save(os.path.join(filePath, filename))

# delete original bgm picture
def deleteImages(filePath, imageFormat):
    command = "del " + filePath + "\\*." + imageFormat
    os.system(command)

# The data sets are divided into training sets and sample sets
def makeSet(proportion):
    """
    :param proportion: user input the proportion of trainSet number
    :return:
    """
    path = ['trainingSet', 'testingSet']
    for each in path:
        if not os.path.exists(each):
            os.mkdir(each)
    src = 'orl_faces\\s'

    for i in range(40):
        random_list = os.listdir(src + str(i + 1))
        print("processing the number s{0} directory.....".format(i + 1))
        random.shuffle(random_list)
        k = proportion * 10
        for j in range(len(random_list)):
            dstPath = path[0] if j < k else path[1]
            filename = random_list[j]
            srcFilePath = os.path.join(src + str(i + 1), filename)
            moveFile(srcFilePath, dstPath)
            srcfile = os.path.join(dstPath, filename)
            dstfile = os.path.join(dstPath, str(10 * (i + 1) + int(filename.split('.')[0])) + '.pgm')
            os.rename(srcfile, dstfile)
            print("move samples from directory {0} ----> {1}".format(srcFilePath, dstfile))

# change the gray picture to row vector
def img2vector(filename):
    img = mpimg.imread(filename)
    # print(img.shape)
    # plt.imshow(img)
    # plt.show()
    # hog = Hog_descriptor(img, cell_size = 4, bin_size = 8)
    # vector, image = hog.extract()
    # return vector[0] # 1*32個特徵值

    return img.reshape(1, -1)

# 讀取訓練集和資料集
def readData(trainFilePath, testFilePath):
    trainingFileList = os.listdir(trainFilePath)
    testFileList = os.listdir(testFilePath)

    trainingLen = len(trainingFileList)
    trainLabels = []
    trainSet = np.zeros((trainingLen, 92 * 112)) # 92 * 112
    for i in np.arange(trainingLen):
        filename = trainingFileList[i]
        classNum = int(filename.split('.')[0])
        classNum = math.ceil(classNum / 10) - 1
        trainLabels.append(classNum)
        trainSet[i] = img2vector(os.path.join(trainFilePath, filename))

    testingLen = len(testFileList)
    testSet = np.zeros((testingLen, 92 * 112))
    testLabels = []
    for i in np.arange(testingLen):
        filename = testFileList[i]
        classNum = int(filename.split('.')[0])
        classNum = math.ceil(classNum / 10) - 1
        testLabels.append(classNum)
        testSet[i] = img2vector(os.path.join(testFilePath, filename))

    return {'trainSet' : trainSet, 'trainLabels' : trainLabels,
            'testSet' : testSet, 'testLabels' : testLabels}

# 進行歸一化處理 normalization
def maxmin_norm(array):
    """
    :param array: 每行為一個樣本,每列為一個特徵,且只包含資料,沒有包含標籤
    :return:
    """
    maxcols = array.max(axis = 0)
    mincols = array.min(axis = 0)
    data_shape = array.shape
    data_rows, data_cols = data_shape
    t = np.empty((data_rows, data_cols))
    for i in range(data_cols):
        t[:, i] = (array[:, i] - mincols[i]) / (maxcols[i] - mincols[i])
    return t

# KNN classifier
def kNNClassify(inX, dataSet, labels, k = 3):
    """
    :param inX: 測試的樣本的112*92灰度資料
    :param dataSet: 訓練集
    :param labels: 訓練集的標籤列表('1' '2' ..... '40'共40類別)
    :param k: k值
    :return: 預測標籤label
    distance是[5 50 149...]是測試樣本距離每個訓練樣本的距離向量40 * 1
    """
    distance = np.sum(np.power((dataSet - inX), 2), axis = 1) # 計算歐幾里得距離
    sortedArray = np.argsort(distance, kind = "quicksort")[:k]

    # 給距離加入權重
    w = []
    for i in range(k):
        w.append((distance[sortedArray[k-1]] - distance[sortedArray[i]])\
                 / (distance[sortedArray[k-1]] - distance[sortedArray[0]]))

    count = np.zeros(41)
    temp = 0
    for each in sortedArray:
        count[labels[each]] += 1 + w[temp]
        temp += 1

    label = np.argmax(count) # 如果label中有多個一樣的樣本數,那麼取第一個最大的樣本類別
    return label

def main(k):
    # generate the trainingSet and the testingSet
    if not os.path.exists('testingSet') and not os.path.exists("trainingSet"):
        prompt = "Please input the proportion(0~1): "
        while True:
            receive = input(prompt)
            proportion = round(float(receive), 1)
            if  0 < proportion < 1:
                break
            prompt = "Please input again ! :) :"
        makeSet(proportion)

    # read data
    data = readData('trainingSet', 'testingSet')
    trainSet = data['trainSet']
    trainLabels = data['trainLabels']
    testSet = data['testSet']
    testLabels = data['testLabels']

    # normalization
    temp = trainSet.shape[0]
    array = np.vstack((trainSet, testSet))
    normalized_array = maxmin_norm(array)
    trainSet, testSet = normalized_array[:temp], normalized_array[temp:]

    correct_count = 0
    test_number = testSet.shape[0]
    string = "test sample serial number: {0}, sample label: {1}, classify label: {2}------>correct?: {3}"
    for i in np.arange(test_number):
       label =  kNNClassify(testSet[i], trainSet, trainLabels, k = k)
       if label == testLabels[i]:
           correct_count += 1
       print(string.format(i + 1, testLabels[i], label, label == testLabels[i]))

    print("face recognization accuracy: {}%".format((correct_count / test_number) * 100))
    return (correct_count / test_number) * 100

# verify the proper k
def selectK():
    x = list()
    y = list()
    for i in range(3, 11):
        x.append(i)
        y.append(main(i))
    plt.plot(x, y)
    plt.show()

if __name__ == "__main__":
   main(4)

# bmpToJpg('orl_faces\\s1')
# print(img2vector('orl_faces\s1\\1.jpg').shape)
# deleteImages('orl_faces\s1', 'jpg')

40個人臉可以用show_face(flag = True)函式來顯示到一張圖中

人臉圖如下所示:

 

結果: 可以看到準確率96.25%,現在的用神經網路來進行人臉識別準確率已經99.5%左右了,這個是小白練手了

............................................................................
test sample serial number: 58, sample label: 36, classify label: 36------>correct?: True
test sample serial number: 59, sample label: 3, classify label: 3------>correct?: True
test sample serial number: 60, sample label: 37, classify label: 37------>correct?: True
test sample serial number: 61, sample label: 3, classify label: 3------>correct?: True
test sample serial number: 62, sample label: 37, classify label: 37------>correct?: True
test sample serial number: 63, sample label: 38, classify label: 38------>correct?: True
test sample serial number: 64, sample label: 38, classify label: 38------>correct?: True
test sample serial number: 65, sample label: 39, classify label: 39------>correct?: True
test sample serial number: 66, sample label: 39, classify label: 39------>correct?: True
test sample serial number: 67, sample label: 40, classify label: 40------>correct?: True
test sample serial number: 68, sample label: 40, classify label: 40------>correct?: True
test sample serial number: 69, sample label: 4, classify label: 4------>correct?: True
test sample serial number: 70, sample label: 4, classify label: 4------>correct?: True
test sample serial number: 71, sample label: 5, classify label: 5------>correct?: True
test sample serial number: 72, sample label: 5, classify label: 5------>correct?: True
test sample serial number: 73, sample label: 6, classify label: 6------>correct?: True
test sample serial number: 74, sample label: 6, classify label: 6------>correct?: True
test sample serial number: 75, sample label: 7, classify label: 7------>correct?: True
test sample serial number: 76, sample label: 7, classify label: 7------>correct?: True
test sample serial number: 77, sample label: 8, classify label: 8------>correct?: True
test sample serial number: 78, sample label: 8, classify label: 8------>correct?: True
test sample serial number: 79, sample label: 9, classify label: 9------>correct?: True
test sample serial number: 80, sample label: 9, classify label: 9------>correct?: True
face recognization accuracy: 96.25%

K值的選取:

通過不斷嘗試K值的選取,最終是3,4識別率最高

結論:

1.演算法複雜度太多,還需要經過一些手段對人臉資料特徵進行提取和壓縮

2.認識到了自己知識的不足之處,希望接下來的學習中能夠繼續加油!