【python】OpenCV—Local Translation Warps

文章目录

1、功能描述

利用液化效果实现瘦脸美颜

交互式的液化效果原理来自 Gustafsson A. Interactive image warping[D]. , 1993.

2、原理分析

上面描述很清晰了,鼠标初始在 C,也即形变范围的圆心在 C,形变半径 r m a x r_{max} rmax,形变方向 C→M,

圆圈内原始 U 位置会被形变到 X,可以简单直白理解为拉伸后 U 位置的值给了 X 位置,此时 U 位置空置了,需要插值

插值公示 93 年的论文中直接给出了,我们尝试 coding

这里还涉及到插值,我们回顾下比较常见的双线性插值原理

3、代码实现

导入必要的库函数

python 复制代码
import dlib
import cv2
import numpy as np
import math

载入人脸检测器和人脸关键点检测模型

python 复制代码
predictor_path = "./shape_predictor_68_face_landmarks.dat"

# 使用dlib自带的frontal_face_detector作为我们的特征提取器
detector = dlib.get_frontal_face_detector()  # 人脸检测器
predictor = dlib.shape_predictor(predictor_path)  # 关键点检测模型

读入图片,调用 face_thin_auto 函数,实现瘦脸

python 复制代码
def main():
    src = cv2.imread(r'./1.jpg')  # (1546, 1236, 3)
    # cv2.imshow('src', src)
    face_thin_auto(src)
    cv2.waitKey(0)


if __name__ == '__main__':
    main()

看看 face_thin_auto 函数的实现细节

python 复制代码
def face_thin_auto(src):
    landmarks = landmark_dec_dlib_fun(src)

    point_img = src.copy()
    for index, landmark in enumerate(landmarks[0]):
        cv2.circle(point_img, center=np.array(landmark)[0], radius=5, color=(255, 0, 0), thickness=-1)
        cv2.putText(point_img, str(index), org=(landmark[0,0]-30, landmark[0,1]),
                    fontFace=cv2.FONT_HERSHEY_TRIPLEX,fontScale=0.5, color=(0,255,0))
    cv2.imwrite("point.jpg", point_img)

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

    thin_image = src
    landmarks_node = landmarks[0]
    endPt = landmarks_node[16]  # matrix([[753, 450]])
    for index in range(3, 14, 2):
        start_landmark = landmarks_node[index]
        end_landmark = landmarks_node[index + 2]
        r = math.sqrt((start_landmark[0, 0] - end_landmark[0, 0]) **2 +
                      (start_landmark[0, 1] - end_landmark[0, 1]) **2)
        thin_image = localTranslationWarp(thin_image, start_landmark[0, 0],
                                          start_landmark[0, 1], endPt[0, 0], endPt[0, 1], r)

    # 显示
    # cv2.imshow('thin', thin_image)
    cv2.imwrite(r'./thin.jpg', thin_image)

landmark_dec_dlib_fun 检测人脸和人脸关键点,实现如下

python 复制代码
def landmark_dec_dlib_fun(img_src):
    img_gray = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
    cv2.imwrite("gray.jpg", img_gray)
    land_marks = []

    rects = detector(img_gray, 0)  # 人脸检测,[[(336, 286) (782, 732)]]
    plot_img = img_src.copy()
    for i in range(len(rects)):  # 遍历检测到的人脸
        cv2.rectangle(plot_img, (rects[i].left(), rects[i].top()),  (rects[i].right(), rects[i].bottom()),
                      color=(0,255,0), thickness=10)
        land_marks_node = np.matrix([[p.x, p.y] for p in predictor(img_gray, rects[i]).parts()])
        land_marks.append(land_marks_node)
    cv2.imwrite("face_det.jpg", plot_img)
    return land_marks

先把图片变成灰度图,然后人脸检测,绘制人脸检测结果,人脸关键点检测,返回关键点坐标


face_thin_auto 函数接下来绘制人脸关键点,一共 68 个

遍历关键点,3,5,7,9,11,13

也即 C = 3,5,7,9,11,13,M = 16

r m a x r_{max} rmax 为关键点 3-5 的距离,5-7 的距离,7-9 的距离,9-11 的距离,11-13 的距离,13-15 的距离

调用 localTranslationWarp 求瘦脸后的图片,

