本文面向 OpenCV 初学者,手把手教你写一个实时摄像头程序,自动检测人脸,并预测每个人的年龄和性别。文中会详细解释模型文件、代码逻辑和常见问题。

一、效果展示

运行程序后,摄像头会实时显示画面,每个人脸会被绿色框标出,并标注出性别(boy/girl)和年龄段(如 25-32years)。就像下面这样(示意图):

[摄像头画面]
┌──────────────────────┐
│  ┌─────┐             │
│  │ boy,│             │
│  │25-32│             │
│  └─────┘             │
│                      │
└──────────────────────┘

二、准备工作

2.1 安装 Python 库

pip install opencv-python pillow numpy
  • opencv-python:图像处理、摄像头、深度学习推理

  • pillow:在图片上绘制中文文字

  • numpy:OpenCV 底层依赖

2.2 下载预训练模型文件

你需要以下 6 个文件(放在项目的 model/ 目录下):

用途 结构文件 权重文件
人脸检测 opencv_face_detector.pbtxt opencv_face_detector_uint8.pb
性别预测 deploy_gender.prototxt gender_net.caffemodel
年龄预测 deploy_age.prototxt age_net.caffemodel

这些文件在 OpenCV 官方仓库或 CSDN 上都能搜到,注意文件名必须完全一致。

2.3 中文字体(可选)

如果你希望显示中文,请准备一个中文字体文件(如 simsun.ttc),放在 font/ 目录下。
如果你不想折腾,可以直接显示英文(我会在代码中给出两种方案)。

三、完整代码(可直接运行)

将以下代码保存为 age_gender_live.py

import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

# ---------- 1. 加载模型 ----------
faceProto = "model/opencv_face_detector.pbtxt"
faceModel = "model/opencv_face_detector_uint8.pb"
ageProto = "model/deploy_age.prototxt"
ageModel = "model/age_net.caffemodel"
genderProto = "model/deploy_gender.prototxt"
genderModel = "model/gender_net.caffemodel"

faceNet = cv2.dnn.readNet(faceModel, faceProto)
ageNet = cv2.dnn.readNet(ageModel, ageProto)
genderNet = cv2.dnn.readNet(genderModel, genderProto)

# ---------- 2. 定义标签和均值 ----------
ageList = ['0-2years','3-6years','8-12years','15-20years',
           '25-32years','38-43years','48-53years','60-100']
genderList = ['boy','girl']
mean = (78.426, 87.76891, 114.8958477)   # 年龄/性别模型需要的均值

# ---------- 3. 人脸检测函数(带画框)----------
def getFaceBoxes(net, frame):
    h, w = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(frame, 1.0, (300,300), [104,117,123], swapRB=True)
    net.setInput(blob)
    detections = net.forward()      # 形状 (1,1,N,7)
    faceBoxes = []
    for i in range(detections.shape[2]):
        conf = detections[0,0,i,2]
        if conf > 0.7:
            x1 = int(detections[0,0,i,3] * w)
            y1 = int(detections[0,0,i,4] * h)
            x2 = int(detections[0,0,i,5] * w)
            y2 = int(detections[0,0,i,6] * h)
            faceBoxes.append([x1,y1,x2,y2])
            cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
    return frame, faceBoxes

# ---------- 4. 中文绘制函数(可选)----------
def drawChinese(img, text, pos, color=(0,255,0), size=25):
    img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img_pil)
    font = ImageFont.truetype("font/simsun.ttc", size, encoding="utf-8")
    draw.text(pos, text, color, font=font)
    return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)

# ---------- 5. 主程序:摄像头实时推理 ----------
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

while True:
    ret, frame = cap.read()
    if not ret:
        break
    frame = cv2.flip(frame, 1)          # 水平镜像,更像照镜子

    # 步骤1:人脸检测
    frame, faceBoxes = getFaceBoxes(faceNet, frame)

    if len(faceBoxes) == 0:
        cv2.imshow("Age Gender Predict", frame)
        if cv2.waitKey(1) == 27:
            break
        continue

    # 步骤2:对每个人脸预测年龄和性别
    for (x1,y1,x2,y2) in faceBoxes:
        face = frame[y1:y2, x1:x2]       # 切片裁剪人脸区域
        if face.size == 0:
            continue

        # 准备输入(年龄/性别模型要求 227x227)
        blob = cv2.dnn.blobFromImage(face, 1.0, (227,227), mean, swapRB=False)

        # 性别预测
        genderNet.setInput(blob)
        genderPred = genderNet.forward()
        gender = genderList[genderPred[0].argmax()]

        # 年龄预测
        ageNet.setInput(blob)
        agePred = ageNet.forward()
        age = ageList[agePred[0].argmax()]

        # 显示结果
        label = f"{gender},{age}"
        # 如果你没有中文字体,用下面这行英文显示:
        # cv2.putText(frame, f"{gender} {age}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
        frame = drawChinese(frame, label, (x1, y1-25))

    cv2.imshow("Age Gender Predict", frame)
    if cv2.waitKey(1) == 27:    # ESC 退出
        break

