美颜(磨皮,大眼)opencv python实现

本文是利用opencv python 的美颜(磨皮,大眼)实现。

1 磨皮

1.1 导向滤波

磨皮使用的是导向滤波进行磨皮。关于导向滤波的介绍,可以看我的另一篇文章导向滤波与opencv python实现

2.2 椭圆肤色模型

可以先使用椭圆肤色模型对皮肤部分进行提取,得到掩膜数组,然后利用位运算将滤波后的图像与原图结合,这样就做到了对背景的保护。

def YCrCb_ellipse_model(img):
    skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
    cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #绘制椭圆弧线
    YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间
    (Y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
    skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜
    (x,y) = Cr.shape
    for i in range(0, x):
        for j in range(0, y):
            if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在椭圆区间中
                skin[i][j] = 255
    res = cv2.bitwise_and(img,img, mask = skin)
    return skin,res

可以对掩膜数组进行一次开运算,消除细小区域。

def mopi(img):
    skin,_ = ellipse_skin_model.YCrCb_ellipse_model(img)#获得皮肤的掩膜数组
    #进行一次开运算
    kernel = np.ones((3,3),dtype=np.uint8)
    skin = cv2.erode(skin,kernel=kernel)
    skin = cv2.dilate(skin,kernel=kernel)
    img1 = guided_filter(img/255.0,img/255.0,10,eps=0.001)*255
    img1 = np.array(img1,dtype=np.uint8)
    img1 = cv2.bitwise_and(img1,img1,mask=skin)#将皮肤与背景分离
    skin = cv2.bitwise_not(skin)
    img1 = cv2.add(img1,cv2.bitwise_and(img,img,mask=skin))#磨皮后的结果与背景叠加
    fig, ax = plt.subplots(1, 2)
    # ax[0].imshow(skin)
    # ax[1].imshow(img1[:, :, ::-1])
    # plt.show()
    return img1

得到的结果如下图
在这里插入图片描述

2 人脸关键点提取

在美颜中,要想进行大眼,瘦脸等操作,需要首先提取出人脸的关键点才可以做坐标变换。
这里,使用了Google的mediapipe进行检测。一般来说应该使用dlib的,但我折腾一个小时没装上。。。
在这里插入图片描述

import cv2
import mediapipe as mp
mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils
def get_face_key_point(img):
  with mp_face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.5) as face_detection:
    results = face_detection.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    if not results.detections:
      return None
    annotated_image = img.copy()
    r,w,c =img.shape
    for detection in results.detections:
      left_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.LEFT_EYE)
      right_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.RIGHT_EYE)
      left_eye_pos = [int(left_eye.x * w), int(left_eye.y * r)]
      right_eye_pos = [int(right_eye.x * w), int(right_eye.y * r)]
      return  left_eye_pos,right_eye_pos

3 大眼​

3.1 原理

这里参考了论文Interactive Image Warping的局部缩放算法。
局部缩放公式:
在这里插入图片描述
Alt
由上公式可得,当 r r r等于 r m a x r_{max} rmax时,该式等于 r r r

如果 a > 0 a>0 a>0,即r乘上一个0~1的数,即变换后的点相较于变换前的点相对于圆心外扩了。也就是局部放大。
如果 a < 0 a<0 a<0,即r乘上一个大于1的数,即变换后的点相较于变换前的点相对于圆心内缩了。也就是局部缩小。

根据上述公式,我们可以得到一个原图与输出图的像素坐标映射,通过双线性插值可以得到输出图的对应像素值。

3.2 代码

局部放大函数:

