基于Dlib与OpenCV的Python美颜系统:人脸关键点检测与图像处理实战
2026/9/10 3:40:46 网站建设 项目流程

简介:一套基于Python的人工智能美颜系统完整工程,面向图像处理、深度学习和Python应用开发者。项目整合OpenCV/PIL图像处理、CNN人脸关键点检测,以及磨皮、美白、平滑等美颜算法,可直接运行和二次开发。压缩包包含10个文件,主要有Python源码、界面定义、人脸关键点模型和说明文档,整体约70MB,并附有图标、启动脚本等辅助内容,便于研读和部署。截至目前已有2485人学习或下载,源码入口清晰,可跟踪从图像加载、特征定位到美颜渲染的完整流程。通过阅读代码,还能掌握高斯滤波、肤色检测、数据增强等实用技巧,并将思路迁移到实时美颜、虚拟化妆等场景。

1. 从人脸关键点到美颜系统:一套可复现的技术栈

这套基于 Python 的人工智能美颜系统,核心文件是AIMakeup.pyUi_MakupGUI.py和那个 99 MB 的shape_predictor_68_face_landmarks.dat。真正的美颜逻辑并不依赖 GPU 和大型 CNN,而是传统图像处理加一个人脸关键点模型:先用 Dlib 68 点模型定位眼睛、鼻子、嘴巴、下巴轮廓,再用 OpenCV 做磨皮、美白、瘦脸,最后用 PyQt5 把流程包装成带滑块和实时预览的桌面工具。这个组合在 2024 年看来依然非常实用,因为它把「需要重训练的部分」压缩到了极小,把「效果控制自由度」交给了算法参数。对于想入门 Python 图像处理、又不想从零训练人脸模型的开发者,这套源代码是很好的对照样本;对已经写过简单滤镜的工程师,里面磨皮顺序和瘦脸映射的参数选择也值得拆开看看。

2. 人脸关键点检测:68 点模型的加载与坐标提取

2.1 为什么选 Dlib 68 点而不是 OpenCV 自带人脸检测

OpenCV 自带的 Haar 或 DNN 人脸检测只能输出一个矩形框,能告诉你脸在哪,但不知道眼睛、鼻子、嘴在哪个像素。美颜算法要做的磨皮避开眼睛嘴巴、瘦脸只动脸颊、美白保护嘴唇,全部依赖像素级的五官位置。所以这套项目选择了 Dlib 的shape_predictor_68_face_landmarks.dat,它输出 68 个二维坐标点,点位的顺序是固定协议:0 号点在下颌左侧,16 号点在下颌右侧,27 号点在鼻梁起点,30 号点在鼻尖,48 到 59 号是外嘴唇。这个顺序和数量是模型训练时定义好的,不能随意换,所以后续所有区域分割都要以这张坐标表为基准。

AIMakeup.py里加载模型时用到的是 Dlib 自带的get_frontal_face_detector(),而不是重新写一个人脸检测器。这样做的原因是 Dlib 的检测器和同一个作者发布的关键点模型之间兼容性最好,检测框的地理位置直接喂给predictor,不需要做坐标归一化。下面这段是打开图片并提取 68 点的最小流程:

import dlib import cv2 import numpy as np detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") img_bgr = cv2.imread("im.png") img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) faces = detector(img_rgb, 1) if len(faces) == 0: print("没有检测到人脸") else: shape = predictor(img_rgb, faces[0]) pts = np.array([[p.x, p.y] for p in shape.parts()]) print(pts.shape) # (68, 2)

这段代码有两个关键点。detector(img_rgb, 1)的第二个参数是图像金字塔的上采样次数,填 1 表示对上采样一次,相当于把图像放大一倍再检测,能提高小脸的召回率,但速度会慢三倍左右。shape.parts()返回 68 个dlib.point对象,直接取xy转成 NumPy 数组,后续做掩膜和映射都基于这个数组。这里必须先把 OpenCV 读取的 BGR 图像转成 RGB,因为 Dlib 的人脸检测器内部是按 RGB 驱动的,直接传 BGR 虽然也能检测,但关键点定位精度会有肉眼可见的下降。

2.2 按语义区域索引分割面部器官

拿到 68 点之后,美颜算法需要的是「局部操作区域」。脸颊区域、眼睛区域、嘴巴区域在索引上是连续排列的,这是 Dlib 68 点协议的排列方式决定的。下表是这套系统里最常用的区域索引:

