惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角
飞鸿踏雪 · 2025-01-14 · via 元视角

注:本文在创作过程中得到了 ChatGPT、DeepSeek、Kimi 的智能辅助支持,由作者本人完成最终审阅。

在 “视频是不能 P 的” 系列文章中,博主曾先后分享过人脸检测、人脸识别等相关主题的内容。今天,博主想和大家讨论的是人脸分类问题。你是否曾在人群中认错人,或是盯着熟人的照片却一时想不出对方的名字?这种 “脸盲症” 的困扰,不仅在生活中令人感到尴尬,在整理照片时更是让人头疼不已。想象一下,某次聚会结束后,你的手机里存了上百张照片——有你的笑脸、朋友的自拍,甚至还有一部分陌生面孔混杂其中。手动将这些照片按人物分类,不仅费时费力,还可能会因为 “脸盲” 而频繁出错。此时,你是否期待有一种技术,可以像魔法一样,自动将这些照片按人物分类?事实上,这种 “魔法” 已经存在,它的名字叫做 K-Means 聚类分析。作为一种经典的无监督学习算法,K-Means 能够通过分析人脸特征,自动将相似的面孔归类到一起,完全无需人工干预。接下来,为了彻底根治 “脸盲症”,我们将详细介绍如何使用 K-Means 聚类分析来实现这一目标,哈利·波特拥有魔法,而我们则拥有科技。

实现过程

如图所示,我们将按照下面的流程来达成 “自动分类人脸” 这一目标。其中,Dlib 负责提取人脸特征向量、Scikit-Learn 中的 K-Means 负责聚类分析、Matplotlib 负责结果的可视化:

基于 K-Means 聚类分析实现人脸照片的快速分类示意图 基于 K-Means 聚类分析实现人脸照片的快速分类示意图

K-Means 简介

K-Means 是一种广泛应用的聚类算法,其基本原理是将数据集分成 K 个簇,目标是让每个簇内的数据点尽可能相似,而不同簇之间的数据点尽可能差异明显。K-Means 的执行过程如下:

  1. 随机选取 K 个初始中心点。

  2. 将每个数据点分配到距离最近的中心点所对应的簇。

  3. 更新每个簇的中心点,通常取簇内所有数据点的均值。

  4. 重复步骤 2 和 3,直到中心点不再发生变化或达到预设的最大迭代次数。

如下图所示,图中展示了四种不同的聚类数据分布情况,按照从左到右、自上而下的顺序:

  • 图一:簇划分不正确或者簇数量假设错误
  • 图二:数据分布具有各向异性,簇的形状是一个拉长的椭圆形,而不是对称的圆形
  • 图三:各个簇之间的方差不同,绿色簇分布更紧密,而黄色簇分布更稀疏
  • 图四:簇的大小不均匀,黄色簇数据点较少,而紫色簇数据点较多

四种不同的聚类数据分布情况 四种不同的聚类数据分布情况

因此,适用于 K-Means 的数据通常满足:

  • 簇是球状且分布均匀
  • 簇的大小相近
  • 簇无明显噪声点或者离群点
  • 数据是各向同性分布
  • 簇的数量已知
  • 数据维度适中

如何确定 K 值

在使用 K-Means 之前,我们需要确定 K 值,即簇的数量。下面是三种常用的确定 K 值的方法:

  • 肘部法则/手肘图法(Elbow Method):通过计算不同 K 值下的聚类误差平方和(SSE, Sum of Squared Errors),找到误差下降速度明显减缓的 “拐点”,这个拐点对应的 K 值即为最佳聚类数。
  • 轮廓系数法(Silhouette Coefficient):通过计算每个数据点的轮廓系数,评估聚类效果,选择轮廓系数最大的 K 值。轮廓系数结合了聚类的紧密度(同一簇内样本的相似度)和分离度(不同簇之间的差异度)。
  • 戴维斯-博尔丁评分(Davies-Bouldin Index):通过计算每个簇的簇内距离(样本到簇中心的平均距离)与簇间距离(不同簇中心之间的距离)的比值,评估聚类效果,选择评分最低的 K 值。

