用 Python 构建一个人脸识别命令行工具

2026-09-01 36 预计阅读时间: 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.

预计阅读时间:8 分钟

人脸识别并不只是“找到图片里有没有人脸”。一个完整的小工具通常需要完成两步:先检测人脸的位置,再把检测到的人脸编码与已知人物进行比对并标注出来。本文使用 Python 和 face_recognition 库,从零实现一个可以在命令行运行的人脸识别工具。

从人脸检测到身份识别

人脸检测回答的是“人脸在哪里”,输出通常是一组边界框,例如图片中某张脸的上、右、下、左坐标。

人脸识别则需要进一步把每张脸转换成数值特征,也就是人脸编码。程序可以将未知图片中的编码与已知人物的编码进行比较,从而判断两张脸是否属于同一个人。

可以把处理流程概括为:

  1. 从已知人物目录加载照片。
  2. 为每张已知照片生成人脸编码。
  3. 读取待识别图片并检测其中的人脸。
  4. 为检测到的人脸生成编码。
  5. 与已知编码比较,并输出人物姓名。
  6. 在图片上绘制边框和标签。

face_recognition 对这些步骤提供了比较直接的 Python API,适合构建教程、内部工具和原型。

准备项目环境

下面的示例假设使用 Python 3.9 或更高版本。face_recognition 依赖 dlib,在部分系统上安装时可能需要 C++ 编译工具,因此建议先在虚拟环境中操作。

python -m venv .venv

# macOS/Linux
source .venv/bin/activate

# Windows PowerShell
# .venv\\Scripts\\Activate.ps1

python -m pip install --upgrade pip
python -m pip install face_recognition pillow

创建如下目录结构,并把已知人物照片放入 known/

face-tool/
├── recognize.py
├── known/
│   ├── alice.jpg
│   └── bob.jpg
└── input.jpg

文件名会被当作人物姓名。例如 alice.jpg 会产生标签 alice。为了让识别结果更稳定,每张已知照片最好只包含一个清晰、正面的脸。

实现命令行识别器

下面是一个完整示例。它会读取 known/ 中的照片,识别输入图片里的所有人脸,并将结果保存到指定的输出文件。

运行前,将代码保存为 recognize.py,然后根据自己的目录修改命令参数即可。

from __future__ import annotations

import argparse
from pathlib import Path

import face_recognition
from PIL import Image, ImageDraw


IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png"}


def load_known_faces(directory: Path) -> tuple[list[str], list[list[float]]]:
    names: list[str] = []
    encodings: list[list[float]] = []

    for path in sorted(directory.iterdir()):
        if path.suffix.lower() not in IMAGE_EXTENSIONS:
            continue

        image = face_recognition.load_image_file(path)
        face_encodings = face_recognition.face_encodings(image)

        if not face_encodings:
            print(f"Skip {path}: no face found")
            continue

        if len(face_encodings) > 1:
            print(f"Skip {path}: expected one face, found {len(face_encodings)}")
            continue

        names.append(path.stem)
        encodings.append(face_encodings[0])

    if not encodings:
        raise RuntimeError(f"No usable reference faces found in {directory}")

    return names, encodings


def recognize_image(
    input_path: Path,
    output_path: Path,
    known_names: list[str],
    known_encodings: list[list[float]],
) -> None:
    image = face_recognition.load_image_file(input_path)
    locations = face_recognition.face_locations(image)
    encodings = face_recognition.face_encodings(image, locations)

    output = Image.fromarray(image)
    draw = ImageDraw.Draw(output)

    for location, encoding in zip(locations, encodings):
        matches = face_recognition.compare_faces(
            known_encodings,
            encoding,
            tolerance=0.6,
        )
        distances = face_recognition.face_distance(known_encodings, encoding)

        label = "Unknown"
        if distances.size:
            best_match_index = int(distances.argmin())
            if matches[best_match_index]:
                label = known_names[best_match_index]

        top, right, bottom, left = location
        draw.rectangle((left, top, right, bottom), outline="lime", width=3)
        draw.text((left, max(0, top - 20)), label, fill="lime")
        print(f"{label}: box=({left}, {top}, {right}, {bottom})")

    output.save(output_path)
    print(f"Saved annotated image to {output_path}")


def main() -> None:
    parser = argparse.ArgumentParser(description="Recognize faces in an image")
    parser.add_argument("input", type=Path, help="Image to analyze")
    parser.add_argument("--known", type=Path, default=Path("known"))
    parser.add_argument("--output", type=Path, default=Path("result.jpg"))
    args = parser.parse_args()

    if not args.input.is_file():
        parser.error(f"Input image does not exist: {args.input}")
    if not args.known.is_dir():
        parser.error(f"Known-face directory does not exist: {args.known}")

    names, encodings = load_known_faces(args.known)
    recognize_image(args.input, args.output, names, encodings)


if __name__ == "__main__":
    main()

执行:

python recognize.py input.jpg --known known --output result.jpg

程序会在终端打印每张脸的识别结果,并生成带有边框和姓名标签的 result.jpg

调整识别准确率

示例中的 tolerance=0.6 是一个可调参数。数值越小,匹配条件越严格,误识别可能减少,但同一个人在光线、角度变化较大时也更容易被标记为 Unknown。数值越大,匹配更宽松,但把不同的人误认为同一个人的风险会上升。

实际项目可以这样实践:

  • 为同一个人保存多张不同角度和光线条件下的照片。
  • 为每张参考照片保存编码,而不是只保留一张编码。
  • 对识别结果使用人脸距离阈值,而不只依赖布尔值 matches
  • 在正式业务中记录低置信度结果,交给人工确认。
  • 对输入图片先缩小,再执行检测,以降低处理时间。

如果图片很大,可以在检测前缩放图片,但要记住绘制边框时需要把坐标换算回原始尺寸。处理视频时也不必逐帧识别,可以每隔几帧检测一次,并缓存短时间内的结果。

使用边界与隐私风险

人脸识别涉及生物特征数据。这个命令行工具适合学习和受控环境中的原型验证,并不意味着可以直接用于门禁、考勤或公共场所监控。

上线前需要明确数据采集授权、保存期限、访问权限和删除机制,同时评估不同光照、肤色、年龄和拍摄角度带来的识别差异。参考照片目录也应按照敏感数据处理,避免提交到公共代码仓库。

落地检查清单

  • 确认每张参考照片包含且只包含一个人脸。
  • 为未知人脸保留 Unknown 分支。
  • 用实际业务图片调节 tolerance,不要盲目使用默认值。
  • 记录识别距离和输入来源,便于排查误识别。
  • 在保存、传输和删除人脸数据前建立明确的隐私策略。

这个示例展示了从人脸检测、特征编码、匹配到结果标注的完整链路。掌握这条链路后,可以继续扩展到批量图片处理、视频流识别,或者将识别逻辑封装成 HTTP 服务。


相关推荐