当前位置: 首页 > news >正文

机器视觉:基于MTCNN与Caffe模型的人脸性别年龄统计系统实现

文章目录

  • 一、前言
  • 二、模型介绍
    • 2.1 MTCNN:高精度人脸检测模型
    • 2.2 Caffe预训练模型:性别与年龄预测
  • 三、模型原理解释
    • 3.1 MTCNN人脸检测原理
    • 3.2 Caffe模型属性预测原理
  • 四、代码解释
    • 4.1 整体结构
    • 4.2 路径配置与依赖导入
    • 4.3 模型加载函数(`load_models`)
    • 4.4 人脸检测函数(`detect_faces`)
    • 4.5 性别年龄预测函数(`predict_age_gender`)
    • 4.6 图片遍历函数(`iter_images`)
    • 4.7 主函数(`main`)
    • 4.8 完整代码
  • 五、总结

一、前言

在计算机视觉领域,人脸属性分析(如性别识别、年龄估算)是重要的研究方向,广泛应用于智能监控、用户画像构建、零售客流分析等场景。传统的人脸属性分析方案常面临检测精度低、模型部署复杂等问题,尤其是在非受控环境下的人脸检测效果往往不尽如人意。
最近在进行数据集规模统计的工作,在上一章进行人脸识别与聚类去除重复Speaker之后,进一步工作是统计样本人脸的性别与年龄分布数据,于是做了一套这样的系统。
本文将对该系统进行详尽的介绍并附上源码,该系统通过MTCNN(Multi-Task Cascaded CNN) 实现高精度人脸检测,结合预训练的Caffe模型完成性别识别与年龄估算,并最终生成可视化统计报告。系统支持批量处理图片文件夹中的所有图像,自动统计人脸总数、性别分布、年龄段分布及估算平均年龄,同时记录包含多张人脸的图片路径,为后续分析提供数据支撑。无论是用于个人学习计算机视觉技术,还是企业级的客流属性分析需求,这套系统都具备良好的实用性和可扩展性。

二、模型介绍

本系统采用“人脸检测 + 属性预测”的两阶段架构,分别使用MTCNN和Caffe预训练模型完成对应任务,两种模型在各自领域均具备成熟、高效的特点。

2.1 MTCNN:高精度人脸检测模型

MTCNN(Multi-Task Cascaded Convolutional Neural Networks)是2016年提出的多任务级联CNN模型,核心优势在于同时优化人脸检测、人脸关键点定位两个任务,在保证检测速度的同时大幅提升了非受控环境下的检测精度。

  • 核心特点

    1. 级联结构:分为P-Net(Proposal Network)、R-Net(Refine Network)、O-Net(Output Network)三阶段,逐步筛选和优化人脸框,减少误检与漏检。
    2. 多任务学习:在检测人脸的同时预测5/68个人脸关键点(双眼、鼻尖、两角嘴角),为后续人脸对齐提供支持(本系统暂未用到关键点,但保留扩展能力)。
    3. 轻量级:模型参数少,推理速度快,适合批量图片处理场景。
  • 本系统应用:替代传统的OpenCV DNN人脸检测器,解决侧脸、遮挡、小尺寸人脸检测效果差的问题,检测置信度阈值设为0.6(可根据需求调整)。

2.2 Caffe预训练模型:性别与年龄预测

性别和年龄预测采用两个独立的Caffe预训练模型,均基于经典的CNN架构设计,在公开数据集(如IMDB-WIKI)上训练得到,具备良好的泛化能力。

模型类型输入要求输出结果核心用途
性别预测模型227×227×3 RGB图像(需减去均值)二分类概率(Female/Male)判断人脸性别
年龄预测模型227×227×3 RGB图像(需减去均值)8个年龄段概率估算人脸所属年龄段
  • 年龄分段说明:模型将年龄划分为8个区间,每个区间对应一个“中间值”用于计算平均年龄,具体映射如下:
    • 年龄段:(0-2) → 中间值1;(4-6) → 中间值5;(8-12) → 中间值10
    • (15-20) → 中间值18;(25-32) → 中间值28;(38-43) → 中间值40
    • (48-53) → 中间值50;(60-100) → 中间值70

