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

推荐订阅源

WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
T
Threatpost
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
M
MIT News - Artificial intelligence
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
S
Security Affairs
P
Proofpoint News Feed
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
S
Security @ Cisco Blogs
H
Hacker News: Front Page
Security Archives - TechRepublic
Security Archives - TechRepublic
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
月光博客
月光博客
量子位
博客园_首页
The Last Watchdog
The Last Watchdog
D
DataBreaches.Net
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
The Register - Security
The Register - Security
Schneier on Security
Schneier on Security
H
Help Net Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
F
Full Disclosure
C
Cyber Attacks, Cyber Crime and Cyber Security
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
L
Lohrmann on Cybersecurity
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
Microsoft Security Blog
Microsoft Security Blog

元视角

.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