用 Python 构建人脸识别工具:从检测、编码到模型验证

2026-09-05 48 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

人脸识别并不只是“找出照片里有没有脸”。一个完整的工具通常要经历人脸检测、边界框定位、特征编码、身份比对和结果验证几个步骤。理解这些概念,才能正确判断识别结果,也能避免把“检测到人脸”误认为“识别出身份”。

人脸检测与人脸识别不是一回事

人脸检测回答的是:图像中的什么位置出现了人脸?检测结果通常是一个或多个边界框(bounding box),例如:

(top, right, bottom, left)

在人脸识别库 face_recognition 中,这四个值分别表示边界框的上、右、下、左坐标。检测阶段只负责定位目标,并不判断这个人是谁。

人脸识别则需要继续处理每个人脸:

  1. 根据边界框截取或定位人脸区域。
  2. 将人脸转换为固定长度的特征向量,也就是 face encoding。
  3. 将待识别的人脸编码与已知人员的编码进行距离比较。
  4. 根据阈值决定是否匹配。

人脸编码不是原始图片,也不是姓名。它是一种用于比较人脸视觉特征的数值表示。两个编码之间的距离越小,通常说明它们越相似。

一个可运行的人脸识别示例

下面的示例使用 face_recognition 和 Pillow。运行前准备如下目录:

face-demo/
├── recognize.py
├── known_faces/
│   └── alice.jpg
└── unknown.jpg

alice.jpg 是已知人员照片,文件名会作为识别标签;unknown.jpg 是待识别图片。安装依赖:

python -m pip install face_recognition pillow

创建 recognize.py

from pathlib import Path

import face_recognition

KNOWN_DIR = Path("known_faces")
UNKNOWN_IMAGE = Path("unknown.jpg")


def load_known_faces():
    known_encodings = []
    known_names = []

    for image_path in sorted(KNOWN_DIR.glob("*")):
        if image_path.suffix.lower() not in {".jpg", ".jpeg", ".png"}:
            continue

        image = face_recognition.load_image_file(image_path)
        locations = face_recognition.face_locations(image)
        encodings = face_recognition.face_encodings(image, locations)

        if len(encodings) != 1:
            print(f"跳过 {image_path}: 需要恰好检测到一张脸,实际为 {len(encodings)} 张")
            continue

        known_encodings.append(encodings[0])
        known_names.append(image_path.stem)

    return known_encodings, known_names


def recognize_image(image_path, known_encodings, known_names, tolerance=0.6):
    image = face_recognition.load_image_file(image_path)
    locations = face_recognition.face_locations(image)
    encodings = face_recognition.face_encodings(image, locations)

    results = []
    for location, encoding in zip(locations, encodings):
        distances = face_recognition.face_distance(known_encodings, encoding)

        if len(distances) == 0:
            name = "unknown"
            best_distance = None
        else:
            best_index = distances.argmin()
            best_distance = float(distances[best_index])
            name = (
                known_names[best_index]
                if best_distance <= tolerance
                else "unknown"
            )

        results.append({
            "name": name,
            "box": location,
            "distance": best_distance,
        })

    return results


def main():
    known_encodings, known_names = load_known_faces()
    if not known_encodings:
        raise RuntimeError("known_faces 中没有可用的单人照片")

    results = recognize_image(
        UNKNOWN_IMAGE,
        known_encodings,
        known_names,
    )

    for result in results:
        print(
            f"name={result['name']}, "
            f"box={result['box']}, "
            f"distance={result['distance']}"
        )


if __name__ == "__main__":
    main()

运行:

python recognize.py

输出可能类似于:

name=alice, box=(42, 220, 190, 73), distance=0.41

这里的 box 是人脸在图片中的位置,distance 是待识别人脸与最佳候选编码之间的距离。距离低于 tolerance 时,代码才会接受该候选人。

阈值不是准确率的替代品

0.6 是常见的起始阈值,但它不是适用于所有数据集的通用答案。降低阈值通常会让系统更谨慎,减少误认,但也可能增加“无法识别”的情况;提高阈值则可能提高召回率,同时增加错误匹配。

更可靠的做法是准备验证数据集,分别记录:

  • 同一个人的不同照片,作为正样本。
  • 不同人的照片,作为负样本。
  • 光照、角度、表情和分辨率不同的样本。

可以用简单脚本观察不同阈值下的匹配结果:

import numpy as np
import face_recognition

# 假设这些编码已经从验证集加载完成
positive_distances = np.array([0.32, 0.41, 0.48, 0.55])
negative_distances = np.array([0.63, 0.71, 0.82, 0.91])

for threshold in [0.45, 0.50, 0.55, 0.60, 0.65]:
    true_positive = np.mean(positive_distances <= threshold)
    false_positive = np.mean(negative_distances <= threshold)
    print(
        f"threshold={threshold:.2f} "
        f"true_positive_rate={true_positive:.2f} "
        f"false_positive_rate={false_positive:.2f}"
    )

这个例子中的距离是演示数据,不代表特定模型的真实表现。实际项目应使用与部署环境相似的图片进行验证,并根据业务风险选择阈值。例如,照片门禁和照片分类对误认的容忍度不同,不能直接复用同一个配置。

构建工具时容易忽略的边界

一张图片可能包含多张脸

不要默认 face_encodings(image)[0] 就是目标人物。多脸场景下,应保留每个边界框与编码的对应关系,并分别输出结果。上面的示例通过 zip(locations, encodings) 保持了这种对应关系。

已知照片质量会影响结果

用于建立人员档案的照片最好清晰、正面、光照稳定,并且每张图片只包含一个人。如果已知照片中的脸很小、被遮挡或严重倾斜,编码本身就可能不稳定。

最佳匹配不等于可信匹配

从候选列表中选出距离最小的人,只能说明他是当前候选中最相似的一个。只有当最小距离同时低于经过验证的阈值时,才应该把结果标记为匹配。没有已知编码时,也必须明确返回 unknown,而不是随意选择一个姓名。

隐私与误识别风险需要单独处理

人脸识别涉及生物特征数据。上线前应明确数据保存周期、访问权限、用户告知和删除机制。对于门禁、支付或身份核验等高风险场景,人脸结果不应成为唯一决策依据,还需要人工复核或其他身份验证方式。

一份实用检查清单

  • 能否区分人脸检测和身份识别?
  • 是否理解边界框坐标的顺序?
  • 是否为每张人脸生成独立编码?
  • 是否处理了零张脸、多张脸和未知人员?
  • 是否用正样本和负样本验证过阈值?
  • 是否记录了距离,而不是只保存姓名?
  • 是否评估了光照、角度、遮挡和图片质量的影响?
  • 是否为误识别、隐私和数据删除设计了流程?

一个可用的人脸识别工具,核心不只是调用模型,而是把检测结果、编码距离、阈值和业务决策分开。先让输出可解释,再用真实验证数据调整阈值,通常比直接追求一个看似准确的单次结果更可靠。


相关推荐