diff --git a/.gitignore b/.gitignore index 7ed2541..a13d7b9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .env demo/kb_output/*.jpg demo/kb_output/*.png +**/flagged/ # build outputs dist build diff --git a/demo/locals.py b/demo/locals.py index aa26848..03c2334 100644 --- a/demo/locals.py +++ b/demo/locals.py @@ -292,4 +292,20 @@ LOCALES = { "label": "抠图图像", }, }, + "beauty_tab": { + "en": { + "label": "Beauty", + }, + "zh": { + "label": "美颜", + }, + }, + "whitening_strength": { + "en": { + "label": "whitening strength", + }, + "zh": { + "label": "美白强度", + }, + }, } diff --git a/demo/processor.py b/demo/processor.py index afc5f81..28b979e 100644 --- a/demo/processor.py +++ b/demo/processor.py @@ -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( diff --git a/demo/ui.py b/demo/ui.py index 7c50394..6c70b6c 100644 --- a/demo/ui.py +++ b/demo/ui.py @@ -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, diff --git a/hivision/creator/__init__.py b/hivision/creator/__init__.py index 378e1d2..90de7a0 100644 --- a/hivision/creator/__init__.py +++ b/hivision/creator/__init__.py @@ -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, diff --git a/hivision/creator/context.py b/hivision/creator/context.py index 58c8e4d..f59cae3 100644 --- a/hivision/creator/context.py +++ b/hivision/creator/context.py @@ -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__( diff --git a/hivision/plugin/beauty/__init__.py b/hivision/plugin/beauty/__init__.py new file mode 100644 index 0000000..3cfb398 --- /dev/null +++ b/hivision/plugin/beauty/__init__.py @@ -0,0 +1 @@ +from .beauty_tools import BeautyTools diff --git a/hivision/plugin/beauty/beauty_tools.py b/hivision/plugin/beauty/beauty_tools.py new file mode 100644 index 0000000..758f3c7 --- /dev/null +++ b/hivision/plugin/beauty/beauty_tools.py @@ -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 diff --git a/hivision/plugin/beauty/grind_skin.py b/hivision/plugin/beauty/grind_skin.py new file mode 100644 index 0000000..3040284 --- /dev/null +++ b/hivision/plugin/beauty/grind_skin.py @@ -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))) diff --git a/hivision/plugin/beauty/lut/lut_origin.png b/hivision/plugin/beauty/lut/lut_origin.png new file mode 100644 index 0000000..743fc12 Binary files /dev/null and b/hivision/plugin/beauty/lut/lut_origin.png differ diff --git a/hivision/plugin/beauty/thin_face.py b/hivision/plugin/beauty/thin_face.py new file mode 100644 index 0000000..6e92133 --- /dev/null +++ b/hivision/plugin/beauty/thin_face.py @@ -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))) diff --git a/hivision/plugin/beauty/whitening.py b/hivision/plugin/beauty/whitening.py new file mode 100644 index 0000000..562c56c --- /dev/null +++ b/hivision/plugin/beauty/whitening.py @@ -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()