面部区域索引范围美颜用途
下颌轮廓0-16瘦脸边界判断
左眉17-21眉毛保护
右眉22-26眉毛保护
鼻梁与鼻翼27-35瘦脸拉向的中心轴
左眼36-41磨皮避开区域
右眼42-47磨皮避开区域
外嘴唇48-59美白保护区域
内嘴唇与牙齿60-67美白保护区域

在上述索引基础上,可以快速生成一张「脸部皮肤区域」掩膜,方法是取所有点做凸包,然后填充为白色。AIMakeup.py里常用的是cv2.convexHull配合cv2.fillConvexPoly

def create_face_mask(img_shape, pts): mask = np.zeros(img_shape[:2], dtype=np.uint8) hull = cv2.convexHull(pts) cv2.fillConvexPoly(mask, hull, 255) return mask

这里没有直接把整张小脸作为操作区域,而是先建立掩膜,原因是后续的磨皮和美白只应该作用在皮肤像素上,如果直接对整张图做卷积,头发、眉毛、眼睛边缘都会糊掉。实际使用时,还会在这个掩膜上减去嘴唇和眉毛的关键点凸包,让这些区域保持锐利。这个减法用的就是cv2.bitwise_and,把嘴唇区域掩膜取反后与皮肤掩膜做与运算。

3. 磨皮、美白、瘦脸:三大核心算法的 OpenCV 实现

3.1 磨皮:双边滤波加掩膜融合

磨皮最稳妥的算法不是高斯模糊,而是双边滤波。高斯模糊会把整张脸变成塑料感,而双边滤波在平滑的同时会根据像素颜色差异保留边缘,因此眼皮、鼻翼、发际线这些对比强烈的地方不容易被直接抹掉。OpenCV 中对应的实现是cv2.bilateralFilter,它有三个核心参数:d是每个像素参与滤波的邻域直径,sigmaColor控制多大颜色差会被视为边缘,sigmaSpace控制空间距离的影响范围。

def smooth_face(img, mask, strength=0.6): smooth = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75) # 保留20%原图细节,80%来自磨皮图,避免整张脸过于失真 blended = cv2.addWeighted(img, 1 - strength, smooth, strength, 0) return cv2.bitwise_and(blended, blended, mask=mask)

参数说明:d=9是速度和安全性的折中,如果设置到 15 以上,磨皮痕迹更弱但耗时明显增加,对 1080P 图片会卡到不可用。sigmaColor=75表示颜色差超过 75 的像素会被保留边缘,肤色内部差异通常只有 20 到 40,因此既能把痘印斑点磨掉,又不会把嘴唇边缘糊掉。addWeighted的第一系数1-strength是原图占比,第二系数是磨皮图占比,strength由 UI 上的滑块传入,调成 0.3 是轻微磨皮,调成 0.8 就是重度美颜。bitwise_and最后用掩膜把眼睛眉毛区域重新抹掉,这样原图和磨皮图只会在皮肤区域发生混合。

实际调试时要注意:这个方案对均匀肤色效果最好,如果脸上有大面积阴影,建议先把图像从 BGR 转到 Lab 色彩空间,只对 L 通道做双边滤波,再合并回 a 和 b 通道。这样能避免颜色边缘出现光晕,尤其适合逆光场景。

3.2 美白:YUV 亮度通道调整与嘴唇保护

美白不能直接增加 RGB 三个通道的像素值,那样会让人物皮肤发黄,同时把嘴唇的高光一起提亮,嘴唇看起来像涂了一层油。更合理的路线是转到 YUV 色彩空间,只提升 Y 通道的亮度,U 和 V 通道保持不变。因为 U/V 通道保存的是色度信息,亮度提升后皮肤会变通透,而不是单纯变白。

def whiten(img, amount=30): img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV) y, u, v = cv2.split(img_yuv) y = cv2.add(y, amount) y = cv2.convertScaleAbs(y) # 避免溢出后出现黑斑 img_yuv = cv2.merge([y, u, v]) white = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR) return white

amount是亮度增量,范围在 10 到 50 之间比较安全。cv2.add是饱和加运算,像素值最大 255,不会像 NumPy 直接加法那样回绕成 0。convertScaleAbs防止y通道在被add之后出现负数或 float 类型,保证后续merge不会报类型错误。

但纯亮度提升会连嘴唇一起变亮。所以在whiten内部一般再传入一个保护掩膜,用 2.2 节提到的嘴唇关键点区域构建白色区域,然后对三个通道做cv2.addWeighted混合。也可以只在 Y 通道上用蒙版做np.where,让嘴唇区域保持原来的 Y 值。效果上,蒙版过渡区最好做 5 像素的膨胀后高斯模糊,否则嘴周会出现一圈明显的白边。

