Advertisement
Zastin

debandshit.py - v0.3 (VS)

Jul 15th, 2017
1,842
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 34.74 KB | None | 0 0
  1. ##############################################################################################
  2. ##############################################################################################
  3. #### Deband Shit, by Zastin -  Contains some filters, a f3kdb wrapper, and some masks     ####
  4. #### I really shouldn't be in charge of naming things...                                  ####
  5. #### -- v0.3.0 - "port" of Dither_bilateral_multistage, sans limiting                     ####
  6. #### -- v0.2.0 - wrapper around GuidedFilter, LimitFilter and rangemask added             ####
  7. #### -- v0.1.0 - modified GuidedFilter from muvsfunc added                                ####
  8. #### -- v0.0.2 - add documentation and some tweaking                                      ####
  9. #### -- v0.0.1 - lumamask() - fixed for floats & rangemask() - separate luma/chroma radii ####
  10. #### -- v0.0.0 - initial "release"                                                        ####
  11. ##############################################################################################
  12. ##############################################################################################
  13.  
  14. import vapoursynth as vs
  15. import mvsfunc as mvf
  16. import muvsfunc as muf
  17. import fvsfunc as fvf
  18. import functools
  19. import math
  20.  
  21.  
  22.  
  23.  
  24.  
  25. #####################################################
  26. #####################################################
  27. #####################################################
  28. ###############                       ###############
  29. ###############   Debanding Filters   ###############
  30. ###############                       ###############
  31. #####################################################
  32. #####################################################
  33. #####################################################
  34.  
  35.  
  36.  
  37. def Dither_bilateral(src, ref=None, radius=None, thr=0.35, wmin=1.0, subspl=0, planes=None):
  38.     """ Dither's Gradfun3 mode 2, for Vapoursynth
  39.        Not much different from using core.avsw.Eval with Dither_bilateral_multistage, just with normal value rounding and without Dither_limit_dif16
  40.        If you want the rounding to be exactly the same, replace the applicable lines from here: https://pastebin.com/raw/gZKHCFkd
  41.        Default setting of 'radius' changes to reflect the resolution
  42.        
  43.        Radius auto-adjust: 480p  ->  9
  44.                            720p  -> 12
  45.                            810p  -> 13
  46.                            900p  -> 14
  47.                            1080p -> 16
  48.                            2K    -> 20
  49.                            4K    -> 32
  50.        
  51.        Basic usage: flt = db.Dither_bilateral(src, thr=1/3)
  52.                     flt = mvf.LimitFilter(flt, src, thr=1/3)
  53.    """
  54.     if radius is None:
  55.         radius = max( (src.width - 1280)/160 + 12, (src.height - 720)/ 90 + 12 )
  56.    
  57.     planes = list(range(numplanes)) if planes is None else [planes] if isinstance(planes, int) else planes
  58.     y, u, v = [3 if x in planes else 1 for x in range(3)]
  59.    
  60.     thr_1 = round(max(thr * 4.5, 1.25), 1)
  61.     thr_2 = round(max(thr * 9, 5), 1)
  62.     subspl_2 = subspl if subspl in (0,1) else subspl / 2
  63.     r4 = round(max(radius * 4 / 3, 4))
  64.     r2 = round(max(radius * 2 / 3, 3))
  65.     r1 = round(max(radius     / 3, 2))
  66.    
  67.     clips = [src.fmtc.nativetostack16()]
  68.     clip_names = ["c"]
  69.     ref_t = "c"
  70.     if ref is not None:
  71.         ref_s = ref.fmtc.nativetostack16()
  72.         clips += [ref_s]
  73.         clip_names += ["ref"]
  74.         ref_t = "ref"
  75.    
  76.     avs_stuff = "c.Dither_bilateral16(radius={r4}, thr={thr_1}, flat=0.75, wmin={wmin}, ref={ref_t}, subspl={subspl},   y={y}, u={u}, v={v})"
  77.     avs_stuff += ".Dither_bilateral16(radius={r2}, thr={thr_2}, flat=0.25, wmin={wmin}, ref={ref_t}, subspl={subspl_2}, y={y}, u={u}, v={v})"
  78.     avs_stuff += ".Dither_bilateral16(radius={r1}, thr={thr_2}, flat=0.50, wmin={wmin}, ref={ref_t}, subspl={subspl_2}, y={y}, u={u}, v={v})"
  79.     avs_stuff = avs_stuff.format(ref_t=ref_t, r4=r4, r2=r2, r1=r1, thr_1=thr_1, thr_2=thr_2, wmin=wmin, subspl=subspl, subspl_2=subspl_2, y=y, u=u, v=v)
  80.    
  81.     return core.avsw.Eval(avs_stuff, clips=clips, clip_names=clip_names).fmtc.stack16tonative()
  82. #TODO: rewrite to take arrays for most parameters (should at least save the extra stack16 conversions)
  83.  
  84.  
  85.  
  86. def Deband(clip, radius=None, str=None, thr=None, thrc=None, smode=2, mask=None, bin=None, planes=None, **args):
  87.     """
  88.   NOTE: GuidedFilter is really, really strong. Using a (much) lower value on 'str' relative to 'thr' is recommended
  89.         I don't even use GF at all anymore but I recall even values like 0.001 having an effect on banding, but my memory is shit so no guarantees
  90.  
  91.   Somewhat like GradFun3, uses GuidedFilter for the debanding
  92.   radius is now an int array
  93.   thr is split into str and thr:
  94.       thr and thrc are for LimitFilter and take floats/integers
  95.           set them to 0 to disable limiting
  96.       str is for GuidedFilter and takes an int array or flt/int
  97.       they try to copy each other if only one is set
  98.   smode sets the regulation mode, but its more or less like how it was in gf3: 0-2 and higher = safer on detail
  99.   mask is also an int array, and uses a normal chroma mask instead of using a luma mask for all 3 planes (can't be changed)
  100.   thr_det is replaced by bin, and is the threshold for std.Binarize. The user is expected to adjust it to the bitdepth and
  101.       sample type, but default behavior should be about the same as gf3
  102.   planes does what it always does
  103.   args is there for various reasons:
  104.       elast, ref, or even src from LimitFilter
  105.       guidance, etc from GuidedFilter
  106.       setting range/range_in = 1 if you have a full range YUV/GRAY source with integer samples (it's important-ish)
  107.      
  108.   Default settings for 16 bit input (1080p 4:2:0):
  109.   clip = Deband(clip, radius=[16, 10], str=1/3, thr=1/3, thrc=1/3, smode=2, elast=3, mask=2, bin=384, planes=[0, 1, 2])
  110.  
  111.   P.S. It works in whatever bitdepth and sample you give it so try not to use 8 bit (or set thr and thrc = 0)
  112.        I've also gotten weird results with LimitFilter in 32 bit before, so probably just stick to 16 bit
  113.   """
  114.     core = vs.core
  115.    
  116.     bits = clip.format.bits_per_sample
  117.     isint = clip.format.sample_type == vs.INTEGER
  118.     numplanes = clip.format.num_planes
  119.    
  120.     planes = list(range(numplanes)) if planes is None else [planes] if isinstance(planes, int) else planes
  121.    
  122.     str, thr, thrc = parse_params(str, thr, thrc, numplanes)
  123.    
  124.     mask = [math.ceil(clip.height/540)] if mask is None else mask if isinstance(mask, list) else [mask]
  125.     while len(mask) < 3:
  126.         mask += [mask[-1]]
  127.    
  128.     bdf = [1.5/255] if args.get('range_in', clip.format.color_family in [vs.RGB,vs.YCOCG]) else [1.5/219, 1.5/224]
  129.     bdi = [math.ceil(0.005859375 * (1<<bits))]
  130.     bin = (bdi if isint else bdf) if bin is None else bin if isinstance(bin, list) else [bin]
  131.     while len(bin) < numplanes:
  132.         bin += [bin[-1]]
  133.    
  134.     mask = [mask[i] if i in planes else 0 for i in range(3)]
  135.     bin = [bin[i] if i in planes else 0 for i in range(numplanes)]
  136.    
  137.     bplanes, mplanes = [[]]*2
  138.     for i in range(numplanes):
  139.         if i in planes and mask[i] > 0:
  140.             mplanes += [i]
  141.             if bin[i] > 0:
  142.                 bplanes += [i]
  143.    
  144.     deband = GuidedFilter(clip, radius=radius, thr=thr, regulation_mode=smode, planes=planes, **args)
  145.     if any([thr, thrc]):
  146.         deband = mvf.LimitFilter(flt=deband, src=args.get('src', clip), thr=thr, thrc=thrc, **args)
  147.    
  148.     if max(mask):
  149.         rmask = rangemask(clip, mask[0], mask[1])
  150.         if max(bin) > 0:
  151.             rmask = rmask.std.Binarize(threshold=bin, planes=bplanes)
  152.         rgvs = core.rgvs if isint else core.rgsf
  153.         for i in range(min(3, max(mask))):
  154.             rmask = rgvs.RemoveGrain(rmask, [[22,11,20][i] if mask[p] > i and bin[p] > 0 else 0 for p in range(numplanes)])
  155.         deband = deband.std.MaskedMerge(clip, rmask, planes=planes)
  156.        
  157.     return deband
  158.    
  159. def parse_params(str, thr, thrc, numplanes):
  160.     if str is None: # try to take values from thr, then thrc, and if all else fails use 1/3
  161.         str = []
  162.         if thr is not None:
  163.             str += [thr]
  164.         if thrc is not None:
  165.             str += [thrc]
  166.         if len(str)==0:
  167.             str += [1/3]
  168.    
  169.     while len(str) < numplanes:
  170.         str += [str[-1]]
  171.    
  172.     # when parsing thrc try thr first before str
  173.     if thrc is None:
  174.         thrc = str[-1] if thr is None else thr
  175.     if thr is None:
  176.         thr = str[0]
  177.     # probably unnecessary but mt_lut was faster with the same expression for all planes so maybe std.Expr is too?
  178.     # Its not a lut though so I doubt it
  179.     if thr == thrc or numplanes==1:
  180.         thrc = None
  181.        
  182.     return [str, thr, thrc]
  183.  
  184.  
  185.  
  186. # This is flat-out theft, but I wanted "planes" and separable strengths & radii so fuck it
  187.  
  188. def GuidedFilter(input, guidance=None, radius=None, thr=1/3, regulation=None, regulation_mode=2, use_gauss=False, fast=None, subsampling_ratio=4, use_fmtc1=False, kernel1='point', kernel1_args=None, use_fmtc2=False, kernel2='bilinear', kernel2_args=None, planes=None, **depth_args):
  189.     """Guided Filter - fast edge-preserving smoothing algorithm
  190.    Author: Kaiming He et al. (http://kaiminghe.com/eccv10/)
  191.    The guided filter computes the filtering output by considering the content of a guidance image.
  192.    It can be used as an edge-preserving smoothing operator like the popular bilateral filter,
  193.    but it has better behaviors near edges.
  194.    The guided filter is also a more generic concept beyond smoothing:
  195.    It can transfer the structures of the guidance image to the filtering output,
  196.    enabling new filtering applications like detail enhancement, HDR compression,
  197.    image matting/feathering, dehazing, joint upsampling, etc.
  198.    All the internal calculations are done at 32-bit float.
  199.    Args:
  200.        input: Input clip.
  201.        guidance: (clip) Guidance clip used to compute the coefficient of the linear translation on 'input'.
  202.            It must has the same clip properties as 'input'.
  203.            If it is None, it will be set to input, with duplicate calculations being omitted.
  204.            Default is None.
  205.        radius: (int[]) Box / Gaussian filter's radius.
  206.            If box filter is used, the range of radius is 1 ~ 12(fast=False) or 1 ~ 12*subsampling_ratio in VapourSynth R38 or older because of the limitation of std.Convolution().
  207.            For gaussian filter, the radius can be much larger, even reaching the width/height of the clip.
  208.            Default for YUV420 is [16, 10] @ 1080p, [12, 8] at 720p and [9, 7] at 480p.
  209.        thr: (float[]) Alternate way to specify 'regulation'.
  210.            Set 'range_in' if you have a full range YUV source so it scales correctly
  211.            Default is 1/3
  212.        regulation: (float[]) A criterion for judging whether a patch has high variance and should be preserved, or is flat and should be smoothed.
  213.            Similar to the range variance in the bilateral filter.
  214.            Default is thr divided by the number of possible 8 bit values for each plane. Set 'range_in' if needed
  215.        regulation_mode: (int) Tweak on regulation.
  216.            It was mentioned in [1] that the local filters such as the Bilateral Filter (BF) or Guided Image Filter (GIF)
  217.            would concentrate the blurring near these edges and introduce halos.
  218.            The author of Weighted Guided Image Filter (WGIF) [3] argued that,
  219.            the Lagrangian factor (regulation) in the GIF is fixed could be another major reason that the GIF produces halo artifacts.
  220.            In [3], a WGIF was proposed to reduce the halo artifacts of the GIF.
  221.            An edge aware factor was introduced to the constraint term of the GIF,
  222.            the factor makes the edges preserved better in the result images and thus reduces the halo artifacts.
  223.            In [4], a gradient domain guided image filter is proposed by incorporating an explicit first-order edge-aware constraint.
  224.            The proposed filter is based on local optimization
  225.            and the cost function is composed of a zeroth order data fidelity term and a first order regularization term.
  226.            So the factors in the new local linear model can represent the images more accurately near edges.
  227.            In addition, the edge-aware factor is multi-scale, which can separate edges of an image from fine details of the image better.
  228.            0: Guided Filter [1]
  229.            1: Weighted Guided Image Filter [3]
  230.            2: Gradient Domain Guided Image Filter [4]
  231.            Default is 2.
  232.        use_gauss: Whether to use gaussian guided filter [1]. This replaces mean filter with gaussian filter.
  233.            Guided filter is rotationally asymmetric and slightly biases to the x/y-axis because a box window is used in the filter design.
  234.            The problem can be solved by using a gaussian weighted window instead. The resulting kernels are rotationally symmetric.
  235.            The authors of [1] suggest that in practice the original guided filter is always good enough.
  236.            Gaussian is performed by core.tcanny.TCanny(mode=-1).
  237.            The sigma is set to r/sqrt(2).
  238.            Default is False.
  239.        fast: (bool) Whether to use fast guided filter [2].
  240.            This method subsamples the filtering input image and the guidance image,
  241.            computes the local linear coefficients, and upsamples these coefficients.
  242.            The upsampled coefficients are adopted on the original guidance image to produce the output.
  243.            This method reduces the time complexity from O(N) to O(N^2) for a subsampling ratio s.
  244.            Default is True if the version number of VapourSynth is less than 39, otherwise is False.
  245.        subsampling_ratio: (float) Only works when fast=True.
  246.            Generally should be no less than 'radius'.
  247.            Default is 4.
  248.        use_fmtc1, use_fmtc2: (bool) Whether to use fmtconv in subsampling / upsampling.
  249.            Default is False.
  250.            Note that fmtconv's point subsampling may causes pixel shift.
  251.        kernel1, kernel2: (string) Subsampling/upsampling kernels.
  252.            Default is 'point'and 'bilinear'.
  253.        kernel1_args, kernel2_args: (dict) Additional parameters passed to resizers in the form of dict.
  254.            Default is {}.
  255.        planes: (int[]) Which planes should be processed
  256.            Default processes all planes
  257.        depth_args: (dict) Additional arguments passed to fvf.Depth() in the form of keyword arguments.
  258.            Default is {}.
  259.    Ref:
  260.        [1] He, K., Sun, J., & Tang, X. (2013). Guided image filtering. IEEE transactions on pattern analysis and machine intelligence, 35(6), 1397-1409.
  261.        [2] He, K., & Sun, J. (2015). Fast guided filter. arXiv preprint arXiv:1505.00996.
  262.        [3] Li, Z., Zheng, J., Zhu, Z., Yao, W., & Wu, S. (2015). Weighted guided image filtering. IEEE Transactions on Image Processing, 24(1), 120-129.
  263.        [4] Kou, F., Chen, W., Wen, C., & Li, Z. (2015). Gradient domain guided image filtering. IEEE Transactions on Image Processing, 24(11), 4528-4539.
  264.    """
  265.     core = vs.core
  266.     funcName = 'GuidedFilter'
  267.  
  268.     if not isinstance(input, vs.VideoNode):
  269.         raise TypeError(funcName + ': \"input\" must be a clip!')
  270.  
  271.     # Plane processing stuff
  272.     def get_expr_array(expr, planes, num_planes):
  273.         return [expr[i] if i in planes else '' for i in range(num_planes)]
  274.     numpl = input.format.num_planes
  275.     planes = list(range(numpl)) if planes is None else [planes] if isinstance(planes, int) else planes
  276.     fmtc_planes = [3 if i in planes else 1 for i in range(numpl)]
  277.  
  278.     # Get clip's properties
  279.     inbits = input.format.bits_per_sample
  280.     width = input.width
  281.     height = input.height
  282.    
  283.     if regulation is None:
  284.         thr = [thr] * numpl if not isinstance(thr, list) else thr[:numpl]
  285.         while len(thr) < numpl:
  286.             thr += [thr[-1]]
  287.         full = depth_args.get('range_in', input.format.color_family in (vs.RGB, vs.YCOCG))
  288.         size = [220, 225, 225] if full else [256] * 3
  289.         regulation = [thr[i]/size[i] for i in range(numpl)]
  290.        
  291.     if radius is None:
  292.         width_c = width / (1 << input.format.subsampling_w)
  293.         height_c= height/ (1 << input.format.subsampling_h)
  294.        
  295.         rad = max( (width - 1280)/160 + 12, (height - 720)/ 90 + 12 )
  296.         radc= max( (width_c-1280)/160 + 12, (height_c-720)/ 90 + 12 )
  297.         radius = [round(rad), round(radc)][:numpl]
  298.  
  299.     if guidance is not None:
  300.         if not isinstance(guidance, vs.VideoNode):
  301.             raise TypeError(funcName + ': \"guidance\" must be a clip!')
  302.         if input.format.id != guidance.format.id:
  303.             raise TypeError(funcName + ': \"guidance\" must be of the same format as \"input\"!')
  304.         if input.width != guidance.width or input.height != guidance.height:
  305.             raise TypeError(funcName + ': \"guidance\" must be of the same size as \"input\"!')
  306.         if input == guidance: # Remove redundant computation
  307.             guidance = None
  308.  
  309.     if fast is None:
  310.         fast = False if core.version_number() >= 39 else True
  311.  
  312.     if kernel1_args is None:
  313.         kernel1_args = {}
  314.     if kernel2_args is None:
  315.         kernel2_args = {}
  316.  
  317.     # Bitdepth conversion and variable names modification to correspond to the paper
  318.     p = fvf.Depth(input, 32, range_in=depth_args.get('range_in'))
  319.     I = fvf.Depth(guidance, 32, range_in=depth_args.get('range_in')) if guidance is not None else p
  320.     r = [radius] if isinstance(radius, int) else radius[:numpl]
  321.     eps = [regulation] * numpl if not isinstance(regulation, list) else regulation[:numpl]
  322.     while len(eps) < numpl:
  323.         eps += [eps[-1]]
  324.     s = subsampling_ratio
  325.  
  326.     # Back up guidance image
  327.     I_src = I
  328.  
  329.     # Fast guided filter's subsampling
  330.     if fast:
  331.         down_w = round(width / s + 0.5)
  332.         down_h = round(height / s + 0.5)
  333.         if use_fmtc1:
  334.             p = core.fmtc.resample(p, down_w, down_h, kernel=kernel1, planes=fmtc_planes, **kernel1_args)
  335.             I = core.fmtc.resample(I, down_w, down_h, kernel=kernel1, planes=fmtc_planes, **kernel1_args) if guidance is not None else p
  336.         else: # use zimg
  337.             p = eval('core.resize.{kernel}(p, down_w, down_h, **kernel1_args)'.format(kernel=kernel1.capitalize()))
  338.             I = eval('core.resize.{kernel}(I, down_w, down_h, **kernel1_args)'.format(kernel=kernel1.capitalize())) if guidance is not None else p
  339.  
  340.         r = round(r / s + 0.5)
  341.  
  342.     # Select the shape of the kernel. As the width of BoxFilter in this module is (radius*2-1) rather than (radius*2+1), radius should be increased by one.
  343.     Filter = functools.partial(core.tcanny.TCanny, sigma=[val/2 * math.sqrt(2) for val in r], mode=-1, planes=planes) if use_gauss else functools.partial(BoxFilter, radius=[val+1 for val in r], planes=planes)
  344.     Filter_r1 = functools.partial(core.tcanny.TCanny, sigma=1/2 * math.sqrt(2), mode=-1, planes=planes) if use_gauss else functools.partial(BoxFilter, radius=1+1, planes=planes)
  345.  
  346.  
  347.     # Edge-Aware Weighting, equation (5) in [3], or equation (9) in [4].
  348.     def FLT(n, f, clip, core, eps0, planes, numpl):
  349.         frameMean = f.props.PlaneStatsAverage
  350.  
  351.         return core.std.Expr(clip, get_expr_array(['x {eps0} + {avg} *'.format(avg=frameMean, eps0=eps0)]*numpl, planes, numpl))
  352.  
  353.  
  354.     # Compute the optimal value of a of Gradient Domain Guided Image Filter, equation (12) in [4]
  355.     def FLT2(n, f, cov_Ip, weight_in, weight, var_I, core, eps, planes, numpl):
  356.         frameMean = f.props.PlaneStatsAverage
  357.         frameMin = f.props.PlaneStatsMin
  358.  
  359.         alpha = frameMean
  360.         kk = -4 / (frameMin - alpha - 1e-6) # Add a small num to prevent divided by 0
  361.        
  362.         expr = ['x {eps} 1 1 1 {kk} y {alpha} - * exp + / - * z / + a {eps} z / + /'.format(eps=e, kk=kk, alpha=alpha) for e in eps]
  363.        
  364.         return core.std.Expr([cov_Ip, weight_in, weight, var_I], get_expr_array(expr, planes, numpl))
  365.  
  366.     # Compute local linear coefficients.
  367.     mean_p = Filter(p)
  368.     mean_I = Filter(I) if guidance is not None else mean_p
  369.     I_square = core.std.Expr([I], get_expr_array(['x dup *']*numpl, planes, numpl))
  370.     corr_I = Filter(I_square)
  371.     corr_Ip = Filter(core.std.Expr([I, p], get_expr_array(['x y *']*numpl, planes, numpl))) if guidance is not None else corr_I
  372.  
  373.     var_I = core.std.Expr([corr_I, mean_I], get_expr_array(['x y dup * -']*numpl, planes, numpl))
  374.     cov_Ip = core.std.Expr([corr_Ip, mean_I, mean_p], get_expr_array(['x y z * -']*numpl, planes, numpl)) if guidance is not None else var_I
  375.  
  376.     if regulation_mode: # 0: Original Guided Filter, 1: Weighted Guided Image Filter, 2: Gradient Domain Guided Image Filter
  377.         if r != 1:
  378.             mean_I_1 = Filter_r1(I)
  379.             corr_I_1 = Filter_r1(I_square)
  380.             var_I_1 = core.std.Expr([corr_I_1, mean_I_1], get_expr_array(['x y dup * -']*numpl, planes, numpl))
  381.         else: # r == 1
  382.             var_I_1 = var_I
  383.  
  384.         if regulation_mode == 1: # Weighted Guided Image Filter
  385.             weight_in = var_I_1
  386.         else: # regulation_mode == 2, Gradient Domain Guided Image Filter
  387.             weight_in = core.std.Expr([var_I, var_I_1], get_expr_array(['x y * sqrt']*numpl, planes, numpl))
  388.  
  389.         eps0 = 0.001 ** 2 # Epsilon in [3] and [4]
  390.         denominator = core.std.Expr([weight_in], get_expr_array(['1 x {} + /'.format(eps0)]*numpl, planes, numpl))
  391.  
  392.         denominator = core.std.PlaneStats(denominator, plane=[0])
  393.         weight = core.std.FrameEval(denominator, functools.partial(FLT, clip=weight_in, core=core, eps0=eps0, planes=planes, numpl=numpl), prop_src=[denominator]) # equation (5) in [3], or equation (9) in [4]
  394.  
  395.         if regulation_mode == 1: # Weighted Guided Image Filter
  396.             a = core.std.Expr([cov_Ip, var_I, weight], get_expr_array(['x y {eps} z / + /'.format(eps=e) for e in eps], planes, numpl))
  397.         else: # regulation_mode == 2, Gradient Domain Guided Image Filter
  398.             weight_in = core.std.PlaneStats(weight_in, plane=[0])
  399.             a = core.std.FrameEval(weight, functools.partial(FLT2, cov_Ip=cov_Ip, weight_in=weight_in, weight=weight, var_I=var_I, core=core, eps=eps, planes=planes, numpl=numpl), prop_src=[weight_in])
  400.     else: # regulation_mode == 0, Original Guided Filter
  401.         a = core.std.Expr([cov_Ip, var_I], get_expr_array(['x y {} + /'.format(e) for e in eps], planes, numpl))
  402.  
  403.     b = core.std.Expr([mean_p, a, mean_I], get_expr_array(['x y z * -']*numpl, planes, numpl))
  404.  
  405.  
  406.     mean_a = Filter(a)
  407.     mean_b = Filter(b)
  408.  
  409.     # Fast guided filter's upsampling
  410.     if fast:
  411.         if use_fmtc2:
  412.             mean_a = core.fmtc.resample(mean_a, width, height, kernel=kernel2, planes=fmtc_planes, **kernel2_args)
  413.             mean_b = core.fmtc.resample(mean_b, width, height, kernel=kernel2, planes=fmtc_planes, **kernel2_args)
  414.         else: # use zimg
  415.             mean_a = eval('core.resize.{kernel}(mean_a, {w}, {h}, **kernel2_args)'.format(kernel=kernel2.capitalize(), w=width, h=height))
  416.             mean_b = eval('core.resize.{kernel}(mean_b, {w}, {h}, **kernel2_args)'.format(kernel=kernel2.capitalize(), w=width, h=height))
  417.  
  418.     # Linear translation
  419.     q = core.std.Expr([mean_a, I_src, mean_b], get_expr_array(['x y * z +']*numpl, planes, numpl))
  420.  
  421.     # Final bitdepth conversion
  422.     try:
  423.         return fvf.Depth(q, inbits, **depth_args)
  424.     except TypeError:
  425.         return fvf.Depth(q, **depth_args)
  426.        
  427. def BoxFilter(clip, radius, planes):
  428.     core = vs.core
  429.  
  430.     numpl = clip.format.num_planes
  431.    
  432.     radius = [radius] * numpl if isinstance(radius, int) else radius
  433.     while len(radius) < numpl:
  434.         radius += [radius[-1]]
  435.        
  436.     if len(planes) == numpl and min(radius) == max(radius):
  437.         return muf.BoxFilter(clip, radius=radius[0], planes=planes)
  438.    
  439.     clips = [core.std.ShufflePlanes(clip, i, vs.GRAY) for i in range(numpl)]
  440.     clips = [muf.BoxFilter(clips[i], radius=radius[i], planes=planes) if i in planes else clips[i] for i in range(numpl)]
  441.    
  442.     return core.std.ShufflePlanes(clips, [0]*3, vs.YUV)
  443.  
  444.  
  445.  
  446. ###############################################################################
  447. # f3kbilateral - f3kdb multistage bilateral-esque filter                      #
  448. ###############################################################################
  449. # This thing is more of a last resort for extreme banding                     #
  450. # With that in mind, 40~60 is probably an effective range for y & c strengths #
  451. # I did use range=20, y=160 to scene-filter some horrendous fades, though     #
  452. ###############################################################################
  453. def f3kbilateral(clip, range=None, y=50, c=0, thr=0.6, elast=3., thrc=None):
  454.     core = vs.core
  455.    
  456.     f = clip.format
  457.     bits = f.bits_per_sample
  458.     isGRAY = f.color_family==vs.GRAY
  459.    
  460.     range = (12 if clip.width<1800 and clip.height<1000 else 16) if range is None else range
  461.     r1 = round(range*4/3)
  462.     r2 = round(range*2/3)
  463.     r3 = round(range/3)
  464.     y1 = y//2
  465.     y2 = y
  466.     y3 = y
  467.     c1 = c//2
  468.     c2 = c
  469.     c3 = c
  470.    
  471.     if c==0:
  472.         flt0 = togray(clip, 16)
  473.     else:
  474.         flt0 = fvf.Depth(clip, 16)
  475.    
  476.     flt1 = f3kdb(flt0, r1, y1, c1)
  477.     flt2 = f3kdb(flt1, r2, y2, c2)
  478.     flt3 = f3kdb(flt2, r3, y3, c3)
  479.    
  480.     flt = mvf.LimitFilter(flt3, flt2, ref=flt0, thr=thr, elast=elast, thrc=thrc)
  481.     flt = fvf.Depth(flt, bits)
  482.     if c==0 and not isGRAY:
  483.         flt = muf.MergeChroma(flt, clip)
  484.    
  485.     return flt
  486.  
  487.  
  488. ######################################################################################################################
  489. # f3kpf - f3kdb with a simple prefilter by mawen1250 - https://www.nmm-hd.org/newbbs/viewtopic.php?f=7&t=1495#p12163 #
  490. ######################################################################################################################
  491. # Since the prefilter is a straight gaussian+average blur, f3kdb's effect becomes very strong, very fast             #
  492. # Functions more or less like gradfun3 without the detail mask                                                       #
  493. ######################################################################################################################
  494. def f3kpf(clip, range=None, y=40, cb=40, cr=None, thr=0.3, elast=2.5, thrc=None):
  495.     core = vs.core
  496.    
  497.     f = clip.format
  498.     bits = f.bits_per_sample
  499.     isGRAY = f.color_family==vs.GRAY
  500.    
  501.     range = (12 if clip.width<1800 and clip.height<1000 else 16) if range is None else range
  502.     cr = cb if cr is None else cr
  503.    
  504.     if cr==0 and cb==0:
  505.         clp = togray(clip, 32)
  506.     else:
  507.         clp = fvf.Depth(clip, 32)
  508.     blur32 = clp.std.Convolution([1,2,1,2,4,2,1,2,1]).std.Convolution([1,1,1,1,1,1,1,1,1], planes=0)
  509.     blur16 = fvf.Depth(blur32, 16)
  510.     diff = clp.std.MakeDiff(blur32)
  511.     f3k = f3kdb(blur16, range, y, cb, cr)
  512.     f3k = mvf.LimitFilter(f3k, blur16, thr=thr, elast=elast, thrc=thrc)
  513.     f3k = fvf.Depth(f3k, 32)
  514.     out = f3k.std.MergeDiff(diff)
  515.     out = fvf.Depth(out, bits)
  516.     if cr==0 and cb==0 and not isGRAY:
  517.         out = muf.MergeChroma(out, clip)
  518.     return out
  519.  
  520.  
  521. ############################################
  522. # lfdeband - some Avisynth filter I ported #
  523. ############################################
  524. def lfdeband(clip):
  525.     core = vs.core
  526.    
  527.     f =  clip.format
  528.     bits = f.bits_per_sample
  529.     wss = 1 << f.subsampling_w
  530.     hss = 1 << f.subsampling_h
  531.     w = clip.width
  532.     h = clip.height
  533.     dw = round(w/2)
  534.     dh = round(h/2)
  535.    
  536.     clp = fvf.Depth(clip, 32)
  537.     dsc = clp.fmtc.resample(dw-dw%wss, dh-dh%hss, kernel='spline64')
  538.     ddb = f3kdb(dsc, range=30, y=80, cb=80, cr=80, grainy=0, grainc=0)
  539.     ddif = ddb.std.MakeDiff(dsc)
  540.     dif = ddif.fmtc.resample(w, h, kernel='spline64')
  541.     out = clp.std.MergeDiff(dif)
  542.     return fvf.Depth(out, bits)
  543.  
  544.  
  545. ###########################################################################################
  546. # f3kdb - wrapper function                                                                #
  547. # allows 32 bit in/out, and sets most parameters automatically; otherwise it's just f3kdb #
  548. ###########################################################################################
  549. def f3kdb(clip, range=None, y=40, cb=None, cr=None, grainy=0, grainc=0, depth=None):
  550.     core = vs.core
  551.    
  552.     f = clip.format
  553.     cf = f.color_family
  554.     bits = f.bits_per_sample
  555.     H16 = f.sample_type==vs.FLOAT and bits==16
  556.     tv_range = cf==vs.GRAY or cf==vs.YUV
  557.    
  558.     range = (12 if clip.width<1800 and clip.height<1000 else 16) if range is None else range
  559.     cb = (y if cf==vs.RGB else y//2) if cb is None else cb
  560.     cr = cb if cr is None else cr
  561.     output_depth = bits if depth is None else depth
  562.    
  563.     if bits > 16:
  564.         clip = fvf.Depth(clip, 16)
  565.     else:
  566.         clip = forceint16(clip)
  567.    
  568.     deb = core.f3kdb.Deband(clip, range, y, cb, cr, grainy, grainc, keep_tv_range=tv_range, output_depth=min(output_depth, 16))
  569.    
  570.     if output_depth==32:
  571.         return fvf.Depth(deb, 32)
  572.     else:
  573.         return forceint16(deb, undo=H16)
  574.  
  575.  
  576. #######################################################
  577. #######################################################
  578. #######################################################
  579. ######################           ######################
  580. ######################   Masks   ######################
  581. ######################           ######################
  582. #######################################################
  583. #######################################################
  584. #######################################################
  585.  
  586.  
  587. ##########################################################################################################
  588. # rangemask - min/max mask with separate luma/chroma radii                                               #
  589. ##########################################################################################################
  590. # rad/radc are the luma/chroma equivalent of gradfun3's "mask" parameter                                 #
  591. # the way gradfun3's mask works is on an 8 bit scale, with rounded dithering of high depth input         #
  592. # As such, when following this filter with a Binarize, use the following conversion steps based on input #
  593. # -  8 bit = Binarize(2) or Binarize(thr_det)                                                            #
  594. # - 16 bit = Binarize(384) or Binarize((thr_det - 0.5) * 256)                                            #
  595. # - floats = Binarize(0.005859375) or Binarize((thr_det - 0.5) / 256)                                    #
  596. ##########################################################################################################
  597. # when radii are equal to 1, this filter becomes identical to mt_edge("min/max", 0, 255, 0, 255)         #
  598. ##########################################################################################################
  599. def rangemask(clip, rad=2, radc=None):
  600.     core = vs.core
  601.    
  602.     isRGB = clip.format.color_family==vs.RGB
  603.     radc = (rad if isRGB else 0) if radc is None else radc
  604.     if radc==0:
  605.         clip = togray(clip, int16=False)
  606.     ma = maxm(clip, rad, radc)
  607.     mi = minm(clip, rad, radc)
  608.    
  609.     expr = 'x y -'
  610.     if not rad:
  611.         expr = ['','x y -']
  612.     if not radc:
  613.         expr = ['x y -','']
  614.    
  615.     return core.std.Expr([ma, mi], expr)
  616.  
  617.  
  618. #########################################################################################
  619. # lumamask - mask each pixel according to its luma value                                #
  620. #########################################################################################
  621. # - lo: all pixels below this threshold will be binary                                  #
  622. # - hi: all pixels above this threshold will be binary                                  #
  623. #       all pixels in-between will be scaled from black to white                        #
  624. # - invert: when true, masks dark areas (pixels below lo will be white, and vice versa) #
  625. #########################################################################################
  626. def lumamask(clip, lo, hi, invert=True):
  627.     core = vs.core
  628.    
  629.     f = clip.format
  630.     bits = f.bits_per_sample
  631.     isINT = f.sample_type==vs.INTEGER
  632.     peak = (1 << bits) - 1 if isINT else 1
  633.    
  634.     if invert:
  635.         mexpr = 'x {lo} < {peak} x {hi} > 0 {peak} x {lo} - {peak} {hi} {lo} - / * - ? ?'.format(peak=peak,lo=lo,hi=hi)
  636.     else:
  637.         mexpr = 'x {lo} < 0 x {hi} > {peak} 0 x {lo} - {peak} {lo} {hi} - / * - ? ?'.format(peak=peak,lo=lo,hi=hi)
  638.    
  639.     clip = togray(clip, bits, 1)
  640.     mask = clip.std.Expr(mexpr)
  641.    
  642.     return mask
  643.  
  644.  
  645.  
  646.  
  647.  
  648.  
  649.  
  650.  
  651.  
  652.  #####################
  653.  # Utility Functions #
  654.  #####################
  655.  
  656. def togray(clip, bits=None, dmode=3, range=None, int16=True):
  657.     core = vs.core
  658.    
  659.     f = clip.format
  660.     cf = f.color_family
  661.     isGRAY = cf==vs.GRAY
  662.     isYUV = cf==vs.YUV
  663.     in_st = f.sample_type
  664.     in_bits = f.bits_per_sample
  665.    
  666.     bits = in_bits if bits==None else bits
  667.     st = (vs.INTEGER if int16 else (in_st if in_bits==16 else vs.INTEGER)) if bits < 32 else vs.FLOAT
  668.    
  669.     if (in_bits, in_st) == (bits, st) and isGRAY:
  670.         return clip
  671.     elif (in_bits, in_st) == (bits, st) and isYUV:
  672.         return mvf.GetPlane(clip)
  673.     else:
  674.         format = core.register_format(vs.GRAY, st, bits, 0, 0)
  675.         matrix = None if isGRAY or isYUV else GetMatrix(clip, id=True)
  676.         dither_type = dmode if isinstance(dmode, str) else 'ordered' if dmode==0 else 'none' if dmode<3 else 'error_diffusion'
  677.         return clip.resize.Spline36(format=format.id, matrix=matrix, dither_type=dither_type, range_in=range, range=range)
  678.  
  679. def forceint16(clip, undo=False):
  680.     core = vs.core
  681.    
  682.     f = clip.format
  683.     dst_st = vs.FLOAT if undo else vs.INTEGER
  684.    
  685.     if f.bits_per_sample!=16 or f.sample_type==dst_st:
  686.         return clip
  687.     else:
  688.         format = core.register_format(f.color_family, dst_st, 16, f.subsampling_w, f.subsampling_h)
  689.         return clip.resize.Spline36(format=format.id)
  690.  
  691. def maxm(clip, sy=2, sc=2):
  692.     core = vs.core
  693.    
  694.     yp = sy>=sc
  695.     yiter = 1 if yp else 0
  696.     cp = sc>=sy
  697.     citer = 1 if cp else 0
  698.     planes = [0] if yp and not cp else [1,2] if cp and not yp else [0,1,2]
  699.     coor = [0, 1, 0, 1, 1, 0, 1, 0] if (max(sy,sc) % 3) != 1 else [1, 1, 1, 1, 1, 1, 1, 1]
  700.    
  701.     if sy>0 or sc>0:
  702.         return maxm(clip.std.Maximum(planes=planes, coordinates=coor), sy=sy-yiter, sc=sc-citer)
  703.     else:
  704.         return clip
  705.  
  706. def minm(clip, sy=2, sc=2):
  707.     core = vs.core
  708.    
  709.     yp = sy>=sc
  710.     yiter = 1 if yp else 0
  711.     cp = sc>=sy
  712.     citer = 1 if cp else 0
  713.     planes = [0] if yp and not cp else [1,2] if cp and not yp else [0,1,2]
  714.     coor = [0, 1, 0, 1, 1, 0, 1, 0] if (max(sy,sc) % 3) != 1 else [1, 1, 1, 1, 1, 1, 1, 1]
  715.    
  716.     if sy>0 or sc>0:
  717.         return minm(clip.std.Minimum(planes=planes, coordinates=coor), sy=sy-yiter, sc=sc-citer)
  718.     else:
  719.         return clip
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement