天天看點

R語言——基于SVD的人臉識别(圖像識别亦可)

人臉識别可以說是當今社會十分熱門而且應用廣泛的一項技術,應用于案件偵破、裝置解鎖、網絡資訊安全等多領域。在圖像識别中一個最大的難點在于圖像的資料量很大,如何充分地利用資訊。不同的照片具有不同的像素和比例,本文用 python 進行圖檔像素處理,R 語言做 RGB 格式向灰階圖檔的轉換以及SVD分解。有關R的部分可以看我另外一篇文章(基于R語言的SVD圖像處理)我在本篇文章中使用三種方法:線性回歸最小殘差法、logistic 回歸、最短歐幾裡德距離法進行人臉識别性别判定。

思路概述

首先收集挑選訓練集,盡量選取和測試集相一緻的訓練集, 然後基于訓練集和測試集的不同照片的像素尺寸的不同,必須進行處理。另外,因 RGB 式彩色圖檔是高維數組,考慮如何處理為矩陣以友善資料分析。

其次要差別目标圖檔是男是女,很容易想到聚類或者說分類,按照一定方式将訓練集按男女聚在一起,計算測試集和訓練集之間的某個量來差別是男是女。如果采用歐幾裡德距離,即歐幾裡德最短距離法,也可以采用線性回歸最小殘差來區分屬于哪一類。另外,男女為兩分類變量,引入虛拟變量 0/1 代表男女,進行 logistic 回歸也可預測結果。

另外,因為圖像資料為超高維矩陣,無法直接應用于這些方法,而且會出現過拟合和效率低下甚至處理器難以計算的情況,是以必須進行資料降維來提取關鍵資訊進行算法實作,我采用奇異值分解進行資料處理,選取适當的奇異值(奇異向量) 來進行實作。

圖像初步處理

首先需要将你的訓練集以及樣本集處理為相同像素的圖檔,可是使用如下python代碼:

from PIL import Image

def produceImage(file_in, width, height, file_out):
    image = Image.open(file_in)
    resized_image = image.resize((width, height), Image.ANTIALIAS)
    resized_image.save(file_out)

if __name__ == '__main__':
    file_in = '/Users/tyc_219/Desktop/tex/test_%03d.jpg'
    width = 200
    height = 200
    file_out = '/Users/tyc_219/Desktop/training/sam_%03d.jpg'
    produceImage(file_in, width, height, file_out)
           

其中的width以及height你可以自己指定,我在這裡指定的大小是200*200像素。

另外,所有照片為彩色圖檔,用R中的 readJPEG 讀取圖檔得到的是三個矩陣,RGB 三層數組的疊加。這樣對于我們處理資料不友善,是以需要将圖檔轉換為灰階圖檔即矩陣。我們對三個矩陣進行權重平均來講三個矩陣化為單矩陣包含人臉特征但是友善處理。

權重值公式為:

G r a y = 0.299 ∗ R + 0.587 ∗ G + 0.114 ∗ B Gray = 0.299 ∗ R + 0.587 ∗ G + 0.114 ∗ B Gray=0.299∗R+0.587∗G+0.114∗B

圖像像素處理和灰階轉換應用于所有圖檔。

算法

線性回歸殘差法

線性回歸的殘差平方和大小反映了模型拟合的程度,反映了變量之間的相關程度,如果測試對象為男性,則以該測試對象為因變量,以訓練集中男性資料為因變量,則模型應有較好的拟合,相反,如果以女性訓練集為因變量進行拟合,拟合效果将不如前者。 是以我們對每一個測試對象,分别以男女訓練集進行線性拟合,比較模型拟合結果的殘差平方和,根據最小殘差平方和将其分類為男或女。

但是以男女訓練集為因變量,次元過高,變量個數較多,造成拟合效率不高,甚至過拟合現象。故使用奇異值分解,對訓練集圖像進行矩陣近似,選擇關鍵資訊,選擇部分較大奇異值對應的 U 矩陣中的奇異向量進行拟合。

