简介:面向Python开发者及计算机视觉爱好者的智能口红色号检测推荐系统完整工程,基于Dlib人脸68点特征与face_recognition库完成人脸检测与唇部区域定位,结合TensorFlow及colorsys色彩转换技术,对嘴唇颜色进行提取并匹配相近色号,适用于美妆电商、虚拟试妆、个性化推荐等场景。资源共84个文件,包含10个Python源码、37个txt说明与数据、21张PNG图像、8个JSON配置、UI界面、PDF文献以及ipynb示例,压缩包仅3.03MB,目录结构清晰,便于按需取用。目前已有464人学习下载。工程覆盖数据预处理与系统搭建两大模块,具体包括唇部轮廓提取、蒙版创建、嘴唇区域划分、图片颜色提取、色号库比对与结果输出;同时提供基于PyQt5和QCandyUi的图形界面,附带完整数据集、训练模型与开发文档,可直接运行或二次扩展,也可作为毕业设计、课程项目的参考范例。 口红试色这件事,线上买怕有色差,线下专柜又没法一次性试遍所有品牌的热门色号。我一直在想,能不能用深度学习直接分析人脸照片里的唇部颜色,再把颜色映射到具体品牌的口红色号上。这个想法其实并不复杂,核心就是把“口红色号检测推荐”拆成人脸关键点检测、唇部区域颜色提取、色号分类推荐三段来完成。这篇文章我直接把整个系统的搭建过程、关键代码逻辑、训练细节和踩过的坑都梳理一遍,希望能给想做类似桌面级深度学习应用的朋友一个完整参考。
改出口红推荐这个完整功能之前,先把需求拆开来看。这个项目本质上要解决三件事:第一,从一张普通的人脸照片里准确找到嘴唇的位置;第二,把嘴唇区域的颜色信息提取出来,转化成可计算的色彩特征;第三,把颜色特征映射到具体品牌的口红色号上,并且能给出合理的推荐结果。这三个环节分别对应了Dlib的人脸关键点检测、颜色空间转换与特征提取、TensorFlow的分类模型训练,最后由PyQt5把整个流程封装成一个可视化桌面应用。
这套技术选型是经过反复比较后确定的。Dlib的68点人脸关键点检测模型非常成熟,尤其对唇部区域的定位精度很高,且推理速度快,纯CPU环境也能跑得很流畅。TensorFlow则负责后端深度学习模型的训练和推理,生态完善,部署方便。PyQt5负责界面层,C++后端加Python绑定的方式让它性能和开发效率都不错,做这种工具型桌面应用很合适。整个项目使用Python作为胶水语言,把这三部分串起来,逻辑清晰,工程上也不会引入不必要的复杂度。
1. 系统整体架构与设计思路
1.1 为什么用Dlib定位嘴唇而不是直接训练目标检测模型
很多人看到“检测嘴唇”第一反应是用YOLO或者SSD这类目标检测模型,但在实际试过之后,这个场景用Dlib的68点关键点检测方案要合理得多。关键点检测返回的是嘴唇轮廓的坐标点集合,能精确定位到唇峰、唇角这些细节位置,而目标检测返回的是矩形框,上嘴唇和下嘴唇的分界、唇峰位置这些关键信息会丢失得很严重。
这个项目里,口红色号的判断高度依赖唇部颜色的准确提取。如果用矩形框裁剪,会混入大量脸部皮肤和牙齿的颜色,对后续的颜色特征提取造成严重干扰。Dlib的68点关键点模型中,标号为48到67的20个点完整覆盖了嘴唇的内外轮廓,通过连接这些点构建掩膜,就能精确提取出纯嘴唇区域的像素信息。
安装Dlib时可能遇到编译失败的问题。Dlib的安装包体积较大(约100MB),且安装时会自动编译C++扩展,耗时几分钟,还依赖CMake和C++编译环境。建议安装Visual Studio Build Tools(包含C++桌面开发组件)后再安装,或者使用预编译的whl文件,能显著提升安装成功率。另外,Dlib的模型文件(如shape_predictor_68_face_landmarks.dat)需要单独下载,这是开源社区提供的预训练模型,网上可以找到。
1.2 颜色分析与色号映射的整体流程
系统对唇部颜色处理的核心链路是:人脸检测 → 关键点定位 → 唇部掩膜提取 → 颜色空间转换 → 颜色特征统计 → 分类模型推荐色号。链路里的每一步权重不同,前两步是基础,颜色特征统计和分类推荐是核心。
颜色特征提取的关键点在于,不能直接在RGB空间做简单的平均色计算。RGB空间对光照变化太敏感,不同光源、色温下同一个口红色号测出的RGB值差异很大。我最终采用的方案是把唇部区域转换到LAB颜色空间,L通道表示亮度,A通道表示红绿色度,B通道表示黄蓝色度。口红的颜色差异主要体现在A通道和B通道上,L通道受光照影响最大(亮度变化),所以在特征提取时降低L通道的权重,能有效提升模型对不同光照条件的鲁棒性。同时保留唇部中心区域(像素点总数的60%左右)的颜色统计值,丢弃唇边模糊区域,这样处理后的特征更集中。
1.3 PyQt5应用的界面交互逻辑
界面是给普通人用的,不能要求用户理解算法原理,所以交互逻辑必须足够直观。主界面设计为三个区域:左侧是图片上传与预览区,用户上传一张清晰的正脸照片后,系统自动绘制出人脸关键点,并在嘴唇位置叠加一个半透明色块,直观显示检测到的唇部区域;右侧是色号推荐区,展示Top5推荐色号,每个色号带有一个品牌色卡、色号名和匹配度百分比,点击色号卡片可以查看推荐的妆容搭配参考;底部是操作日志区,实时显示检测过程中的步骤信息。
考虑到后续扩展性,界面里还加入了两个实用小功能:肤色基调识别和“我的色号”收藏列表。肤色基调识别通过分析脸部非唇部区域的皮肤颜色,判断用户是冷色调还是暖色调,在推荐色号时根据肤色基调做二次排序,冷皮用户优先推荐偏蓝调的口红,暖皮用户优先推荐偏橘调的口红。这个细节虽然增加了开发量,但实际使用体验提升非常明显。整个界面基于QStackedWidget做页面切换,后续想增加虚拟试妆功能时,也不用手动重构整体布局。
2. 核心原理与关键细节解析
2.1 Dlib人脸关键点定位的数学原理
Dlib的人脸关键点检测使用基于梯度增强学习的回归树集成方法,核心思路是逐级回归,从粗到细地逼近每个关键点的精确位置。整个过程分两个阶段:先用HOG特征检测人脸区域,再把人脸区域归一化到固定尺寸,然后使用一系列级联回归器预测关键点位置。
每个回归器都基于像素差异特征进行判断,即某个关键点在当前估计位置附近的某几个像素点之间的灰度值差,这个差值对位置偏移方向具有指示性。通过训练数据,模型学习了大量这样的弱分类器,组合后能精确预测嘴唇的轮廓点位置。理解这个原理对使用很有帮助:既然算法依赖灰度差,那么输入照片的光照均匀度就直接影响检测精度。实际使用中,背光、局部阴影严重的照片会导致唇部关键点定位偏移,这一点需要在UI层面增加提示,引导用户选择光线均匀的正面照片。
68点模型的坐标序号是固定的:0到16号是脸部轮廓线,17到26号是眉毛,27到35号是鼻子区域,36到47号是眼睛区域,48到67号是嘴唇区域。其中48到59号是外唇轮廓(12个点),60到67号是内唇轮廓(8个点)。编写代码时,用如下方式提取嘴唇坐标点:
import dlib import numpy as np # 外唇轮廓: 48-59, 内唇轮廓: 60-67 LIPS_OUTER_POINTS = list(range(48, 60)) LIPS_INNER_POINTS = list(range(60, 68)) ALL_LIPS_POINTS = LIPS_OUTER_POINTS + LIPS_INNER_POINTS detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") def extract_lips_points(gray_img): faces = detector(gray_img, 1) if len(faces) == 0: return None shape = predictor(gray_img, faces[0]) coords = np.zeros((68, 2), dtype=np.int32) for i in range(68): coords[i] = (shape.part(i).x, shape.part(i).y) return coords[ALL_LIPS_POINTS]2.2 TensorFlow色号分类模型的训练策略
色号分类模型本质上是一个图像分类任务,但和普通物体分类有本质区别:普通分类关注的是物体的结构特征(猫有耳朵、飞机有机翼),而口红色号分类关注的是颜色特征,且不同色号之间的差异往往很细微,比如豆沙色系的相邻色号肉眼几乎无法区分。
基于这个特点,训练阶段的策略做了三方面调整。第一,使用轻量级网络结构,最终选用MobileNetV2作为backbone,在ImageNet上预训练后迁移学习。颜色特征不需要太深的网络提取高维语义信息,浅层特征已经足够,MobileNetV2的参数量远小于ResNet50,推理速度快,打包体积也小。第二,输入图像上做专门的数据增强,在HSV空间随机扰动色相(H通道偏移量控制在±3度范围内)和饱和度(S通道缩放0.9~1.1倍),模拟不同屏幕显示和光线条件下的颜色差异。第三,定义合适的采样策略,训练集中如果同一种色号的样本数差异很大,训练时按类别进行加权采样,让数量少的色号不被淹没。
实际训练时,数据集按色号类别划分,每个类别至少50张标注图片,总共覆盖约120个常见色号,训练集、验证集、测试集按8:1:1划分。训练轮数设置20轮,早期训练使用学习率0.001,第10轮后衰减到0.0001。损失函数使用多分类交叉熵,优化器选择Adam。
其实在训练细节上,最影响效果的往往不是模型结构,而是数据标注质量。采集样本时,同一支口红不同厚涂薄涂的颜色差异巨大。我在标注时强制要求每张图片标注涂抹厚度标签,训练时把厚度作为辅助分类信息加入模型。这样推荐时能根据用户照片里唇色是厚涂还是薄涂来微调推荐结果,比如自然裸妆感的薄涂唇色就不会优先推荐饱和度过高的正红色。
2.3 PyQt5实时显示与线程管理
PyQt5的GUI界面有一个容易踩的坑:耗时操作如果在主线程执行,界面会卡死,表现为窗口无响应,严重时整个应用崩溃。人脸检测和模型推理都是耗时操作,单纯一张照片可能很快,但如果后续加上摄像头实时检测,帧处理耗时就不能忽视了。
设计上采用QThread工作者线程配合信号槽机制。主线程负责界面渲染和用户交互,后台线程执行人脸检测和模型推理,通过pyqtSignal把检测结果传回主线程。这样既保证界面流畅,也避免多个线程同时操作界面对象导致的时序问题。
from PyQt5.QtCore import QThread, pyqtSignal class InferenceThread(QThread): result_ready = pyqtSignal(dict) def __init__(self, image, model_pipeline, parent=None): super().__init__(parent) self.image = image self.pipeline = model_pipeline def run(self): result = self.pipeline.process(self.image) self.result_ready.emit(result)3. 实操过程与核心环节实现
3.1 环境配置与依赖版本避坑
先说环境。Python推荐3.9或3.10版本,TensorFlow在Python 3.11及以上版本存在兼容性问题;Dlib在Windows上安装需要C++编译环境;PyQt5的安装难度最低。整个环境的依赖清单和稳定版本组合我已经整理进requirements.txt,按文件一次安装即可。
需要特别强调的是,TensorFlow版本和CUDA/cuDNN的匹配关系非常严格,GPU版本配置不正确会直接报类似“无法加载DLL”的错误。对大部分本地运行场景,CPU版本其实够用。MobileNetV2模型在CPU上推理一张人脸照片的耗时约200到400毫秒,完全可以接受。如果想用GPU加速,建议严格对照TensorFlow官方版本表检查CUDA版本。
| 依赖库 | 推荐版本 | 说明 |
|---|---|---|
| Python | 3.9或3.10 | 避免3.11及以上版本 |
| tensorflow | 2.10或2.13 | CPU版即可满足需求 |
| dlib | 19.24.0 | 需C++编译环境或预编译whl |
| PyQt5 | 5.15.9 | 稳定版本 |
| opencv-python | 4.8.x | 图像IO与预处理 |
| numpy | 1.24.x | 数值计算 |
3.2 数据集构建与色号标注流程
数据集是这个项目里的重头戏,也是决定成败的关键,模型设计和参数调优做得再好,数据不行全都白费。我采用了“公开数据+自采数据”的混合方案:公开部分使用约8000张带唇色标签的人脸图片,自采部分用不同光线条件下拍摄的真人唇部照片补充约2000张,重点关注在暗光、暖黄光、冷白光下的颜色偏移。
自采部分的数据增强流程很关键,增强通过程序自动完成。原始图片先按唇部关键点对齐裁剪,缩放到224×224像素统一尺寸,然后做三组随机增强:第一组调整亮度(系数0.8~1.2),模拟不同环境光线;第二组调整白平衡(色温偏移±500K),模拟不同光源色温;第三组做轻微水平翻转和旋转(±5度),扩充样本多样性。增强后的数据集从约10000张扩展到了50000张,显著降低模型过拟合风险。
标注环节最考验耐心。120个色号,每个色号需要确保正负样本数量均衡。设计了一个专门的小工具,显示唇部裁剪图,标注色号ID的同时记录唇部掩膜和中心点坐标。标注过程中要特别注意的一种情况是:部分色号在不同肤色上的表现差异很大,深肤色上的正红色和浅肤色上的砖红色在视觉上可能很接近。因此每张样本标注时还要同步记录肤色区间,模型训练时把肤色信息也作为输入特征之一。
3.3 模型训练评估与调优记录
训练过程分两个阶段进行。第一阶段用ImageNet预训练的MobileNetV2冻结backbone,只训练最后的全连接分类头,学习率0.001,训练10轮,验证准确率在72%左右徘徊。这个阶段的作用是让分类头先适应色号分布的固有特征,收敛速度快。
第二阶段解冻backbone的后半部分,用0.0001的学习率整体微调,再训练10轮,验证准确率提升到88%左右。训练过程中的loss曲线走势稳定,没有明显过拟合迹象。测试集上的混淆矩阵显示,最常见,也是相对难解决的,是相邻色号之间的误判,比如豆沙色系里偏灰调和偏粉调的色号容易被混淆。针对这个问题,我给模型加了一个后处理逻辑:预测结果取Top3,结合肤色检测结果从Top3里挑最合适的色号返回,而不是直接返回置信度最高的那一个。这个微调之后,用户在主观评价中认为推荐结果“合理可用”的比例从71%提升到了86%,提升非常显著。
训练完成后的模型转换为TensorFlow Lite格式进一步压缩体积,从原来的约14MB压缩到约8MB,推理速度提升约30%,精度几乎没有损失。因为模型结构简单,转换过程中没有遇到需要手工修正算子的麻烦问题。
3.4 PyQt5界面功能实现细节
界面层我重点讲两个容易出问题的地方。第一个是图片展示区域的交互逻辑。用户上传图片后,要在同一个控件上完成三件事:显示原始照片、绘制关键点标注、叠加唇色提取结果的可视化色块。这个叠加绘制过程如果在主线程同步绘制,图片分辨率较高时会明显卡顿。我的做法是先用OpenCV完成所有绘制操作,生成最终效果图,再一次性传给QLabel显示,不在界面控件上做逐层叠加。
第二个是下拉框和超链接的问题。网络上有人在QComboBox的下拉框上遇到闪退问题,实测在5.15.9版本上也会出现,表现是点击下拉箭头时程序直接退出。排查后确认这是QComboBox的view容器在某些样式表配置下的兼容性bug。规避方案是:样式表中不设置下拉框内部的滚动条样式;如果遇到闪退,改用按钮QPushButton搭配QMenu弹层的方式实现下拉选择。
日志区还增加了富文本显示功能。处理进度用不同颜色区分:步骤开始为灰色、执行中为蓝色、完成为绿色、报错为红色。点击日志里的图片路径超链接可以直接打开对应的原始图片,方便排查问题。这个功能用QTextBrowser的anchorClicked信号配合QSslError处理实现,开发过程中没有遇到特殊障碍。
4. 常见问题与排查技巧实录
4.1 Dlib安装失败的三种解决路径
Dlib安装失败是几乎所有初次接触这个库的人都会碰到的问题。症状基本有两种:一是安装过程中卡住不动,最后报编译错误退出;二是直接报“Microsoft Visual C++ 14.0 is required”,提示缺少C++编译器。
针对这两种情况,解决办法按可行性排序如下:第一条路是安装VS Build Tools的C++桌面开发组件后再安装,这是微软官方推荐的路线,但下载安装包约2GB,耗时长;第二条路是直接寻找与Python版本对应的dlib预编译whl文件安装,可以跳过本地编译环节,安装速度快且成功率极高;第三条路是用conda从conda-forge频道安装,conda会自动处理依赖关系,不用手动安装编译工具。如果三条路都不顺利,还有一种终极大法:降低Python版本到3.7或3.8,再用pip安装dlib,基本不会报错。
4.2 TensorFlow DLL加载失败分析
热词里提到的“tensorflow dll diagnostic”是Windows环境下TensorFlow安装后最常见的报错,现象是import tensorflow时报错,提示某个DLL无法加载或者找不到指定的模块。这个问题90%的原因是缺少运行时依赖,TensorFlow依赖Visual C++ Redistributable,尤其是在Windows Server或精简版Windows系统上,经常没有预装这个运行库。
排查思路分三步走:先安装最新的Visual C++ Redistributable包,这个修复了绝大多数情况;如果问题还在,进一步确认Python架构版本,TensorFlow要求64位Python,32位Python会导致DLL加载失败;最后检查Python路径中是否存在多个TensorFlow安装副本,有时候不同虚拟环境之间的包混用会导致DLL冲突。需要单独说明的是:验证TensorFlow GPU版本是否正常工作,不能只看导入是否成功,要实际执行一个简单矩阵运算并确认相关GPU库被加载。
4.3 唇色提取偏色与推荐不准确问题
当嘴唇区域颜色提取结果和肉眼观察明显不一致时,问题几乎都出在图像处理前端。我遇到过三种典型情况:白色LED灯下的照片整体偏冷,提取出的唇色比实际颜色偏紫;餐厅暖黄灯光下的照片整体偏暖,唇色偏橘;手机开启美颜滤镜后,唇部颜色被柔和处理,饱和度降低,和真实色号差距很大。
针对偏色问题,需要在进入模型前的预处理阶段加入白平衡校正。基于灰度世界假设做白平衡:假设图像中所有颜色的平均值接近灰色,如果检测到整体色调有偏移,就计算各通道均值,把偏移量补偿回去。美颜照片问题在算法层面无法根治,只能在UI层面添加提示文案,建议用户上传无滤镜的原始照片。
推荐准确率同样受评分逻辑影响。系统返回的匹配度分数并非模型分类概率的简单归一化,而是融合了三个因素:模型Top1的置信度(权重0.5)、唇色与色号库中各色号平均颜色的距离相似度(权重0.3)、肤色适配度(权重0.2),这个评分公式是在多次用户调研后调整出来的。
4.4 PyQt5界面卡顿与内存泄漏排查
界面一次性加载大量高清图片时,内存占用激增甚至导致程序闪退的问题,排查后发现根因是:每张图片在显示前都转换为QPixmap对象,而QPixmap的底层位图存储非常消耗内存,大量图片没有及时释放导致内存持续增长。
解决方案是引入QPixmap的缩放机制,显示前先按显示区域大小等比例缩放图片再加载,而不是加载原图后让控件自动缩放;同时增加一个简单的LRU缓存,限制最多保留最近20张处理过的图片,超出部分手动清除底层数据。还有一个容易被忽略的点是:QLabel设置图片时,旧的QPixmap如果没有正确释放,也会产生内存累积。在切换到新图片前,先调用clear()方法清理旧内容。
5. 扩展方向与后续优化空间
这个项目完成后,扩展方向其实很多。最自然的下一步是接入摄像头实时检测,把静态照片换成动态视频流,增加关键点跟踪逻辑后可以实现实时唇色分析。摄像头场景下挑战不同,主要是帧率要求(需要保持更高帧率)、模糊帧处理(运动过程中容易出模糊帧)、以及不同角度下关键点定位稳定性。这些对Dlib来说难度不大,64点模型在单人脸场景下跟踪稳定性很好。
另一个值得考虑的方向是接入真实品牌色号库。当前系统的色号分类基于自建色号体系,与品牌专柜口红色号之间没有一一对应的映射关系。可以按热门品牌的经典色号扩充数据集,把模型输出层改为品牌色号ID,就能实现“某个品牌的某个色号最适合你”这种更直接的推荐。这部分工作工程量大但技术难度不高,主要是数据采集和标注的时间投入,严格按流程操作基本没有技术风险。
色号数据和模型都可以单独研究。如果想加入新的色号类别,只需要准备该色号的训练图片并运行训练脚本,程序里写好类别的注册逻辑,新增色号不会影响已有功能。当前模型还属于静态推荐的范畴,没有考虑季节、场合这些动态因素。后续可以把季节特征和场合标签加入训练特征,让推荐系统从“什么颜色适合你”进化到“当前场景下什么颜色更适合”,这也是这类系统下一步的主要演进方向。
最后再分享一个实用的小经验:这类桌面应用的模型文件和数据资源不要直接打包进代码目录,单独放在应用的同级目录下,首次启动时检查文件完整性,缺失时给出明确提示。这样模型升级时不需要重新发布整个应用程序,替换文件即可完成升级,实际维护会方便很多。
本文还有配套的精品资源,点击获取