三、模型原理解释

3.1 MTCNN人脸检测原理

MTCNN通过三阶段级联网络逐步优化人脸检测结果,每一步都基于前一步的输出进行更精细的处理,具体流程如下:

  1. P-Net(Proposal Network)

    • 输入:原始图片(通过图像金字塔生成不同尺度的图片)。
    • 功能:快速生成大量“候选人脸框”,同时预测每个候选框的置信度(是否为人脸)和边界框回归值(用于调整框的位置)。
    • 输出:经过置信度筛选和非极大值抑制(NMS)后的候选人脸框。
  2. R-Net(Refine Network)

    • 输入:P-Net输出的候选人脸框( resize 为24×24)。
    • 功能:进一步筛选错误的候选框,修正边界框位置,提升检测精度。
    • 输出:再次经过置信度筛选和NMS后的人脸框,数量比P-Net大幅减少。
  3. O-Net(Output Network)

    • 输入:R-Net输出的人脸框( resize 为48×48)。
    • 功能:最终确认人脸框,输出更高精度的边界框回归值和5个人脸关键点坐标。
    • 输出:最终的人脸检测框(本系统仅使用框坐标,关键点可用于后续扩展)。

通过这种“粗筛→精筛→最终确认”的流程,MTCNN既能保证检测速度,又能有效避免误检和漏检,尤其适合复杂场景下的人脸检测。

3.2 Caffe模型属性预测原理

性别和年龄预测模型均基于CNN的特征提取与分类逻辑,核心流程一致,仅在输出层的任务定义上有所区别:

通用流程(性别/年龄预测):

  1. 图像预处理

    • 将MTCNN检测到的人脸框裁剪为独立的“人脸图像”,并 resize 为227×227(模型要求输入尺寸)。
    • 减去像素均值:对RGB三个通道分别减去固定均值(78.426337, 87.768914, 114.895847),消除光照变化对模型的影响。
    • 生成Blob:将处理后的图像转换为Caffe模型要求的Blob格式(批量维度×通道维度×高度×宽度)。
  2. 特征提取

    • 通过多层卷积层(Convolution)、激活函数(如ReLU)和池化层(Pooling)提取人脸图像的高层特征。
    • 例如:卷积层通过卷积核捕捉边缘、纹理等低级特征,深层卷积层则整合低级特征形成“人脸部件”(如眼睛、鼻子)等高级特征。
  3. 属性预测

    • 性别预测:输出层为2个神经元(对应Female和Male),采用Softmax激活函数,输出两个类别的概率,概率最大的类别即为预测性别。
    • 年龄预测:输出层为8个神经元(对应8个年龄段),同样采用Softmax激活函数,概率最大的神经元对应的年龄段即为预测结果,再通过“中间值”映射用于平均年龄计算。

四、代码解释

4.1 整体结构

代码分为路径配置、模型加载、核心功能函数、主流程五个模块,以下按模块逐一解析:

4.2 路径配置与依赖导入

