目录

一、算法概述

1.1 算法简介

1.2 数学原理

1.2.1 数据预处理

1.2.2 协方差矩阵

1.2.3 特征值和特征向量

1.2.4 选择主成分

1.2.5 数据投影

1.3 PCA步骤

二、算法实现

2.1 导入数据

2.2 数据集处理

2.3 PCA降维

2.4 结果可视化

三、完整代码

四、实验总结

4.1 PCA人脸识别的优势

4.2 PCA人脸识别的局限


一、算法概述

1.1 算法简介

主成分分析(Principal Component Analysis,简称PCA)是一种常用的线性降维技术,其目标是将高维数据投影到低维空间中,同时尽可能保留原始数据中的主要信息。它通过将原始数据变换到新的坐标系统中,使得新的坐标轴(主成分)方向上的方差最大化,从而提取出数据的主要结构。

1.2 数学原理

1.2.1 数据预处理

在进行PCA之前,通常需要对数据进行标准化处理。将每个特征的均值调整为0,标准差调整为1。这一步骤可以消除不同特征之间的量纲差异和均值差异,使PCA的结果更加合理。

假设原始数据集为 X,其中每一行表示一个样本,每一列表示一个特征。对于每个特征 xi​,计算其均值 μi​ 和标准差 σi​:

对每个数据点 xji​ 进行标准化:

标准化后的数据记为 X′。

1.2.2 协方差矩阵

PCA的核心是计算数据的协方差矩阵。协方差矩阵是一个方阵,其对角线元素表示各个特征的方差,非对角线元素表示不同特征之间的协方差。协方差矩阵反映了数据中各个特征之间的相关性。

对于标准化后的数据集 X′,协方差矩阵 C 的计算公式为:

其中,n 是数据点的数量,是 X′ 的转置。

1.2.3 特征值和特征向量

计算协方差矩阵 C 的特征值 λ 和特征向量 v。特征值表示数据在对应特征向量方向上的方差大小,特征值越大,说明该方向上的数据变化越显著。特征向量则表示数据的主成分方向。通常选择特征值最大的若干个特征向量作为主成分。

协方差矩阵 C 的特征值和特征向量的计算公式为:

其中,λ 是特征值,v 是对应的特征向量。

1.2.4 选择主成分

选择前 k 个特征值最大的特征向量,构成投影矩阵 P。这 k 个特征向量对应的方向就是数据的主要变化方向,即主成分方向。

设 v1​,v2​,…,vk​ 是特征值最大的 k 个特征向量,则投影矩阵 P 为:

1.2.5 数据投影

将标准化后的数据 X′ 投影到这 k 个主成分方向上,得到降维后的数据 Y:

其中,Y 是降维后的数据集,每一行表示一个样本在低维空间中的表示。

1.3 PCA步骤

  1. 数据标准化:对数据进行均值为0、标准差为1的标准化处理。

  2. 计算协方差矩阵:根据标准化后的数据计算协方差矩阵。

  3. 求解特征值和特征向量:计算协方差矩阵的特征值和特征向量,并按照特征值从大到小排序。

  4. 选择主成分:选择前 k 个特征值最大的特征向量,构成投影矩阵。

  5. 数据投影:将原始数据投影到这 k 个主成分方向上,得到降维后的数据。

二、算法实现

2.1 导入数据

数据集共包含40个不同人的400张图像,此数据集包含40个类,每个类含10张图片。

对于每个类,选择前7张图片用于训练,后3张图片用于测试。将图像缩放至原来的0.5倍,最后选择100个特征向量进行降维。

import os
import cv2
import numpy as np
from typing import Union
import matplotlib.pyplot as plt

# 1. 准备
scaled = 0.5  # 图像缩放至原来的scaled倍
train_num = 7  # 7张用于训练
img_num = 10   # 总共10张图片
num_k = 100    # 选择100个特征
save_path = './ORL_eigenfaces'  # 保存特征脸的路径
dataset_path = r"C:\Users\wu\Desktop\ORL_Faces"  # 数据集路径
cov_matrix_path = './PCA_ORL.npy'  # 保存PCA特征向量的路径

2.2 数据集处理

将每个类的前7张图片使用img2np转为一维向量,作为训练集。计算每个类前7张图片的平均特征向量,用于后面的对比。

class_names = [name for name in os.listdir(dataset_path) if os.path.isdir(os.path.join(dataset_path, name))]
print(f'数据集总共有 {len(class_names)} 个类')

# 列表示特征值,行表示图像
samples_data = []
avg_feature = []  # 每个类的平均特征