cap.release()
cv2.destroyAllWindows()

四、核心知识点详解(小白必看)

4.1 预训练模型:什么是 .pbtxt 和 .pb

  • .pbtxt:网络的结构文件(相当于建筑图纸)

  • .pb 或 .caffemodel:网络的权重文件(相当于钢筋混凝土和装修)

两者缺一不可。cv2.dnn.readNet(权重, 结构) 的作用就是加载别人已经训练好的模型,我们直接使用,不需要自己训练。

4.2 detections 到底长什么样?

人脸检测网络的输出 detections 是一个四维数组,形状为 (1, 1, N, 7)

  • 前两维固定为 1(batch 和 class 维度)

  • 第三维 N 表示检测到的人脸候选框数量

  • 第四维 7 表示每个框的 7 个信息:[图片ID, 类别ID, 置信度, x1_norm, y1_norm, x2_norm, y2_norm]

所以提取第 i 个框的置信度要写成 detections[0, 0, i, 2],而不是 detections[i][2]

4.3 frame 是什么?为什么能 frame[y1:y2, x1:x2]

在 OpenCV 中,frame 是一个 NumPy 三维数组,形状为 (高, 宽, 3),存储着 BGR 三个通道的像素值。
对数组进行切片 [y1:y2, x1:x2] 就能截取出矩形区域——也就是人脸部分的图像,赋值给 face 变量,供后续模型使用。

4.4 blobFromImage 做了什么?

将原始图像转换成神经网络可接受的格式:

  • 缩放到模型要求的尺寸(人脸检测 300×300,年龄性别 227×227)

  • 减去均值(使数据分布接近训练时的状态)

  • 可选交换通道顺序(BGR ↔ RGB)

  • 输出四维数组 [1, 3, H, W]

4.5 如何从模型输出得到性别和年龄?

性别模型输出一个 (1,2) 的数组,例如 [[0.2, 0.8]],分别表示 [boy, girl] 的概率。
argmax() 返回最大值的索引(这里是 1),再从 genderList 中取出对应标签 'girl'
年龄模型同理,输出 (1,8),对应 ageList 中的 8 个年龄段。

四、常见问题与解决方案

Q1:运行时提示“无法找到模型文件”

A:检查路径。可以把模型文件放在脚本同级的 model/ 文件夹内,或者使用绝对路径。

Q2:中文显示为方块或报错

A:要么将正确的字体文件放到 font/ 目录下,要么改用英文显示(代码中已给出备选)。

Q3:检测不到人脸或者误检太多

A:调整置信度阈值。代码中使用 >0.7,可以尝试降低到 0.5(提高召回率)或升高到 0.9(提高精确率)。

Q4:程序很卡,CPU 占用高

A:年龄性别模型比较消耗性能。可以:

  • 降低摄像头分辨率:cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)

  • 每隔 3 帧才做一次年龄性别预测,其余帧只做人脸检测

五、总结与拓展

通过这个项目,你已经掌握了:

  • 如何使用 OpenCV DNN 模块加载预训练模型

  • 如何实时进行人脸检测、年龄估计、性别识别

  • 如何在图像上绘制中文文字

  • 理解 detections 四维数组的索引方式

你可以继续尝试:

  1. 换成更轻量的人脸检测器(如 MediaPipe),提高速度

  2. 增加表情识别、口罩检测等任务

  3. 将结果保存到文件或上传到服务器

学习的本质就是不断拆解、实验、总结。希望这篇文章能帮你迈出 OpenCV 深度学习应用的第一步。有任何问题,欢迎在评论区交流!


本文代码已测试环境:Python 3.8,OpenCV 4.5.4,Windows 10。模型文件请自行准备。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