数字图像处理-美图秀秀:大眼算法

简介

本项目是以matlab为主语言并设计GUI界面的一款简易美图秀秀,包含基础的图像处理和一些常见美颜算法

对于一些matlab较难实现的算法采用C++或python来实现

⭐️ github地址:https://github.com/mibbp/MeituShow

里面有我完整的代码,你想直接运行记得看readme配置一下环境,本博客更多的是讲解原理

具体功能包括:

  • 增加图像亮度,对比度
  • 美白人像
  • 采用双边滤波算法磨皮
  • 采用液化算法并用dlib提取特征点实现瘦脸
  • 基于液化算法并用dlib提取特征点实现大眼
  • 采用dlib提取特征点,采用Andrew求凸包并用BFS实现唇彩
  • 采用SRCNN超分辨率算法实现提升照片像素
  • 采用Beauty-GAN算法实现彩妆迁移

大眼算法

这里和上一个瘦脸算法是一个东西其实,所以就不过多去讲原理了,就是逆变换了一下,就瘦脸我们说的是一个范围他往里收缩,越靠近中心收缩强度越大,越靠近边界越小,边界外不收缩,而大眼则是放过来,我们越靠近中心变化越小,越靠近边界变化越大,边界外不变,先说原版的我这里做了一点优化

原版

e9d86371110f41d0b92255b1064d3df3.png

以眼睛中心为中心点,对眼睛区域向外放大,就实现了大眼的效果。大眼的基本公式如下
f s ( r ) = ( 1 − ( r r m a x − 1 ) 2 a ) r f_s(r) = \big(1-( \frac{r}{r_{max}} -1 )^2 a\big)r fs(r)=(1(rmaxr1)2a)r
假设眼睛中心点为 O ( x , y ) O(x,y) O(x,y),大眼区域半径为 R a d i u s Radius Radius,当前点位为 A ( x 1 , y 1 ) A(x1,y1) A(x1,y1),对其进行改进,加入大眼程度形变强度变量Strength,其中Strength的取值范围为0~100。
d i s 2 = ( x 1 − x ) 2 + ( y 1 − y ) 2 K 0 = S t r e n g t h / 100.0 k = 1.0 − ( 1.0 − d i s 2 R a d i u s 2 ) K 0 x d = ( x 1 − x ) k + x y d = ( y 1 − y ) k + y dis^2 = (x_1-x)^2 + (y_1 - y)^2 \\ K_0 = Strength/100.0 \\ k = 1.0 - (1.0-\frac{dis^2}{Radius^2})K_0 \\ x_d = (x_1 - x)k+x y_d = (y_1 - y)k+y dis2=(x1x)2+(y1y)2K0=Strength/100.0k=1.0(1.0Radius2dis2)K0xd=(x1x)k+xyd=(y1y)k+y

Mbp-ImageWarping

原版的有一个最关键的问题就是眼睛是椭圆,你用圆形的效果并不是很好,你用圆形公式计算的中心半径啥的都不是准确的,所以这里我自己优化了一下,改成椭圆的了,中间肯定会涉及大量计算几何,如果我还是高三那我秒解,但是我现在大三了所以可能会有某些地方写的很冗余

首先,你的先计算出眼睛的一个椭圆方程,这样才好方便接下来的计算,那这时候就得需要根据眼睛的特征点来计算,首先你要知道如果是以dlib 68特征点模型提取人脸特征点的话那么37 ~ 42就是左眼,43 ~ 48就是右眼,这里就以左眼做讲解

image.png

我一开始的设计是求出38,42的中点,求出39,41中点,然后以这俩中点作为焦点,然后以三直线相交形成的三角形的中心作为中心

image.png

但是后来我发现可以直接拟合这六个特征点求出椭圆方程

Eye = []
for i in range(startIndex, endIndex+1):
    # startIndex就是眼睛起始特征点比如左眼就是37,end就是终点
    # landmarks_node是提取的68个特征点
    Eye.append([landmarks_node[i][0, 0], landmarks_node[i][0, 1]])
    ellipseEye = cv2.fitEllipse(np.array(Eye))
    
# ellipse_Eye[0] 椭圆中心
# ellipse_Eye[1] 短轴和长轴
# 其他属性可以网上自查

然后就可以根据拟合出的椭圆建立椭圆方程,然后计算出椭圆的焦点,焦距,短轴,长轴等基本属性

然后套上面那个圆的公式就好了

代码

import dlib
import cv2
import numpy as np
import math

