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.299R+0.587G+0.114B
图像像素处理和灰度转换应用于所有图片。

算法

线性回归残差法

线性回归的残差平方和大小反映了模型拟合的程度,反映了变量之间的相关程度,如果测试对象为男性,则以该测试对象为因变量,以训练集中男性数据为因变量,则模型应有较好的拟合,相反,如果以女性训练集为因变量进行拟合,拟合效果将不如前者。 因此我们对每一个测试对象,分别以男女训练集进行线性拟合,比较模型拟合结果的残差平方和,根据最小残差平方和将其分类为男或女。
但是以男女训练集为因变量,维度过高,变量个数较多,造成拟合效率不高,甚至过拟合现象。故使用奇异值分解,对训练集图像进行矩阵近似,选择关键信息,选择部分较大奇异值对应的 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)

————————————————未经允许,请勿转载——————————————————

你可能感兴趣的:(R语言——基于SVD的人脸识别(图像识别亦可))