diff --git a/README.md b/README.md index 25a5378..34ef6e2 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ To try to solve this problem has been developed _pre-_ and _post-_ process filte The main filters introduced are: -**Degrain** : Since a BW clip with noise/grain can create artifacts on colored frames, this filter allows to reduce the noise/grain contained in the frames. This filter is applied only to the frames to be colored. At the end of the coloring process the original noise/grain of the clip is restored. The strength of the filter has range [0-5], the suggested value is 3 (if = 0 the filter is not applied). +**Degrain** : Since a BW clip with noise/grain can create artifacts on colored frames (this effect is more evident with DDColor), this filter allows to reduce the noise/grain contained in the frames. This filter is applied only to the frames to be colored by DDColor. At the end of the coloring process the original noise/grain of the clip is restored. The strength of the filter has range [0-5], the suggested value is 1 (if = 0 the filter is not applied). **Chroma Smoothing** : This filter allows to reduce the _vibrancy_ of colors assigned by Deoldify/DDcolor by using the parameters _de-saturation_ and _de-vibrancy_ (the effect on _vibrancy_ will be visible only if the option **chroma resize** is enabled, otherwise this parameter has effect on the _luminosity_). The area impacted by the filter is defined by the thresholds dark/white. All the pixels with luma below the dark threshold will be impacted by the filter, while the pixels above the white threshold will be left untouched. All the pixels in the middle will be gradually impacted depending on the luma value. diff --git a/pyproject.toml b/pyproject.toml index 8289a6a..b316b6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ exclude = [ [project] name = "vsdeoldify" -version = "3.5.0" +version = "3.5.1" description = "Deoldify function for VapourSynth" readme = "README.md" requires-python = ">=3.10" diff --git a/vsdeoldify/__init__.py b/vsdeoldify/__init__.py index 1f58d1d..bf22998 100644 --- a/vsdeoldify/__init__.py +++ b/vsdeoldify/__init__.py @@ -4,7 +4,7 @@ Date: 2024-02-29 version: LastEditors: Dan64 -LastEditTime: 2024-05-03 +LastEditTime: 2024-05-08 ------------------------------------------------------------------------------- Description: ------------------------------------------------------------------------------- @@ -33,7 +33,7 @@ warnings.filterwarnings("ignore", category=UserWarning, message="Arguments other than a weight enum or `None`.*?") warnings.filterwarnings("ignore", category=UserWarning, message="torch.nn.utils.weight_norm is deprecated.*?") -__version__ = "3.5.0" +__version__ = "3.5.1" package_dir = os.path.dirname(os.path.realpath(__file__)) model_dir = os.path.join(package_dir, "models") @@ -92,8 +92,9 @@ def ddeoldify_main(clip: vs.VideoNode, Preset: str = 'Fast', ColorFix: str = 'Vi 'Green->Red', 'Green->Blue', 'Red->Brown', - 'Red->Blue' - :param degrain_strength: strenght of denoise/degrain pre-filter applied on BW clip, if = 0 the pre-filter is disabled, range [0-5], suggested = 3 + 'Red->Blue' + 'Yellow->Rose' + :param degrain_strength: strenght of denoise/degrain pre-filter applied on BW clip, if = 0 the pre-filter is disabled, range [0-5], default = 0 :param enable_fp16: Enable/disable FP16 in ddcolor inference, range [True, False] """ # Select presets / tuning @@ -143,8 +144,8 @@ def ddeoldify_main(clip: vs.VideoNode, Preset: str = 'Fast', ColorFix: str = 'Vi # Select Color Mapping ColorMap = ColorMap.lower() - colormap = ['none', 'blue->brown', 'blue->red', 'blue->green', 'green->brown', 'green->red', 'green->blue', 'red->brown', 'red->blue'] - hue_map = ["none", "180:280|+140,0.1", "180:280|+100,0.1", "180:280|+220,0.1", "80:180|+260,0.1", "80:180|+220,0.1", "80:180|+140,0.1", "300:360,0:20|+40,0.3", "300:360,0:20|+260,0.3"] + colormap = ['none', 'blue->brown', 'blue->red', 'blue->green', 'green->brown', 'green->red', 'green->blue', 'red->brown', 'red->blue', 'yellow->rose'] + hue_map = ["none", "180:280|+140,0.4", "180:280|+100,0.4", "180:280|+220,0.4", "80:180|+260,0.4", "80:180|+220,0.4", "80:180|+140,0.4", "300:360,0:20|+40,0.6", "300:360,0:20|+260,0.6", "30:90|+300,0.8"] try: cl_id = colormap.index(ColorMap) @@ -265,7 +266,7 @@ def ddeoldify( for a movie with a lot of dark scenes is suggested alpha > 1, if=0 is not activated, range [>=0] [7] : gamma_min: minimum value for gamma, range (default=0.5) [>0.1] [8] : "chroma adjustment" parameter (optional), if="none" is disabled (see the README) - :param degrain_strength: strenght of denoise/degrain pre-filter applied on BW clip, if = 0 the pre-filter is disabled, range [0-5], suggested = 3 + :param degrain_strength: strenght of denoise/degrain pre-filter applied on BW clip, if = 0 the pre-filter is disabled, range [0-5], default = 0 :param cmc_tresh: chroma_threshold (%), used by: Constrained "Chroma Merge range" [0-1] (0.01=1%) :param lmm_p: parameters for method: "Luma Masked Merge" (see method=4 for a full explanation) [0] : luma_mask_limit: luma limit for build the mask used in Luma Masked Merge, range [0-1] (0.01=1%) @@ -332,18 +333,10 @@ def ddeoldify( if chroma_resize: frame_size = min(max(ddcolor_rf, deoldify_rf) * 16, clip.width) # frame size calculation for inference() clip_orig = clip; - clip = clip.resize.Spline64(width=frame_size, height=frame_size) - - try: - d_clip = vs_degrain(clip, strength=degrain_strength, device_id=device_index) - except: - vs.core.log_message(2, "ddeoldify: KNLMeansCL is not installed/loaded properly") - d_clip = clip - - clip = d_clip + clip = clip.resize.Spline64(width=frame_size, height=frame_size) clipa = vs_deoldify(clip, method=method, model=deoldify_model, render_factor=deoldify_rf, tweaks_enabled=dotweak, tweaks=dotweak_p, package_dir=package_dir) - clipb = vs_ddcolor(clip, method=method, model=ddcolor_model, render_factor=ddcolor_rf, tweaks_enabled=ddtweak, tweaks=ddtweak_p, enable_fp16=ddcolor_enable_fp16, device_index=device_index) + clipb = vs_ddcolor(clip, method=method, model=ddcolor_model, render_factor=ddcolor_rf, tweaks_enabled=ddtweak, tweaks=ddtweak_p, dstrength=degrain_strength, enable_fp16=ddcolor_enable_fp16, device_index=device_index) clip_colored = vs_combine_models(clip_a=clipa, clip_b=clipb, method=method, sat=[deoldify_sat, ddcolor_sat], hue=[deoldify_hue, ddcolor_hue], clipb_weight=merge_weight, CMC_p=cmc_tresh, LMM_p=lmm_p, ALM_p = alm_p, invert_clips=cmb_sw) diff --git a/vsdeoldify/vsslib/restcolor.py b/vsdeoldify/vsslib/restcolor.py index 22044eb..ccdcc26 100644 --- a/vsdeoldify/vsslib/restcolor.py +++ b/vsdeoldify/vsslib/restcolor.py @@ -4,7 +4,7 @@ Date: 2024-04-08 version: LastEditors: Dan64 -LastEditTime: 2024-04-29 +LastEditTime: 2024-05-08 ------------------------------------------------------------------------------- Description: ------------------------------------------------------------------------------- @@ -27,7 +27,7 @@ Restore the colors of past/future frame. The restore is applied using a mask to selectect only the gray images on HSV color space. The ranges that OpenCV manage for HSV format are the following: -- Hue range is [0,179], +- Hue range is [-180,+180], - Saturation range is [0,255] - Value range is [0,255]. For the 8-bit images, H is converted to H/2 to fit to the [0,255] range. @@ -53,7 +53,7 @@ def restore_color(img_color: Image = None, img_gray: Image = None, sat: float=1. scenechange = np.mean(hsv_mask)/255 if (tht_scen > 0 and tht_scen < 1 and scenechange > tht_scen): - if hue_adjust: + if hue_adjust!="" and hue_adjust!="none": return adjust_hue_range(img_gray, hue_adjust=hue_adjust) else: return img_gray @@ -69,11 +69,14 @@ def restore_color(img_color: Image = None, img_gray: Image = None, sat: float=1. np_restored = np_image_mask_merge(np_gray, np_color_sat, mask_rgb) if weight > 0: - np_restored = np_weighted_merge(np_restored, np_gray, weight) + np_restored = np_weighted_merge(np_restored, np_gray, weight) # merge with gray frame + if weight < 0: + np_restored = np_weighted_merge(np_restored, np_color_sat, -weight) # merge with colored frame + img_restored = Image.fromarray(np_restored,'RGB').convert('RGB') - if hue_adjust: + if hue_adjust!="" and hue_adjust!="none": return adjust_hue_range(img_restored, hue_adjust=hue_adjust) else: return img_restored @@ -87,7 +90,11 @@ def restore_color(img_color: Image = None, img_gray: Image = None, sat: float=1. Change a given range of colors in HSV color space. The range is defined by the hue values in degree (range: 0-360) In OpenCV, for the 8-bit images, H is converted to H/2 to fit to the [0,255] range. -So the range of hue in the HSV color space of OpenCV is [0,179] +So the range of hue in the HSV color space of OpenCV is [0,179]. +hue_range syntax: "hue1_min:hue1_max,..,hueN_min,hueN_max|adjust, weight" +where: +adjust: if > 0 and < 10 -> saturation parameter else -> hue_shift +weight: if > 0 -> merge with desaturared frame, if < 0 -> merge with colored orginal frame """ def adjust_hue_range(img_color: Image = None, hue_adjust: str='none', return_mask: bool=False) -> Image: @@ -118,9 +125,9 @@ def adjust_chroma(img_color: Image = None, hue_range: str='none', sat: float = 0 hsv_color = cv2.cvtColor(np_color, cv2.COLOR_RGB2HSV) - #apply hue correction, range [-180.+180], converted to [-90.+90] + #apply hue correction, range [-180,+180] if hue != 0: - np_gray[:, :, 0] = np_gray[:, :, 0] + hue*0.5 + np_gray[:, :, 0] = np_hue_add(np_gray[:, :, 0], hue) # desatured the color image if sat != 1: @@ -149,7 +156,9 @@ def adjust_chroma(img_color: Image = None, hue_range: str='none', sat: float = 0 np_restored = np_weighted_merge(np_restored, np_gray_rgb, weight) else: np_restored = np_weighted_merge(np_restored, np_color, weight) - + if weight < 0: + np_restored = np_weighted_merge(np_restored, np_color, -weight) + return Image.fromarray(np_restored,'RGB').convert('RGB') def np_image_chroma_tweak(img_color_rgb: np.ndarray, sat: float = 1, bright: float = 0, hue: int = 0, hue_adjust: str='none') -> np.ndarray: @@ -206,11 +215,14 @@ def np_image_chroma_tweak(img_color_rgb: np.ndarray, sat: float = 1, bright: flo np_restored = np_image_mask_merge(img_color_rgb, np_gray_rgb, mask_rgb) - if hue==0: - np_restored = np_weighted_merge(np_restored, np_gray_rgb, weight) - else: - np_restored = np_weighted_merge(np_restored, img_color_rgb, weight) - + if weight > 0: + if hue==0: + np_restored = np_weighted_merge(np_restored, np_gray_rgb, weight) + else: + np_restored = np_weighted_merge(np_restored, img_color_rgb, weight) + if weight < 0: + np_restored = np_weighted_merge(np_restored, img_color_rgb, -weight) + return np_restored @@ -253,7 +265,7 @@ def _parse_hue_adjust(hue_adjust: str='none') -> (): hue_range = p[0] - if p==1: + if num==1: return (hue_range, sat, hue, weight) sw = p[1].split(",") diff --git a/vsdeoldify/vsslib/vsfilters.py b/vsdeoldify/vsslib/vsfilters.py index b6ec3b0..c650299 100644 --- a/vsdeoldify/vsslib/vsfilters.py +++ b/vsdeoldify/vsslib/vsfilters.py @@ -4,7 +4,7 @@ Date: 2024-04-08 version: LastEditors: Dan64 -LastEditTime: 2024-05-03 +LastEditTime: 2024-05-08 ------------------------------------------------------------------------------- Description: ------------------------------------------------------------------------------- @@ -94,10 +94,13 @@ def vs_chroma_stabilizer_ex(clip: vs.VideoNode = None, nframes: int = 5, mode: s #vs.core.log_message(2, "algo= " + str(algo)) if algo == 0: - clip_rgb = _average_clips_ex(clip=clip, weight_list=weight_list, sat=sat, tht=tht, weight=weight, tht_scen=tht_scen, hue_adjust=hue_adjust) + clip_rgb = _average_clips_ex(clip=clip, weight_list=weight_list, sat=sat, tht=tht, weight=weight, tht_scen=tht_scen, hue_adjust="none") else: - clip_rgb = _average_frames_ex(clip=clip, weight_list=weight_list, sat=sat, tht=tht, weight=weight, tht_scen=tht_scen, hue_adjust=hue_adjust) - + clip_rgb = _average_frames_ex(clip=clip, weight_list=weight_list, sat=sat, tht=tht, weight=weight, tht_scen=tht_scen, hue_adjust="none") + + #hue adjustment applied only on the final frame + clip_rgb = vs_adjust_clip_hue(clip=clip_rgb, hue_adjust=hue_adjust) + return clip_rgb def _build_avg_arithmetic(nframes: int = 5) -> list: @@ -283,6 +286,9 @@ def color_frame(n, f, sat: float = 1.0, tht: int = 0, weight: float = 0.2, tht_s """ def vs_adjust_clip_hue(clip: vs.VideoNode = None, hue_adjust: str='none') -> vs.VideoNode: + if hue_adjust=="" or hue_adjust=="none": + return clip + def color_frame(n, f, hue_adjust: str='none'): f_out = f.copy() if n < 1: @@ -524,32 +530,32 @@ def copy_luma_frame(n, f): Function to remove noise/grain from clip, strenght control the amount of noise/grain removed, if = 0 the filter is not applied. It is based on function KNLMeansCL() with GPU suppot enabled. """ -def vs_degrain(clip: vs.VideoNode = None, strength: int = 3, device_id: int = 0) -> vs.VideoNode: +def vs_degrain(clip: vs.VideoNode = None, strength: int = 1, device_id: int = 0) -> vs.VideoNode: if strength == 0: return clip match strength: case 1: - dstr = 1.5 + dstr = 0.5 dtmp = 1 case 2: - dstr = 2.5 + dstr = 1.0 dtmp = 1 case 3: - dstr = 3.5 + dstr = 1.5 dtmp = 1 case 4: - dstr = 5.5 - dtmp = 2 + dstr = 2.5 + dtmp = 1 case 5: - dstr = 8.5 - dtemp = 2 + dstr = 3.5 + dtmp = 2 case _: raise vs.Error("ddeoldify: not supported strength value: " + strength) clip = clip.resize.Bicubic(format=vs.YUV444PS, matrix_s="709", range_s="full") - clip = vs.core.knlm.KNLMeansCL(clip=clip, d=dtemp, a=2, s=4, h=dstr, channels='Y', device_type="gpu", device_id=device_id) + clip = vs.core.knlm.KNLMeansCL(clip=clip, d=dtmp, a=2, s=4, h=dstr, channels='Y', device_type="gpu", device_id=device_id) clip = clip.resize.Bicubic(format=vs.RGB24, matrix_in_s="709", range_s="full", dither_type="error_diffusion") return clip diff --git a/vsdeoldify/vsslib/vsmodels.py b/vsdeoldify/vsslib/vsmodels.py index a6f51b3..c4b085a 100644 --- a/vsdeoldify/vsslib/vsmodels.py +++ b/vsdeoldify/vsslib/vsmodels.py @@ -4,7 +4,7 @@ Date: 2024-04-08 version: LastEditors: Dan64 -LastEditTime: 2024-04-27 +LastEditTime: 2024-05-08 ------------------------------------------------------------------------------- Description: ------------------------------------------------------------------------------- @@ -92,7 +92,7 @@ def deoldify_colorize(n: int, f: vs.VideoFrame, colorizer: ModelImageVisualizer ------------------------------------------------------------------------------- wrapper to function ddcolor() with tweak pre-process. """ -def vs_ddcolor(clip: vs.VideoNode, method: int = 2, model: int = 0, render_factor: int = 24, tweaks_enabled: bool = False, tweaks: list = [0.0, 0.9, 0.7, False, 0.3, 0.3], enable_fp16: bool = True, device_index: int = 0, num_streams: int = 1) -> vs.VideoNode: +def vs_ddcolor(clip: vs.VideoNode, method: int = 2, model: int = 0, render_factor: int = 24, tweaks_enabled: bool = False, tweaks: list = [0.0, 0.9, 0.7, False, 0.3, 0.3], dstrength: int = 0, enable_fp16: bool = True, device_index: int = 0, num_streams: int = 1) -> vs.VideoNode: if method == 0: return None @@ -101,7 +101,14 @@ def vs_ddcolor(clip: vs.VideoNode, method: int = 2, model: int = 0, render_facto # input size must a multiple of 32 input_size = math.trunc(render_factor/2)*32 - + + try: + d_clip = vs_degrain(clip, strength=dstrength, device_id=device_index) + except Exception as error: + vs.core.log_message(2, "ddeoldify: KNLMeansCL error -> " + str(error)) + d_clip = clip + clip = d_clip + # unpack tweaks bright = tweaks[0] cont = tweaks[1] @@ -123,7 +130,7 @@ def vs_ddcolor(clip: vs.VideoNode, method: int = 2, model: int = 0, render_facto else: clipb = vs_tweak(clip, bright=bright, cont=cont, gamma=gamma) else: - clipb = clip + clipb = clip # adjusting clip's color space to RGBH for vsDDColor if enable_fp16: clipb = vsddcolor.ddcolor(clipb.resize.Bicubic(format=vs.RGBH, range_s="full"), model=model, input_size=input_size, device_index=device_index, num_streams=num_streams)