一般來說:當選擇 10~20 個較大奇異值對應的奇異向量進行拟合并判定時,可以得到很高的準确率

Logistic 回歸

因為我們要估計的目标是性别,即二進制變量,那麼我們就可以使用 logistic 回歸進行拟合,并用拟合結果估計測試集的性别。

同樣,因為測試集和訓練集資料量較大,我們需要進行奇異值分解。這裡将訓練集和測試集合并再進行奇異值分解,這樣,選擇适當個數的分解後前幾個奇異值對應的奇異向量的個數進行拟合,再選擇測試集對應的相應個數的奇異向量進行估計。

歐幾裡德距離法

我們可以對訓練集根據性别不同進行一個簡單平均,即可得到平均臉,通過平均臉可以觀察到不同性别所顯示出來的面部特征,歐幾裡德最短距離法就是基于平均臉而進行判别,我們已經得到男女平均臉,即兩個類,那麼我們就可以跟據樣本與兩個類之間的距離來将樣本分類,這裡我們采用歐幾裡德距離。即計算測試集與男女平均臉矩陣向量之間的距離來将測試集的所有樣本分類。

R代碼

下面給出供大家參考的R代碼,如有不足,大家可以自行改進。

rm(list = ls())
library(RSpectra)
library(jpeg)
library(animation)
#設定讀取路徑
setwd("/Users/tyc_219/Desktop/training")
png("new_pic%03d.png")
r <- 0.299
g <- 0.587
b <- 0.114
#讀取圖檔,訓練集是200張圖檔
for(i in 1:200)
{
  pic <- readJPEG(sprintf("sam_%03d.jpg", i))
  R <- pic[,,1]
  G <- pic[,,2]
  B <- pic[,,3]
  new_pic <- r*R + g*G + b*B   #通過灰階進行圖像轉化
  #将轉化後的灰階圖導出為.jpg
  writeJPEG(new_pic, sprintf("new_pic%03d.jpg", i))   
}
dev.off()

train <- matrix(0, nrow = 200, ncol = 40000)   #建立訓練集矩陣,每一行為一張圖檔
for(j in 1:200)
{
  ma <- readJPEG(sprintf("new_pic%03d.jpg", j))
  train[j,] <- as.vector(t(ma))
}
str(train)

#建立訓練集性别矩陣,1代表男性,0代表女性
sex <- c(1,1,1,0,1,0,1,0,1,0,1,1,1,0,1,1,1,0,0,1,1,1,0,1,1,0,0,0,0,1,0,1,1,
         1,1,0,0,1,1,1,1,0,0,1,0,1,1,1,1,1,0,1,1,0,1,1,1,0,0,0,0,1,1,1,0,0,1,1,1,1,0,
         1,0,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,1,1,1,1,
         0,0,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,0,0,1,0,0,0,1,1,0,1,0,0,1,0,0,1,0,1,0,
         0,0,1,0,1,0,1,0,0,1,0,0,1,0,0,0,1,0,1,1,0,0,1,1,1,1,0,1,1,0,0,0,0,0,
         1,1,1,1,0,1,1,0,1,1,1,1,1,0,0,1,1,1,1,1)
sex <- matrix(sex, nrow = 1)

male_female <- 0:1
pic_mean <- matrix(0, 40000, 2)
for (k in male_female) {
  index <- (k == sex)  
  imgi <- train[index, , drop = FALSE] #定位男女在訓練樣本圖像矩陣的行,并提取出來
  imgi.mean <- colMeans(imgi) #求該性别對應的訓練集在各行的平均值,作為該性别的對比值
  pic_mean[,k + 1] <- imgi.mean   #将各性别的圖像矩陣(平均值)放入pic_mean矩陣中
}

par(mfrow = c(1, 2)) #畫出基于訓練集的男女平均臉
for (i in 1:2) {
  image(matrix(pic_mean[, i], ncol = 200)[,200:1], col = gray(0:255/255))
}