import os
import cv2
import numpy as np
from collections import Counter, defaultdict
from datetime import datetime
from mtcnn import MTCNN   # 人脸检测库
from tqdm import tqdm  # 进度条库# -------- 路径配置 --------
THIS_DIR = os.path.dirname(__file__)  # 当前脚本所在目录
PROJECT_ROOT = os.path.abspath(os.path.join(THIS_DIR, '..'))  # 项目根目录
AGE_GENDER_DIR = os.path.join(PROJECT_ROOT, 'age_gender')  # 性别/年龄模型目录
FACE_DIR = os.path.join(PROJECT_ROOT, 'face', 'face_dir')  # 待处理图片目录
RESULT_TXT = os.path.join(PROJECT_ROOT, 'face', '性别及年龄统计.txt')  # 结果输出文件# -------- 模型文件路径 --------
AGE_PROTO = os.path.join(AGE_GENDER_DIR, 'age_deploy.prototxt')  # 年龄模型配置文件
AGE_MODEL = os.path.join(AGE_GENDER_DIR, 'age_net.caffemodel')  # 年龄模型权重
GENDER_PROTO = os.path.join(AGE_GENDER_DIR, 'gender_deploy.prototxt')  # 性别模型配置文件
GENDER_MODEL = os.path.join(AGE_GENDER_DIR, 'gender_net.caffemodel')  # 性别模型权重# 年龄分段与中间值配置
AGE_LIST = ['(0-2)', '(4-6)', '(8-12)', '(15-20)', '(25-32)', '(38-43)', '(48-53)', '(60-100)']
AGE_MID = [1, 5, 10, 18, 28, 40, 50, 70]  # 用于计算平均年龄
GENDER_LIST = ['Female', 'Male']  # 性别选项# 全局MTCNN检测器(避免重复初始化,提升效率)
mtcnn_detector = MTCNN()
  • 核心作用:定义项目目录结构、模型文件路径、业务配置(年龄分段),并初始化全局MTCNN检测器(减少重复创建实例的开销)。
  • 依赖说明:需提前安装mtcnnpip install mtcnn)、opencv-pythonpip install opencv-python)、tqdmpip install tqdm)等库。