def Local_scaling_warps(img,cx,cy,r_max,a):
    img1 = np.copy(img)
    for y in range(cy-r_max,cy+r_max+1):
        d = int(math.sqrt(r_max**2-(y-cy)**2))
        x0 = cx-d
        x1 = cx+d
        for x in range(x0,x1+1):
            r = math.sqrt((x-cx)**2 + (y-cy)**2)
            for c in range(3):
                if r<=r_max:
                    vector_c = np.array([cx, cy])
                    vector_r =np.array([x,y])-vector_c
                    f_s = (1-((r/r_max-1)**2)*a)
                    vector_u = vector_c+f_s*vector_r#原坐标
                    img1[y][x][c] = bilinear_interpolation(img,vector_u,c)
    return img1

双线性插值:

def bilinear_interpolation(img,vector_u,c):
    ux,uy=vector_u
    x1,x2 = int(ux),int(ux+1)
    y1,y2 = int(uy),int(uy+1)

    # f_x_y1 = (x2-ux)/(x2-x1)*img[x1][y1]+(ux-x1)/(x2-x1)*img[x2][y1]
    # f_x_y2 = (x2 - ux) / (x2 - x1) * img[x1][y2] + (ux - x1) / (x2 - x1) * img[x2][y2]

    f_x_y1 = (x2-ux)/(x2-x1)*img[y1][x1][c]+(ux-x1)/(x2-x1)*img[y1][x2][c]
    f_x_y2 = (x2 - ux) / (x2 - x1) * img[y2][x1][c] + (ux - x1) / (x2 - x1) * img[y2][x2][c]

    f_x_y = (y2-uy)/(y2-y1)*f_x_y1+(uy-y1)/(y2-y1)*f_x_y2
    return int(f_x_y)

4 简单界面

这里利用pyqt5简单设计了一个界面。
其中下方的两个滑块分别对应放缩的系数 a a a和放缩范围 r r r
左边显示的是原图,右边是处理后的图像。可见小姐姐皮肤变好了,眼睛也变大了。
在这里插入图片描述
将参数稍微调下,会变得有点阴间哈哈哈。
在这里插入图片描述

# -*- coding: utf-8 -*-
# @Time : 2022/10/14 19:05
# @Author : shuoshuo
# @File : main.py
# @Project : 美颜
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import *
from PyQt5.QtCore  import Qt
from PyQt5.QtGui import QPixmap, QImage
from PIL import Image,ImageQt
from qtpy import QtGui
import beautify
import face_detect
class My_win(QWidget):

    def __init__(self,img):
        super().__init__()

        self.initUI(img)


    def initUI(self,img):
        self.r_max = 40
        self.a = 0.5
        self.left_eye_pos,self.right_eye_pos = face_detect.get_face_key_point(img)

        self.img = img.copy()
        
        self.mopi_btn = QPushButton("磨皮",self)
        self.big_eye_btn  = QPushButton("大眼",self)
        self.save_btn = QPushButton("保存",self)
        
        self.a_slider =QSlider(Qt.Horizontal)
        self.a_slider.setRange(1,99)
        self.a_slider.setSingleStep(5)
        self.a_slider.setToolTip('大眼程度')


        self.r_slider = QSlider(Qt.Horizontal)
        self.r_slider.setRange(1,99)
        self.r_slider.setSingleStep(5)
        self.r_slider.setToolTip('大眼范围')


        self.r_slider.valueChanged.connect(self.r_change)
        self.a_slider.valueChanged.connect(self.a_change)
        self.big_eye_btn.clicked.connect(self.big_eye)
        self.mopi_btn.clicked.connect(self.mopi)
        self.save_btn.clicked.connect(self.save)

        self.original_pic = QLabel(self)
        self.result_pic = QLabel(self)

        self.button_box = QHBoxLayout()
        # self.button_box.addStretch(1)
        self.button_box.addWidget(self.mopi_btn)
        self.button_box.addWidget(self.big_eye_btn)
        self.button_box.addWidget(self.save_btn)

        self.slider_box = QHBoxLayout()
        self.slider_box.addWidget(self.a_slider)
        self.slider_box.addWidget(self.r_slider)
        
        self.hbox  = QHBoxLayout()
        self.hbox.addStretch(1)
        self.hbox.addWidget(self.original_pic)
        self.hbox.addWidget(self.result_pic)

        self.vbox = QVBoxLayout()
        self.vbox.addStretch(1)
        self.vbox.addLayout(self.button_box)
        self.vbox.addLayout(self.slider_box)
        self.vbox.addLayout(self.hbox)

        self.setLayout(self.vbox )

        self.show_img(img,frame=self.original_pic)
        self.show_img(self.img,frame=self.result_pic)
        # self.setGeometry(100,100,800,600)
        self.setWindowTitle("拉闸美颜")
        self.show()

    def show_img(self,img,frame):
        r,w,c = img.shape
        image = QtGui.QImage(img.data, w, r,w*3, QtGui.QImage.Format_RGB888).rgbSwapped()

        frame.setPixmap(QtGui.QPixmap.fromImage(image))
        # frame.setGeometry(pos[0],pos[1],r,w)
    def big_eye(self):
        self.result = beautify.big_eye(self.img,r_max=self.r_max,a=self.a,
                                           left_eye_pos=self.left_eye_pos,right_eye_pos=self.right_eye_pos)
        self.result = np.array(self.result, dtype=np.uint8)
        self.show_img(self.result, frame=self.result_pic)
    def mopi(self):
        self.img = beautify.mopi(self.img)
        # self.img = np.array(self.img*255,dtype=np.uint8)
        self.show_img(self.img, frame=self.result_pic)
    def a_change(self):
        self.a = self.a_slider.value()/100
        self.big_eye()
    def r_change(self):
        self.r_max = self.r_slider.value()
        self.big_eye()
    def save(self):
        cv2.imwrite('output.jpg',self.result)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    img = cv2.imread('img.png')
    # img = cv2.resize(img,(400,300))
    ex = My_win(img)
    sys.exit(app.exec_())