如图所示,分别展示了不同方法下的最佳 K 值,综合考虑三种评估方案,此处取 K=6:

肘部法则/手肘图法 肘部法则/手肘图法

轮廓系数法 轮廓系数法

戴维斯-博尔丁评分 戴维斯-博尔丁评分

提取人脸特征

为了对人脸照片进行聚类,我们首先需要提取人脸特征。为此,我们使用 Dlib 库,它提供了一个基于深度学习的预训练模型,该模型能够高效地将人脸图像转换为 128 维的特征向量。

import dlib
import cv2
import numpy as np

# 加载人脸检测器和特征提取器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("./models/shape_predictor_68_face_landmarks.dat")
face_model = dlib.face_recognition_model_v1("./models/dlib_face_recognition_resnet_model_v1.dat")

# 提取人脸特征
def extract_face_features(image_path):
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    faces = detector(gray)
    if not faces:
        return None

    max_face = max(faces, key=lambda rect: rect.width() * rect.height())
    shape = predictor(gray, max_face)
    feature = face_model.compute_face_descriptor(img, shape)
    return np.array(feature)

K-Means 聚类

接下来,在提取了人脸特征值后,我们可以使用 Scikit-Learn 中的 K-Means 方法进行聚类:

from sklearn.cluster import KMeans

X, image_paths = load_dataset(dataset_path)

scaler = StandardScaler()
X = scaler.fit_transform(X)

# 使用 K-means 进行聚类
num_clusters = 6
kmeans = KMeans(n_clusters=num_clusters, n_init=50, random_state=42, init='k-means++')
kmeans.fit(X)

# 获取聚类标签
labels = kmeans.labels_

其中,load_dataset() 方法用于返回特征值和图片路径,因为最终我们需要将这些图片移动到不同的文件夹中,以实现图片分类。下面是该方法的具体实现:

def load_dataset(dataset_path):
    results = [
        (features, image_path)
        for image_name in os.listdir(dataset_path)
        if (image_path := os.path.join(dataset_path, image_name))
        and (features := extract_face_features(image_path)) is not None 
    ]

    X, image_paths = zip(*results) if results else ([], [])
    return np.array(X), list(image_paths)

这里,简单说一下各个参数对于 K-Means 的影响:

  • 使用 StandardScaler 对矩阵 X 进行标准化处理,将每一列的均值调整为 0,标准差调整为 1。标准化后的数据更适合 K-Means 聚类,因为 K-Means 对特征的尺度比较敏感。
  • 参数 n_clusters 取值为 6,表示将数据分为 6 个簇,该值由最佳的 K 值确定。
  • 参数 n_init 取值为 50,表示运行 K-Means 的次数,每次将使用不同的初始质心,确保可以得到最优结果。
  • 参数 random_state 取值为 42,设置随机种子,确保结果可以复现。
  • 参数 init 取值为 k-means++,表示使用 K-Means++ 算法初始化质心,避免陷入局部最优。

更多的参数及细节,请参考官方文档:https://scikit-learn.org.cn/view/108.html#2.3.2.%20K-means

PCA 降维与可视化

理论上,这一步可以省略。不过,为了更加直观地展示聚类结果,我们可以使用 PCA(Principal Component Analysis,主成分分析)对人脸特征进行降维,将高维特征映射到低维空间。在本文中,我们会将 128 维的特征向量映射到 2 维平面,并通过 Matplotlib 库进行可视化展示,其中的关键代码展示如下:

from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

# 使用 PCA 降维到 2 维
pca = PCA(n_components=2)
X_reduced = pca.fit_transform(X)

# 获取聚类质心
centers = kmeans.cluster_centers_
centers_reduced = pca.transform(centers)

# 绘制每个聚类的散点
plt.figure(figsize=(12, 10))