predictor_path = 'D:/dlib-shape/shape_predictor_68_face_landmarks.dat'

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(predictor_path)


def landmark_dec_dlib_fun(img_src):
    img_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)

    land_marks = []

    rects = detector(img_gray, 0)

    for i in range(len(rects)):
        land_marks_node = np.matrix([[p.x, p.y] for p in predictor(img_gray, rects[i]).parts()])

        land_marks.append(land_marks_node)

    return land_marks


def getEllipseCross(p1x, p1y, p2x, p2y, a, b, centerX, centerY):
    resx = 0
    resy = 0
    k = (p1y - p2y) / (p1x - p2x);
    m = p1y - k * p1x;
    A = (b * b + (a * a * k * k))
    B = 2 * a * a * k * m
    C = a * a * (m * m - b * b)

    X1 = (-B + math.sqrt(B * B - (4 * A * C))) / (2 * A)
    X2 = (-B - math.sqrt(B * B - (4 * A * C))) / (2 * A)

    # Y1 = math.sqrt(1 - (b * b * X1 * X1 ) / (a * a) )
    # Y2 = math.sqrt(1 - (b * b * X2 * X2 ) / (a * a) )

    Y1 = k * X1 + m
    Y2 = k * X2 + m

    if getDis(p2x, p2y, X1, Y1) < getDis(p2x, p2y, X2, Y2):
        resx = X1
        resy = Y1
    else:
        resx = X2
        resy = Y2

    return [resx + centerX, resy + centerY]


def getLinearEquation(p1x, p1y, p2x, p2y):
    sign = 1
    a = p2y - p1y
    if a < 0:
        sign = -1
        a = sign * a
    b = sign * (p1x - p2x)
    c = sign * (p1y * p2x - p1x * p2y)
    return [a, b, c]

def getDis(p1x, p1y, p2x, p2y):
    return math.sqrt((p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y))

def get_line_cross_point(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y):
    # print(p1x, p1y)
    # print(p2x, p2y)
    # print(p3x, p3y)
    # print(p4x, p4y)
    a0, b0, c0 = getLinearEquation(p1x, p1y, p2x, p2y)
    a1, b1, c1 = getLinearEquation(p3x, p3y, p4x, p4y)
    # print(a0,b0,c0)
    # print(a1,b1,c1)

    D = a0*b1-a1*b0
    if D==0:
        return None
    x = (b0*c1-b1*c0)/D
    y = (a1*c0-a0*c1)/D
    return x, y