4 完整代码

# -*- coding: utf-8 -*-
# @Time : 2022/10/5 22:48
# @Author : shuoshuo
# @File : beautify.py
# @Project : 美颜
import math

import cv2
import numpy as np
import matplotlib.pyplot as plt
import face_detect
import ellipse_skin_model
def bilinear_interpolation(img,vector_u,c):
    ux,uy=vector_u
    x1,x2 = int(ux),int(ux+1)
    y1,y2 = int(uy),int(uy+1)

    # f_x_y1 = (x2-ux)/(x2-x1)*img[x1][y1]+(ux-x1)/(x2-x1)*img[x2][y1]
    # f_x_y2 = (x2 - ux) / (x2 - x1) * img[x1][y2] + (ux - x1) / (x2 - x1) * img[x2][y2]

    f_x_y1 = (x2-ux)/(x2-x1)*img[y1][x1][c]+(ux-x1)/(x2-x1)*img[y1][x2][c]
    f_x_y2 = (x2 - ux) / (x2 - x1) * img[y2][x1][c] + (ux - x1) / (x2 - x1) * img[y2][x2][c]

    f_x_y = (y2-uy)/(y2-y1)*f_x_y1+(uy-y1)/(y2-y1)*f_x_y2
    return int(f_x_y)
def Local_scaling_warps(img,cx,cy,r_max,a):
    img1 = np.copy(img)
    for y in range(cy-r_max,cy+r_max+1):
        d = int(math.sqrt(r_max**2-(y-cy)**2))
        x0 = cx-d
        x1 = cx+d
        for x in range(x0,x1+1):
            r = math.sqrt((x-cx)**2 + (y-cy)**2) #求出当前位置的半径
            for c in range(3):
                vector_c = np.array([cx, cy])
                vector_r =np.array([x,y])-vector_c
                f_s = (1-((r/r_max-1)**2)*a)
                vector_u = vector_c+f_s*vector_r#原坐标
                img1[y][x][c] = bilinear_interpolation(img,vector_u,c)
    return img1

