京公网安备 11010802034615号
经营许可证编号:京B2-20210330
这一次,我拒绝了Python,选择了Go
如今,神经网络已经非常流行,人们将它用于各种任务,特别是人脸识别应用。
最近,我用一个以 Go 语言为后端的软件,实现了一个人脸识别项目。它能够识别出上传照片中的人像 (如流行歌手)是谁。这听起来不错,我决定试一下也给你们介绍一下项目的整个过程。
需要说明的是,我尽可能地将所需的系统配置控制在较低水平,以便更多用户可以通过使用便宜的服务器来进行安装,而这也是为什么实现过程不使用 CUDA
或 GPU 的原因——虽然你现在可以很容易地租用这样的服务器,但它需要很高的成本,从而也会将很多潜在的使用者拒之门外。如果它只需要 CPU
而不需要外部依赖就能工作,情况会好很多。

▌选择合适的语言
如果你询问数据科学家或者那些有神经网络实践经验的工作者,几乎所有人都会建议你使用 Python
语言来解决机器学习任务。考虑到语言社区,可用库的数量,语言的简单性等,Python 语言确实是一个明智的选择。此外,在 Python
中,你还可以通过一些精彩的实例说明和文档来找到一些受欢迎的人脸识别库。
然而,这一次,我决定选用 Go 语言,主要有几以下几个原因:
我的论坛是用 Go 语言编写的,我个人也真的很喜欢以 single-binary 为后端所带来的便捷性。因此,在后端部署并整合人脸识别过程,而不需要 Python 实现的一些依赖和 IPC,这是很棒的。
Go 语言通常比 Python 更快,消耗的内存更少。任何高性能 Python 库的关键部分都是用 C / C++
语言编写的,因此,无论如何你都会有 Python VM 的开销。我偏爱于更快的语言,除非这种语言会严重影响开发时间。我不会用 C或C++ 作为
Web 应用程序编写的主要语言,但 Go语言很好,它几乎和 Python 一样简单。
我没有在 Go 语言中找到人脸识别的有关库,因此用 Go 语言实现这样一个应用,对于整个社区而言,都是一件有趣又有帮助的事。
▌选择合适的框架
如前所述,神经网络以及相应的实现框架如今正被广泛地使用。仅在计算机视觉领域,可用的框架就有 Caffe,Torch,TensorFlow 等。
但是,有一个非常酷的机器学习库 —— dlib 库,一下就吸引了我的注意力。首先,它是用 C ++ 语言编写的,因此你可以使用 cgo
轻松地创建 Go 语言绑定。其次,在 Wild benchmarks 基准的人脸识别任务上,据说它能实现 99.38%
的准确性,这听起来是很不可思议的。再者,现在一些流行的人脸识别库 face_recognition 和 openface 在底层都使用 dlib
库,因此它在该任务上会是一个非常好的选择。
▌安装依赖项
一旦框架确定下来,那么我们要如何在机器上开发并部署这个项目呢?首先,C++ 依赖项的安装将会有很大的困难,因为你无法通过简便的“go
get”或“pip
install”命令来实现。要么只能希望你的操作系统存储库中提供这些依赖库,要么你只能通过繁琐的编译过程来安装,这样的话,这个问题就更加令人讨厌,因为有许多人都在
dlib 编译过程碰到问题。
如果你不得不通过编译过程来安装,那么可以参考一下下面的教程,也许会有帮助
https://gist.github.com/ageitgey/629d75c1baac34dfa5ca2a1928a7aeaf
幸运的是,我们有更好的选择:如果用户的目标系统已知,我们可以构建 dlib 库的二进制安装包来大大简化整个过程。说到服务器软件,Ubuntu 几乎是系统标配,因此首先要保证你能支持这个系统。
Ubuntu的标准仓库中自带有 dlib库,但其版本太旧了:人脸识别仅支持 dlib19.3 版本,所以我们需要构建自己的包。我为 Ubuntu 16.04 和 18.04 创建了 PPA (自定义存储库),安装过程非常简单,如下:
sudo add-apt-repository ppa:kagamih/dlib
sudo apt-get update
sudo apt-get install libdlib-dev
以上命令将安装最新的 dlib19.15 版本及 Intel 的数学核心库,对于 Intel 处理器而言,这似乎是标准 BLAS 和 LAPACK 接口的最快实现。
对于 Debian sid 和 Ubuntu 18.10 (尚未发布) 而言,标准仓库中同样提供了 dlib 的安装过程,你只需要如下命令:
sudo apt-get install libdlib-dev libopenblas-dev
这将使用 OpenBLAS 来代替 MKL,实现的速度同样非常快。或者,你也可以通过 enable non-free package 并安装 libmkl-dev 来实现。
我们还需要 libjpeg 来加载 JPEG 图像:在 Ubuntu 上安装 libjpeg-turbo8-dev 包,或在 Debian 上安装 libjpeg62-turbo-dev。
到目前为止,我没有给出其他系统的安装说明,如果你在安装 dlib 过程中碰到问题,可以访问我的 github 希望能为你提供合理有效的安装建议。
GitHub 地址:
https://github.com/Kagami/go-face
此外,我还考虑为 dlib 库提供 Docker 镜像
(其中有少部分内容已存在),许多具有复杂依赖关系的项目都倾向于使用这种分布式方法。但在我看来,一个本机包能够为用户提供更好的体验,你不需要在控制台编写长命令,也不需要处理
sandbox 环境中的内容。
写入依赖库
当前人脸识别库地工作原理通常是:通过为照片上的每张人脸返回一组数字 (矢量嵌入或描述符) 来比较区分它们,并通过比较这些数字来找到图像中人的名字 (通常是通过计算欧几里德距离向量,得到属于同一个人的两张人脸的最小距离)。这个概念这次就不在这里赘述了。
创建图像中人脸的原始代码并不是个重要的问题,这个过程几乎是遵循官方的例子就可以了。你可以查看 facerec.cc 及其相应的头文件 facerec.h,其中定义了 5 个函数和几个在 Go 语言和 dlib 库之间的交互结构。
在这里,虽然 dlib
库支持所有流行的图像格式,但它只能从文件中加载它们。这将导致混乱,因为我们通常只会将图像保存在内存中并将其写入临时文件。因此,在这里我使用
libjpeg
来编写自己的图像加载器。由于大多数照片都以该格式存储的,因此这种格式的加载器足以胜任大部分的需要,以后有需要我还会添加其他格式的图像加载器。
我把 C++ 和 Go 语言的连接层放在 face.go 中。它提供了 Face 结构,用于保存图像中人脸的坐标及其描述符,并通过 Recognizer 为所有操作提供接口,如初始化和实际识别。
一旦我们有了描述符,我们能做什么呢?在最简单的情况下,你可以通过比较未知描述符与所有已知描述符之间的欧几里德距离。但这并不完美,即使是当前最先进的人脸识别技术也会得到错误的答案。如果想稍微改善一下结果,我们需要使用每个人的许多图像,并检查这些图像中是否有非常接近于所提供的人脸。
这也正是分类器 classify.cc 所做的工作。首先,计算距离,然后对这些距离进行排序,计算同一个人在前 10 个最小距离中的点击数。)
诸如支持向量机,将会在这个任务上提供更好的算法性能。 dlib 甚至为训练此类模型提供了便捷的 API。很少有文章会提到 SVM 在大型数据集上的性能,因此我打算先在大型集合上测试它。
使用
下面得到的结果你可以在 github 中查看:
import "github.com/Kagami/go-face"
GitHub 地址:
https://github.com/Kagami/go-face
相关的所有结构和方法概述,请参阅 GoDoc 文档,主要包括以下几个内容:
初始化识别器
识别所有的已知图像并收集描述符
将具有相应类别的已知描述符传递给识别器
获取未知图像的描述符
对其类别进行分类
以下是一个工作示例,来说明了上述的所有步骤:
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/Kagami/go-face"
)
// Path to directory with models and test images. Here it's
// assumed it points to the
// <https://github.com/Kagami/go-face-testdata> clone.
const dataDir = "testdata"
// This example shows the basic usage of the package: create an
// recognizer, recognize faces, classify them using few known
// ones.
func main() {
// Init the recognizer.
rec, err := face.NewRecognizer(dataDir)
if err != nil {
log.Fatalf("Can't init face recognizer: %v", err)
}
// Free the resources when you're finished.
defer rec.Close()
// Test image with 10 faces.
testImagePristin := filepath.Join(dataDir, "pristin.jpg")
// Recognize faces on that image.
faces, err := rec.RecognizeFile(testImagePristin)
if err != nil {
log.Fatalf("Can't recognize: %v", err)
}
if len(faces) != 10 {
log.Fatalf("Wrong number of faces")
}
// Fill known samples. In the real world you would use a lot of
// images for each person to get better classification results
// but in our example we just get them from one big image.
var samples []face.Descriptor
var cats []int32
for i, f := range faces {
samples = append(samples, f.Descriptor)
// Each face is unique on that image so goes to its own
// category.
cats = append(cats, int32(i))
}
// Name the categories, i.e. people on the image.
labels := []string{
"Sungyeon", "Yehana", "Roa", "Eunwoo", "Xiyeon",
"Kyulkyung", "Nayoung", "Rena", "Kyla", "Yuha",
}
// Pass samples to the recognizer.
rec.SetSamples(samples, cats)
// Now let's try to classify some not yet known image.
testImageNayoung := filepath.Join(dataDir, "nayoung.jpg")
nayoungFace, err := rec.RecognizeSingleFile(testImageNayoung)
if err != nil {
log.Fatalf("Can't recognize: %v", err)
}
if nayoungFace == nil {
log.Fatalf("Not a single face on the image")
}
catID := rec.Classify(nayoungFace.Descriptor)
if catID < 0 {
log.Fatalf("Can't classify")
}
// Finally print the classified label. It should be "Nayoung".
fmt.Println(labels[catID])
}
运行下面命令:
mkdir -p ~/go && cd ~/go # Or cd to your $GOPATH
mkdir -p src/go-face-example && cd src/go-face-example
git clone https://github.com/Kagami/go-face-testdata testdata
edit main.go # Paste example code
go get .
../../bin/go-face-example
由于在 dlib 的代码中大量使用了 C++ 模板,因此需要一些时间来编译 go-face (在我的 i7 上大约需要运行 1 分钟)。 幸运的是,Go 语言能够构建输出缓存,这样可以在今后构建的时候速度更快。
上面的示例输出应打印“Nayoung”,表示能够正确识别出未知图像。
模型
go-face 需要 shape_predictor_5_face_landmarks.dat 和
dlib_face_recognition_resnet_model_v1.dat 模型才能开始工作。你可以从 dlib-models 仓库中下载它们:
mkdir models && cd models
wget https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2
bunzip2 shape_predictor_5_face_landmarks.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2
bunzip2 dlib_face_recognition_resnet_model_v1.dat.bz2
此外,当你要运行示例代码时,还可以通过 go-face-testdata 仓库来访问这些模型。
未来的工作
我对结果非常满意,通过简单的 API,得到不错的识别结果,还可以轻松嵌入到 Go 的应用程序中。当然,还有需要改进的地方:
为了追求简单性和速度,在创建描述符时,go-face 无法对图像进行一些预处理,如抖动。但是,增加图像预处理操作是很有必要的,因为它可能会提高识别的性能。
Dlib 库支持很多图像格式 (如 JPEG,PNG,GIF,BMP,DNG),但是 go-face 目前只能实现 JPEG 格式,未来的工作我们希望可以支持更多的格式。
正如 dlib 的作者 Davis 所建议的,相比于搜索最小距离,采用多类 SVM 可能会得到更好的分类结果,因此还需要进行额外的测试验证。
在 go-face 中,除非真的需要,不然我尽量不复制值,但实际上它还测试过大样本 (10,000+人脸数据集) 的测试性能,可能存在一些瓶颈,有待日后完善。
从人脸提取特征向量是一个强大的概念,因为你不需要收集自己的训练数据,这也是一项非常艰巨的任务 (Davis 曾提到创建 dlib 中
ResNet 模型所用到的 300 万张人脸数据集),但为了获得更高的识别性能这可能也是无法避免的,因此值得为自己模型的训练提供相应的工具。
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
在 “神经网络与卡尔曼滤波融合” 的理论基础上,Python 凭借其丰富的科学计算库(NumPy、FilterPy)、深度学习框架(PyTorch、T ...
2025-10-23在工业控制、自动驾驶、机器人导航、气象预测等领域,“状态估计” 是核心任务 —— 即从含噪声的观测数据中,精准推断系统的真 ...
2025-10-23在数据分析全流程中,“数据清洗” 恰似烹饪前的食材处理:若食材(数据)腐烂变质、混杂异物(脏数据),即便拥有精湛的烹饪技 ...
2025-10-23在人工智能领域,“大模型” 已成为近年来的热点标签:从参数超 1750 亿的 GPT-3,到万亿级参数的 PaLM,再到多模态大模型 GPT-4 ...
2025-10-22在 MySQL 数据库的日常运维与开发中,“更新数据是否会影响读数据” 是一个高频疑问。这个问题的答案并非简单的 “是” 或 “否 ...
2025-10-22在企业数据分析中,“数据孤岛” 是制约分析深度的核心瓶颈 —— 用户数据散落在注册系统、APP 日志、客服记录中,订单数据分散 ...
2025-10-22在神经网络设计中,“隐藏层个数” 是决定模型能力的关键参数 —— 太少会导致 “欠拟合”(模型无法捕捉复杂数据规律,如用单隐 ...
2025-10-21在特征工程流程中,“单变量筛选” 是承上启下的关键步骤 —— 它通过分析单个特征与目标变量的关联强度,剔除无意义、冗余的特 ...
2025-10-21在数据分析全流程中,“数据读取” 常被误解为 “简单的文件打开”—— 双击 Excel、执行基础 SQL 查询即可完成。但对 CDA(Cert ...
2025-10-21在实际业务数据分析中,我们遇到的大多数数据并非理想的正态分布 —— 电商平台的用户消费金额(少数用户单次消费上万元,多数集 ...
2025-10-20在数字化交互中,用户的每一次操作 —— 从电商平台的 “浏览商品→加入购物车→查看评价→放弃下单”,到内容 APP 的 “点击短 ...
2025-10-20在数据分析的全流程中,“数据采集” 是最基础也最关键的环节 —— 如同烹饪前需备好新鲜食材,若采集的数据不完整、不准确或不 ...
2025-10-20在数据成为新时代“石油”的今天,几乎每个职场人都在焦虑: “为什么别人能用数据驱动决策、升职加薪,而我面对Excel表格却无从 ...
2025-10-18数据清洗是 “数据价值挖掘的前置关卡”—— 其核心目标是 “去除噪声、修正错误、规范格式”,但前提是不破坏数据的真实业务含 ...
2025-10-17在数据汇总分析中,透视表凭借灵活的字段重组能力成为核心工具,但原始透视表仅能呈现数值结果,缺乏对数据背景、异常原因或业务 ...
2025-10-17在企业管理中,“凭经验定策略” 的传统模式正逐渐失效 —— 金融机构靠 “研究员主观判断” 选股可能错失收益,电商靠 “运营拍 ...
2025-10-17在数据库日常操作中,INSERT INTO SELECT是实现 “批量数据迁移” 的核心 SQL 语句 —— 它能直接将一个表(或查询结果集)的数 ...
2025-10-16在机器学习建模中,“参数” 是决定模型效果的关键变量 —— 无论是线性回归的系数、随机森林的树深度,还是神经网络的权重,这 ...
2025-10-16在数字化浪潮中,“数据” 已从 “辅助决策的工具” 升级为 “驱动业务的核心资产”—— 电商平台靠用户行为数据优化推荐算法, ...
2025-10-16在大模型从实验室走向生产环境的过程中,“稳定性” 是决定其能否实用的关键 —— 一个在单轮测试中表现优异的模型,若在高并发 ...
2025-10-15