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.認識到了自己知識的不足之處,希望接下來的學習中能夠繼續加油!