def big_eye(img,r_max,a,left_eye_pos=None,right_eye_pos=None):
    img0 = img.copy()
    if left_eye_pos==None or right_eye_pos==None:
        left_eye_pos,right_eye_pos=face_detect.get_face_key_point(img)
    img0 = cv2.circle(img0,left_eye_pos,radius=10,color=(0,0,255))
    img0 = cv2.circle(img0,right_eye_pos,radius=10,color=(0,0,255))
    img= Local_scaling_warps(img,left_eye_pos[0],left_eye_pos[1],r_max=r_max,a=a)
    img = Local_scaling_warps(img,right_eye_pos[0],right_eye_pos[1],r_max=r_max,a=a)
    return img
def guided_filter(I,p,win_size,eps):
    assert I.any() <=1 and p.any()<=1
    mean_I = cv2.blur(I,(win_size,win_size))
    mean_p = cv2.blur(p,(win_size,win_size))

    corr_I = cv2.blur(I*I,(win_size,win_size))
    corr_Ip = cv2.blur(I*p,(win_size,win_size))

    var_I = corr_I-mean_I*mean_I
    cov_Ip = corr_Ip - mean_I*mean_p

    a = cov_Ip/(var_I+eps)
    b = mean_p-a*mean_I

    mean_a = cv2.blur(a,(win_size,win_size))
    mean_b = cv2.blur(b,(win_size,win_size))

    q = mean_a*I + mean_b

    return q
def mopi(img):
    skin,_ = ellipse_skin_model.YCrCb_ellipse_model(img)#获得皮肤的掩膜数组
    #进行一次开运算
    kernel = np.ones((3,3),dtype=np.uint8)
    skin = cv2.erode(skin,kernel=kernel)
    skin = cv2.dilate(skin,kernel=kernel)
    img1 = guided_filter(img/255.0,img/255.0,10,eps=0.001)*255
    img1 = np.array(img1,dtype=np.uint8)
    img1 = cv2.bitwise_and(img1,img1,mask=skin)#将皮肤与背景分离
    skin = cv2.bitwise_not(skin)
    img1 = cv2.add(img1,cv2.bitwise_and(img,img,mask=skin))#磨皮后的结果与背景叠加
    # fig, ax = plt.subplots(1, 2)
    # ax[0].imshow(skin)
    # ax[1].imshow(img1[:, :, ::-1])
    # plt.show()
    return img1
if __name__ == '__main__':

    img = cv2.imread('img.png')

    img1 = mopi(img)
    big_eye(img1,r_max=40,a=0.8)


# -*- coding: utf-8 -*-
# @Time : 2022/10/18 21:54
# @Author : shuoshuo
# @File : ellipse_skin_model.py
# @Project : 美颜
import numpy as np
import cv2