python 复制代码
def localTranslationWarp(srcImg, startX, startY, endX, endY, radius):
    ddradius = float(radius * radius)
    copyImg = srcImg.copy()

    # 计算公式中的|m-c|^2
    ddmc = (endX - startX) ** 2 + (endY - startY) ** 2
    H, W, C = srcImg.shape
    for i in range(W):
        for j in range(H):
            # 计算该点是否在形变圆的范围之内
            # 优化,第一步,直接判断是会在(startX,startY)的矩阵框中
            if math.fabs(i - startX) > radius and math.fabs(j - startY) > radius:
                continue  # 不在 continue

            distance = (i - startX) ** 2 + (j - startY) ** 2

            if (distance < ddradius):
                # 计算出(i,j)坐标的原坐标
                # 计算公式中右边平方号里的部分
                ratio = (ddradius - distance) / (ddradius - distance + ddmc)
                ratio = ratio ** 2

                # 映射原位置
                UX = i - ratio * (endX - startX)
                UY = j - ratio * (endY - startY)

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

    return copyImg

localTranslationWarp 仅作用与以 C 为圆心, r m a x r_{max} rmax 范围内的像素点,像素点的坐标求法代入公式计算,值用插值求出

双线性插值实现

python 复制代码
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(float) * (float(x2) - ux) * (float(y2) - uy)
        part2 = src[y1, x2].astype(float) * (ux - float(x1)) * (float(y2) - uy)
        part3 = src[y2, x1].astype(float) * (float(x2) - ux) * (uy - float(y1))
        part4 = src[y2, x2].astype(float) * (ux - float(x1)) * (uy - float(y1))

        insertValue = part1 + part2 + part3 + part4

        return insertValue.astype(np.int8)

我们看看

python 复制代码
        part1 = src[y1, x1].astype(float) * (float(x2) - ux) * (float(y2) - uy)
        part2 = src[y1, x2].astype(float) * (ux - float(x1)) * (float(y2) - uy)
        part3 = src[y2, x1].astype(float) * (float(x2) - ux) * (uy - float(y1))
        part4 = src[y2, x2].astype(float) * (ux - float(x1)) * (uy - float(y1))

对应

f ( Q 11 ) ∗ x 2 − x x 2 − x 1 ∗ y 2 − y y 2 − y 1 = f ( Q 11 ) ∗ ( x 2 − x ) ∗ ( y 2 − y ) f(Q_{11}) * \frac{x_2 - x}{x_2- x_1} * \frac{y_2 - y}{y_2- y_1} = f(Q_{11}) * (x_2 - x) * (y_2 - y) f(Q11)∗x2−x1x2−x∗y2−y1y2−y=f(Q11)∗(x2−x)∗(y2−y)

f ( Q 21 ) ∗ x − x 1 x 2 − x 1 ∗ y 2 − y y 2 − y 1 = f ( Q 21 ) ∗ ( x − x 1 ) ∗ ( y 2 − y ) f(Q_{21}) * \frac{x - x_1}{x_2- x_1} * \frac{y_2 - y}{y_2- y_1} = f(Q_{21}) * (x - x_1) * (y_2 - y) f(Q21)∗x2−x1x−x1∗y2−y1y2−y=f(Q21)∗(x−x1)∗(y2−y)

f ( Q 12 ) ∗ x 2 − x x 2 − x 1 ∗ y − y 1 y 2 − y 1 = f ( Q 12 ) ∗ ( x 2 − x ) ∗ ( y − y 1 ) f(Q_{12}) * \frac{x_2 - x}{x_2- x_1} * \frac{y - y_1}{y_2- y_1} = f(Q_{12}) * (x_2 - x) * (y - y_1) f(Q12)∗x2−x1x2−x∗y2−y1y−y1=f(Q12)∗(x2−x)∗(y−y1)

f ( Q 22 ) ∗ x − x 1 x 2 − x 1 ∗ y − y 1 y 2 − y 1 = f ( Q 22 ) ∗ ( x − x 1 ) ∗ ( y − y 1 ) f(Q_{22}) * \frac{x - x_1}{x_2- x_1} * \frac{y - y_1}{y_2- y_1} = f(Q_{22}) * (x - x_1) * (y - y_1) f(Q22)∗x2−x1x−x1∗y2−y1y−y1=f(Q22)∗(x−x1)∗(y−y1)

4、效果展示

输入

输出

再明显一点试试

输入

输出

肉眼看不太明显,对比工具看比较明显

缩小下图片的输入分辨率


效果会明显一些

5、完整代码

python 复制代码
import dlib
import cv2
import numpy as np
import math

predictor_path = "./shape_predictor_68_face_landmarks.dat"

