mirror of
https://github.com/Zeyi-Lin/HivisionIDPhotos.git
synced 2026-09-21 04:36:40 +08:00
Merge branch 'master' of https://github.com/Zeyi-Lin/HivisionIDPhotos
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
.env
|
||||
demo/kb_output/*.jpg
|
||||
demo/kb_output/*.png
|
||||
**/flagged/
|
||||
# build outputs
|
||||
dist
|
||||
build
|
||||
|
||||
@@ -292,4 +292,20 @@ LOCALES = {
|
||||
"label": "抠图图像",
|
||||
},
|
||||
},
|
||||
"beauty_tab": {
|
||||
"en": {
|
||||
"label": "Beauty",
|
||||
},
|
||||
"zh": {
|
||||
"label": "美颜",
|
||||
},
|
||||
},
|
||||
"whitening_strength": {
|
||||
"en": {
|
||||
"label": "whitening strength",
|
||||
},
|
||||
"zh": {
|
||||
"label": "美白强度",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+9
-3
@@ -41,7 +41,7 @@ class IDPhotoProcessor:
|
||||
face_detect_option,
|
||||
head_measure_ratio=0.2,
|
||||
top_distance_max=0.12,
|
||||
top_distance_min=0.10,
|
||||
whitening_strength=0,
|
||||
):
|
||||
top_distance_min = top_distance_max - 0.02
|
||||
|
||||
@@ -115,6 +115,7 @@ class IDPhotoProcessor:
|
||||
idphoto_json["size_mode"] in LOCALES["size_mode"][language]["choices"][1]
|
||||
)
|
||||
|
||||
# 生成证件照
|
||||
try:
|
||||
result = creator(
|
||||
input_image,
|
||||
@@ -122,7 +123,9 @@ class IDPhotoProcessor:
|
||||
size=idphoto_json["size"],
|
||||
head_measure_ratio=head_measure_ratio,
|
||||
head_top_range=(top_distance_max, top_distance_min),
|
||||
whitening_strength=whitening_strength,
|
||||
)
|
||||
# 如果检测到人脸数量不等于1
|
||||
except FaceError:
|
||||
return [
|
||||
gr.update(value=None), # img_output_standard
|
||||
@@ -136,7 +139,7 @@ class IDPhotoProcessor:
|
||||
),
|
||||
None, # file_download (assuming it should be None or have no update)
|
||||
]
|
||||
|
||||
# 如果 API 错误
|
||||
except APIError as e:
|
||||
return [
|
||||
gr.update(value=None), # img_output_standard
|
||||
@@ -150,13 +153,14 @@ class IDPhotoProcessor:
|
||||
),
|
||||
None, # file_download (assuming it should be None or have no update)
|
||||
]
|
||||
|
||||
# 证件照生成正常
|
||||
else:
|
||||
(result_image_standard, result_image_hd, _, _, _, _) = result
|
||||
|
||||
result_image_standard_png = np.uint8(result_image_standard)
|
||||
result_image_hd_png = np.uint8(result_image_hd)
|
||||
|
||||
# 纯色渲染
|
||||
if (
|
||||
idphoto_json["render_mode"]
|
||||
== LOCALES["render_mode"][language]["choices"][0]
|
||||
@@ -167,6 +171,7 @@ class IDPhotoProcessor:
|
||||
result_image_hd = np.uint8(
|
||||
add_background(result_image_hd, bgr=idphoto_json["color_bgr"])
|
||||
)
|
||||
# 上下渐变渲染
|
||||
elif (
|
||||
idphoto_json["render_mode"]
|
||||
== LOCALES["render_mode"][language]["choices"][1]
|
||||
@@ -185,6 +190,7 @@ class IDPhotoProcessor:
|
||||
mode="updown_gradient",
|
||||
)
|
||||
)
|
||||
# 中心渐变渲染
|
||||
else:
|
||||
result_image_standard = np.uint8(
|
||||
add_background(
|
||||
|
||||
+26
@@ -63,6 +63,7 @@ def create_ui(
|
||||
value=human_matting_models[0],
|
||||
)
|
||||
|
||||
# TAB1 - 关键参数
|
||||
with gr.Tab(
|
||||
LOCALES["key_param"][DEFAULT_LANG]["label"]
|
||||
) as key_parameter_tab:
|
||||
@@ -105,6 +106,7 @@ def create_ui(
|
||||
value=LOCALES["render_mode"][DEFAULT_LANG]["choices"][0],
|
||||
)
|
||||
|
||||
# TAB2 - 高级参数
|
||||
with gr.Tab(
|
||||
LOCALES["advance_param"][DEFAULT_LANG]["label"]
|
||||
) as advance_parameter_tab:
|
||||
@@ -140,6 +142,20 @@ def create_ui(
|
||||
interactive=True,
|
||||
)
|
||||
|
||||
# TAB3 - 美颜
|
||||
with gr.Tab(
|
||||
LOCALES["beauty_tab"][DEFAULT_LANG]["label"]
|
||||
) as beauty_parameter_tab:
|
||||
whitening_option = gr.Slider(
|
||||
label=LOCALES["whitening_strength"][DEFAULT_LANG]["label"],
|
||||
minimum=0,
|
||||
maximum=10,
|
||||
value=2,
|
||||
step=1,
|
||||
interactive=True,
|
||||
)
|
||||
|
||||
# TAB4 - 水印
|
||||
with gr.Tab(
|
||||
LOCALES["watermark_tab"][DEFAULT_LANG]["label"]
|
||||
) as watermark_parameter_tab:
|
||||
@@ -387,6 +403,12 @@ def create_ui(
|
||||
matting_image_accordion: gr.update(
|
||||
label=LOCALES["matting_image"][language]["label"]
|
||||
),
|
||||
beauty_parameter_tab: gr.update(
|
||||
label=LOCALES["beauty_tab"][language]["label"]
|
||||
),
|
||||
whitening_option: gr.update(
|
||||
label=LOCALES["whitening_strength"][language]["label"]
|
||||
),
|
||||
}
|
||||
|
||||
def change_color(colors):
|
||||
@@ -425,6 +447,7 @@ def create_ui(
|
||||
return {custom_image_kb: gr.update(visible=False)}
|
||||
|
||||
# ---------------- 绑定事件 ----------------
|
||||
# 语言切换
|
||||
language_options.input(
|
||||
change_language,
|
||||
inputs=[language_options],
|
||||
@@ -458,6 +481,8 @@ def create_ui(
|
||||
watermark_text_space,
|
||||
watermark_options,
|
||||
matting_image_accordion,
|
||||
beauty_parameter_tab,
|
||||
whitening_option,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -502,6 +527,7 @@ def create_ui(
|
||||
face_detect_model_options,
|
||||
head_measure_ratio_option,
|
||||
top_distance_option,
|
||||
whitening_option,
|
||||
],
|
||||
outputs=[
|
||||
img_output_standard,
|
||||
|
||||
@@ -12,7 +12,8 @@ from typing import Tuple
|
||||
import hivision.creator.utils as U
|
||||
from .context import Context, ContextHandler, Params, Result
|
||||
from .human_matting import extract_human
|
||||
from .face_detector import detect_face_mtcnn, detect_face_face_plusplus
|
||||
from .face_detector import detect_face_mtcnn
|
||||
from hivision.plugin.beauty.whitening import make_whitening
|
||||
from .photo_adjuster import adjust_photo
|
||||
|
||||
|
||||
@@ -56,15 +57,19 @@ class IDCreator:
|
||||
head_height_ratio: float = 0.45,
|
||||
head_top_range: float = (0.12, 0.1),
|
||||
face: Tuple[int, int, int, int] = None,
|
||||
whitening_strength: int = 0,
|
||||
) -> Result:
|
||||
"""
|
||||
证件照处理函数
|
||||
:param image: 输入图像
|
||||
:param change_bg_only: 是否只需要换底
|
||||
:param change_bg_only: 是否只需要抠图
|
||||
:param crop_only: 是否只需要裁剪
|
||||
:param size: 输出的图像大小(h,w)
|
||||
:param head_measure_ratio: 人脸面积与全图面积的期望比值
|
||||
:param head_height_ratio: 人脸中心处在全图高度的比例期望值
|
||||
:param head_top_range: 头距离顶部的比例(max,min)
|
||||
:param face: 人脸坐标
|
||||
:param whitening_strength: 美白强度
|
||||
|
||||
:return: 返回处理后的证件照和一系列参数
|
||||
"""
|
||||
@@ -77,6 +82,7 @@ class IDCreator:
|
||||
head_top_range=head_top_range,
|
||||
crop_only=crop_only,
|
||||
face=face,
|
||||
whitening_strength=whitening_strength,
|
||||
)
|
||||
self.ctx = Context(params)
|
||||
ctx = self.ctx
|
||||
@@ -87,8 +93,15 @@ class IDCreator:
|
||||
ctx.origin_image = ctx.processing_image.copy()
|
||||
self.before_all and self.before_all(ctx)
|
||||
|
||||
# 美白
|
||||
if ctx.params.whitening_strength > 0:
|
||||
ctx.processing_image = make_whitening(
|
||||
ctx.processing_image, ctx.params.whitening_strength
|
||||
)
|
||||
|
||||
# 1. 人像抠图
|
||||
if not ctx.params.crop_only:
|
||||
# 调用抠图工作流
|
||||
self.matting_handler(ctx)
|
||||
self.after_matting and self.after_matting(ctx)
|
||||
else:
|
||||
@@ -115,6 +128,8 @@ class IDCreator:
|
||||
result_image_hd, result_image_standard, clothing_params, typography_params = (
|
||||
adjust_photo(ctx)
|
||||
)
|
||||
|
||||
# 4. 返回结果
|
||||
ctx.result = Result(
|
||||
standard=result_image_standard,
|
||||
hd=result_image_hd,
|
||||
|
||||
@@ -21,6 +21,7 @@ class Params:
|
||||
head_height_ratio: float = 0.45,
|
||||
head_top_range: float = (0.12, 0.1),
|
||||
face: Tuple[int, int, int, int] = None,
|
||||
whitening_strength: int = 0,
|
||||
):
|
||||
self.__size = size
|
||||
self.__change_bg_only = change_bg_only
|
||||
@@ -29,6 +30,7 @@ class Params:
|
||||
self.__head_height_ratio = head_height_ratio
|
||||
self.__head_top_range = head_top_range
|
||||
self.__face = face
|
||||
self.__whitening_strength = whitening_strength
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
@@ -58,6 +60,10 @@ class Params:
|
||||
def face(self):
|
||||
return self.__face
|
||||
|
||||
@property
|
||||
def whitening_strength(self):
|
||||
return self.__whitening_strength
|
||||
|
||||
|
||||
class Result:
|
||||
def __init__(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .beauty_tools import BeautyTools
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
@author: cuny
|
||||
@file: MakeBeautiful.py
|
||||
@time: 2022/7/7 20:23
|
||||
@description:
|
||||
美颜工具集合文件,作为暴露在外的插件接口
|
||||
"""
|
||||
|
||||
from .grind_skin import grindSkin
|
||||
from .whitening import MakeWhiter
|
||||
from .thin_face import thinFace
|
||||
import numpy as np
|
||||
|
||||
|
||||
def BeautyTools(
|
||||
input_image: np.ndarray,
|
||||
landmark,
|
||||
thinStrength: int,
|
||||
thinPlace: int,
|
||||
grindStrength: int,
|
||||
whiterStrength: int,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
美颜工具的接口函数,用于实现美颜效果
|
||||
Args:
|
||||
input_image: 输入的图像
|
||||
landmark: 瘦脸需要的人脸关键点信息,为fd68返回的第二个参数
|
||||
thinStrength: 瘦脸强度,为0-10(如果更高其实也没什么问题),当强度为0或者更低时,则不瘦脸
|
||||
thinPlace: 选择瘦脸区域,为0-2之间的值,越大瘦脸的点越靠下
|
||||
grindStrength: 磨皮强度,为0-10(如果更高其实也没什么问题),当强度为0或者更低时,则不磨皮
|
||||
whiterStrength: 美白强度,为0-10(如果更高其实也没什么问题),当强度为0或者更低时,则不美白
|
||||
Returns:
|
||||
output_image 输出图像
|
||||
"""
|
||||
try:
|
||||
_, _, _ = input_image.shape
|
||||
except ValueError:
|
||||
raise TypeError("输入图像必须为3通道或者4通道!")
|
||||
# 三通道或者四通道图像
|
||||
# 首先进行瘦脸
|
||||
input_image = thinFace(
|
||||
input_image, landmark, place=thinPlace, strength=thinStrength
|
||||
)
|
||||
# 其次进行磨皮
|
||||
input_image = grindSkin(src=input_image, strength=grindStrength)
|
||||
# 最后进行美白
|
||||
makeWhiter = MakeWhiter()
|
||||
input_image = makeWhiter.run(input_image, strength=whiterStrength)
|
||||
return input_image
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
@author: cuny
|
||||
@file: GrindSkin.py
|
||||
@time: 2022/7/2 14:44
|
||||
@description:
|
||||
磨皮算法
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def grindSkin(src, grindDegree: int = 3, detailDegree: int = 1, strength: int = 9):
|
||||
"""
|
||||
Dest =(Src * (100 - Opacity) + (Src + 2 * GaussBlur(EPFFilter(Src) - Src)) * Opacity) /100
|
||||
人像磨皮方案,后续会考虑使用一些皮肤区域检测算法来实现仅皮肤区域磨皮,增加算法的精细程度——或者使用人脸关键点
|
||||
https://www.cnblogs.com/Imageshop/p/4709710.html
|
||||
Args:
|
||||
src: 原图
|
||||
grindDegree: 磨皮程度调节参数
|
||||
detailDegree: 细节程度调节参数
|
||||
strength: 融合程度,作为磨皮强度(0 - 10)
|
||||
|
||||
Returns:
|
||||
磨皮后的图像
|
||||
"""
|
||||
if strength <= 0:
|
||||
return src
|
||||
dst = src.copy()
|
||||
opacity = min(10.0, strength) / 10.0
|
||||
dx = grindDegree * 5 # 双边滤波参数之一
|
||||
fc = grindDegree * 12.5 # 双边滤波参数之一
|
||||
temp1 = cv2.bilateralFilter(src[:, :, :3], dx, fc, fc)
|
||||
temp2 = cv2.subtract(temp1, src[:, :, :3])
|
||||
temp3 = cv2.GaussianBlur(temp2, (2 * detailDegree - 1, 2 * detailDegree - 1), 0)
|
||||
temp4 = cv2.add(cv2.add(temp3, temp3), src[:, :, :3])
|
||||
dst[:, :, :3] = cv2.addWeighted(temp4, opacity, src[:, :, :3], 1 - opacity, 0.0)
|
||||
return dst
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_image = cv2.imread("test_image/7.jpg")
|
||||
output_image = grindSkin(src=input_image)
|
||||
cv2.imwrite("grindSkinCompare.png", np.hstack((input_image, output_image)))
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
@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.0
|
||||
):
|
||||
"""
|
||||
采用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.0):
|
||||
"""
|
||||
瘦脸程序接口,输入人脸关键点信息和强度,即可实现瘦脸
|
||||
注意处理四通道图像
|
||||
Args:
|
||||
src: 原图
|
||||
landmark: 关键点信息
|
||||
place: 选择瘦脸区域,为0-4之间的值
|
||||
strength: 瘦脸强度,输入值在0-10之间,如果小于或者等于0,则不瘦脸
|
||||
|
||||
Returns:
|
||||
瘦脸后的图像
|
||||
"""
|
||||
strength = min(100.0, strength * 10.0)
|
||||
if strength <= 0.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)))
|
||||
@@ -0,0 +1,83 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import gradio as gr
|
||||
|
||||
|
||||
class LutWhite:
|
||||
CUBE64_ROWS = 8
|
||||
CUBE64_SIZE = 64
|
||||
CUBE256_SIZE = 256
|
||||
CUBE_SCALE = CUBE256_SIZE // CUBE64_SIZE
|
||||
|
||||
def __init__(self, lut_image):
|
||||
self.lut = self._create_lut(lut_image)
|
||||
|
||||
def _create_lut(self, lut_image):
|
||||
reshape_lut = np.zeros(
|
||||
(self.CUBE256_SIZE, self.CUBE256_SIZE, self.CUBE256_SIZE, 3), dtype=np.uint8
|
||||
)
|
||||
for i in range(self.CUBE64_SIZE):
|
||||
tmp = i // self.CUBE64_ROWS
|
||||
cx = (i % self.CUBE64_ROWS) * self.CUBE64_SIZE
|
||||
cy = tmp * self.CUBE64_SIZE
|
||||
cube64 = lut_image[cy : cy + self.CUBE64_SIZE, cx : cx + self.CUBE64_SIZE]
|
||||
if cube64.size == 0:
|
||||
continue
|
||||
cube256 = cv2.resize(cube64, (self.CUBE256_SIZE, self.CUBE256_SIZE))
|
||||
reshape_lut[i * self.CUBE_SCALE : (i + 1) * self.CUBE_SCALE] = cube256
|
||||
return reshape_lut
|
||||
|
||||
def apply(self, src):
|
||||
b, g, r = src[:, :, 0], src[:, :, 1], src[:, :, 2]
|
||||
return self.lut[b, g, r]
|
||||
|
||||
|
||||
class MakeWhiter:
|
||||
def __init__(self, lut_image):
|
||||
self.lut_white = LutWhite(lut_image)
|
||||
|
||||
def run(self, src: np.ndarray, strength: int) -> np.ndarray:
|
||||
strength = np.clip(strength / 10.0, 0, 1)
|
||||
if strength <= 0:
|
||||
return src
|
||||
img = self.lut_white.apply(src[:, :, :3])
|
||||
return cv2.addWeighted(src[:, :, :3], 1 - strength, img, strength, 0)
|
||||
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
default_lut = cv2.imread(os.path.join(base_dir, "lut/lut_origin.png"))
|
||||
make_whiter = MakeWhiter(default_lut)
|
||||
|
||||
|
||||
def make_whitening(image, strength):
|
||||
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
output_image = make_whiter.run(image, strength)
|
||||
return cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB)
|
||||
|
||||
|
||||
def make_whitening_png(image, strength):
|
||||
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA)
|
||||
|
||||
b, g, r, a = cv2.split(image)
|
||||
bgr_image = cv2.merge((b, g, r))
|
||||
|
||||
b_w, g_w, r_w = cv2.split(make_whiter.run(bgr_image, strength))
|
||||
output_image = cv2.merge((b_w, g_w, r_w, a))
|
||||
|
||||
return cv2.cvtColor(output_image, cv2.COLOR_RGBA2BGRA)
|
||||
|
||||
|
||||
# 启动Gradio应用
|
||||
if __name__ == "__main__":
|
||||
demo = gr.Interface(
|
||||
fn=make_whitening_png,
|
||||
inputs=[
|
||||
gr.Image(type="pil", image_mode="RGBA", label="Input Image"),
|
||||
gr.Slider(0, 10, step=1, label="Whitening Strength"),
|
||||
],
|
||||
outputs=gr.Image(type="pil"),
|
||||
title="Image Whitening Demo",
|
||||
description="Upload an image and adjust the whitening strength to see the effect.",
|
||||
)
|
||||
demo.launch()
|
||||
Reference in New Issue
Block a user