Spaces:
Running
Running
File size: 12,416 Bytes
d5d20be |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
"""
@author: cuny
@file: ThinFace.py
@time: 2022/7/2 15:50
@description:
瘦脸算法,用到了图像局部平移法
先使用人脸关键点检测,然后再使用图像局部平移法
需要注意的是,这部分不会包含dlib人脸关键点检测,因为考虑到模型载入的问题
"""
import cv2
import math
import numpy as np
class TranslationWarp(object):
"""
本类包含瘦脸算法,由于瘦脸算法包含了很多个版本,所以以类的方式呈现
前两个算法没什么好讲的,网上资料很多
第三个采用numpy内部的自定义函数处理,在处理速度上有一些提升
最后采用cv2.map算法,处理速度大幅度提升
"""
# 瘦脸
@staticmethod
def localTranslationWarp(srcImg, startX, startY, endX, endY, radius):
# 双线性插值法
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.float64) * (float(x2) - ux) * (float(y2) - uy)
part2 = src[y1, x2].astype(np.float64) * (ux - float(x1)) * (float(y2) - uy)
part3 = src[y2, x1].astype(np.float64) * (float(x2) - ux) * (uy - float(y1))
part4 = src[y2, x2].astype(np.float64) * (ux - float(x1)) * (uy - float(y1))
insertValue = part1 + part2 + part3 + part4
return insertValue.astype(np.int8)
ddradius = float(radius * radius) # 圆的半径
copyImg = srcImg.copy() # copy后的图像矩阵
# 计算公式中的|m-c|^2
ddmc = (endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)
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
distance = (i - startX) * (i - startX) + (j - startY) * (j - startY)
if distance < ddradius:
# 计算出(i,j)坐标的原坐标
# 计算公式中右边平方号里的部分
ratio = (ddradius - distance) / (ddradius - distance + ddmc)
ratio = ratio * ratio
# 映射原位置
UX = i - ratio * (endX - startX)
UY = j - ratio * (endY - startY)
# 根据双线性插值法得到UX,UY的值
# start_ = time.time()
value = BilinearInsert(srcImg, UX, UY)
# print(f"双线性插值耗时;{time.time() - start_}")
# 改变当前 i ,j的值
copyImg[j, i] = value
return copyImg
# 瘦脸pro1, 限制了for循环的遍历次数
@staticmethod
def localTranslationWarpLimitFor(srcImg, startP: np.matrix, endP: np.matrix, radius: float):
startX, startY = startP[0, 0], startP[0, 1]
endX, endY = endP[0, 0], endP[0, 1]
# 双线性插值法
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.float64) * (float(x2) - ux) * (float(y2) - uy)
part2 = src[y1, x2].astype(np.float64) * (ux - float(x1)) * (float(y2) - uy)
part3 = src[y2, x1].astype(np.float64) * (float(x2) - ux) * (uy - float(y1))
part4 = src[y2, x2].astype(np.float64) * (ux - float(x1)) * (uy - float(y1))
insertValue = part1 + part2 + part3 + part4
return insertValue.astype(np.int8)
ddradius = float(radius * radius) # 圆的半径
copyImg = srcImg.copy() # copy后的图像矩阵
# 计算公式中的|m-c|^2
ddmc = (endX - startX) ** 2 + (endY - startY) ** 2
# 计算正方形的左上角起始点
startTX, startTY = (startX - math.floor(radius + 1), startY - math.floor((radius + 1)))
# 计算正方形的右下角的结束点
endTX, endTY = (startX + math.floor(radius + 1), startY + math.floor((radius + 1)))
# 剪切srcImg
srcImg = srcImg[startTY: endTY + 1, startTX: endTX + 1, :]
# db.cv_show(srcImg)
# 裁剪后的图像相当于在x,y都减少了startX - math.floor(radius + 1)
# 原本的endX, endY在切后的坐标点
endX, endY = (endX - startX + math.floor(radius + 1), endY - startY + math.floor(radius + 1))
# 原本的startX, startY剪切后的坐标点
startX, startY = (math.floor(radius + 1), math.floor(radius + 1))
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
distance = (i - startX) * (i - startX) + (j - startY) * (j - startY)
if distance < ddradius:
# 计算出(i,j)坐标的原坐标
# 计算公式中右边平方号里的部分
ratio = (ddradius - distance) / (ddradius - distance + ddmc)
ratio = ratio * ratio
# 映射原位置
UX = i - ratio * (endX - startX)
UY = j - ratio * (endY - startY)
# 根据双线性插值法得到UX,UY的值
# start_ = time.time()
value = BilinearInsert(srcImg, UX, UY)
# print(f"双线性插值耗时;{time.time() - start_}")
# 改变当前 i ,j的值
copyImg[j + startTY, i + startTX] = value
return copyImg
# # 瘦脸pro2,采用了numpy自定义函数做处理
# def localTranslationWarpNumpy(self, srcImg, startP: np.matrix, endP: np.matrix, radius: float):
# startX , startY = startP[0, 0], startP[0, 1]
# endX, endY = endP[0, 0], endP[0, 1]
# ddradius = float(radius * radius) # 圆的半径
# copyImg = srcImg.copy() # copy后的图像矩阵
# # 计算公式中的|m-c|^2
# ddmc = (endX - startX)**2 + (endY - startY)**2
# # 计算正方形的左上角起始点
# startTX, startTY = (startX - math.floor(radius + 1), startY - math.floor((radius + 1)))
# # 计算正方形的右下角的结束点
# endTX, endTY = (startX + math.floor(radius + 1), startY + math.floor((radius + 1)))
# # 剪切srcImg
# self.thinImage = srcImg[startTY : endTY + 1, startTX : endTX + 1, :]
# # s = self.thinImage
# # db.cv_show(srcImg)
# # 裁剪后的图像相当于在x,y都减少了startX - math.floor(radius + 1)
# # 原本的endX, endY在切后的坐标点
# endX, endY = (endX - startX + math.floor(radius + 1), endY - startY + math.floor(radius + 1))
# # 原本的startX, startY剪切后的坐标点
# startX ,startY = (math.floor(radius + 1), math.floor(radius + 1))
# H, W, C = self.thinImage.shape # 获取图像的形状
# index_m = np.arange(H * W).reshape((H, W))
# triangle_ufunc = np.frompyfunc(self.process, 9, 3)
# # start_ = time.time()
# finalImgB, finalImgG, finalImgR = triangle_ufunc(index_m, self, W, ddradius, ddmc, startX, startY, endX, endY)
# finaleImg = np.dstack((finalImgB, finalImgG, finalImgR)).astype(np.uint8)
# finaleImg = np.fliplr(np.rot90(finaleImg, -1))
# copyImg[startTY: endTY + 1, startTX: endTX + 1, :] = finaleImg
# # print(f"图像处理耗时;{time.time() - start_}")
# # db.cv_show(copyImg)
# return copyImg
# 瘦脸pro3,采用opencv内置函数
@staticmethod
def localTranslationWarpFastWithStrength(srcImg, startP: np.matrix, endP: np.matrix, radius, strength: float = 100.):
"""
采用opencv内置函数
Args:
srcImg: 源图像
startP: 起点位置
endP: 终点位置
radius: 处理半径
strength: 瘦脸强度,一般取100以上
Returns:
"""
startX, startY = startP[0, 0], startP[0, 1]
endX, endY = endP[0, 0], endP[0, 1]
ddradius = float(radius * radius)
# copyImg = np.zeros(srcImg.shape, np.uint8)
# copyImg = srcImg.copy()
maskImg = np.zeros(srcImg.shape[:2], np.uint8)
cv2.circle(maskImg, (startX, startY), math.ceil(radius), (255, 255, 255), -1)
K0 = 100 / strength
# 计算公式中的|m-c|^2
ddmc_x = (endX - startX) * (endX - startX)
ddmc_y = (endY - startY) * (endY - startY)
H, W, C = srcImg.shape
mapX = np.vstack([np.arange(W).astype(np.float32).reshape(1, -1)] * H)
mapY = np.hstack([np.arange(H).astype(np.float32).reshape(-1, 1)] * W)
distance_x = (mapX - startX) * (mapX - startX)
distance_y = (mapY - startY) * (mapY - startY)
distance = distance_x + distance_y
K1 = np.sqrt(distance)
ratio_x = (ddradius - distance_x) / (ddradius - distance_x + K0 * ddmc_x)
ratio_y = (ddradius - distance_y) / (ddradius - distance_y + K0 * ddmc_y)
ratio_x = ratio_x * ratio_x
ratio_y = ratio_y * ratio_y
UX = mapX - ratio_x * (endX - startX) * (1 - K1 / radius)
UY = mapY - ratio_y * (endY - startY) * (1 - K1 / radius)
np.copyto(UX, mapX, where=maskImg == 0)
np.copyto(UY, mapY, where=maskImg == 0)
UX = UX.astype(np.float32)
UY = UY.astype(np.float32)
copyImg = cv2.remap(srcImg, UX, UY, interpolation=cv2.INTER_LINEAR)
return copyImg
def thinFace(src, landmark, place: int = 0, strength=30.):
"""
瘦脸程序接口,输入人脸关键点信息和强度,即可实现瘦脸
注意处理四通道图像
Args:
src: 原图
landmark: 关键点信息
place: 选择瘦脸区域,为0-4之间的值
strength: 瘦脸强度,输入值在0-10之间,如果小于或者等于0,则不瘦脸
Returns:
瘦脸后的图像
"""
strength = min(100., strength * 10.)
if strength <= 0.:
return src
# 也可以设置瘦脸区域
place = max(0, min(4, int(place)))
left_landmark = landmark[4 + place]
left_landmark_down = landmark[6 + place]
right_landmark = landmark[13 + place]
right_landmark_down = landmark[15 + place]
endPt = landmark[58]
# 计算第4个点到第6个点的距离作为瘦脸距离
r_left = math.sqrt(
(left_landmark[0, 0] - left_landmark_down[0, 0]) ** 2 +
(left_landmark[0, 1] - left_landmark_down[0, 1]) ** 2
)
# 计算第14个点到第16个点的距离作为瘦脸距离
r_right = math.sqrt((right_landmark[0, 0] - right_landmark_down[0, 0]) ** 2 +
(right_landmark[0, 1] - right_landmark_down[0, 1]) ** 2)
# 瘦左边脸
thin_image = TranslationWarp.localTranslationWarpFastWithStrength(src, left_landmark[0], endPt[0], r_left, strength)
# 瘦右边脸
thin_image = TranslationWarp.localTranslationWarpFastWithStrength(thin_image, right_landmark[0], endPt[0], r_right, strength)
return thin_image
if __name__ == "__main__":
import os
from hycv.FaceDetection68.faceDetection68 import FaceDetection68
local_file = os.path.dirname(__file__)
PREDICTOR_PATH = f"{local_file}/weights/shape_predictor_68_face_landmarks.dat" # 关键点检测模型路径
fd68 = FaceDetection68(model_path=PREDICTOR_PATH)
input_image = cv2.imread("test_image/4.jpg", -1)
_, landmark_, _ = fd68.facePoints(input_image)
output_image = thinFace(input_image, landmark_, strength=30.2)
cv2.imwrite("thinFaceCompare.png", np.hstack((input_image, output_image)))
|