4.3 模型加载函数(load_models

def load_models():# 仅加载年龄/性别模型;人脸检测改为全局MTCNNage_net = cv2.dnn.readNet(AGE_MODEL, AGE_PROTO)gender_net = cv2.dnn.readNet(GENDER_MODEL, GENDER_PROTO)return None, age_net, gender_net  # 第一个位置返回None,兼容旧调用逻辑
  • 功能:通过OpenCV的dnn模块加载Caffe格式的年龄和性别模型。
  • 兼容性处理:返回值第一个位置为None(原逻辑中用于返回OpenCV人脸检测模型,现替换为MTCNN,保留返回格式避免报错)。

4.4 人脸检测函数(detect_faces

def detect_faces(face_net, img, conf_thresh=0.6):"""使用MTCNN进行人脸检测参数:face_net: 兼容旧参数(现未使用)img: 输入图像(BGR格式,OpenCV默认读取格式)conf_thresh: 检测置信度阈值返回:boxes: 人脸框列表,格式为[(x1,y1,x2,y2), ...](左上角(x1,y1),右下角(x2,y2))"""results = mtcnn_detector.detect_faces(img)  # MTCNN检测结果boxes = []h, w = img.shape[:2]  # 图像高度和宽度for r in results:confidence = r.get('confidence', 0)  # 检测置信度if confidence >= conf_thresh and 'box' in r:x, y, w_box, h_box = r['box']  # MTCNN返回的框:x,y为左上角坐标,w_box/h_box为宽高# 修正框坐标(避免超出图像边界)x1 = max(0, x)y1 = max(0, y)x2 = min(x + w_box, w - 1)y2 = min(y + h_box, h - 1)# 确保框为有效区域(宽高均大于0)if x2 > x1 and y2 > y1:boxes.append((x1, y1, x2, y2))return boxes
  • 核心逻辑:调用MTCNN检测器获取人脸检测结果,过滤低置信度(<0.6)的框,并修正坐标避免超出图像边界。
  • 参数兼容face_net参数保留(原逻辑中用于传递OpenCV人脸检测模型),现未使用,确保旧代码调用不报错。

4.5 性别年龄预测函数(predict_age_gender

def predict_age_gender(age_net, gender_net, face_img):"""预测单张人脸的性别和年龄参数:age_net: 年龄预测模型gender_net: 性别预测模型face_img: 裁剪后的人脸图像(BGR格式)返回:gender: 预测性别(Female/Male)age: 预测年龄段(如(25-32))age_mid: 年龄段中间值(如28)"""# 图像预处理:转换为Blob格式并减去均值blob = cv2.dnn.blobFromImage(face_img, 1.0,  # 缩放因子(227, 227),  # 模型输入尺寸(78.426337, 87.768914, 114.895847),  # 各通道均值(BGR顺序)swapRB=False  # 无需交换R和B通道(OpenCV读取为BGR,模型要求BGR))# 性别预测gender_net.setInput(blob)  # 设置模型输入gender_pred = gender_net.forward()  # 推理得到预测结果gender = GENDER_LIST[gender_pred[0].argmax()]  # 取概率最大的类别# 年龄预测age_net.setInput(blob)  # 设置模型输入age_pred = age_net.forward()  # 推理得到预测结果age_idx = age_pred[0].argmax()  # 取概率最大的年龄段索引age = AGE_LIST[age_idx]  # 年龄段标签age_mid = AGE_MID[age_idx]  # 年龄段中间值return gender, age, age_mid
  • 关键步骤
    1. blobFromImage:将人脸图像转换为模型可接受的Blob格式,同时完成缩放、尺寸调整和均值减法。
    2. 模型推理:通过setInput设置输入,forward执行推理,得到预测概率分布。
    3. 结果解析:通过argmax获取概率最大的类别索引,映射为性别标签和年龄段。

4.6 图片遍历函数(iter_images

def iter_images(root):"""遍历目录下所有支持的图片文件(递归遍历子目录)参数:root: 根目录路径返回:图片文件路径生成器"""exts = {'.jpg', '.jpeg', '.png', '.bmp'}  # 支持的图片格式for dirpath, _, filenames in os.walk(root):  # 递归遍历目录for f in filenames:# 检查文件后缀是否在支持的格式中(不区分大小写)if os.path.splitext(f.lower())[1] in exts:yield os.path.join(dirpath, f)  # 返回完整文件路径
  • 功能:递归遍历指定目录下的所有图片文件,仅保留支持的格式(JPG、PNG等),通过生成器(yield)减少内存占用。

4.7 主函数(main

主函数是系统的核心流程入口,负责串联所有模块,完成“图片加载→人脸检测→属性预测→统计分析→结果输出”的全流程:

def main():if not os.path.exists(FACE_DIR):print("face_dir 不存在:", FACE_DIR)returnface_net, age_net, gender_net = load_models()  # face_net 现在恒为 Nonegender_counter = Counter()age_counter = Counter()age_mid_values = []file_face_count = defaultdict(int)total_faces = 0# 预收集所有图片路径用于进度条image_paths = list(iter_images(FACE_DIR))total_images = len(image_paths)if total_images == 0:print("未找到任何图片,目录:", FACE_DIR)returnfor img_path in tqdm(image_paths, desc='处理图片', unit='张'):img = cv2.imread(img_path)if img is None:continueboxes = detect_faces(face_net, img)for (x1, y1, x2, y2) in boxes:face = img[y1:y2, x1:x2]if face.size == 0:continuetry:gender, age_range, age_mid = predict_age_gender(age_net, gender_net, face)except Exception as e:# 单张失败不影响整体continuegender_counter[gender] += 1age_counter[age_range] += 1age_mid_values.append(age_mid)file_face_count[img_path] += 1total_faces += 1avg_age = round(sum(age_mid_values) / len(age_mid_values), 2) if age_mid_values else 0.0lines = []lines.append("统计时间: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))lines.append(f"扫描图片数: {total_images}")lines.append(f"检测到人脸总数: {total_faces}")lines.append("性别统计:")for g in GENDER_LIST:lines.append(f"  {g}: {gender_counter.get(g,0)}")lines.append("年龄段统计:")for a in AGE_LIST:lines.append(f"  {a}: {age_counter.get(a,0)}")lines.append(f"估算平均年龄: {avg_age}")lines.append("(文件级人脸计数仅列出>1者)")for fp, c in file_face_count.items():if c > 1:lines.append(f"  {fp}: {c}")os.makedirs(os.path.dirname(RESULT_TXT), exist_ok=True)with open(RESULT_TXT, 'w', encoding='utf-8') as f:f.write('\n'.join(lines))print("统计完成,结果写入:", RESULT_TXT)print('\n'.join(lines[:10]), '...')

4.8 完整代码

import os
import cv2
import numpy as np
from collections import Counter, defaultdict
from datetime import datetime
from mtcnn import MTCNN   # 新增:使用与 face_check.py 相同的 MTCNN 检测
from tqdm import tqdm  # 进度条# -------- 路径配置 --------
THIS_DIR = os.path.dirname(__file__)
PROJECT_ROOT = os.path.abspath(os.path.join(THIS_DIR, '..'))
AGE_GENDER_DIR = os.path.join(PROJECT_ROOT, 'age_gender')
FACE_DIR = os.path.join(PROJECT_ROOT, 'face', 'face_dir')
RESULT_TXT = os.path.join(PROJECT_ROOT, 'face', '性别及年龄统计.txt')# -------- 模型文件 --------
AGE_PROTO = os.path.join(AGE_GENDER_DIR, 'age_deploy.prototxt')
AGE_MODEL = os.path.join(AGE_GENDER_DIR, 'age_net.caffemodel')
GENDER_PROTO = os.path.join(AGE_GENDER_DIR, 'gender_deploy.prototxt')
GENDER_MODEL = os.path.join(AGE_GENDER_DIR, 'gender_net.caffemodel')
# 下面两个(OpenCV DNN人脸检测)已不再使用,保留不报错;如需可删除
# FACE_PROTO = os.path.join(AGE_GENDER_DIR, 'opencv_face_detector.pbtxt')
# FACE_MODEL = os.path.join(AGE_GENDER_DIR, 'opencv_face_detector_uint8.pb')AGE_LIST = ['(0-2)', '(4-6)', '(8-12)', '(15-20)', '(25-32)', '(38-43)', '(48-53)', '(60-100)']
AGE_MID = [1, 5, 10, 18, 28, 40, 50, 70]  # 用于估算平均年龄
GENDER_LIST = ['Female', 'Male']
# 新增:全局 MTCNN 检测器,与 face_check.py 保持一致
mtcnn_detector = MTCNN()def load_models():# 仅加载年龄/性别模型;人脸检测改为全局 MTCNNage_net = cv2.dnn.readNet(AGE_MODEL, AGE_PROTO)gender_net = cv2.dnn.readNet(GENDER_MODEL, GENDER_PROTO)return None, age_net, gender_net  # 第一个位置兼容旧调用def detect_faces(face_net, img, conf_thresh=0.6):"""使用与 face_check.py 相同的 MTCNN 进行检测返回: [(x1,y1,x2,y2), ...]"""results = mtcnn_detector.detect_faces(img)boxes = []h, w = img.shape[:2]for r in results:confidence = r.get('confidence', 0)if confidence >= conf_thresh and 'box' in r:x, y, w_box, h_box = r['box']x1 = max(0, x)y1 = max(0, y)x2 = min(x + w_box, w - 1)y2 = min(y + h_box, h - 1)if x2 > x1 and y2 > y1:boxes.append((x1, y1, x2, y2))return boxesdef predict_age_gender(age_net, gender_net, face_img):blob = cv2.dnn.blobFromImage(face_img, 1.0, (227, 227), (78.426337, 87.768914, 114.895847), swapRB=False)gender_net.setInput(blob)gender_pred = gender_net.forward()gender = GENDER_LIST[gender_pred[0].argmax()]age_net.setInput(blob)age_pred = age_net.forward()age_idx = age_pred[0].argmax()age = AGE_LIST[age_idx]age_mid = AGE_MID[age_idx]return gender, age, age_middef iter_images(root):exts = {'.jpg', '.jpeg', '.png', '.bmp'}for dirpath, _, filenames in os.walk(root):for f in filenames:if os.path.splitext(f.lower())[1] in exts:yield os.path.join(dirpath, f)def main():if not os.path.exists(FACE_DIR):print("face_dir 不存在:", FACE_DIR)returnface_net, age_net, gender_net = load_models()  # face_net 现在恒为 Nonegender_counter = Counter()age_counter = Counter()age_mid_values = []file_face_count = defaultdict(int)total_faces = 0# 预收集所有图片路径用于进度条image_paths = list(iter_images(FACE_DIR))total_images = len(image_paths)if total_images == 0:print("未找到任何图片,目录:", FACE_DIR)returnfor img_path in tqdm(image_paths, desc='处理图片', unit='张'):img = cv2.imread(img_path)if img is None:continueboxes = detect_faces(face_net, img)for (x1, y1, x2, y2) in boxes:face = img[y1:y2, x1:x2]if face.size == 0:continuetry:gender, age_range, age_mid = predict_age_gender(age_net, gender_net, face)except Exception as e:# 单张失败不影响整体continuegender_counter[gender] += 1age_counter[age_range] += 1age_mid_values.append(age_mid)file_face_count[img_path] += 1total_faces += 1avg_age = round(sum(age_mid_values) / len(age_mid_values), 2) if age_mid_values else 0.0lines = []lines.append("统计时间: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))lines.append(f"扫描图片数: {total_images}")lines.append(f"检测到人脸总数: {total_faces}")lines.append("性别统计:")for g in GENDER_LIST:lines.append(f"  {g}: {gender_counter.get(g,0)}")lines.append("年龄段统计:")for a in AGE_LIST:lines.append(f"  {a}: {age_counter.get(a,0)}")lines.append(f"估算平均年龄: {avg_age}")lines.append("(文件级人脸计数仅列出>1者)")for fp, c in file_face_count.items():if c > 1:lines.append(f"  {fp}: {c}")os.makedirs(os.path.dirname(RESULT_TXT), exist_ok=True)with open(RESULT_TXT, 'w', encoding='utf-8') as f:f.write('\n'.join(lines))print("统计完成,结果写入:", RESULT_TXT)print('\n'.join(lines[:10]), '...')if __name__ == "__main__":main()

五、总结

本文介绍的人脸性别与年龄统计系统,聚焦人脸属性分析需求,采用 “MTCNN 人脸检测 + Caffe 预训练模型属性预测” 的两阶段架构,攻克传统方案检测精度低、非受控环境适配差的问题。系统可批量处理指定目录图片,通过 MTCNN 精准筛选人脸(过滤低置信度框并修正边界),再借助 Caffe 模型完成性别分类与 8 个年龄段估算,最终输出人脸总数、性别 / 年龄段分布、平均年龄及含多张人脸的图片路径,同时生成结构化文本报告。代码模块化设计清晰(路径配置、模型加载、核心功能函数、主流程),兼顾技术学习与企业级客流分析等实际场景,具备良好的实用性与可扩展性。

http://www.dtcms.com/a/419509.html

相关文章:

  • 手机网站开发升上去专门做消防器材的网站
  • Docker进程中的守护进程原理解析
  • ApplicationContext接口实现(四)
  • PyQt python 异步任务,多线程,进阶版
  • 磁盘物理坏块与逻辑坏块的区别
  • net asp网站开发长春哪有做网站公司
  • 【机器学习】监督学习 —— 决策树(Decision Tree)
  • (基于江协科技)51单片机入门:5.定时器
  • 怎么制作个人门户网站东莞常平中转场
  • 强化学习原理(四)
  • 做网站 毕业设计长沙企业网页设计哪家专业
  • 菊风可视化回溯解决方案,为金融业务合规打造全流程“可回溯”能力
  • 蜜度AI审校从技术到服务全面突破 为出版内容校对注入新活力
  • 单一索引,覆盖索引,联合索引
  • BentoML推出llm-optimizer开源框架:让LLM性能调优变简单?
  • Cherry Studio实战使用
  • Python 类型提示:Literal 类型
  • 仿造别人的网站侵权吗做网站被抓
  • 做中文网站的公司海安网站设计公司
  • 浏览器获取到网页的流程
  • 解析网站怎么做wordpress 小程序 插件
  • SQL 性能优化:为什么少用函数在 WHERE 条件中?
  • 迁西网站开发上海网络技术有限公司
  • 如何利用服务器做网站沈阳建设工程信息网中项网
  • 推广网站的方法有哪些建设网站账务处理
  • [Windows] 3D软件 Blender 5.0 alpha版
  • 计算机视觉(opencv)——基于 dlib 轮廓绘制
  • 帕累托概念Pareto
  • 海外云服务器压力测试,如何评估服务器性能与稳定性
  • python建设网站常州网站建设智博