for class_name in class_names:
    class_path = os.path.join(dataset_path, class_name)
    if not os.path.isdir(class_path):  # 如果不是目录,则跳过
        continue
    img_names = os.listdir(class_path)
    class_feature = []  # 计算每个类前train_num的平均特征
    for img_name in img_names[:train_num]:  # 取前七张参与训练
        img_data = img2np(os.path.join(class_path, img_name), scaled=scaled)
        samples_data.append(img_data)
        class_feature.append(img_data)

    avg_feature.append(np.array(class_feature).mean(axis=0))

samples_data = np.array(samples_data)
avg_feature = np.array(avg_feature)

print(f'数据集特征矩阵形状: {samples_data.shape},平均特征矩阵形状:{avg_feature.shape}')

数据集共有 40 个类
数据集特征矩阵形状: (280, 2576),平均特征矩阵形状:(40, 2576)

2.3 PCA降维

PCA计算时间较长,因此先对图像进行缩放,并保存降维后的特征矩阵,方便后续调用。

# 3. PCA降维
# PCA算法保留前k个特征向量
def pca(X: np.ndarray, k: int) -> np.ndarray:
    # 计算每个特征均值
    mean = np.mean(X, axis=0)
    # 数据标准化
    norm_X = X - mean
    # 计算协方差矩阵
    cov_matrix = np.cov(norm_X, rowvar=False)
    # 计算特征值与特征向量
    eig_val, eig_vec = np.linalg.eig(cov_matrix)
    # 按照特征值的绝对值大小降序排序
    sorted_index = np.argsort(np.abs(eig_val))[::-1]
    # 选择前k个特征值对应的特征向量
    selected_vectors = eig_vec[:, sorted_index[:k]]
    return selected_vectors

if os.path.exists(cov_matrix_path):
    pca_vectors = np.load(cov_matrix_path)
else:
    pca_vectors = pca(samples_data, num_k)  # 选择100个特征向量
    np.save(cov_matrix_path, pca_vectors)  # 训练时间较长,可以保存方便调用

print(f'PCA降维后的特征矩阵形状: {pca_vectors.shape}')

2.4 结果可视化

将特征向量保存为特征脸并输出。

# 5. 结果展示
# 保存前k个特征向量的特征脸
def save_eigenfaces(eigenvectors: np.ndarray,
                    k: int,
                    img_size: tuple[int, int],  # (h, w)
                    path: Union[str]) -> bool:
    if not os.path.exists(path):
        os.mkdir(path)  # 创建保存路径文件夹
    for i in range(k):
        eigenfaces = np.real(eigenvectors[:, i])
        norm_eigenfaces = ((eigenfaces - np.min(eigenfaces)) /
                           (np.max(eigenfaces) - np.min(eigenfaces)) * 255)
        norm_eigenface_uint8 = norm_eigenfaces.astype(np.uint8)
        shaped_eigenface = norm_eigenface_uint8.reshape(img_size)
        cv2.imwrite(os.path.join(path, f'eigenfaces{i}.png'), shaped_eigenface)
    return True

save_eigenfaces(pca_vectors, 100, (46, 56), save_path)  # 原大小为112 x 92,缩放后为56 x 46

# 显示前16个特征脸
plt.figure(figsize=(8, 8))
for i in range(16):  # 遍历前16个特征脸
    plt.subplot(4, 4, i + 1)  # 创建子图
    eigenface_path = os.path.join(save_path, f'eigenfaces{i}.png')
    eigenface_img = cv2.imread(eigenface_path, cv2.IMREAD_GRAYSCALE)
    plt.imshow(eigenface_img, cmap='gray')  # 显示灰度图像
    plt.title(f'eigenfaces{i}')  # 添加标题
    plt.xticks([])  # 去掉x轴刻度
    plt.yticks([])  # 去掉y轴刻度
plt.show()  # 显示图像

三、完整代码

import os
import cv2
import numpy as np
from typing import Union
import matplotlib.pyplot as plt

# 1. 准备
scaled = 0.5  # 图像缩放至原来的scaled倍
train_num = 7  # 7张用于训练
img_num = 10   # 总共10张图片
num_k = 100    # 选择100个特征
save_path = './ORL_eigenfaces'  # 保存特征脸的路径
dataset_path = r"C:\Users\wu\Desktop\ORL_Faces"  # 数据集路径
cov_matrix_path = './PCA_ORL.npy'  # 保存PCA特征向量的路径

# 图像转为一维向量
def img2np(img_path: Union[str], scaled: float = 1.) -> np.ndarray:
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)  # 直接读取为灰度图
    h, w = img.shape[0], img.shape[1]
    img = cv2.resize(img, (int(w * scaled), int(h * scaled)))  # 注意宽高顺序
    return img.flatten()

