commit PersonMaskUltra, ImageScaleRestoreV2, ImageScaleByAspectRatioV2, ImageRemoveAlpha, ImageCombineAlpha nodes.
@@ -17,6 +17,9 @@ Nodes are divided into 5 groups according to their functions: LayerStyle, LayerC
|
||||
|
||||
## Update
|
||||
**If the dependency package error after updating, please reinstall the relevant dependency packages. for details, please refer to [here](https://github.com/chflame163/ComfyUI_LayerStyle/issues/5).
|
||||
* Commit [ImageRemoveAlpha](#ImageRemoveAlpha) and [ImageCombineAlpha](#ImageCombineAlpha) nodes, alpha channel of the image can be removed or merged.
|
||||
* Commit [ImageScaleRestoreV2](#ImageScaleRestoreV2) and [ImageScaleByAspectRatioV2](#ImageScaleByAspectRatioV2) nodes, supports scaling images to specified long or short edge sizes.
|
||||
* Commit [PersonMaskUltra](#PersonMaskUltra) node, Generate masks for portrait's face, hair, body skin, clothing, or accessories. the model code for this node comes from [a-person-mask-generator](https://github.com/djbielejeski/a-person-mask-generator).
|
||||
* Commit [LightLeak](#LightLeak) node, this filter simulate the light leakage effect of the film.
|
||||
* Commit [Film](#Film) node, this filter simulate the grain, dark edge, and blurred edge of the film, support input depth map to simulate defocus. it is reorganize and encapsulate of [digitaljohn/comfyui-propost](https://github.com/digitaljohn/comfyui-propost).
|
||||
* Commit [ImageAutoCrop](#ImageAutoCrop) node, which is designed to generate image materials for training models.
|
||||
@@ -376,6 +379,15 @@ Outputs:
|
||||
* mask: If have mask input, the scaled mask will be output.
|
||||
* original_size: The original size data of the image is used for subsequent node recovery.
|
||||
|
||||
### <a id="table1">ImageScaleRestoreV2</a>
|
||||
The V2 upgraded version of ImageScaleRestore.
|
||||
|
||||
Node options:
|
||||

|
||||
The following changes have been made based on ImageScaleRestore:
|
||||
* scale_by: Allow scaling by specified size on long or short sides.
|
||||
* scale_by_length: When scale_by is set to "longest", this will be used as the length of the long edge of the image; When set to "shortest", it serves as the length of the short edge.
|
||||
|
||||
### <a id="table1">ImageMaskScaleAs</a>
|
||||
Scale the image or mask to the size of the reference image (or reference mask).
|
||||

|
||||
@@ -417,6 +429,15 @@ The _fill_ mode does not maintain frame ratio and fills the screen with width an
|
||||
* longest_side: When the scale_by_longest_side is set to True, this will be used this value to the long edge of the image. when the original_size have input, this setting will be ignored.
|
||||
|
||||
|
||||
### <a id="table1">ImageScaleByAspectRatioV2</a>
|
||||
V2 Upgraded Version of ImageScaleByAspectRatio
|
||||
|
||||
Node options:
|
||||

|
||||
The following changes have been made based on ImageScaleByAspectRatio:
|
||||
* scale_to_side: Allow scaling by specified size on long or short sides.
|
||||
* scale_to_length: When scale_by_side is set to "longest", this will be used as the length of the long edge of the image; When set to "shortest", it serves as the length of the short edge.
|
||||
|
||||
### <a id="table1">ImageShift</a>
|
||||
Shift the image. this node supports the output of displacement seam masks, making it convenient to create continuous textures.
|
||||

|
||||
@@ -599,6 +620,20 @@ Node options:
|
||||

|
||||
* mode: Channel mode, include RGBA, YCbCr, LAB adn HSV.
|
||||
|
||||
### <a id="table1">ImageRemoveAlpha</a>
|
||||

|
||||
Remove the alpha channel from the image and convert it to RGB mode. you can choose to fill the background and set the background color.
|
||||
|
||||
Node options:
|
||||

|
||||
* fill_background: Whether to fill the background.
|
||||
* background_color<sup>4</sup>: Color of background.
|
||||
|
||||
|
||||
### <a id="table1">ImageCombineAlpha</a>
|
||||

|
||||
Merge the image and mask into an RGBA mode image containing an alpha channel.
|
||||
|
||||
### <a id="table1">ImageAutoCrop</a>
|
||||

|
||||
Automatically cutout and crop the image according to the mask. it can specify the background color, aspect ratio, and size for output image. this node is designed to generate the image materials for training models.
|
||||
@@ -711,6 +746,26 @@ Node options:
|
||||
* white_point: Edge white sampling threshold.
|
||||
* process_detail: Set to false here will skip edge processing to save runtime.
|
||||
|
||||
### <a id="table1">PersonMaskUltra</a>
|
||||
Generate masks for portrait's face, hair, body skin, clothing, or accessories. Compared to the previous A Person Mask Generator node, this node has ultra-high edge details.
|
||||
The model code for this node comes from [a-person-mask-generator](https://github.com/djbielejeski/a-person-mask-generator), edge processing code from [ComfyUI-Image-Filters](https://github.com/spacepxl/ComfyUI-Image-Filters).
|
||||
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
* face: Face recognition.
|
||||
* hair: Hair recognition.
|
||||
* body: Body skin recognition.
|
||||
* clothes: Clothing recognition.
|
||||
* accessories: Identification of accessories (such as backpacks).
|
||||
* background: Background recognition.
|
||||
* confidence: Recognition threshold, lower values will output more mask ranges.
|
||||
* detail_range: Edge detail range.
|
||||
* black_point: Edge black sampling threshold.
|
||||
* white_point: Edge white sampling threshold.
|
||||
* process_detail: Set to false here will skip edge processing to save runtime.
|
||||
|
||||
### <a id="table1">PixelSpread</a>
|
||||
Pixel expansion preprocessing on the masked edge of an image can effectively improve the edges of image composit.
|
||||

|
||||
|
||||
@@ -13,52 +13,55 @@
|
||||
|
||||
## 更新说明
|
||||
**如果本插件更新后出现依赖包错误,请重新安装相关依赖包。详情见[这里](https://github.com/chflame163/ComfyUI_LayerStyle/issues/5)。
|
||||
* 添加[LightLeak](#LightLeak) 节点,这个滤镜模拟胶片漏光效果。
|
||||
* 添加[Film](#Film) 节点, 这个滤镜模拟胶片的颗粒、暗边和边缘模糊,支持输入深度图模拟虚焦,是[digitaljohn/comfyui-propost](https://github.com/digitaljohn/comfyui-propost)的重新封装。
|
||||
* 添加[ImageAutoCrop](#ImageAutoCrop) 节点, 这个节点是为生成训练模型的图片素材而设计的。
|
||||
* 添加[ImageScaleByAspectRatio](#ImageScaleByAspectRatio) 节点, 可按画幅比例缩放图像。
|
||||
* 改正[LUT Apply](#LUT) 节点渲染出现色阶的bug, 并增加log色彩空间支持。*log色彩空间图片请加载专门的log lut。
|
||||
* 添加[CreateGradientMask](#CreateGradientMask) 节点。添加 [LayerImageTransform](#LayerImageTransform) 和 [LayerMaskTransform](#LayerMaskTransform) 节点。
|
||||
* 添加[MaskEdgeUltraDetail](#MaskEdgeUltraDetail) 节点,给粗糙的遮罩进行处理获得超精细的边缘。添加 [Exposure](#Exposure) 节点,调整图像曝光。
|
||||
* 添加[Sharp & Soft](#Sharp) 节点,可提升或抹平图像细节。新增[MaskByDifferent](#MaskByDifferent)节点,比较两张图片并输出Mask。新增[SegmentAnythingUltra](#SegmentAnythingUltra)节点,提升遮罩边缘质量。*如果没有安装SegmentAnything, 需要手动下载模型。
|
||||
* 添加 [ImageRemoveAlpha](#ImageRemoveAlpha) 和 [ImageCombineAlpha](#ImageCombineAlpha) 节点,可移除或合并图片的alpha通道。
|
||||
* 添加 [ImageScaleRestoreV2](#ImageScaleRestoreV2) 和 [ImageScaleByAspectRatioV2](#ImageScaleByAspectRatioV2) 节点。支持按指定的长边或短边尺寸缩放图像。
|
||||
* 添加 [PersonMaskUltra](#PersonMaskUltra) 节点,为人物生成脸、头发、身体皮肤、衣服或配饰的遮罩。本节点的模型代码来自[a-person-mask-generator](https://github.com/djbielejeski/a-person-mask-generator)。
|
||||
* 添加 [LightLeak](#LightLeak) 节点,这个滤镜模拟胶片漏光效果。
|
||||
* 添加 [Film](#Film) 节点, 这个滤镜模拟胶片的颗粒、暗边和边缘模糊,支持输入深度图模拟虚焦,是[digitaljohn/comfyui-propost](https://github.com/digitaljohn/comfyui-propost)的重新封装。
|
||||
* 添加 [ImageAutoCrop](#ImageAutoCrop) 节点, 这个节点是为生成训练模型的图片素材而设计的。
|
||||
* 添加 [ImageScaleByAspectRatio](#ImageScaleByAspectRatio) 节点, 可按画幅比例缩放图像。
|
||||
* 改正 [LUT Apply](#LUT) 节点渲染出现色阶的bug, 并增加log色彩空间支持。*log色彩空间图片请加载专门的log lut。
|
||||
* 添加 [CreateGradientMask](#CreateGradientMask) 节点。添加 [LayerImageTransform](#LayerImageTransform) 和 [LayerMaskTransform](#LayerMaskTransform) 节点。
|
||||
* 添加 [MaskEdgeUltraDetail](#MaskEdgeUltraDetail) 节点,给粗糙的遮罩进行处理获得超精细的边缘。添加 [Exposure](#Exposure) 节点,调整图像曝光。
|
||||
* 添加 [Sharp & Soft](#Sharp) 节点,可提升或抹平图像细节。新增[MaskByDifferent](#MaskByDifferent)节点,比较两张图片并输出Mask。新增[SegmentAnythingUltra](#SegmentAnythingUltra)节点,提升遮罩边缘质量。*如果没有安装SegmentAnything, 需要手动下载模型。
|
||||
* 所有节点已全面支持批量图片,为创作视频提供方便。( CropByMask 节点仅支持相同尺寸的切除, 如果输入批量mask_for_crop,将使用第一张的数据。)
|
||||
* 添加[RemBgUltra](#RemBgUltra) 和 [PixelSpread](#PixelSpread) 节点,显著提升了遮罩质量。*RemBgUltra需手动下载模型。
|
||||
* 添加[TextImage](#TextImage) 节点,生成文字图像和遮罩。
|
||||
* 添加 [RemBgUltra](#RemBgUltra) 和 [PixelSpread](#PixelSpread) 节点,显著提升了遮罩质量。*RemBgUltra需手动下载模型。
|
||||
* 添加 [TextImage](#TextImage) 节点,生成文字图像和遮罩。
|
||||
* 图像之间的[混合模式](#混合模式)增加新类型,现在支持多达19种混合模式。新增color_burn颜色加深, color_dodge颜色减淡, linear_burn线性加深, linear_dodge线性减淡, overlay叠加, soft_light柔光, hard_light强光, vivid_light亮光, pin_light点光, linear_light线性光, hard_mix实色混合。新增的混合模式适用于所有支持混合模式的节点。
|
||||
* 添加[ColorMap](#ColorMap) 滤镜节点,用于制作伪彩色热力图效果。
|
||||
* 添加[WaterColor](#WaterColor) 和 [SkinBeauty](#SkinBeauty) 节点。这是两个图像滤镜,生成水彩画和磨皮效果。
|
||||
* 添加[ImageShift](#ImageShift) 节点,使图片产生位移,可输出位移接缝遮罩,方便制作连续贴图。
|
||||
* 添加[ImageMaskScaleAs](#ImageMaskScaleAs) 节点,可根据参考图片调整图像或遮罩大小。
|
||||
* 添加[ImageScaleRestore](#ImageScaleRestore) 节点,用于配合CropByMask进行局部放大修复工作。
|
||||
* 添加[CropByMask](#CropByMask) 和 [RestoreCropBox](#RestoreCropBox)节点。此二者配合可将图片局部裁切重绘然后还原。
|
||||
* 添加[ColorAdapter](#ColorAdapter) 节点,可自动调整图片色调。
|
||||
* 添加[MaskStroke](#MaskStroke) 节点,可产生mask描边。
|
||||
* 添加[LayerColor](#LayerColor)节点组,用于调整图像颜色。包括[LUT Apply](#LUT),[Gamma](#Gamma), [Brightness & Contrast](#Brightness), [RGB](#RGB), [YUV](#YUV), [LAB](#LAB)和[HSV](#HSV)。
|
||||
* 添加[ImageChannelSplit](#ImageChannelSplit)和[ImageChannelMerge](#ImageChannelMerge)节点。
|
||||
* 添加[MaskMotionBlur](#MaskMotionBlur)节点。
|
||||
* 添加[SoftLight](#SoftLight)节点。
|
||||
* 添加[ChannelShake](#ChannelShake)节点,这是一个滤镜,能产生类似抖音logo的通道错位效果。
|
||||
* 添加[MaskGradient](#MaskGradient)节点,可使mask产生渐变。
|
||||
* 添加[GetColorTone](#GetColorTone)节点,可以获取图片的主色或平均色。添加[MaskGrow](#MaskGrow)和[MaskEdgeShrink](#MaskEdgeShrink)节点。
|
||||
* 添加 [ColorMap](#ColorMap) 滤镜节点,用于制作伪彩色热力图效果。
|
||||
* 添加 [WaterColor](#WaterColor) 和 [SkinBeauty](#SkinBeauty) 节点。这是两个图像滤镜,生成水彩画和磨皮效果。
|
||||
* 添加 [ImageShift](#ImageShift) 节点,使图片产生位移,可输出位移接缝遮罩,方便制作连续贴图。
|
||||
* 添加 [ImageMaskScaleAs](#ImageMaskScaleAs) 节点,可根据参考图片调整图像或遮罩大小。
|
||||
* 添加 [ImageScaleRestore](#ImageScaleRestore) 节点,用于配合CropByMask进行局部放大修复工作。
|
||||
* 添加 [CropByMask](#CropByMask) 和 [RestoreCropBox](#RestoreCropBox)节点。此二者配合可将图片局部裁切重绘然后还原。
|
||||
* 添加 [ColorAdapter](#ColorAdapter) 节点,可自动调整图片色调。
|
||||
* 添加 [MaskStroke](#MaskStroke) 节点,可产生mask描边。
|
||||
* 添加 [LayerColor](#LayerColor)节点组,用于调整图像颜色。包括[LUT Apply](#LUT),[Gamma](#Gamma), [Brightness & Contrast](#Brightness), [RGB](#RGB), [YUV](#YUV), [LAB](#LAB)和[HSV](#HSV)。
|
||||
* 添加 [ImageChannelSplit](#ImageChannelSplit)和[ImageChannelMerge](#ImageChannelMerge)节点。
|
||||
* 添加 [MaskMotionBlur](#MaskMotionBlur)节点。
|
||||
* 添加 [SoftLight](#SoftLight)节点。
|
||||
* 添加 [ChannelShake](#ChannelShake)节点,这是一个滤镜,能产生类似抖音logo的通道错位效果。
|
||||
* 添加 [MaskGradient](#MaskGradient)节点,可使mask产生渐变。
|
||||
* 添加 [GetColorTone](#GetColorTone)节点,可以获取图片的主色或平均色。添加[MaskGrow](#MaskGrow)和[MaskEdgeShrink](#MaskEdgeShrink)节点。
|
||||
|
||||
* 添加[MaskBoxDetect](#MaskBoxDetect)节点,可以通过mask自动探测位置并输出到合成节点。添加[XY to Percent](#Percent)节点,将绝对坐标转换为percent坐标。添加[GaussianBlur](#GaussianBlur)节点。添加[GetImageSize](#GetImageSize)节点。
|
||||
* 添加[ExtendCanvas](#ExtendCanvas)节点。
|
||||
* 添加[ImageBlendAdvance](#ImageBlendAdvance)节点。这个节点允许合成尺寸不同的背景图和图层,提供了更加自由的合成体验。
|
||||
添加[PrintInfo](#PrintInfo)节点作为工作流调试辅助工具。
|
||||
* 添加[ColorImage](#ColorImage)和[GradientImage](#GradientImage)节点,用于生成纯色和渐变色图像。
|
||||
* 添加[GradientOverlay](#GradientOverlay),[ColorOverlay](#ColorOverlay)节点。增加无效mask输入判断,当输入无效mask时将其忽略。
|
||||
* 添加[InnerGlow](#InnerGlow), [InnerShadow](#InnerShadow), [MotionBlur](#MotionBlur)节点。
|
||||
* 添加 [MaskBoxDetect](#MaskBoxDetect)节点,可以通过mask自动探测位置并输出到合成节点。添加[XY to Percent](#Percent)节点,将绝对坐标转换为percent坐标。添加[GaussianBlur](#GaussianBlur)节点。添加[GetImageSize](#GetImageSize)节点。
|
||||
* 添加 [ExtendCanvas](#ExtendCanvas)节点。
|
||||
* 添加 [ImageBlendAdvance](#ImageBlendAdvance)节点。这个节点允许合成尺寸不同的背景图和图层,提供了更加自由的合成体验。
|
||||
添加 [PrintInfo](#PrintInfo)节点作为工作流调试辅助工具。
|
||||
* 添加 [ColorImage](#ColorImage)和[GradientImage](#GradientImage)节点,用于生成纯色和渐变色图像。
|
||||
* 添加 [GradientOverlay](#GradientOverlay),[ColorOverlay](#ColorOverlay)节点。增加无效mask输入判断,当输入无效mask时将其忽略。
|
||||
* 添加 [InnerGlow](#InnerGlow), [InnerShadow](#InnerShadow), [MotionBlur](#MotionBlur)节点。
|
||||
* 所有已完成的节点重新命名,节点分为4组:LayerStyle, LayerMask, LayerUtility, LayerFilter。
|
||||
因为重新命名,包含旧版节点的工作流需手动替换新版节点。
|
||||
* [OuterGlow](#OuterGlow)节点修改,增加亮度、灯光颜色、辉光颜色选项。
|
||||
* 添加[MaskInvert](#MaskInvert)节点。
|
||||
* 添加[Stroke](#Stroke)节点。
|
||||
* 添加[MaskPreview](#MaskPreview)节点。
|
||||
* 添加[ImageOpacity](#ImageOpacity)节点。
|
||||
* 添加 [MaskInvert](#MaskInvert)节点。
|
||||
* 添加 [Stroke](#Stroke)节点。
|
||||
* 添加 [MaskPreview](#MaskPreview)节点。
|
||||
* 添加 [ImageOpacity](#ImageOpacity)节点。
|
||||
* layer_mask修改为非必选, 默认使用layer_image的alpha通道,允许通过输入mask改变之,但是尺寸必须一致。
|
||||
* 添加[ImageBlend](#ImageBlend)节点。
|
||||
* 添加[OuterGlow](#OuterGlow)节点。
|
||||
* 首个节点[DropShadow](#DropShadow)提交。
|
||||
* 添加 [ImageBlend](#ImageBlend)节点。
|
||||
* 添加 [OuterGlow](#OuterGlow)节点。
|
||||
* 首个节点 [DropShadow](#DropShadow)提交。
|
||||
|
||||
|
||||
# <a id="table1">LayerStyle</a>
|
||||
@@ -365,6 +368,16 @@
|
||||
* mask: 如果有mask输入,将输出缩放后的mask。
|
||||
* original_size: 图像的原始大小数据,用于后续节点进行恢复。
|
||||
|
||||
### <a id="table1">ImageScaleRestoreV2</a>
|
||||
ImageScaleRestore的V2升级版。
|
||||
|
||||
节点选项说明:
|
||||

|
||||
在ImageScaleRestore基础上做了如下改变:
|
||||
* scale_by: 允许按长边或短边指定尺寸缩放。
|
||||
* scale_by_length: scale_by被设置为"longest"时,此项将作为是图像长边的长度; 设置为"shortest"时,作为短边的长度。
|
||||
|
||||
|
||||
### <a id="table1">ImageMaskScaleAs</a>
|
||||
将图像或遮罩缩放到参考图像(或遮罩)的大小。
|
||||

|
||||
@@ -406,6 +419,15 @@
|
||||
* mask: 如果有mask输入,将输出缩放后的遮罩。
|
||||
* original_size: 图像的原始大小数据,用于后续节点进行恢复。
|
||||
|
||||
### <a id="table1">ImageScaleByAspectRatioV2</a>
|
||||
ImageScaleByAspectRatio的V2升级版
|
||||
|
||||
节点选项说明:
|
||||

|
||||
在ImageScaleByAspectRatio基础上做了如下改变:
|
||||
* scale_to_side: 允许按长边或短边指定尺寸缩放。
|
||||
* scale_to_length: scale_by_side被设置为"longest"时,此项将作为是图像长边的长度; 设置为"shortest"时,作为短边的长度。
|
||||
|
||||
### <a id="table1">ImageShift</a>
|
||||
使图片产生位移。此节点支持位移接缝遮罩的输出,方便制作连续贴图。
|
||||

|
||||
@@ -586,6 +608,21 @@
|
||||

|
||||
* mode: 通道模式。包含RGBA, YCbCr, LAB和HSV。
|
||||
|
||||
### <a id="table1">ImageRemoveAlpha</a>
|
||||

|
||||
移除图片的alpha通道,将图片转换为RGB模式。可选择填充背景以及设置背景颜色。
|
||||
|
||||
节点选项说明:
|
||||

|
||||
* fill_background: 是否填充背景。
|
||||
* background_color<sup>4</sup>: 背景颜色。
|
||||
|
||||
|
||||
### <a id="table1">ImageCombineAlpha</a>
|
||||

|
||||
将图片与遮罩合并为包含alpha通道的RGBA模式的图片。
|
||||
|
||||
|
||||
### <a id="table1">ImageAutoCrop</a>
|
||||

|
||||
自动抠图并按照遮罩裁切图片。可指定生成图片的背景颜色、长宽比和大小。这个节点是为生成训练模型的图片素材而设计的。
|
||||
@@ -700,6 +737,27 @@ cropped_mask:
|
||||
* white_point: 边缘黑色采样阈值。
|
||||
* process_detail: 此处设为False将跳过边缘处理以节省运行时间。
|
||||
|
||||
### <a id="table1">PersonMaskUltra</a>
|
||||
为人物生成脸、头发、身体皮肤、衣服或配饰的遮罩。与之前的A Person Mask Generator节点相比,这个节点具有超高的边缘细节。
|
||||
本节点的模型代码来自[a-person-mask-generator](https://github.com/djbielejeski/a-person-mask-generator),边缘处理代码来自spacepxl的[ComfyUI-Image-Filters](https://github.com/spacepxl/ComfyUI-Image-Filters)。
|
||||
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
* face: 脸部识别。
|
||||
* hair: 头发识别。
|
||||
* body: 身体皮肤识别。
|
||||
* clothes: 衣服识别。
|
||||
* accessories: 配饰(例如背包)识别。
|
||||
* background: 背景识别。
|
||||
* confidence: 识别阈值,更低的值将输出更多的遮罩范围。
|
||||
* detail_range: 边缘细节范围。
|
||||
* black_point: 边缘黑色采样阈值。
|
||||
* white_point: 边缘黑色采样阈值。
|
||||
* process_detail: 此处设为False将跳过边缘处理以节省运行时间。
|
||||
|
||||
|
||||
### <a id="table1">PixelSpread</a>
|
||||
对图像的遮罩边缘部分进行像素扩张预处理,可有效改善图像合成的边缘。
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 161 KiB |
@@ -0,0 +1,59 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'ImageCombineAlpha'
|
||||
|
||||
class ImageCombineAlpha:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
channel_mode = ['RGBA', 'YCbCr', 'LAB', 'HSV']
|
||||
return {
|
||||
"required": {
|
||||
"RGB_image": ("IMAGE", ), #
|
||||
"mask": ("MASK",), #
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("RGBA_image",)
|
||||
FUNCTION = 'image_combine_alpha'
|
||||
CATEGORY = '😺dzNodes/LayerUtility'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def image_combine_alpha(self, RGB_image, mask):
|
||||
|
||||
ret_images = []
|
||||
input_images = []
|
||||
input_masks = []
|
||||
|
||||
for i in RGB_image:
|
||||
input_images.append(torch.unsqueeze(i, 0))
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
for m in mask:
|
||||
input_masks.append(torch.unsqueeze(m, 0))
|
||||
|
||||
max_batch = max(len(input_images), len(input_masks))
|
||||
for i in range(max_batch):
|
||||
_image = input_images[i] if i < len(input_images) else input_images[-1]
|
||||
_mask = input_masks[i] if i < len(input_masks) else input_masks[-1]
|
||||
r, g, b, _ = image_channel_split(tensor2pil(_image).convert('RGB'), 'RGB')
|
||||
ret_image = image_channel_merge((r, g, b, tensor2pil(_mask).convert('L')), 'RGBA')
|
||||
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerUtility: ImageCombineAlpha": ImageCombineAlpha
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerUtility: ImageCombineAlpha": "LayerUtility: ImageCombineAlpha"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'ImageRemoveAlpha'
|
||||
|
||||
class ImageRemoveAlpha:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"RGBA_image": ("IMAGE", ), #
|
||||
"fill_background": ("BOOLEAN", {"default": False}),
|
||||
"background_color": ("STRING", {"default": "#000000"}),
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
RETURN_NAMES = ("RGB_image", )
|
||||
FUNCTION = 'image_remove_alpha'
|
||||
CATEGORY = '😺dzNodes/LayerUtility'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def image_remove_alpha(self, RGBA_image, fill_background, background_color):
|
||||
|
||||
ret_images = []
|
||||
|
||||
for i in RGBA_image:
|
||||
i = torch.unsqueeze(i, 0)
|
||||
_image = tensor2pil(i)
|
||||
if _image.mode != "RGBA":
|
||||
log(f"Error: {NODE_NAME} skipped, because the input image is not RGBA.", message_type='error')
|
||||
return (RGBA_image)
|
||||
if fill_background:
|
||||
alpha = _image.split()[-1]
|
||||
ret_image = Image.new('RGB', size=_image.size, color=background_color)
|
||||
ret_image.paste(_image, mask=alpha)
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
else:
|
||||
ret_images.append(pil2tensor(tensor2pil(i).convert('RGB')))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0), )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerUtility: ImageRemoveAlpha": ImageRemoveAlpha
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerUtility: ImageRemoveAlpha": "LayerUtility: ImageRemoveAlpha"
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import torch
|
||||
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'ImageScaleByAspectRatio V2'
|
||||
|
||||
class ImageScaleByAspectRatioV2:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
ratio_list = ['original', 'custom', '1:1', '3:2', '4:3', '16:9', '2:3', '3:4', '9:16']
|
||||
fit_mode = ['letterbox', 'crop', 'fill']
|
||||
method_mode = ['lanczos', 'bicubic', 'hamming', 'bilinear', 'box', 'nearest']
|
||||
multiple_list = ['8', '16', 'None']
|
||||
scale_to_list = ['None', 'longest', 'shortest']
|
||||
return {
|
||||
"required": {
|
||||
"aspect_ratio": (ratio_list,),
|
||||
"proportional_width": ("INT", {"default": 1, "min": 1, "max": 999, "step": 1}),
|
||||
"proportional_height": ("INT", {"default": 1, "min": 1, "max": 999, "step": 1}),
|
||||
"fit": (fit_mode,),
|
||||
"method": (method_mode,),
|
||||
"round_to_multiple": (multiple_list,),
|
||||
"scale_to_side": (scale_to_list,), # 是否按长边缩放
|
||||
"scale_to_length": ("INT", {"default": 1024, "min": 4, "max": 999999, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"image": ("IMAGE",), #
|
||||
"mask": ("MASK",), #
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "BOX",)
|
||||
RETURN_NAMES = ("image", "mask", "original_size")
|
||||
FUNCTION = 'image_scale_by_aspect_ratio'
|
||||
CATEGORY = '😺dzNodes/LayerUtility'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def image_scale_by_aspect_ratio(self, aspect_ratio, proportional_width, proportional_height,
|
||||
fit, method, round_to_multiple, scale_to_side, scale_to_length,
|
||||
image=None, mask = None,
|
||||
):
|
||||
orig_images = []
|
||||
orig_masks = []
|
||||
orig_width = 0
|
||||
orig_height = 0
|
||||
target_width = 0
|
||||
target_height = 0
|
||||
ratio = 1.0
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
if image is not None:
|
||||
for i in image:
|
||||
i = torch.unsqueeze(i, 0)
|
||||
orig_images.append(i)
|
||||
orig_width, orig_height = tensor2pil(orig_images[0]).size
|
||||
if mask is not None:
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
for m in mask:
|
||||
m = torch.unsqueeze(m, 0)
|
||||
orig_masks.append(m)
|
||||
_width, _height = tensor2pil(orig_masks[0]).size
|
||||
if (orig_width > 0 and orig_width != _width) or (orig_height > 0 and orig_height != _height):
|
||||
log(f"Error: {NODE_NAME} skipped, because the mask is does'nt match image.", message_type='error')
|
||||
return (None, None,)
|
||||
elif orig_width + orig_height == 0:
|
||||
orig_width = _width
|
||||
orig_height = _height
|
||||
|
||||
if orig_width + orig_height == 0:
|
||||
log(f"Error: {NODE_NAME} skipped, because the image or mask at least one must be input.", message_type='error')
|
||||
return (None, None,)
|
||||
|
||||
if aspect_ratio == 'original':
|
||||
ratio = orig_width / orig_height
|
||||
elif aspect_ratio == 'custom':
|
||||
ratio = proportional_width / proportional_height
|
||||
else:
|
||||
s = aspect_ratio.split(":")
|
||||
ratio = int(s[0]) / int(s[1])
|
||||
|
||||
# calculate target width and height
|
||||
if orig_width > orig_height:
|
||||
if scale_to_side == 'longest':
|
||||
target_width = scale_to_length
|
||||
target_height = int(target_width / ratio)
|
||||
elif scale_to_side == 'shortest':
|
||||
target_height = scale_to_length
|
||||
target_width = int(target_height * ratio)
|
||||
else:
|
||||
target_width = orig_width
|
||||
target_height = int(target_width / ratio)
|
||||
else:
|
||||
if scale_to_side == 'longest':
|
||||
target_height = scale_to_length
|
||||
target_width = int(target_height * ratio)
|
||||
elif scale_to_side == 'shortest':
|
||||
target_width = scale_to_length
|
||||
target_height = int(target_width / ratio)
|
||||
else:
|
||||
target_height = orig_height
|
||||
target_width = int(target_height * ratio)
|
||||
|
||||
if round_to_multiple != 'None':
|
||||
multiple = int(round_to_multiple)
|
||||
target_width = num_round_to_multiple(target_width, multiple)
|
||||
target_height = num_round_to_multiple(target_height, multiple)
|
||||
|
||||
_mask = Image.new('L', size=(target_width, target_height), color='black')
|
||||
_image = Image.new('RGB', size=(target_width, target_height), color='black')
|
||||
|
||||
resize_sampler = Image.LANCZOS
|
||||
if method == "bicubic":
|
||||
resize_sampler = Image.BICUBIC
|
||||
elif method == "hamming":
|
||||
resize_sampler = Image.HAMMING
|
||||
elif method == "bilinear":
|
||||
resize_sampler = Image.BILINEAR
|
||||
elif method == "box":
|
||||
resize_sampler = Image.BOX
|
||||
elif method == "nearest":
|
||||
resize_sampler = Image.NEAREST
|
||||
|
||||
if len(orig_images) > 0:
|
||||
for i in orig_images:
|
||||
_image = tensor2pil(i).convert('RGB')
|
||||
_image = fit_resize_image(_image, target_width, target_height, fit, resize_sampler)
|
||||
ret_images.append(pil2tensor(_image))
|
||||
if len(orig_masks) > 0:
|
||||
for m in orig_masks:
|
||||
_mask = tensor2pil(m).convert('L')
|
||||
_mask = fit_resize_image(_mask, target_width, target_height, fit, resize_sampler).convert('L')
|
||||
ret_masks.append(image2mask(_mask))
|
||||
if len(ret_images) > 0 and len(ret_masks) >0:
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
|
||||
elif len(ret_images) > 0 and len(ret_masks) == 0:
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0), None,)
|
||||
elif len(ret_images) == 0 and len(ret_masks) > 0:
|
||||
log(f"{NODE_NAME} Processed {len(ret_masks)} image(s).", message_type='finish')
|
||||
return (None, torch.cat(ret_masks, dim=0),)
|
||||
else:
|
||||
log(f"Error: {NODE_NAME} skipped, because the available image or mask is not found.", message_type='error')
|
||||
return (None, None,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerUtility: ImageScaleByAspectRatio V2": ImageScaleByAspectRatioV2
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerUtility: ImageScaleByAspectRatio V2": "LayerUtility: ImageScaleByAspectRatio V2"
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'ImageScaleRestore V2'
|
||||
|
||||
class ImageScaleRestoreV2:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
method_mode = ['lanczos', 'bicubic', 'hamming', 'bilinear', 'box', 'nearest']
|
||||
scale_by_list = ['by_scale', 'longest', 'shortest']
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
"scale": ("FLOAT", {"default": 1, "min": 0.01, "max": 100, "step": 0.01}),
|
||||
"method": (method_mode,),
|
||||
"scale_by": (scale_by_list,), # 是否按长边缩放
|
||||
"scale_by_length": ("INT", {"default": 1024, "min": 4, "max": 999999, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
"mask": ("MASK",), #
|
||||
"original_size": ("BOX",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", "BOX",)
|
||||
RETURN_NAMES = ("image", "mask", "original_size")
|
||||
FUNCTION = 'image_scale_restore'
|
||||
CATEGORY = '😺dzNodes/LayerUtility'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def image_scale_restore(self, image, scale, method,
|
||||
scale_by, scale_by_length,
|
||||
mask = None, original_size = None
|
||||
):
|
||||
|
||||
l_images = []
|
||||
l_masks = []
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
for l in image:
|
||||
l_images.append(torch.unsqueeze(l, 0))
|
||||
m = tensor2pil(l)
|
||||
if m.mode == 'RGBA':
|
||||
l_masks.append(m.split()[-1])
|
||||
|
||||
if mask is not None:
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
l_masks = []
|
||||
for m in mask:
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(m, 0)).convert('L'))
|
||||
|
||||
max_batch = max(len(l_images), len(l_masks))
|
||||
|
||||
orig_width, orig_height = tensor2pil(l_images[0]).size
|
||||
if original_size is not None:
|
||||
target_width = original_size[0]
|
||||
target_height = original_size[1]
|
||||
else:
|
||||
target_width = int(orig_width * scale)
|
||||
target_height = int(orig_height * scale)
|
||||
if scale_by == 'longest':
|
||||
if orig_width > orig_height:
|
||||
target_width = scale_by_length
|
||||
target_height = int(target_width * orig_height / orig_width)
|
||||
else:
|
||||
target_height = scale_by_length
|
||||
target_width = int(target_height * orig_width / orig_height)
|
||||
if scale_by == 'shortest':
|
||||
if orig_width < orig_height:
|
||||
target_width = scale_by_length
|
||||
target_height = int(target_width * orig_height / orig_width)
|
||||
else:
|
||||
target_height = scale_by_length
|
||||
target_width = int(target_height * orig_width / orig_height)
|
||||
if target_width < 4:
|
||||
target_width = 4
|
||||
if target_height < 4:
|
||||
target_height = 4
|
||||
resize_sampler = Image.LANCZOS
|
||||
if method == "bicubic":
|
||||
resize_sampler = Image.BICUBIC
|
||||
elif method == "hamming":
|
||||
resize_sampler = Image.HAMMING
|
||||
elif method == "bilinear":
|
||||
resize_sampler = Image.BILINEAR
|
||||
elif method == "box":
|
||||
resize_sampler = Image.BOX
|
||||
elif method == "nearest":
|
||||
resize_sampler = Image.NEAREST
|
||||
|
||||
for i in range(max_batch):
|
||||
|
||||
_image = l_images[i] if i < len(l_images) else l_images[-1]
|
||||
|
||||
_canvas = tensor2pil(_image).convert('RGB')
|
||||
ret_image = _canvas.resize((target_width, target_height), resize_sampler)
|
||||
ret_mask = Image.new('L', size=ret_image.size, color='white')
|
||||
if mask is not None:
|
||||
_mask = l_masks[i] if i < len(l_masks) else l_masks[-1]
|
||||
ret_mask = _mask.resize((target_width, target_height), resize_sampler)
|
||||
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
ret_masks.append(image2mask(ret_mask))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0), [orig_width, orig_height],)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerUtility: ImageScaleRestore V2": ImageScaleRestoreV2
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerUtility: ImageScaleRestore V2": "LayerUtility: ImageScaleRestore V2"
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class MaskEdgeUltraDetail:
|
||||
"mask_grow": ("INT", {"default": 0, "min": -999, "max": 999, "step": 1}),
|
||||
"fix_gap": ("INT", {"default": 0, "min": 0, "max": 32, "step": 1}),
|
||||
"fix_threshold": ("FLOAT", {"default": 0.75, "min": 0.01, "max": 0.99, "step": 0.01}),
|
||||
"detail_range": ("INT", {"default": 12, "min": 0, "max": 256, "step": 1}),
|
||||
"detail_range": ("INT", {"default": 12, "min": 1, "max": 256, "step": 1}),
|
||||
"black_point": ("FLOAT", {"default": 0.01, "min": 0.01, "max": 0.98, "step": 0.01}),
|
||||
"white_point": ("FLOAT", {"default": 0.99, "min": 0.02, "max": 0.99, "step": 0.01}),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
from .imagefunc import *
|
||||
from functools import reduce
|
||||
import wget
|
||||
import mediapipe as mp
|
||||
import folder_paths
|
||||
from .segment_anything_func import *
|
||||
|
||||
NODE_NAME = 'PersonMaskUltra'
|
||||
|
||||
def get_a_person_mask_generator_model_path() -> str:
|
||||
model_folder_name = 'mediapipe'
|
||||
model_name = 'selfie_multiclass_256x256.tflite'
|
||||
|
||||
model_folder_path = os.path.join(folder_paths.models_dir, model_folder_name)
|
||||
model_file_path = os.path.join(model_folder_path, model_name)
|
||||
|
||||
if not os.path.exists(model_file_path):
|
||||
model_url = f'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_multiclass_256x256/float32/latest/{model_name}'
|
||||
print(f"Downloading '{model_name}' model")
|
||||
os.makedirs(model_folder_path, exist_ok=True)
|
||||
wget.download(model_url, model_file_path)
|
||||
|
||||
return model_file_path
|
||||
|
||||
|
||||
class PersonMaskUltra:
|
||||
|
||||
def __init__(self):
|
||||
# download the model if we need it
|
||||
get_a_person_mask_generator_model_path()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required":
|
||||
{
|
||||
"images": ("IMAGE",),
|
||||
"face": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"hair": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"body": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"clothes": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"accessories": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"background": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"confidence": ("FLOAT", {"default": 0.4, "min": 0.05, "max": 0.95, "step": 0.01},),
|
||||
"detail_range": ("INT", {"default": 16, "min": 1, "max": 256, "step": 1}),
|
||||
"black_point": ("FLOAT", {"default": 0.01, "min": 0.01, "max": 0.98, "step": 0.01}),
|
||||
"white_point": ("FLOAT", {"default": 0.99, "min": 0.02, "max": 0.99, "step": 0.01}),
|
||||
"process_detail": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", )
|
||||
RETURN_NAMES = ("image", "mask", )
|
||||
FUNCTION = 'person_mask_ultra'
|
||||
CATEGORY = '😺dzNodes/LayerMask'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def get_mediapipe_image(self, image: Image) -> mp.Image:
|
||||
# Convert image to NumPy array
|
||||
numpy_image = np.asarray(image)
|
||||
image_format = mp.ImageFormat.SRGB
|
||||
# Convert BGR to RGB (if necessary)
|
||||
if numpy_image.shape[-1] == 4:
|
||||
image_format = mp.ImageFormat.SRGBA
|
||||
elif numpy_image.shape[-1] == 3:
|
||||
image_format = mp.ImageFormat.SRGB
|
||||
numpy_image = cv2.cvtColor(numpy_image, cv2.COLOR_BGR2RGB)
|
||||
return mp.Image(image_format=image_format, data=numpy_image)
|
||||
|
||||
def person_mask_ultra(self, images, face, hair, body, clothes,
|
||||
accessories, background, confidence,
|
||||
detail_range, black_point, white_point, process_detail):
|
||||
|
||||
a_person_mask_generator_model_path = get_a_person_mask_generator_model_path()
|
||||
a_person_mask_generator_model_buffer = None
|
||||
with open(a_person_mask_generator_model_path, "rb") as f:
|
||||
a_person_mask_generator_model_buffer = f.read()
|
||||
image_segmenter_base_options = mp.tasks.BaseOptions(model_asset_buffer=a_person_mask_generator_model_buffer)
|
||||
options = mp.tasks.vision.ImageSegmenterOptions(
|
||||
base_options=image_segmenter_base_options,
|
||||
running_mode=mp.tasks.vision.RunningMode.IMAGE,
|
||||
output_category_mask=True)
|
||||
# Create the image segmenter
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
with mp.tasks.vision.ImageSegmenter.create_from_options(options) as segmenter:
|
||||
for image in images:
|
||||
# image = torch.unsqueeze(image, 0)
|
||||
orig_image = tensor2pil(image.unsqueeze(0)).convert('RGB')
|
||||
# Convert the Tensor to a PIL image
|
||||
i = 255. * image.cpu().numpy()
|
||||
image_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
# create our foreground and background arrays for storing the mask results
|
||||
mask_background_array = np.zeros((image_pil.size[0], image_pil.size[1], 4), dtype=np.uint8)
|
||||
mask_background_array[:] = (0, 0, 0, 255)
|
||||
mask_foreground_array = np.zeros((image_pil.size[0], image_pil.size[1], 4), dtype=np.uint8)
|
||||
mask_foreground_array[:] = (255, 255, 255, 255)
|
||||
# Retrieve the masks for the segmented image
|
||||
media_pipe_image = self.get_mediapipe_image(image=image_pil)
|
||||
segmented_masks = segmenter.segment(media_pipe_image)
|
||||
masks = []
|
||||
if background:
|
||||
masks.append(segmented_masks.confidence_masks[0])
|
||||
if hair:
|
||||
masks.append(segmented_masks.confidence_masks[1])
|
||||
if body:
|
||||
masks.append(segmented_masks.confidence_masks[2])
|
||||
if face:
|
||||
masks.append(segmented_masks.confidence_masks[3])
|
||||
if clothes:
|
||||
masks.append(segmented_masks.confidence_masks[4])
|
||||
if accessories:
|
||||
masks.append(segmented_masks.confidence_masks[5])
|
||||
image_data = media_pipe_image.numpy_view()
|
||||
image_shape = image_data.shape
|
||||
# convert the image shape from "rgb" to "rgba" aka add the alpha channel
|
||||
if image_shape[-1] == 3:
|
||||
image_shape = (image_shape[0], image_shape[1], 4)
|
||||
mask_background_array = np.zeros(image_shape, dtype=np.uint8)
|
||||
mask_background_array[:] = (0, 0, 0, 255)
|
||||
mask_foreground_array = np.zeros(image_shape, dtype=np.uint8)
|
||||
mask_foreground_array[:] = (255, 255, 255, 255)
|
||||
mask_arrays = []
|
||||
if len(masks) == 0:
|
||||
mask_arrays.append(mask_background_array)
|
||||
else:
|
||||
for i, mask in enumerate(masks):
|
||||
condition = np.stack((mask.numpy_view(),) * image_shape[-1], axis=-1) > confidence
|
||||
mask_array = np.where(condition, mask_foreground_array, mask_background_array)
|
||||
mask_arrays.append(mask_array)
|
||||
# Merge our masks taking the maximum from each
|
||||
merged_mask_arrays = reduce(np.maximum, mask_arrays)
|
||||
# Create the image
|
||||
mask_image = Image.fromarray(merged_mask_arrays)
|
||||
# convert PIL image to tensor image
|
||||
tensor_mask = mask_image.convert("RGB")
|
||||
tensor_mask = np.array(tensor_mask).astype(np.float32) / 255.0
|
||||
tensor_mask = torch.from_numpy(tensor_mask)[None,]
|
||||
tensor_mask = tensor_mask.squeeze(3)[..., 0]
|
||||
_mask = tensor2pil(tensor_mask).convert('L')
|
||||
if process_detail:
|
||||
_mask = tensor2pil(mask_edge_detail(image.unsqueeze(0), pil2tensor(_mask), detail_range, black_point, white_point))
|
||||
ret_image = RGB2RGBA(orig_image, _mask)
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
ret_masks.append(image2mask(_mask))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerMask: PersonMaskUltra": PersonMaskUltra
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerMask: PersonMaskUltra": "LayerMask: PersonMaskUltra"
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class RemBgUltra:
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"detail_range": ("INT", {"default": 8, "min": 0, "max": 256, "step": 1}),
|
||||
"detail_range": ("INT", {"default": 8, "min": 1, "max": 256, "step": 1}),
|
||||
"black_point": ("FLOAT", {"default": 0.01, "min": 0.01, "max": 0.98, "step": 0.01}),
|
||||
"white_point": ("FLOAT", {"default": 0.99, "min": 0.02, "max": 0.99, "step": 0.01}),
|
||||
"process_detail": ("BOOLEAN", {"default": True}),
|
||||
|
||||
@@ -3,6 +3,9 @@ from .segment_anything_func import *
|
||||
|
||||
NODE_NAME = 'SegmentAnythingUltra'
|
||||
|
||||
SAM_MODEL = None
|
||||
DINO_MODEL = None
|
||||
|
||||
class SegmentAnythingUltra:
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -16,7 +19,7 @@ class SegmentAnythingUltra:
|
||||
"sam_model": (list_sam_model(), ),
|
||||
"grounding_dino_model": (list_groundingdino_model(),),
|
||||
"threshold": ("FLOAT", {"default": 0.3, "min": 0, "max": 1.0, "step": 0.01}),
|
||||
"detail_range": ("INT", {"default": 16, "min": 0, "max": 256, "step": 1}),
|
||||
"detail_range": ("INT", {"default": 16, "min": 1, "max": 256, "step": 1}),
|
||||
"black_point": ("FLOAT", {"default": 0.15, "min": 0.01, "max": 0.98, "step": 0.01}),
|
||||
"white_point": ("FLOAT", {"default": 0.99, "min": 0.02, "max": 0.99, "step": 0.01}),
|
||||
"process_detail": ("BOOLEAN", {"default": True}),
|
||||
@@ -30,23 +33,25 @@ class SegmentAnythingUltra:
|
||||
RETURN_NAMES = ("image", "mask", )
|
||||
FUNCTION = "segment_anything_ultra"
|
||||
CATEGORY = '😺dzNodes/LayerMask'
|
||||
|
||||
|
||||
|
||||
def segment_anything_ultra(self, image, sam_model, grounding_dino_model, threshold,
|
||||
detail_range, black_point, white_point, process_detail,
|
||||
prompt, ):
|
||||
|
||||
sam_model = load_sam_model(sam_model)
|
||||
dino_model = load_groundingdino_model(grounding_dino_model)
|
||||
global SAM_MODEL
|
||||
global DINO_MODEL
|
||||
if SAM_MODEL is None: SAM_MODEL = load_sam_model(sam_model)
|
||||
if DINO_MODEL is None: DINO_MODEL = load_groundingdino_model(grounding_dino_model)
|
||||
ret_images = []
|
||||
ret_masks = []
|
||||
|
||||
for i in image:
|
||||
i = torch.unsqueeze(i, 0)
|
||||
item = tensor2pil(i).convert('RGBA')
|
||||
boxes = groundingdino_predict(dino_model, item, prompt, threshold)
|
||||
boxes = groundingdino_predict(DINO_MODEL, item, prompt, threshold)
|
||||
if boxes.shape[0] == 0:
|
||||
break
|
||||
(_, _mask) = sam_segment(sam_model, item, boxes)
|
||||
(_, _mask) = sam_segment(SAM_MODEL, item, boxes)
|
||||
_mask = _mask[0]
|
||||
if process_detail:
|
||||
_mask = tensor2pil(mask_edge_detail(i, _mask, detail_range, black_point, white_point))
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
{
|
||||
"last_node_id": 41,
|
||||
"last_link_id": 53,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
432,
|
||||
374
|
||||
],
|
||||
"size": [
|
||||
318.6980670166016,
|
||||
271.3352774047852
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
39
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1344x768_hair (31).png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
796,
|
||||
662
|
||||
],
|
||||
"size": [
|
||||
346.13137573242193,
|
||||
223.26862304687506
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 40
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "LayerMask: RemBgUltra",
|
||||
"pos": [
|
||||
811,
|
||||
431
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 150
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 39
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
40,
|
||||
42
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
52
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerMask: RemBgUltra"
|
||||
},
|
||||
"widgets_values": [
|
||||
8,
|
||||
0.01,
|
||||
0.99,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1197,
|
||||
661
|
||||
],
|
||||
"size": [
|
||||
343.2526589385732,
|
||||
223.19213123163888
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 50
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1605,
|
||||
655
|
||||
],
|
||||
"size": [
|
||||
344.6191846358872,
|
||||
226.4373271090924
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 51
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "LayerUtility: ImageRemoveAlpha",
|
||||
"pos": [
|
||||
1242,
|
||||
478
|
||||
],
|
||||
"size": {
|
||||
"0": 254.40000915527344,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "RGBA_image",
|
||||
"type": "IMAGE",
|
||||
"link": 42
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "RGB_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
45,
|
||||
50
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerUtility: ImageRemoveAlpha"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
"#0000F0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"type": "Reroute",
|
||||
"pos": [
|
||||
1339,
|
||||
381
|
||||
],
|
||||
"size": [
|
||||
75,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "*",
|
||||
"link": 52
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
53
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"showOutputText": false,
|
||||
"horizontal": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "LayerUtility: ImageCombineAlpha",
|
||||
"pos": [
|
||||
1642,
|
||||
487
|
||||
],
|
||||
"size": {
|
||||
"0": 260.3999938964844,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "RGB_image",
|
||||
"type": "IMAGE",
|
||||
"link": 45
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 53
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "RGBA_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
51
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerUtility: ImageCombineAlpha"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
39,
|
||||
4,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
40,
|
||||
31,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
42,
|
||||
31,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
45,
|
||||
32,
|
||||
0,
|
||||
34,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
50,
|
||||
32,
|
||||
0,
|
||||
36,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
51,
|
||||
34,
|
||||
0,
|
||||
38,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
52,
|
||||
31,
|
||||
1,
|
||||
39,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
53,
|
||||
39,
|
||||
0,
|
||||
34,
|
||||
1,
|
||||
"MASK"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||