3.3 瘦脸:基于映射表的局部像素平移

瘦脸是本项目里最像「人工智能」的部分,因为它用到了人脸关键点提供的位置约束,对图像做像素重映射。其原理可以理解成:以鼻梁中心为锚点,把脸颊区域内的像素往鼻子方向推,推的幅度由像素到锚点的距离决定,越靠近锚点移动越小,越靠近脸边缘移动越大,形成一个平滑的位移场。OpenCV 用cv2.remap做这一步,需要提前构造两个浮点型映射表map_xmap_y

def local_slim(img, center, radius, strength): h, w = img.shape[:2] map_x = np.zeros((h, w), dtype=np.float32) map_y = np.zeros((h, w), dtype=np.float32) cx, cy = center for y in range(h): for x in range(w): dx = cx - x dy = cy - y d = np.sqrt(dx * dx + dy * dy) if d < radius: factor = strength * (1 - d / radius) map_x[y, x] = x + dx * factor map_y[y, x] = y + dy * factor else: map_x[y, x] = x map_y[y, x] = y slim = cv2.remap(img, map_x, map_y, interpolation=cv2.INTER_LINEAR) return slim

这里center一般取鼻尖关键点 30 号的坐标,radius是影响半径,strength是收缩强度。对左脸,像素在锚点的左侧,dx是正数,所以该点会向右移动;对右脸,像素在锚点右侧,dx是负数,该点向左移动。最终结果是整个脸颊向内收敛。注意strength不能太大,0.02 到 0.05 之间变化就已经很明显了,超过 0.1 会让下颌线扭曲。实际使用时还要对左右脸分别设置蒙版,防止嘴巴和眼睛被拉斜。

3.4 主流程组合顺序

AIMakeup.py的处理函数大致是这个顺序:detect -> smooth -> whiten -> slim -> add_back_original。先平滑后美白,因为美白会放大毛孔和噪点,先磨皮可以让肤色区域更干净,美白后的画面不会有颗粒感。瘦脸放在最后,因为像素重映射会轻微破坏磨皮边缘的连续性,如果先瘦脸再磨皮,磨皮会重新抹掉重映射带来的细节断裂,效果反而更自然。

def process_frame(img, smooth_s=0.6, white=30, slim_s=0.03): pts = detect_landmarks(img) mask = create_face_mask(img.shape, pts) skin_mask = exclude_eyes_mouth(mask, pts) smooth = smooth_face(img, skin_mask, smooth_s) white_img = whiten(smooth, white) result = white_img lips_mask = get_lips_mask(img.shape, pts) nose_center = pts[30] result[:, :, 0] = np.where(lips_mask > 0, white_img[:, :, 0], result[:, :, 0]) left_slim = local_slim(result, nose_center, 80, slim_s) return left_slim

这里把嘴唇保护放到了美白之后,防止美白把嘴唇统一刷白。local_slim的函数内部使用双重循环,在实时预览时非常慢,所以源码在实际处理时会先把图像缩放到 480 x 360 再做映射,这一点在第四章会再展开。

4. PyQt5 界面封装:从.ui文件到摄像头实时美颜

4.1 用 pyuic5 把 MakeupGUI.ui 编译成 Ui_MakupGUI.py

压缩包里的MakeupGUI.ui是 Qt Designer 保存的界面文件,它描述窗口上有几个滑块、几个按钮、一个图片显示区,但 Python 代码不能直接运行它。标准做法是用pyuic5命令把.ui转换成可以 import 的 Python 模块:

pyuic5 -o Ui_MakupGUI.py MakeupGUI.ui

执行完成后会生成Ui_MakupGUI.py,文件里定义了一个Ui_MakeupGUI类,包含setupUi(self, MainWindow)方法。这个类只负责搭建控件,不处理业务逻辑。AIMakeup.py负责继承它,并给控件绑定事件。

class MakeupApp(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MakeupGUI() self.ui.setupUi(self) self.ui.slider_smooth.valueChanged.connect(self.on_smooth_change) self.ui.btn_open.clicked.connect(self.open_image)

slider_smooth的信号是valueChanged,Qt 会在滑块数值变化时自动触发on_smooth_change,该方法里读取当前滑块值并存入一个实例变量。这里没有直接把参数传给图像处理函数,原因是实时预览时图像处理函数可能正在后台跑,直接传参容易造成数据竞争。

4.2 图像显示与保存的格式转换

OpenCV 处理完的图像是 BGR 排列的 NumPy 数组,PyQt5 的QLabelQPixmap只认 RGB 或 BGRA。如果忘记转换,界面上的图片会蓝红互换。正确的转换方式是先用cv2.cvtColor转到 RGB,再构造QImage

def cv2_to_qpixmap(cv_img): rgb = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb.shape bytes_per_line = ch * w qimg = QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) return QPixmap.fromImage(qimg)