# 使用dlib自带的frontal_face_detector作为我们的特征提取器
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)
    cv2.imwrite("gray.jpg", img_gray)
    land_marks = []

    rects = detector(img_gray, 0)  # 人脸检测,[[(336, 286) (782, 732)]]
    plot_img = img_src.copy()
    for i in range(len(rects)):  # 遍历检测到的人脸
        cv2.rectangle(plot_img, (rects[i].left(), rects[i].top()),  (rects[i].right(), rects[i].bottom()),
                      color=(0,255,0), thickness=10)
        land_marks_node = np.matrix([[p.x, p.y] for p in predictor(img_gray, rects[i]).parts()])
        land_marks.append(land_marks_node)
    cv2.imwrite("face_det.jpg", plot_img)
    return land_marks


'''
方法: Interactive Image Warping 局部平移算法
'''
def localTranslationWarp(srcImg, startX, startY, endX, endY, radius):
    ddradius = float(radius * radius)
    copyImg = srcImg.copy()

    # 计算公式中的|m-c|^2
    ddmc = (endX - startX) ** 2 + (endY - startY) ** 2
    H, W, C = srcImg.shape
    for i in range(W):
        for j in range(H):
            # 计算该点是否在形变圆的范围之内
            # 优化,第一步,直接判断是会在(startX,startY)的矩阵框中
            if math.fabs(i - startX) > radius and math.fabs(j - startY) > radius:
                continue  # 不在 continue

            distance = (i - startX) ** 2 + (j - startY) ** 2

            if (distance < ddradius):
                # 计算出(i,j)坐标的原坐标
                # 计算公式中右边平方号里的部分
                ratio = (ddradius - distance) / (ddradius - distance + ddmc)
                ratio = ratio ** 2

                # 映射原位置
                UX = i - ratio * (endX - startX)
                UY = j - ratio * (endY - startY)

                # 根据双线性插值法得到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(float) * (float(x2) - ux) * (float(y2) - uy)
        part2 = src[y1, x2].astype(float) * (ux - float(x1)) * (float(y2) - uy)
        part3 = src[y2, x1].astype(float) * (float(x2) - ux) * (uy - float(y1))
        part4 = src[y2, x2].astype(float) * (ux - float(x1)) * (uy - float(y1))

        insertValue = part1 + part2 + part3 + part4

        return insertValue.astype(np.int8)


def face_thin_auto(src):
    landmarks = landmark_dec_dlib_fun(src)

    point_img = src.copy()
    for index, landmark in enumerate(landmarks[0]):
        cv2.circle(point_img, center=np.array(landmark)[0], radius=5, color=(255, 0, 0), thickness=-1)
        cv2.putText(point_img, str(index), org=(landmark[0,0]-30, landmark[0,1]),
                    fontFace=cv2.FONT_HERSHEY_TRIPLEX,fontScale=0.5, color=(0,255,0))
    cv2.imwrite("point.jpg", point_img)

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

    thin_image = src
    landmarks_node = landmarks[0]
    endPt = landmarks_node[16]  # matrix([[753, 450]])
    for index in range(3, 14, 2):
        start_landmark = landmarks_node[index]
        end_landmark = landmarks_node[index + 2]
        r = math.sqrt((start_landmark[0, 0] - end_landmark[0, 0]) **2 +
                      (start_landmark[0, 1] - end_landmark[0, 1]) **2)
        thin_image = localTranslationWarp(thin_image, start_landmark[0, 0],
                                          start_landmark[0, 1], endPt[0, 0], endPt[0, 1], r)

    # 显示
    # cv2.imshow('thin', thin_image)
    cv2.imwrite(r'./thin.jpg', thin_image)


def main():
    src = cv2.imread(r'./1.jpg')  # (1546, 1236, 3)
    # cv2.imshow('src', src)
    face_thin_auto(src)
    cv2.waitKey(0)


if __name__ == '__main__':
    main()

6、参考

相关推荐
YYHYJX1 分钟前
C#学习笔记 --- 简单应用
开发语言·学习·c#
Clockwiseee5 分钟前
JAVA多线程学习
java·开发语言·学习
Nobita Chen5 分钟前
Python实现windows自动关机
开发语言·windows·python
码路刺客6 分钟前
一学就废|Python基础碎片,OS模块
开发语言·python
z千鑫13 分钟前
【Python】Python之Selenium基础教程+实战demo:提升你的测试+测试数据构造的效率!
开发语言·python·selenium
aiee19 分钟前
GO通过SMTP协议发送邮件
开发语言·后端·golang
云浩舟30 分钟前
Golang并发读取json文件数据并写入oracle数据库的项目实践
开发语言·数据库·golang
walkskyer36 分钟前
Golang strconv包详解:高效类型转换实战
android·开发语言·golang
我命由我1234540 分钟前
Android Room 构建问题:There are multiple good constructors
android·开发语言·java-ee·android studio·android jetpack·android-studio·android runtime
编程小筑1 小时前
R语言的语法糖
开发语言·后端·golang