# 2. 数据集处理
class_names = [name for name in os.listdir(dataset_path) if os.path.isdir(os.path.join(dataset_path, name))]
print(f'数据集总共有 {len(class_names)} 个类')

# 列表示特征值,行表示图像
samples_data = []
avg_feature = []  # 每个类的平均特征

for class_name in class_names:
    class_path = os.path.join(dataset_path, class_name)
    if not os.path.isdir(class_path):  # 如果不是目录,则跳过
        continue
    img_names = os.listdir(class_path)
    class_feature = []  # 计算每个类前train_num的平均特征
    for img_name in img_names[:train_num]:  # 取前七张参与训练
        img_data = img2np(os.path.join(class_path, img_name), scaled=scaled)
        samples_data.append(img_data)
        class_feature.append(img_data)

    avg_feature.append(np.array(class_feature).mean(axis=0))

samples_data = np.array(samples_data)
avg_feature = np.array(avg_feature)

print(f'数据集特征矩阵形状: {samples_data.shape},平均特征矩阵形状:{avg_feature.shape}')

# 3. PCA降维
# PCA算法保留前k个特征向量
def pca(X: np.ndarray, k: int) -> np.ndarray:
    # 计算每个特征均值
    mean = np.mean(X, axis=0)
    # 数据标准化
    norm_X = X - mean
    # 计算协方差矩阵
    cov_matrix = np.cov(norm_X, rowvar=False)
    # 计算特征值与特征向量
    eig_val, eig_vec = np.linalg.eig(cov_matrix)
    # 按照特征值的绝对值大小降序排序
    sorted_index = np.argsort(np.abs(eig_val))[::-1]
    # 选择前k个特征值对应的特征向量
    selected_vectors = eig_vec[:, sorted_index[:k]]
    return selected_vectors

if os.path.exists(cov_matrix_path):
    pca_vectors = np.load(cov_matrix_path)
else:
    pca_vectors = pca(samples_data, num_k)  # 选择100个特征向量
    np.save(cov_matrix_path, pca_vectors)  # 训练时间较长,可以保存方便调用

print(f'PCA降维后的特征矩阵形状: {pca_vectors.shape}')

# 4. 结果展示
# 保存前k个特征向量的特征脸
def save_eigenfaces(eigenvectors: np.ndarray,
                    k: int,
                    img_size: tuple[int, int],  # (h, w)
                    path: Union[str]) -> bool:
    if not os.path.exists(path):
        os.mkdir(path)  # 创建保存路径文件夹
    for i in range(k):
        eigenfaces = np.real(eigenvectors[:, i])
        norm_eigenfaces = ((eigenfaces - np.min(eigenfaces)) /
                           (np.max(eigenfaces) - np.min(eigenfaces)) * 255)
        norm_eigenface_uint8 = norm_eigenfaces.astype(np.uint8)
        shaped_eigenface = norm_eigenface_uint8.reshape(img_size)
        cv2.imwrite(os.path.join(path, f'eigenfaces{i}.png'), shaped_eigenface)
    return True

save_eigenfaces(pca_vectors, 100, (46, 56), save_path)  # 原大小为112 x 92,缩放后为56 x 46

# 显示前16个特征脸
plt.figure(figsize=(8, 8))
for i in range(16):  # 遍历前16个特征脸
    plt.subplot(4, 4, i + 1)  # 创建子图
    eigenface_path = os.path.join(save_path, f'eigenfaces{i}.png')
    eigenface_img = cv2.imread(eigenface_path, cv2.IMREAD_GRAYSCALE)
    plt.imshow(eigenface_img, cmap='gray')  # 显示灰度图像
    plt.title(f'eigenfaces{i}')  # 添加标题
    plt.xticks([])  # 去掉x轴刻度
    plt.yticks([])  # 去掉y轴刻度
plt.show()  # 显示图像

四、实验总结

4.1 PCA人脸识别的优势

  • 降维效果显著:PCA可以将高维的人脸图像数据降维到低维空间,大大减少了计算量和存储需求。

  • 特征提取直观:PCA提取的特征脸是基于人脸图像的全局信息,能够较好地反映人脸的主要特征。

  • 算法实现简单:PCA算法的实现相对简单,计算过程较为直观,易于理解和实现。

4.2 PCA人脸识别的局限

  • 对光照和表情变化敏感:PCA主要基于图像的灰度信息,对于光照条件的变化和表情的差异较为敏感。

  • 特征提取不够精细:PCA提取的特征是全局的,对于局部特征的表达能力较弱。

  • 数据量要求较高:PCA需要足够多的训练样本才能准确地估计协方差矩阵,否则可能会导致特征提取不准确。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