QImage的构造参数里bytes_per_line必须等于ch * w,对于 CV_8UC3 数组来说就是3 * w,如果图像数组不是连续内存,还要先调用np.ascontiguousarray,否则rgb.data指向的内存布局可能与 Qt 预期不一致,导致图片出现条纹。

4.3 摄像头实时美颜的 QTimer 调度

实时视频美颜和单张图片的差异在于帧率压力。源码采用QTimer定时器驱动,每隔 30 毫秒从摄像头取一帧,丢给process_frame处理,再把结果setPixmap到界面。这样做的好处是代码简单,不需要手动管理线程生命周期。核心代码:

self.cap = cv2.VideoCapture(0) self.timer = QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(30) def update_frame(self): ret, frame = self.cap.read() if not ret: return frame = cv2.resize(frame, (480, 360)) result = process_frame(frame, self.smooth, self.white, self.slim) pix = cv2_to_qpixmap(result) self.ui.label_preview.setPixmap(pix)

QTimer不是高精度定时器,它受消息循环影响,所以这里的 30 毫秒只是一个目标间隔。实际帧率会随算法的耗时浮动。把输入帧缩小到 480 x 360 之后,Dlib 检测和双边滤波的压力会大幅下降,但输出的预览图清晰度较低,需要在保存原图时再次调用process_frame处理未经缩小的图像,这一版源码采用的就是这种方法。

5. 性能优化与常见排错:让这套系统跑得更稳

5.1 先缩小检测,再放大坐标

Dlib 人脸检测器最耗时的部分是图像金字塔和滑动窗口在整幅图上搜索,直接把 1920 x 1080 的原图丢给detector,一帧可能要花 1.9 秒。常见做法是先把图缩小两倍,检测到人脸框后再把坐标放大回原图:

small_img = cv2.resize(img, (img.shape[1] // 2, img.shape[0] // 2)) face_rect = detector(small_img, 1)[0] face_rect = dlib.rectangle( face_rect.left() * 2, face_rect.top() * 2, face_rect.right() * 2, face_rect.bottom() * 2 ) shape = predictor(img, face_rect)

这样检测耗时能降到原来的四分之一以下,关键点坐标只要保证是原图坐标系即可,predictor本身并不要求接收到的人脸框必须来自原图检测器。

5.2 参数区间参考

磨皮、美白、瘦脸三者之间的参数会互相影响,更适合做成一组合适的默认值再由用户微调。下面是一组可以安全起手的参数:

算法参数推荐区间异常表现
磨皮d7 ~ 11过小无效果,过大脸呈蜡像
磨皮sigmaColor50 ~ 100过小磨皮不彻底,过大肤色断层
美白amount20 ~ 40超过 60 后眼睛高光发灰
瘦脸strength0.02 ~ 0.06超过 0.1 后下颌线扭曲
瘦脸radius60 ~ 120过大会让鼻子和嘴角变形

5.3 模型加载失败和 Dlib 警告

运行AIMakeup.py最常遇到的是RuntimeError: Error loading shape_predictor_68_face_landmarks.dat。原因是程序运行时当前工作目录不在.dat文件所在目录。可以把路径改成绝对路径,或者在__init__里先os.chdir(os.path.dirname(os.path.abspath(__file__))),让程序自动切换模块所在目录。此外 Dlib 在编译时会打印 NNPACK 未加速等警告,这是正常现象,不会影响运行。

5.4 把参数固化为配置文件

AIMakeup.py里参数写死在滑块初始值上,但不适合生产环境。更实用的做法是用 Python 内置的configparser保存用户调整后的设置,下次启动时直接恢复:

import configparser config = configparser.ConfigParser() config.read("makeup.ini") smooth = config.getfloat("param", "smooth", fallback=0.6) white = config.getint("param", "white", fallback=30) config.set("param", "smooth", str(smooth)) with open("makeup.ini", "w") as f: config.write(f)

这是这套源码里最容易被忽略的扩展点。把参数外置之后,根据不同相机、不同肤色给出多组预设,比在代码里反复改常量要可靠得多,也能让这套 Python 美颜系统从「能跑的演示工程」变成「可以交给别人调参的图形化工具」。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询