def localTranslationWarp(srcImg, startIndex, endIndex,Strength,landmarks_node):

    midIndex = (startIndex + endIndex + 1) >> 1

    startDot = landmarks_node[startIndex]
    endDot = landmarks_node[endIndex]
    midDot = landmarks_node[midIndex]

    Eye = []
    for i in range(startIndex, endIndex+1):
        Eye.append([landmarks_node[i][0, 0], landmarks_node[i][0, 1]])
    ellipseEye = cv2.fitEllipse(np.array(Eye))
    # cv2.ellipse(srcImg, ellipseEye, (0, 255, 0), 1)
    # cv2.imshow("eli",srcImg)


    radius = math.sqrt(
        (startDot[0, 0] - midDot[0, 0]) * (startDot[0, 0] - midDot[0, 0]) -
        (startDot[0, 1] - midDot[0, 1]) * (startDot[0, 1] - midDot[0, 1])
    ) / 2
    list = []

    for i in range(0,3):

        tmplist = []
        tmplist = get_line_cross_point(
                        landmarks_node[startIndex + i][0, 0], landmarks_node[startIndex + i][0, 1],
                        landmarks_node[midIndex + i][0, 0], landmarks_node[midIndex + i][0, 1],
                        landmarks_node[startIndex + ((i + 1) % 3)][0, 0], landmarks_node[startIndex + ((i + 1) % 3)][0, 1],
                        landmarks_node[midIndex + ((i + 1) % 3)][0, 0], landmarks_node[midIndex + ((i + 1) % 3)][0, 1]
                  )
        list.append(tmplist)
    # for l in list:
    #     print(l)

    a = getDis(list[0][0], list[0][1], list[1][0], list[1][1])
    b = getDis(list[1][0], list[1][1], list[2][0], list[2][1])
    c = getDis(list[2][0], list[2][1], list[0][0], list[0][1])

    centerX = (a * list[0][0] + b * list[1][0] + c * list[2][0]) / (a + b + c)
    centerY = (a * list[0][1] + b * list[1][1] + c * list[2][1]) / (a + b + c)
    # print(centerX)
    # print(centerY)
    # print(" ")
    width, height, cou = srcImg.shape
    Intensity = 15*512*512/(width*height)

    ddradius = float(radius * radius)
    copyImg = np.zeros(srcImg.shape, np.uint8)
    copyImg = srcImg.copy()

    K0 = Strength / 100.0

    # 计算公式中的|m-c|^2

    eyeWidth = radius
    eyeHeight = getDis((landmarks_node[startIndex+1][0, 0] + landmarks_node[startIndex+2][0, 0]) / 2,
                       (landmarks_node[startIndex+1][0, 1] + landmarks_node[startIndex+2][0, 1]) / 2,
                       (landmarks_node[midIndex+1][0, 0] + landmarks_node[midIndex+2][0, 0]) / 2,
                       (landmarks_node[midIndex+1][0, 1] + landmarks_node[midIndex+2][0, 1]) / 2)
    centerX = ellipseEye[0][0]
    centerY = ellipseEye[0][1]
    ellipseA = ellipseEye[1][1]
    ellipseB = ellipseEye[1][0]
    ellipseC = math.sqrt(ellipseA * ellipseA - ellipseB * ellipseB)
    # print(ellipseA, ellipseB, ellipseC)
    # print(centerX, centerY)
    # ddmc = (endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)
    #
    for i in range(width):
        for j in range(height):
            # 计算该点是否在形变圆的范围之内
            # 优化,第一步,直接判断是会在(startX,startY)的矩阵框中

            # if math.fabs(i - centerX) > ((eyeHeight / 2) * 1.5) or math.fabs(j - centerY) > ((eyeWidth / 2) * 1.5):
            #     continue




            if getDis(i, j, centerX - ellipseC, centerY) + getDis(i, j, centerX + ellipseC, centerY) > 2 * ellipseA:
                continue
            print(i, j)
            [crossX, crossY] = getEllipseCross(0, 0, i - ellipseEye[0][0], j - ellipseEye[0][1], ellipseEye[1][1],
                                               ellipseEye[1][0], ellipseEye[0][0], ellipseEye[0][1])

            print(crossX, crossY)

            radius = getDis(centerX, centerY, crossX, crossY)
            ddradius = radius * radius
            distance = (i - centerX) * (i - centerX) + (j - centerY) * (j - centerY)
            K1 = 1.0 - (1.0 - distance / ddradius) * K0


            # 映射原位置
            UX = (i - centerX) * K1 + centerX
            UY = (j - centerY) * K1 + centerY
            print(UX, UY)

            # 根据双线性插值法得到UX,UY的值
            value = BilinearInsert(srcImg, UX, UY)
            # 改变当前 i ,j的值
            copyImg[j, i] = value

    return copyImg


# 双线性插值法
def BilinearInsert(src, ux, uy):
    w, h, c = src.shape
    if c == 3:
        x1 = int(ux)
        x2 = x1 + 1
        y1 = int(uy)
        y2 = y1 + 1

        part1 = src[y1, x1].astype(np.float) * (float(x2) - ux) * (float(y2) - uy)
        part2 = src[y1, x2].astype(np.float) * (ux - float(x1)) * (float(y2) - uy)
        part3 = src[y2, x1].astype(np.float) * (float(x2) - ux) * (uy - float(y1))
        part4 = src[y2, x2].astype(np.float) * (ux - float(x1)) * (uy - float(y1))

        insertValue = part1 + part2 + part3 + part4

        return insertValue.astype(np.int8)


def face_thin_auto(src,LStrength,RStrength):
    landmarks = landmark_dec_dlib_fun(src)

    # 如果未检测到人脸关键点,就不进行瘦脸
    if len(landmarks) == 0:
        return

    for landmarks_node in landmarks:
        # print(landmarks_node)
        bigEyeImage = localTranslationWarp(src,36,41,LStrength,landmarks_node)
        bigEyeImage = localTranslationWarp(bigEyeImage,42,47,RStrength,landmarks_node)



    cv2.imshow('bigEye', bigEyeImage)
    # cv2.imwrite('C:/Users/mibbp/Pictures/bigEye.jpg', bigEyeImage)




def main(LStrength, RStrength):

    src = cv2.imread('C:/Users/mibbp/Pictures/bytest.jpg')
    cv2.imshow('src', src)
    face_thin_auto(src,LStrength,RStrength)
    cv2.waitKey(0)


if __name__ == '__main__':
    main()

运行结果对比

原图

lrh.jpg

原版

dbbig.jpg

我的版本

bigEye.jpg

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值