Skip to content

Instantly share code, notes, and snippets.

@chikuzen
Last active October 13, 2015 00:37
Show Gist options
  • Save chikuzen/4111749 to your computer and use it in GitHub Desktop.
Save chikuzen/4111749 to your computer and use it in GitHub Desktop.
vapoursynth sample
#finesharp.py - finesharp module for VapourSynth
#original author : Didee (http://forum.doom9.org/showthread.php?t=166082)
# requirement: RemoveGrain.dll, Repair.dll
import vapoursynth as vs
class InvalidArgument(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
def spline(x, coordinates):
def get_matrix(px, py, l):
matrix = []
matrix.append([(i == 0) * 1.0 for i in range(l + 1)])
for i in range(1, l - 1):
p = [0 for t in range(l + 1)]
p[i - 1] = px[i] - px[i - 1]
p[i] = 2 * (px[i + 1] - px[i - 1])
p[i + 1] = px[i + 1] - px[i]
p[l] = 6 * (((py[i + 1] - py[i]) / p[i + 1]) - (py[i] - py[i - 1]) / p[i - 1])
matrix.append(p)
matrix.append([(i == l - 1) * 1.0 for i in range(l + 1)])
return matrix
def equation(matrix, dim):
for i in range(dim):
num = matrix[i][i]
for j in range(dim + 1):
matrix[i][j] /= num
for j in range(dim):
if i != j:
a = matrix[j][i]
for k in range(i, dim + 1):
matrix[j][k] -= a * matrix[i][k]
if not isinstance(coordinates, dict):
raise TypeError('coordinates must be a dict')
length = len(coordinates)
if length < 3:
raise InvalidArgument('coordinates require at least three pairs')
px = [key for key in coordinates.keys()]
py = [val for val in coordinates.values()]
matrix = get_matrix(px, py, length)
equation(matrix, length)
for i in range(length + 1):
if x >= px[i] and x <= px[i + 1]:
break
j = i + 1
h = px[j] - px[i]
s = matrix[j][length] * (x - px[i]) ** 3
s -= matrix[i][length] * (x - px[j]) ** 3
s /= 6 * h
s += (py[j] / h - h * matrix[j][length] / 6) * (x - px[i])
s -= (py[i] / h - h * matrix[i][length] / 6) * (x - px[j])
return s
def clamp(minimum, x, maximum):
return int(max(minimum, min(round(x), maximum)))
class FineSharp(object):
def __init__(self, core):
self.rgrain = core.avs.RemoveGrain
self.repair = core.avs.Repair
self.std = core.std
self.point = core.resize.Point
self.lut_range = None
self.max = 0
self.mid = 0
def mt_lut(self, c1, expr, planes=0):
lut = [clamp(0, expr(x), self.max) for x in self.lut_range]
return self.std.Lut(c1, lut, planes)
def mt_lutxy(self, c1, c2, expr, planes=[0]):
lut = []
for y in self.lut_range:
for x in self.lut_range:
lut.append(clamp(0, expr(x, y), self.max))
return self.std.Lut2([c1, c2], lut, planes)
def mt_adddiff(self, c1, c2, planes=[0]):
expr = ('x y + {mid} -').format(mid=self.mid)
expr = [(i in planes) * expr for i in range(3)]
return self.std.Expr([c1, c2], expr)
def mt_makediff(self, c1, c2, planes=[0]):
expr = ('x y - {mid} +').format(mid=self.mid)
expr = [(i in planes) * expr for i in range(3)]
return self.std.Expr([c1, c2], expr)
def sharpen(self, clip, mode=1, sstr=2.0, cstr=None, xstr=0.19, lstr=1.49,
pstr=1.272, ldmp=None):
if clip.format.color_family != vs.YUV or clip.format.bits_per_sample != 8:
raise InvalidArgument('clip is not 8bit YUV.')
mode = int(mode)
if abs(mode) > 3 or mode == 0:
raise InvalidArgument('mode must be 1, 2, 3, -1, -2 or -3.')
sstr = float(sstr)
if sstr < 0.0:
raise InvalidArgument('sstr needs to be larger than zero.')
if cstr is None:
cstr = spline(sstr, {0:0, 0.5:0.1, 1:0.6, 2:0.9, 2.5:1, 3:1.09,
3.5:1.15, 4:1.19, 8:1.249, 255:1.5})
if mode > 0:
cstr **= 0.8
cstr = float(cstr)
xstr = float(xstr)
if xstr < 0.0:
raise InvalidArgument('xstr needs to be larger than zero.')
lstr = float(lstr)
pstr = float(pstr)
if ldmp is None:
ldmp = sstr + 0.1
ldmp = float(ldmp)
self.max = 2 ** clip.format.bits_per_sample - 1
self.mid = self.max // 2 + 1
self.lut_range = range(self.max + 1)
rg = 20 - (mode > 0) * 9
if sstr < 0.01 and cstr < 0.01 and xstr < 0.01:
return clip
orig = None
if clip.format.id != vs.YUV420P8:
orig = clip
clip = self.point(clip, format=vs.YUV420P8)
if abs(mode) == 1:
c2 = self.rgrain(self.rgrain(clip, 11, -1), 4, -1)
else:
c2 = self.rgrain(self.rgrain(clip, 4, -1), 11, -1)
if abs(mode) == 3:
c2 = self.rgrain(c2, 4, -1)
expr = lambda x, y:(((((abs(x - y) / lstr) ** (1 / pstr)) * sstr)
* ((x - y) / (abs(x - y) + 0.001))) *
(((x - y) ** 2) / (((x - y) ** 2) + ldmp)) + 128)
diff = self.mt_lutxy(clip, c2, expr)
shrp = clip
if sstr >= 0.01:
shrp = self.mt_adddiff(shrp, diff)
if cstr >= 0.01:
diff = self.mt_lut(diff, lambda x: (x - self.mid) * cstr + self.mid)
diff = self.rgrain(diff, rg, -1)
shrp = self.mt_makediff(shrp, diff)
if xstr >= 0.01:
xyshrp = self.mt_lutxy(shrp, self.rgrain(shrp, 20, -1),
lambda x, y: x + (x - y) * 9.9)
rpshrp = self.repair(xyshrp, shrp, 12, 0)
shrp = self.std.Merge([rpshrp, shrp], [1 - xstr, 1.0, 1.0])
if orig is not None:
shrp = self.std.ShufflePlanes([shrp, orig], [0, 1, 2], vs.YUV)
return shrp
def usage(self):
usage = '''
Small and relatively fast realtime-sharpening function, for 1080p,
or after scaling 720p -> 1080p during playback
(to make 720p look more like being 1080p)
It's a generic sharpener. Only for good quality sources!
(If the source is crap, FineSharp will happily sharpen the crap.) ;)
Noise/grain will be enhanced, too. The method is GENERIC.
Modus operandi: A basic nonlinear sharpening method is performed,
then the *blurred* sharp-difference gets subtracted again.
sharpen(clip, mode=1, sstr=2.0, cstr=None, xstr=0.19, lstr=1.49,
pstr=1.272, ldmp=None)
mode: 1 to 3, weakest to strongest. When negative -1 to -3,
a broader kernel for equalisation is used.
sstr: strength of sharpening, 0.0 up to ??
cstr: strength of equalisation, 0.0 to ? 2.0 ?
(recomm. 0.5 to 1.25, default AUTO)
xstr: strength of XSharpen-style final sharpening, 0.0 to 1.0
(but, better don't go beyond 0.249 ...)
lstr: modifier for non-linear sharpening
pstr: exponent for non-linear sharpening
ldmp: "low damp", to not overenhance very small differences
(noise coming out of flat areas, default sstr+1)
'''
return usage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment