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

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
Help Net Security
Help Net Security
Security Archives - TechRepublic
Security Archives - TechRepublic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
W
WeLiveSecurity
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
Vercel News
Vercel News
Microsoft Azure Blog
Microsoft Azure Blog
小众软件
小众软件
Project Zero
Project Zero
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
Know Your Adversary
Know Your Adversary
Last Week in AI
Last Week in AI
腾讯CDC
Schneier on Security
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
量子位
L
Lohrmann on Cybersecurity
J
Java Code Geeks
Cyberwarzone
Cyberwarzone
Recent Announcements
Recent Announcements
IT之家
IT之家
博客园_首页
罗磊的独立博客
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Application and Cybersecurity Blog
Application and Cybersecurity Blog
F
Fortinet All Blogs
博客园 - Franky
P
Palo Alto Networks Blog
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Martin Fowler
Martin Fowler
N
News and Events Feed by Topic
C
Cybersecurity and Infrastructure Security Agency CISA
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
TaoSecurity Blog
TaoSecurity Blog
H
Heimdal Security Blog
G
GRAHAM CLULEY
Cloudbric
Cloudbric

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 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 认证 - 元视角 似花还似非花 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 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 扩展之基于名称的注入实现 - 元视角
视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角
飞鸿踏雪 · 2022-11-02 · via 元视角

本文是 #视频是不能 P 的系列# 的第三篇。此前,我们已经可以通过 OpenCV 或者 Dlib 实现对人脸的检测,并在此基础上实现了某种相对有趣的应用。譬如,利用人脸特征点提取面部轮廓并生成表情包、将图片中的人脸批量替换为精神污染神烦狗 等等。当然,在真实的应用场景中,如果只是检测到人脸,那显然远远不够的,我们更希望识别出这张人脸是谁。此时,我们的思绪将会被再次拉回到人脸识别这个话题。在探索未知世界的过程中,博主发现 OpenCV 自带的 LBPH 方法,即局部二值模式直方图方法,识别精度完全达不到预期效果。所以,博主最终选择了 Dlib 里的特征值方法,即:对每一张人脸计算一个 128 维的向量,再通过计算两个向量间的欧式距离来判断是不是同一张人脸。在此基础上,博主尝试结合 支持向量机 来实现模型训练。因此,这篇文章其实是对整个探索过程的梳理和记录,希望能给大家带来一点启发。

原理说明

如下图所示,假设对于每一个人物 X ,我们有 N 个人脸样本,通过 Dlib 提供的 compute_face_descriptor() 方法,我们可以计算出该人脸样本的特征值,这是一个 128 维度的向量。如果我们对这些人脸样本做同样的处理,我们就可以得到人物 X 的特征值列表 feature_list_of_person_x。在此基础上,利用 MumPy 中的 mean() 方法,我们就可以计算出人物 X 的平均特征 features_mean_person_x。最终,我们把人物 X 的平均特征和名称一起写入到一个 CSV 文件里面。至此,我们已经拥有了一个简单的人脸数据库。

Dlib 人脸识别原理说明图 Dlib 人脸识别原理说明图

可以预见的是,一旦我们把人脸特征数值化,那么,人脸识别就从一个图形学问题变成了数学问题。对于图中的待检测人脸,我们只需要按同样地方式计算出特征值,然后从 CSV 文件中找一个距离它最近的特征即可。这里,博主使用的是欧式距离,并且人为规定了一个阈值 0.4, 即:当这个距离小于 0.4 时,我们认为人脸匹配成功;当这个距离大于 0.4 时,我们认为人脸匹配失败。下面的例子展示了使用 Dlib 计算人脸特征值的基本过程:

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
face_reco_model = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat")

# 计算特征值
image = Image.open('/faces/person/sample-0.jpg')
image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
faces = detector(image, 1)
if len(faces) != 0:
    face = faces[0]
    shape = predictor(image, face)
    face_descriptor = face_reco_model.compute_face_descriptor(image, shape)

此时, face_descriptor 就是当前人脸的特征值,这是一个 128 维的向量。我们只需要对每一个人脸样本重复以上步骤,就可以获得人物 X 的特征值列表,进而计算出人物 X 的特征均值。

实现过程

如下图所示,我们为每一个人物建立了一个文件夹,文件夹的名称即为对应人物的名称:

人脸样本的目录结构 人脸样本的目录结构

显然,对于每一个人物而言,我们只需要遍历该文件夹下的所有图片,即可计算出它的特征均值:

def get_mean_features_of_face(path):
    path = os.path.abspath(path)
    subDirs = [os.path.join(path, f) for f in os.listdir(path)]
    subDirs = list(filter(lambda x:os.path.isdir(x), subDirs))
    for index in range(0, len(subDirs)):
        subDir = subDirs[index]
        person_label = os.path.split(subDir)[-1]
        image_paths = [os.path.join(subDir, f) for f in os.listdir(subDir)]
        image_paths = list(filter(lambda x:os.path.isfile(x), image_paths))
        feature_list_of_person_x = []
        for image_path in image_paths:
            # 计算每一个图片的特征
            feature = get_128d_features_of_face(image_path)
            if feature == 0:
                continue
            
            feature_list_of_person_x.append(feature)
        
        # 计算当前人脸的平均特征
        features_mean_person_x = np.zeros(128, dtype=object, order='C')
        if feature_list_of_person_x:
            features_mean_person_x = 
                np.array(feature_list_of_person_x, dtype=object).mean(axis=0)
        
        yield (features_mean_person_x, person_label)

其中,get_128d_features_of_face() 方法用来计算某个图片的特征值:

def get_128d_features_of_face(image_path):
    image = Image.open(image_path)
    image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
    faces = detector(image, 1)

    if len(faces) != 0:
        shape = predictor(image, faces[0])
        face_descriptor = face_reco_model.compute_face_descriptor(image, shape)
    else:
        face_descriptor = 0
    return face_descriptor

好了,当我们计算出每一个人物的特征均值以后,我们需要把当前人物的名称、特征均值一起写入到 CSV 文件里面,这样做的目的是方便我们后面做人脸识别:

def extract_features_to_csv(faces_dir):
    mean_features_list = list(get_mean_features_of_face(faces_dir))
    with open(FACES_FEATURES_CSV_FILE, "w", newline="") as csvfile:
        writer = csv.writer(csvfile)
        for mean_features in mean_features_list:
            person_features = mean_features[0]
            person_label = mean_features[1]
            person_features = np.insert(person_features, 0, person_label, axis=0)
            writer.writerow(person_features)

此刻,我们就拥有了一个简单的人脸数据库,对于任意一个待检测的人脸,我们只需要计算其特征值,然后再从人脸数据库中找到一个距离最小的特征即可:

def compare_face_fatures_with_database(database, image_path):
    image = Image.open(image_path)
    image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
    faces = detector(image, 1)
    
    campare_results = []
    if len(faces) != 0:
        for i in range(len(faces)):
            face = faces[i]
            shape = predictor(image, faces[0])
            face_descriptor = face_reco_model.compute_face_descriptor(image, shape)
            face_feature_distance_list = []
            for face_data in database:
                # 比对人脸特征,当距离小于 0.4 时认为匹配成功
                dist = get_euclidean_distance(face_descriptor, face_data[1])
                dist = round(dist, 4)

                if dist >= FACES_FATURES_DISTANCE_THRESHOLD:
                    continue

                face_feature_distance_list.append((face_data[0], dist))
            
            # 按距离排序,取最小值进行绘制
            sorted(face_feature_distance_list, key=lambda x:x[1])
            if face_feature_distance_list:
                person_dist = face_feature_distance_list[0][1]
                person_label = face_feature_distance_list[0][0]
                campare_results.append((person_label, person_dist))

    return campare_results

可以注意到,我们会遍历人脸数据库中的每一个特征值,然后计算它和当前人脸特征的欧式距离。接下来,我们会对这些距离进行排序,选取距离最小的一组作为匹配结果,下面的代码片段展示了如何去计算一个欧氏距离:

def get_euclidean_distance(feature_1, feature_2):
    feature_1 = np.array(feature_1)
    feature_2 = np.array(feature_2)
    return np.sqrt(np.sum(np.square(feature_1 - feature_2)))

如下图所示,我们可以输出每一张人脸的名称,以及当前人脸相对于特征均值的距离,经博主测试,这个方案的正确率可以达到 94.58% 左右,是一种相对比较靠谱的做法:

人脸识别效果展示 人脸识别效果展示

不过,这个方案的缺点同样非常明显,即:它必须遍历人脸数据库中的每一条数据,这意味着它的时间复杂度是 O(n)。考虑到现实生活中需要录入的人脸数据可能是成百上千的,这个方案的执行效率注定会越来越低,这迫使我们不得不换一种思路来解决这个问题。重新审视人脸识别这个问题,我们会发现,这其实是一个分类问题,就像我们人为地去给一堆照片贴上标签一样。当然,它并不是一个非此即彼的二分类问题,而是一个多分类的问题。如果你接触过 Scikit Learn,那么,你大概会想到通过某种分类器进行模型训练的思路,这里以支持向量机为例:

# 提取人脸特征和标签
mean_features_list = list(get_mean_features_of_face(faces_dir))
features = list(map(lambda x:x[0], face_encodings_mean_list))
labels = list(map(lambda x:x[1], face_encodings_mean_list))

# 通过支持向量机训练模型
clf = svm.SVC(C=1.0, kernel="linear", gamma='scale', probability=True)
clf.fit(features, labels)

在这种情况下,我们可以利用 joblib.dump()joblib.load() 两个方法对模型进行保存和加载。此时,我们该如何识别一个人脸呢?简单来说,你可以把支持向量机想象成一个线性函数,因此,我们只需要传入待检测图片的特征值,它就可以帮我们计算/预测出人脸的分类。显然,它不需要像前面的方案一样遍历每一条人脸数据,因此,支持向量机这个方案执行效率上要更好一点:

features = get_128d_features_of_face(image_path)
predict_label = clf.predict(features)

当然,世界上没有百分之百完美的方案,支持向量机如果遇到了一张陌生的人脸,它的预测结果就会变得离谱起来,譬如,孙燕姿被识别为堺雅人、堺雅人被识别为胡歌,用鲁迅先生的话讲,屏幕内外充满了快活的空气。虽然,Scikit Learn 里可以用 predict_proba() 方法引入概率来评估结果的准确性,可博主实际使用下来,发现效果并没有好多少。无独有偶,OpenCV 自带的 LBPH 方法,其模型训练和这个思路非常地相似,无非是它接收是一个 Mat 类型的参数:

recognizer = cv2.face.LBPHFaceRecognizer_create()
detector = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml")

# 获取人脸样本及标签
face_samples = get_images_and_labels(DATASETS_DIR)
faces = list(map(lambda x:x[0], face_samples))
faceIds = list(map(lambda x:x[1], face_samples))

# 模型训练
recognizer.train(faces, np.array(faceIds))
recognizer.save('/train/train_data.yml')

# 模型预测
recognizer.read('/train/train_data.yml')
faceId, confidence = recognizer.predict(image)

它会返回人脸的 Id 以及置信度,我们可以结合这个置信度去做进一步的判断,比如置信度超过 50% 就认为它匹配到了人脸。我一开始提到这个方案的精度达不到要求,就是因为它经常给出错误的预测结果,甚至比支持向量机遇到陌生人脸时还要离谱。所以,对于 OpenCV 官方的这个方案呢,我们知道它是怎么回事就可以了,我个人并不推荐在真实的场景中使用这个方法。当然,OpenCV 后来开始支持 CNN,我们值得更好的人脸识别方案,限于篇幅和精力,这个等以后有机会了再展开说说。

人脸识别运用到视频中的效果展示:献给我永远的歌晨 CP 人脸识别运用到视频中的效果展示:献给我永远的歌晨 CP

本文小结

本文分享了 OpenCVDlib 下的人脸识别的实现,由于 OpenCV 自带的 LBPH 方法识别效果不符合预期,博主不得不使用 Dlib 来实现人脸识别。这里采用的思路是,计算出每一个人物的人脸特征均值,它是一个 128 维的向量,通过计算待检测人脸特征值与已知人脸特征值的欧式距离,来判断两张脸是否为同一张脸,经过测试,该方案的识别率可以达到 94.58% ,是一种相对来说比较靠谱的方案。考虑到这个方案的时间复杂度为 O(n) ,我们不得不考虑效率更高的做法。在这个基础上,我们尝试结合支持向量机做进一步的优化,支持向量机最大的问题时,当它面对一张陌生人脸的时候,其预测结果会变得非常离谱,可是在最好的情况下,它的准确率依然接近 97.04%。作为对照组,博主捎带着介绍了一下 OpenCV 自带的 LBPH 方法如何训练模型,权当帮大家扩展思路,只有此中的成败利钝,只有靠大家自己去取舍啦!最后,听说射雕英雄传又要翻拍啦,我宣布我永远都喜欢胡歌这一版的射雕英雄传,哈哈!