#使用不同的性别基礎進行最小二乘法并找到最小殘差,最小殘差對應的性别為判别性别
test_img <- matrix(0, nrow = 23, ncol = 40000)
for(j in 1:23)
{
  ma <- readJPEG(sprintf("test_%03d.jpg", j))
  test_img[j,] <- as.vector(t(ma))
}

image_recognition <- function(test)
{
  resid.norm <- matrix(NA, 2, 1, dimnames = list(0:1, "resid"))
  #對于男女分别進行lm拟合
  for (i in 0:1) {
    img.mat <- t(train[i == sex, , drop = FALSE])
    img.matSVD <- svd(img.mat)
    basis.max <- 30
    resid.norm[i+1, ] <- norm(matrix(lm(test ~ 0 + 
                                        img.matSVD$u[, 1:basis.max])$resid), "F")
  }
  #将殘差最小對應的性别取出作為判别的性别
  rec_sex <- match(min(resid.norm), resid.norm)
  return(rec_sex-1)
}

sex_test <- vector()
name <- c()     #自行加入名稱
system.time(for(m in 1:23)
{
  sex_test[m] <- image_recognition(test_img[m,])
  if(sex_test[m] == 1) sex_test[m] <- "男"
  if(sex_test[m] == 0) sex_test[m] <- "女"
})
cbind(name,sex_test)


logis <- glm(t(sex) ~ ., data = data1, family = "binomial")
summary(logis)

train.svd <- svd(train)
str(train.svd)
u <- train.svd$u
par(mfrow = c(1,2))
plot(1:length(train.svd$d), train.svd$d, xlab="i-th sigma", ylab="sigma", main="Singular Values")
plot(1:length(train.svd$d), cumsum(train.svd$d)/sum(train.svd$d), main="Cumulative Percent of Total Sigmas")
data1 <- as.data.frame(u[,1:25])

sex.true <- c(1,0,0,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1)

#計算兩個矩陣的歐幾裡德距離
ec.distance <- function(X, Y) {
  dim.X <- dim(X)
  dim.Y <- dim(Y)
  sum.X <- matrix(rowSums(X^2), dim.X[1], dim.Y[1])
  sum.Y <- matrix(rowSums(Y^2), dim.X[1], dim.Y[1], byrow = TRUE)
  dist0 <- sum.X + sum.Y - 2 * tcrossprod(X, Y)
  out <- sqrt(dist0)
  return(out)
}

#對于未知性别的人臉圖像,比較它與男女圖像平均值的距離
test.sample <- 1:23

#計算測試集到訓練集平均值的距離
system.time(test.distance <- ec.distance(test_img[test.sample, ], t(pic_mean)))
rec.result <- apply(test.distance, 1, which.min) - 1
rate <- length(rec.result[sex.true == rec.result])/23;rate

#将訓練集與測試集合并後進行SVD分解,矩陣降維後再進行logistic回歸拟合
pics <- rbind(train, test_img)
pics.svd.u <- svd(pics)$u
train.svd.u <- pics.svd.u[1:200,]  #取出訓練集對應的u矩陣部分
x <- cbind(rep(1,23),pics.svd.u[200:223,])


rec <- matrix(0, 20, 1)
system.time(for (i in 1:20) 
{
  #拟合logistic回歸,看在取u矩陣不同的列數時判别率的高低
  logistic.fit <- glm(t(sex) ~ train.svd.u[,1:i], family = binomial)
  beta <- matrix(logistic.fit$coefficients, 1)
  ind <- i + 1
  X <- t(x)[1:ind,]    #x矩陣中第一列為1,對應beta0
  respond <- beta %*% X
  p <- exp(respond)/(1+exp(respond))
  c1 <- p[sex.true == 0] < 0.5    #c1為判别性别為女性與測試集中對應的人人數
  c2 <- p[sex.true == 1] >= 0.5   #c2為判别性别為男性與測試集中對應的人人數
  rec[i,] <- (sum(c1)+sum(c2))/23    #(c1 + c2)/23為判别正确率
})
t(rec)
           

————————————————未經允許,請勿轉載——————————————————

繼續閱讀