# 对于每个聚类,绘制聚类的点和代表性图片
for cluster in range(num_clusters):
    cluster_mask = labels == cluster
    cluster_points = X_reduced[cluster_mask]
        
    # 获取降维后的聚类中心坐标
    x_center, y_center = centers_reduced[cluster]  # 每个聚类的二维坐标

    # 绘制聚类中的数据点
    plt.scatter(cluster_points[:, 0], cluster_points[:, 1], label=f'Cluster {cluster}')

    # 选择每个聚类中最接近聚类中心的图像
    cluster_indices = np.where(cluster_mask)[0]
    distances = np.linalg.norm(X_reduced[cluster_mask] - centers_reduced[cluster], axis=1)
    closest_image_idx = cluster_indices[np.argmin(distances)]
    closest_image_path = image_paths[closest_image_idx]

    # 读取并处理该图片
    img = extract_face_rect(closest_image_path)
    h, w = img.shape[:2]

    # 设定目标图像大小
    target_size = 100
    aspect_ratio = w / h

    # 根据长宽比计算新宽高
    if aspect_ratio > 1:
        new_w = target_size
        new_h = int(target_size / aspect_ratio)
    else:
        new_h = target_size
        new_w = int(target_size * aspect_ratio)

    # 调整图片大小
    img_resized = cv2.resize(img, (new_w, new_h))

    # 在聚类中心绘制代表性图片
    imagebox = OffsetImage(img_resized, zoom=0.5)
    imagebox.image.axes = plt.gca()
    ab = AnnotationBbox(imagebox, (x_center, y_center),frameon=True,pad=0.5)
    plt.gca().add_artist(ab)

# 绘制聚类中心
plt.scatter(centers_reduced[:, 0], centers_reduced[:, 1], c='red', marker='X', s=200, label='CentroIds')

# 标题、标签和图例
plt.title('K-means Clustering Results (PCA Reduced)')
plt.xlabel('PCA Dimension 1')
plt.ylabel('PCA Dimension 2')
plt.legend()
plt.grid(True)
plt.show()

这段代码看起来非常复杂,它具体做了什么事情呢?实际上,在通过 K-Means 聚类获得分类标签以后,我们开始尝试在每个簇的中心绘制一张人脸图片,这张图片是如何找到的呢?这里我们选取的的是离簇中心最近的那张图片。考虑到,通过 Dlib 提取的人脸特征值都是 128 维的,如果希望将其绘制到二维平面上,就需要通过 PCA 来完成降维。如图所示,通过 PCA 降维后,我们可以将聚类结果可视化。不同颜色代表不同的簇,同一簇内的点表示相似的人脸照片。通过这张图片,相信你会更加直观地理解核心点、边界点、聚类中心、重叠点等概念:

K-Means 聚类结果可视化 K-Means 聚类结果可视化

本文小结

本文尝试探索运用 K-Means 聚类技术对人脸照片进行快速分类的方案。首先,我们介绍了 K-Means 的基本原理及其适用范围,并探讨了如何合理确定簇数 K。随后,博主详细阐述了人脸特征的提取方法、K-Means 聚类的具体实施步骤,以及借助 PCA 降维技术实现数据可视化的流程。这一系列步骤帮助我们高效地对大量人脸照片进行分类。然而,K-Means 并非完美无瑕。它需要预先设定簇数 K,对初始中心点的选择较为敏感,且默认簇为球形或凸形。这导致其在处理非凸形簇或含噪声数据时表现不佳。针对这些局限性,实际应用中可以考虑使用 DBSCAN 等聚类算法,这类方法通常能够自动确定簇的数量,并对噪声数据具有更强的适应性。作为一种经典的聚类算法,K-Means 具有计算高效、实现简单的优点。然而,在普适性、精确度以及对深度特征的利用上,相较于基于深度学习的聚类方法,K-Means 存在一定不足。本文旨在为读者提供一种基础思路,激发其对于聚类分析这一领域的兴趣。本文代码已上传至 Github,更多细节请参考:https://github.com/Regularly-Archive/2024/blob/main/face-classify/kmeans_cluster_classify.py