资讯动态

基于人脸识别的发型推荐系统:从脸型识别到推荐引擎实现

发布时间:2026/10/18 9:51:14 来源:尧图企业网站定制
拿到《基于人脸识别的发型推荐系统》这个题目时我第一反应是这不就是人脸识别 几个 if-else吗真正动手写代码才发现题目里最容易翻车的根本不是人脸识别这一步而是推荐两个字的落地逻辑——你凭什么判断一个人的脸适合哪款发型用什么特征来衡量这个特征选错了后面推荐得再花哨也是错的。这篇文章把我从选题到交付答辩的完整实现过程写出来包括架构设计、脸型识别算法、推荐规则引擎、Flask 交互界面以及拿 200 张真实照片测试时踩到的一堆坑。代码基于 Python核心用到了 OpenCV、MediaPipe 和 Flask。适合正在做课程设计、准备毕业设计或者单纯想做一个人脸 推荐demo 的同学参考——尤其适合不想只交一个玩具 demo、想把系统做扎实且答辩能讲明白的人。1. 从题目到可运行系统整体拆解与技术选型1.1 功能拆分识别、分类、推荐三个环节的真实工作量先泼一盆冷水如果你以为这个题目就是人脸识别 发型图片库答辩时大概率会被问到沉默。真正完整的需求链是这样的图像采集上传或摄像头拍照→ 人脸检测 → 人脸关键点定位 → 脸型特征提取与分类 → 发型库构建 → 推荐匹配 → 结果展示。这中间每一个箭头都是一段要写代码、要有独立逻辑的模块缺一个系统就只是能识别人脸的相册。我实际写完后的工作量占比大致是人脸检测 20%、关键点定位 10%用现成库、脸型特征与分类 30%、推荐规则引擎 25%、前端交互 15%。注意关键点定位虽然只占 10%但它决定了后面所有计算的精度是整个系统里最不能将就的一步——关键点偏一个像素后面的脸型比例就能差出一个分类级别。这个分配比例也提醒了选题的同学如果时间紧优先保分类 推荐这条主线检测环节能调库就别自己造轮子否则很容易把时间耗在检测精度上最后推荐逻辑反而很粗糙。1.2 技术选型为什么是 MediaPipe OpenCV Flask我对技术栈的要求很明确CPU 能跑、跨平台演示稳定、答辩时能讲清楚原理。下面是我对比过的几种常见方案。方案能拿到什么速度部署难度备注OpenCV Haar 级联人脸矩形框快简单拿不到脸型信息只能做检测dlib 68 点68 个关键点中中需要额外模型文件额头/下颌点偏少MediaPipe FaceMesh468 个关键点快简单额头、颧骨、下颌、下巴全覆盖我最终选用MTCNN框 5 点慢中关键点太少不适合脸型分析dlib 的 68 点其实也能做脸型分析但它的额头区域点覆盖不足而脸型分类恰恰最依赖额头—颧骨—下颌—下巴这条轮廓线的相对关系。MediaPipe 的 468 点里单是脸部轮廓就有上百个点取哪几个点算宽度、取哪几个点算下颌角选择空间大很多调试时舒服得多。前端我选了 Flask 单页 HTML没有上 PyQt也没有用 Gradio。原因很实际课程设计和毕业设计答辩都要求现场演示Flask 启动后打开浏览器就能用老师拿手机或平板也能访问比桌面窗口稳得多。Gradio 虽然几行代码就能出交互界面但太像 demo 了答辩时一旦被问你的前端逻辑是什么你只能回答框架生成的非常被动。自己写一个简单页面反而能完整展示前后端联调的能力。2. 脸型识别模块我用几何规则替代了深度学习分类器2.1 脸型五分类先定义问题再谈算法发型推荐的核心依据是脸型。国内发型设计语境里最常见的分法是椭圆/鹅蛋脸、圆脸、方脸国字脸、长脸、心形脸瓜子脸/倒三角有人还会单列出菱形脸。我最终采用五分类方案原因有两条第一五分类已经能覆盖市面上绝大多数发型推荐规则多分一类并不会让推荐结果显著变好第二每增加一个类别分类边界阈值就要重新标定课程设计周期内投入产出比很低。这一步很多人会忽略先想清楚分类输出是什么再决定算法。如果你上来就找脸型分类模型会发现公开可用的现成模型非常少——脸型标注本身带有很强的主观性一张脸在圆和方之间怎么标两个人可能给出不同答案。这也是我最终放弃深度学习方案的核心原因之一后面细说。2.2 四组几何特征的计算与含义脸型分类不直接看长什么样而是看几组比例关系。我基于 MediaPipe 的 FaceMesh 关键点计算了四组特征。先定义要用的关键点索引FaceMesh 标准索引KEY_POINTS { forehead_top: 10, # 额头最高点/发际线中点 forehead_left: 108, # 左侧额头边缘 forehead_right: 337, # 右侧额头边缘 cheek_left: 234, # 左颧骨外缘 cheek_right: 454, # 右颧骨外缘 jaw_left: 132, # 左下颌角 jaw_right: 361, # 右下颌角 chin_bottom: 152, # 下巴最低点 chin_left: 173, # 下巴左下缘 chin_right: 397, # 下巴右下缘 }四组特征分别是脸部长宽比hw_ratio额头顶点到下巴最低点的距离 ÷ 两颧骨外缘的距离。这个比例就像是照片的长宽比长脸的人这个值偏大圆脸的人偏小。下颌宽占比jaw_ratio左右下颌角宽度 ÷ 脸宽。国字脸的典型特征就是下颌角宽这个值会明显偏高。下巴轮廓比chin_ratio下巴左右缘宽度 ÷ 脸宽。心形脸、瓜子脸的下巴收得尖这个值偏小。额头宽占比forehead_ratio左右额头宽度 ÷ 脸宽。这个值主要不是用于脸型分类而是进入推荐引擎用来判断适不适合留刘海。计算代码很简单就是欧氏距离做除法import numpy as np def euclidean(p1, p2): return np.hypot(p1[0] - p2[0], p1[1] - p2[1]) def compute_features(points): face_h euclidean(points[forehead_top], points[chin_bottom]) face_w euclidean(points[cheek_left], points[cheek_right]) jaw_w euclidean(points[jaw_left], points[jaw_right]) chin_w euclidean(points[chin_left], points[chin_right]) forehead_w euclidean(points[forehead_left], points[forehead_right]) return { hw_ratio: face_h / face_w, jaw_ratio: jaw_w / face_w, chin_ratio: chin_w / face_w, forehead_ratio: forehead_w / face_w, }这里有个工程细节像素坐标用相对坐标x、y 都是 0~1 的归一化值即可不需要转像素绝对距离因为最终算的是比值归一化坐标不影响结果。手动应对 pose 变化时x/y 分别归一化反而能抵消一部分图像尺寸差异。2.3 分类规则、阈值标定以及为什么不用 CNN拿到四组比例后我用的分类规则是这样的def classify_face_shape(hw_ratio, jaw_ratio, chin_ratio): # 长脸脸的长度明显大于宽度 if hw_ratio 1.45: return long_face # 偏宽的脸再分方脸和圆脸 if hw_ratio 1.2: return square_face if jaw_ratio 0.85 else round_face # 中间区间下巴明显尖的归为心形脸 if chin_ratio 0.55: return heart_face return oval_face这几个阈值不是拍脑袋定的。我拿着小批量样本约 30 张正面、光照正常的标准脸照片反复调试每张照片同时做人工标注和系统预测人工标偏方的系统如果判成圆了就调 jaw_ratio 的阈值。最早我把方脸/圆脸的边界定在 0.8后来发现亚洲面孔里下颌角偏圆的比例比预想高调到 0.85 之后误判明显减少。为什么坚持用规则几何法而不是上 CNN 分类器三个原因每个都能在答辩时展开讲第一数据标注的一致性太差。脸型本身是个模糊概念圆脸和方脸之间、心形脸和鹅蛋脸之间的边界是渐变的让几个人标注同一批照片一致性很难超过 85%。拿这样的标签训练深度学习模型上限本来就不高。第二可解释性对于课程设计是加分项。长宽比 1.43超过 1.45 才判定为长脸所以这个人更接近中间形态——这种话答辩老师一听就懂而且能追问下去。换成 CNN你只能说模型输出的置信度是 0.62老师继续问为什么就很难接住。第三工程成本可控。规则法在普通 CPU 上运行完全够快不需要 GPU也不牵涉深度学习框架的版本兼容问题。当然如果你想做得更完整可以保留规则法作为 baseline再加一个小型卷积网络做兜底只在边界区间启用——比如 hw_ratio 落在 1.35~1.45 这种说不清的区域时让 CNN 投一票。这个方案作为答辩的进阶亮点非常合适后面扩展部分我还会提到。3. 推荐规则引擎把看着合适变成可计算的评分3.1 单靠脸型映射表为什么不够最原始的推荐逻辑是识别出脸型查表返回一组固定发型。这样做 demo 可以但产品逻辑太薄。原因很简单同是圆脸有人额头宽、有人额头窄有人下颌圆润、有人下颌偏方适合的发型完全不一样。只按脸型一个维度做推荐等于把所有消费者粗略分成五类推荐结果没有说服力。所以我做了一件事把发型师的经验拆解成可计算的特征约束。脸型决定大方向额头、下颌、下巴等细节决定具体款型用户偏好再往上叠加。这样推荐结果不但能给出哪款发型还能给出为什么适合你后者在演示和答辩时都是很好的素材。先说脸型与发型的大方向映射这是我的基础规则表脸型推荐思路尽量避免圆脸高颅顶增加纵向高度侧分/微卷遮挡两颊齐耳波波头、齐刘海贴脸方脸柔化下颌线条斜刘海、微卷、鬓角留发直发贴脸、长度卡在下巴附近长脸用刘海缩短脸部视觉长度增加两侧宽度中分贴头皮长发、高扎马尾心形脸侧分刘海收窄额头下巴附近增加发量中分服帖长发、头顶过于蓬松鹅蛋脸兼容性高五种长度均可尝试无明显禁忌这只是基础映射真正的推荐引擎还要叠加下面几个维度的规则。3.2 发型库的特征标签设计我把每一款发型抽象成一组结构化标签而不是简单的名字加图片。标签包括bangsnone无刘海/ side侧分/斜刘海/ full齐刘海/空气刘海lengthshort / medium / longcurlstraight / wavy / curlyvolumetop头顶蓬松/ crown后脑勺蓬松/ side两侧蓬松covers_cheek是否遮挡脸颊两侧suit每种脸型的推荐度取值 0~10举个例子高颅顶蓬松短发这条记录的标签是bangssidelengthshortcurlwavyvolumetopcovers_cheekTruesuit{round_face: 9, square_face: 7, long_face: 6, heart_face: 5, oval_face: 8}。这些材质数据是自己维护的初期 15~20 款就够用。注意每款发型的 suit 分数不要全给高分要有区分度否则推荐结果永远是同一批没有排序的意义。3.3 加权评分公式与代码实现最终推荐分数由四部分组成脸型适配 40 分、额头适配 25 分、轮廓修饰 15 分、用户偏好 20 分。我试过更复杂的权重最后发现加再多用户也感知不到这四档足够支撑一个课程设计。核心代码如下def recommend_hair(features, profile, top_k5): features: compute_features 的输出 face_type profile: {length: all/short/medium/long, curl: all/straight/wavy/curly} face_type features[face_type] scores {} for style in HAIRSTYLE_DB: score 0.0 # 1. 脸型适配分满分 40 suit style[suit].get(face_type, 5) score suit * 4.0 # 2. 额头适配分满分 25 if features[forehead_ratio] 0.72: # 额头偏宽优先有刘海 score 2.5 if style[bangs] in (side, full) else 0 elif features[forehead_ratio] 0.58: # 额头偏窄无刘海更显清爽 score 2.5 if style[bangs] none else 1.0 # 3. 轮廓修饰分满分 15 if features[jaw_ratio] 0.78 and style[covers_cheek]: score 1.5 if features[chin_ratio] 0.55 and style[volume] side: score 1.5 # 4. 用户偏好分满分 20 if profile[length] all or profile[length] style[length]: score 2.0 if profile[curl] all or profile[curl] style[curl]: score 2.0 scores[style[name]] round(score, 1) ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_k]这套逻辑的精髓在于推荐结果不是按脸型分类后等概率返回而是逐条打分排序。哪怕两个人的脸型判定结果相同只要额头比例或下颌宽度不同推荐排序就会发生变化。你甚至可以在界面上展示每个推荐理由比如高颅顶蓬松短发适合你能纵向拉长脸型且你的额头较宽适合侧分修饰答辩时这一条就很有说服力。4. 代码落地的关键路径从图片输入到前端展示4.1 人脸分析管线与特征计算把前面的模块串起来整个系统的核心管线就是读图 → 关键点提取 → 特征计算 → 脸型分类 → 推荐排序。人脸分析部分我封装成一个函数输入 RGB 图像输出关键点字典import cv2 import mediapipe as mp mp_face_mesh mp.solutions.face_mesh def analyze_face(image_rgb): with mp_face_mesh.FaceMesh( static_image_modeTrue, max_num_faces1, refine_landmarksFalse ) as face_mesh: result face_mesh.process(image_rgb) if not result.multi_face_landmarks: return None landmarks result.multi_face_landmarks[0].landmark points {} for name, idx in KEY_POINTS.items(): lm landmarks[idx] points[name] (lm.x, lm.y) return points注意static_image_modeTrue这个参数。MediaPipe 默认是按视频流优化的模式会做帧间时序平滑处理单张图片时反而会有延迟感。改成静态图像模式后单张图片处理速度更快也避免了一些时序状态残留问题。这是一个容易被忽略、但实测影响很明显的细节。整体流程可以用一个入口函数串起来def run_pipeline(image_bgr, profile): image_rgb cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) points analyze_face(image_rgb) if points is None: return {error: 未检测到人脸请正对摄像头重试} features compute_features(points) features[face_type] classify_face_shape( features[hw_ratio], features[jaw_ratio], features[chin_ratio] ) recs recommend_hair(features, profile) return { face_type: features[face_type], features: {k: round(v, 3) for k, v in features.items() if isinstance(v, float)}, recs: recs, }这里还有个小细节返回的face_type建议同时给一个中文映射前端直接展示不要让前端再去查字典前后端耦合越少越稳。4.2 Flask 接口与前端页面后端只需要两个路由一个渲染页面一个接收图片返回 JSON。from flask import Flask, render_template, request, jsonify app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/recommend, methods[POST]) def recommend(): file request.files.get(image) if file is None: return jsonify({error: no image}), 400 data np.frombuffer(file.read(), np.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) profile { length: request.form.get(length, all), curl: request.form.get(curl, all), } result run_pipeline(img, profile) if error in result: return jsonify(result), 422 return jsonify(result) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)前端页面我用的是单页 HTML 原生 JavaScript页面布局很简单左侧是图片上传区和偏好选择右侧是结果卡片区。重点放在两个交互用input typefile acceptimage/*支持从相册选图captureenvironment可以唤起后置摄像头直接拍照用FormData和fetch调用接口整个过程不需要刷新页面。这段 JavaScript 很常规就不完整贴了唯一想提醒的是接口返回的 features 数值要保留两到三位小数前端展示长宽比 1.38这类信息时更专业也能给答辩增加细节。4.3 摄像头实时模式的一个体验细节如果你的设计说明书里写了支持实时检测那就难免要做摄像头模式。这里有个我从实测中体验出的经验实时推荐千万不要逐帧刷新结果。原因很直观摄像头里人稍微动一下脸型分类结果可能就在圆脸和鹅蛋脸之间跳来跳去推荐的发型列表也跟着频繁变化用户根本看不清体验非常差。我的做法是摄像头画面只做实时框选人脸不做实时推荐用户点一下检测当前画面按钮再取当前帧跑整条管线。这样既保留了实时感又避免了结果闪烁。5. 200 张真实照片实测后踩到的四个坑5.1 测试集怎么组织才有说服力课程设计最容易被人质疑的就是你的数据是不是挑过的。所以我从一开始就决定做一次有组织的测试找了几位同学、同事帮忙拍了 200 张照片分成四组——100 张正面标准光照50 张戴眼镜30 张轻微侧脸偏转角度 15 度以内20 张有刘海或帽檐遮挡。每组照片都先人工标注脸型再与系统输出对比。这套测试数据最后成为我设计文档里最有说服力的一张表答辩时老师看完整个人都放心了。5.2 踩坑一眼镜导致下颌关键点漂移戴眼镜组准确率掉到 84% 左右翻车点主要在下颌。现象是镜框边缘干扰了下颌角关键点的定位导致 jaw_ratio 突然变大长宽比又被压缩个别圆脸直接被误判成方脸。我的处理方案是加一道合理性校验如果 jaw_ratio 算出来超过 0.95正常亚洲面孔很少这么夸张基本可以断定关键点漂移此时降低方脸判定权重并在返回值里带一个 warnings 字段前端提示检测结果可能受眼镜影响建议摘下眼镜重试。5.3 踩坑二刘海/帽檐让额头关键点失效刘海和帽檐遮挡组的准确率只有 60%根源在于 forehead_top额头最高点即点 10不可靠。额头被盖住时MediaPipe 会把刘海边缘误认为额头上沿导致 face_h 变小、hw_ratio 被压下来长脸被误判成圆脸。这个问题的处理要分两层。第一层是提示当检测结果落在边界区间时返回 hint 字段告诉用户露出额头可提高准确率。第二层是兜底如果额头点明显异常可以用眉弓到下巴的距离乘以 1.35 来近似全脸高度。这个系数是我从小批量样本里拟合出来的不算精致但作为兜底足够让分类不至于完全跑飞。5.4 踩坑三侧脸和姿态角让宽度比例失真轻微侧脸组成准确率掉到 73%。原因很好理解脸一偏两颧骨的连线不再垂直于视线测出来的脸宽偏小hw_ratio 会虚高圆脸容易被误判成长脸。我没有硬扛这个问题而是选择前置拦截用两只眼睛的关键点估算头部偏转角度如果偏转角超过约 15 度就直接返回请正对摄像头不给不靠谱的分类结果。宁可让用户重新拍也不要输出一个错误的脸型——这一点在答辩时要重点讲它体现的是你理解系统的失效边界。5.5 实测准确率统计测试分组样本数脸型判定准确率主要误差来源正面标准光照10092%圆脸/方脸边界、鹅蛋脸/心形脸边界戴眼镜5084%下颌角关键点漂移轻微侧脸15 度以内3073%脸宽测量失真刘海/帽檐遮挡2060%额头关键点不可靠准确率的定义是系统判定结果与人工标注一致的比例。这个表放在设计文档的测试章节里直接就能支撑结论系统在正向、无遮挡条件下可用在遮挡和姿态变化条件下需要引导用户。6. 交付物清单、答辩要点与后续扩展思路6.1 交一份能打的项目至少包含这几样课程设计/毕业设计最后交付的不只是代码。按我这次的经验完整的交付物应该是可运行源码包含 README写清楚 Python 版本、依赖安装命令、启动方式。用 requirements.txt 锁版本MediaPipe 和 OpenCV 的版本坑很多。测试数据与统计结果哪怕只有几十张照片也要有组织地分成条件组。上面那张准确率统计表比任何口头描述都有说服力。设计文档至少包含架构模块说明、算法原理、关键代码说明、测试分析与结论四部分。架构图可以用文本或画图工具绘制但不建议用流程图画得太复杂一张清晰的模块图就够。演示视频3 分钟以内覆盖上传照片→返回推荐→调整偏好→重新排序这个完整流程。现场设备出问题时这段视频是救命稻草。6.2 答辩现场演示的节奏安排现场演示最容易翻车的地方不是代码而是现场网络和光线。我的建议是先演正面标准光照的上传照片流程再现场拍一张展示系统对真实摄像头输入的鲁棒性。如果现场拍出来分类不如预期就主动讲出原因——比如这位同学刘海遮挡了额头上沿我们系统会提示露出额头把踩坑经验变成演示亮点。另外一定要提前在演示前把浏览器缓存里的旧版本清掉避免把改了一半的代码暴露给老师。这个小坑我身边不止一个人踩过。6.3 后续扩展方向与我的个人体会这个系统想继续做深方向其实很多加一个年龄识别模型给不同年龄段做差异化推荐加肤色检测把推荐从发型扩展到发色重量级的可以引入生成对抗网络做发型试戴让用户直接在照片上看到效果——那已经是另外一个项目的工作量了适合作为毕业设计的进阶命题。移动端部署则可以考虑把模型转换成轻量化格式塞进手机应用里。最后说点个人体会。做这个项目最大的收获不是我学会了 MediaPipe 或 Flask而是想明白了一个道理推荐的本质是把隐性经验变成显性规则。发型师一眼看出这个脸型适合留长刘海背后藏着的其实是额头宽度、颧骨位置、下颌角大小这些可测量特征的组合判断。写系统的人要做的事情就是把这些说不清道不明的审美判断翻译成代码。只要这一步想通了这类识别 推荐的项目怎么做都不会跑偏。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