介绍
FaceRank是一个基于TensorFlow与Keras框架的CNN人脸打分开源项目,核心功能是通过深度学习模型量化评估人脸吸引力,输出颜值分数,适合深度学习初学者实践图像特征提取与模型训练
核心定位
面向深度学习入门者的人脸吸引力量化工具,通过CNN自动提取人脸特征并打分,提供完整的训练与部署流程,帮助开发者熟悉TensorFlow/Keras在图像任务中的应用。
技术栈
- 框架:TensorFlow(核心)+ Keras(简化模型搭建),支持两种版本实现。
- 核心模型:CNN,含卷积层、池化层、激活函数(如ReLU)、全连接层,用于自动学习人脸边缘、纹理、形状等特征。
- 依赖库:face_recognition(人脸检测与裁剪)、OpenCV(图像处理)、NumPy(数据计算)等。
模型结构
典型流程为“输入(128×128像素人脸图)→ 卷积层(特征提取)→ 池化层(降维)→ 全连接层(特征映射)→ 输出(0-10分颜值分数)”。
部署与使用流程
- 环境搭建:安装Python、TensorFlow/Keras、face_recognition等依赖,建议用Docker解决face_recognition安装问题。
- 数据预处理
- 用find_faces_in_picture.py从原始图片中检测并裁剪人脸,保存至face_image文件夹。
- 用resize_image.py将人脸图统一缩放为128×128像素,适配模型输入。
- 模型训练:运行train_model.py,定义CNN结构、设置损失函数(如MSE)与优化器(如Adam),训练后保存模型至model目录。
- 推理测试:运行run_model.py,加载训练好的模型,输入人脸图输出颜值分数。
- 模型获取:可自行训练,也可从第三方平台下载预训练模型。
应用场景
- 深度学习教学:作为CNN图像任务的实战案例,覆盖数据预处理、模型搭建、训练与部署全流程。
- 趣味应用:社交平台颜值排序、相亲APP匹配辅助、短视频人脸吸引力筛选等。
- 二次开发:可扩展至年龄/性别预测、表情识别等衍生任务。
局限
- 数据偏差:默认数据集多针对特定人群(如年轻女性),泛化性不足,需自定义数据集优化。
- 伦理风险:颜值打分易引发歧视,需遵守隐私保护与公平性原则,避免用于招聘、婚恋等敏感场景。
- 精度限制:开源模型为入门级,打分结果受训练数据质量影响大,需调参或迁移学习提升精度。
项目资源与扩展
- 获取渠道:GitHub(fendouai/FaceRank)、Gitee(国内镜像),含完整代码、中文说明与教程。
- 扩展方向
- 迁移学习:用VGG16、ResNet等预训练模型提升特征提取能力。
- 数据增强:通过旋转、翻转、亮度调整扩充数据集,减少过拟合。
- 部署优化:用TensorFlow Lite适配移动端,或用FastAPI封装为API服务。
难点
在 多输出 需要做额外处理:
- 颜值检测是线性回归,loss是
mse - 亚洲人或欧洲人识别是逻辑回归,loss是
sigmoid - 性别检测是逻辑回归,loss是
sigmoid
训练结果:
- 颜值误差 0.3
- 亚洲/欧洲检测准确率 0.99
- 性别检测准确率 0.98
数据集
使用 华南理工大学人机智能交互实验室 的数据集
介绍
数据集共有5500张正面,包括2000个亚洲女性(AF),2000个亚洲男性(AM),750个白人女性(CF)和750个白人男性(CM)
这些正面具有不同的属性(男性/女性,亚洲人/白种人,年龄)和不同的标签(面部标志,5个等级的美容分数,美容分数分布)
详细介绍见链接:
https://github.com/HCIILAB/SCUT-FBP5500-Database-Release
标签
所有图像都由60名志愿者的美容评分标记为[1、5],并且在每个图像的重要面部组件上还定位了86个面部标志。
缺点:
由于审美不同,我对很多人脸的评分不认同,训练好模型后,预测知名网红、明星时,颜值分数不高,反而 凤姐的颜值颇高
所以 测试自己颜值 时参考就好,不必纠结
下载链接
https://pan.baidu.com/s/1Ff2W2VLJ1ZbWSeV5JbF0Iw(PASSWORD:if7p )
目录说明:
技术
- TensorFlow2.0
- keras
- matplotlib
- numpy
代码
导包
import os,glob
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import numpy as np
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
from PIL import Image
import IPython.display as display
import xml.etree.ElementTree as ET
print(tf.__version__)
print(tf.test.is_gpu_available())
常量
img_path="/kaggle/input/facial-beauty-prediction/SCUT-FBP5500_v2/Images/"
img_size=224
batch_size=8
AUTOTUNE=tf.data.experimental.AUTOTUNE
ac_map={"A":0,"C":1}
fm_map={"F":0,"M":1}
定义读取文本的函数
def read_txt(path):
with open(path,"rt") as f:
lines=f.readlines()
# print(lines)
names=[]
labels=[]
acs=[]
fms=[]
for item in lines :
item=item.strip()
name,label=item.split(" ")
ac=ac_map[name[0]]
fm=fm_map[name[1]]
acs.append(ac)
fms.append(fm)
# print(name,label)
names.append(os.path.join(img_path,name))
labels.append(float(label))
return names,labels,acs,fms
train_names,train_labels,train_acs,train_fms=read_txt("/kaggle/input/facial-beauty-prediction/SCUT-FBP5500_v2/train_test_files/split_of_60%training and 40%testing/train.txt")
train_num=len(train_names)
train_num
test_names,test_labels,test_acs,test_fms=read_txt("/kaggle/input/facial-beauty-prediction/SCUT-FBP5500_v2/train_test_files/split_of_60%training and 40%testing/test.txt")
生成训练数据集(关键)
train_ds=tf.data.Dataset.from_tensor_slices((train_names,(train_labels,train_acs,train_fms)))
train_ds
注意:
传入一个tuple类型,第一个元素train_names是list类型,第二个元素是tuple类型,该tuple里面的每个元素都是list
定义预处理函数
def process_train(path,label):
t=tf.io.read_file(path)
arr=tf.io.decode_jpeg(t,channels=3)
arr=tf.image.resize(arr,(img_size,img_size))
arr = arr/255
return arr,label
train_ds2=train_ds.map(process_train,num_parallel_calls=AUTOTUNE).shuffle(train_num).batch(batch_size).prefetch(AUTOTUNE)
test_ds=tf.data.Dataset.from_tensor_slices((test_names,(test_labels,test_acs,test_fms)))
test_ds2=test_ds.map(process_train,num_parallel_calls=AUTOTUNE).batch(batch_size).prefetch(AUTOTUNE)
构建模型(关键)
def build():
inp=tf.keras.Input((img_size, img_size, 3))
x=tf.keras.layers.Conv2D(64,3,padding='same',activation="relu",)(inp)
x=tf.keras.layers.Conv2D(64,3,padding='same',activation="relu")(x)
x=tf.keras.layers.MaxPooling2D()(x)
x=tf.keras.layers.Conv2D(128,3,padding='same',activation="relu")(x)
x=tf.keras.layers.Conv2D(128,3,padding='same',activation="relu")(x)
x=tf.keras.layers.MaxPooling2D()(x)
x=tf.keras.layers.Conv2D(256,3,padding='same',activation="relu")(x)
x=tf.keras.layers.Conv2D(256,3,padding='same',activation="relu")(x)
x=tf.keras.layers.MaxPooling2D()(x)
x=tf.keras.layers.Conv2D(512,3,padding='same',activation="relu")(x)
x=tf.keras.layers.Conv2D(512,3,padding='same',activation="relu")(x)
x=tf.keras.layers.GlobalAveragePooling2D()(x)
x=tf.keras.layers.Dense(1024,activation="relu")(x)
# x=tf.keras.layers.Dropout(0.5)(x)
# x=tf.keras.layers.Dense(1024,activation="relu")(x)
# x=tf.keras.layers.Dropout(0.5)(x)
score=tf.keras.layers.Dense(1,name="score")(x)
ac=tf.keras.layers.Dense(1,activation="sigmoid",name="ac")(x)
fm=tf.keras.layers.Dense(1,activation="sigmoid",name="fm")(x)
model=tf.keras.Model(inp,[score,ac,fm])
return model
注意:
模型中输出层layer的 name,在 编译 时会用到
model=build()
编译(关键)
编译时要注意,因为是多输出模型,所以要分别设置每个输出的loss,分别设置每个输出的metrics,如下代码:
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss=["mse","binary_crossentropy","binary_crossentropy"],
metrics={"score":"mae","ac":"acc","fm":"acc"})
注意:
metrics是dict类型,key是:score、ac、fm,是模型中layer的name
或者
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0001),
loss=["mse","binary_crossentropy","binary_crossentropy"],
metrics=[["mae"],["acc"],"acc"])
注意:
必须是metrics=[["mae"],"acc","acc"],不能是metrics=["mae","acc","acc"]
训练
history=model.fit(train_ds2,epochs=40,validation_data=test_ds2)
最后一次结果:
Epoch 36/40
217/413 [==============>...............] - ETA: 15s - loss: 0.2152 - score_loss: 0.1608 - ac_loss: 0.0205 - fm_loss: 0.0340 - score_mae: 0.3112 - ac_acc: 0.9948 - fm_acc: 0.9896
可知:
- 颜值 score_mae 误差在0.3
- 亚洲/欧洲检测准确率在0.99
- 性别检测准确率在0.98
查看loss和正确率
plt.plot(history.epoch,history.history["score_mae"],label="score_mae")
plt.plot(history.epoch,history.history["val_score_mae"],label="val_score_mae")
plt.legend()
plt.plot(history.epoch,history.history["ac_acc"],label="ac_acc")
plt.plot(history.epoch,history.history["val_ac_acc"],label="val_ac_acc")
plt.legend()
plt.plot(history.epoch,history.history["fm_acc"],label="fm_acc")
plt.plot(history.epoch,history.history["val_fm_acc"],label="val_fm_acc")
plt.legend()
plt.plot(history.epoch,history.history["score_loss"],label="score_loss")
plt.plot(history.epoch,history.history["val_score_loss"],label="val_score_loss")
plt.legend()
plt.plot(history.epoch,history.history["ac_loss"],label="ac_loss")
plt.plot(history.epoch,history.history["val_ac_loss"],label="val_ac_loss")
plt.legend()
plt.plot(history.epoch,history.history["fm_loss"],label="fm_loss")
plt.plot(history.epoch,history.history["val_fm_loss"],label="val_fm_loss")
plt.legend()
保存模型
model.save("model.h5",save_format="h5")
预测
import tensorflow as tf
import matplotlib.pyplot as plt
img_size=224
ac_map={0:"亚洲",1:"欧洲"}
fm_map={0:"女",1:"男"}
加载模型
model=tf.keras.models.load_model("/Users/mym/Desktop/数据集/SCUT-FBP5500_v2/model颜值亚欧性别.h5")
定义函数
def test(path):
t=tf.io.read_file(path)
arr=tf.io.decode_jpeg(t,channels=3)
plt.imshow(arr)
arr=tf.image.resize(arr,(img_size,img_size))
arr = arr/255
arr=tf.expand_dims(arr,0)
prd=model.predict(arr)
score,ac,fm=tf.squeeze(prd).numpy()
print("score:",score,"ac:",ac,"fm:",fm)
score=int(round(score*20))
ac=1 if ac>0.5 else 0
ac=ac_map.get(ac)
fm=1 if fm>0.5 else 0
fm=fm_map.get(fm)
print("颜值:%s,亚欧:%s,性别:%s"%(score,ac,fm))
test("/Users/mym/Desktop/可删除/OIP.jpeg")