def YCrCb_ellipse_model(img):
    skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
    cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #绘制椭圆弧线
    YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间
    (Y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
    skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜
    (x,y) = Cr.shape
    for i in range(0, x):
        for j in range(0, y):
            if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在椭圆区间中
                skin[i][j] = 255
    res = cv2.bitwise_and(img,img, mask = skin)
    return skin,res
if __name__ == '__main__':
    pass
import cv2
import mediapipe as mp
mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils

def get_face_key_point(img):
  with mp_face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.5) as face_detection:
    results = face_detection.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    if not results.detections:
      return None
    annotated_image = img.copy()
    r,w,c =img.shape
    for detection in results.detections:
      left_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.LEFT_EYE)
      right_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.RIGHT_EYE)
      left_eye_pos = [int(left_eye.x * w), int(left_eye.y * r)]
      right_eye_pos = [int(right_eye.x * w), int(right_eye.y * r)]
      return  left_eye_pos,right_eye_pos
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 19:05
# @Author : shuoshuo
# @File : main.py
# @Project : 美颜
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import *
from PyQt5.QtCore  import Qt
from PyQt5.QtGui import QPixmap, QImage
from PIL import Image,ImageQt
from qtpy import QtGui
import beautify
import face_detect
class My_win(QWidget):

    def __init__(self,img):
        super().__init__()

        self.initUI(img)


    def initUI(self,img):
        self.r_max = 40
        self.a = 0.5
        self.left_eye_pos,self.right_eye_pos = face_detect.get_face_key_point(img)

        self.img = img.copy()
        
        self.mopi_btn = QPushButton("磨皮",self)
        self.big_eye_btn  = QPushButton("大眼",self)
        self.save_btn = QPushButton("保存",self)
        
        self.a_slider =QSlider(Qt.Horizontal)
        self.a_slider.setRange(1,99)
        self.a_slider.setSingleStep(5)
        self.a_slider.setToolTip('大眼程度')


        self.r_slider = QSlider(Qt.Horizontal)
        self.r_slider.setRange(1,99)
        self.r_slider.setSingleStep(5)
        self.r_slider.setToolTip('大眼范围')


        self.r_slider.valueChanged.connect(self.r_change)
        self.a_slider.valueChanged.connect(self.a_change)
        self.big_eye_btn.clicked.connect(self.big_eye)
        self.mopi_btn.clicked.connect(self.mopi)
        self.save_btn.clicked.connect(self.save)

        self.original_pic = QLabel(self)
        self.result_pic = QLabel(self)

        self.button_box = QHBoxLayout()
        # self.button_box.addStretch(1)
        self.button_box.addWidget(self.mopi_btn)
        self.button_box.addWidget(self.big_eye_btn)
        self.button_box.addWidget(self.save_btn)

        self.slider_box = QHBoxLayout()
        self.slider_box.addWidget(self.a_slider)
        self.slider_box.addWidget(self.r_slider)
        
        self.hbox  = QHBoxLayout()
        self.hbox.addStretch(1)
        self.hbox.addWidget(self.original_pic)
        self.hbox.addWidget(self.result_pic)

        self.vbox = QVBoxLayout()
        self.vbox.addStretch(1)
        self.vbox.addLayout(self.button_box)
        self.vbox.addLayout(self.slider_box)
        self.vbox.addLayout(self.hbox)

        self.setLayout(self.vbox )

        self.show_img(img,frame=self.original_pic)
        self.show_img(self.img,frame=self.result_pic)
        # self.setGeometry(100,100,800,600)
        self.setWindowTitle("拉闸美颜")
        self.show()

    def show_img(self,img,frame):
        r,w,c = img.shape
        image = QtGui.QImage(img.data, w, r,w*3, QtGui.QImage.Format_RGB888).rgbSwapped()

        frame.setPixmap(QtGui.QPixmap.fromImage(image))
        # frame.setGeometry(pos[0],pos[1],r,w)
    def big_eye(self):
        self.result = beautify.big_eye(self.img,r_max=self.r_max,a=self.a,
                                           left_eye_pos=self.left_eye_pos,right_eye_pos=self.right_eye_pos)
        self.result = np.array(self.result, dtype=np.uint8)
        self.show_img(self.result, frame=self.result_pic)
    def mopi(self):
        self.img = beautify.mopi(self.img)
        # self.img = np.array(self.img*255,dtype=np.uint8)
        self.show_img(self.img, frame=self.result_pic)
    def a_change(self):
        self.a = self.a_slider.value()/100
        self.big_eye()
    def r_change(self):
        self.r_max = self.r_slider.value()
        self.big_eye()
    def save(self):
        cv2.imwrite('output.jpg',self.result)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    img = cv2.imread('img.png')
    # img = cv2.resize(img,(400,300))
    ex = My_win(img)
    sys.exit(app.exec_())

5 参考文献

Interactive Image Warping
Guided Image Filtering

猜你喜欢

转载自blog.csdn.net/qtzbxjg/article/details/127311842