View Full Version : Idea for a sharpening filter
ilpippo80
1st July 2005, 00:29
Hi all,
Yesterday I had some ideas about a new filter, but before I start coding I would like to share my thoughts to have some suggestions and to know if anything like it has ever been created. I'm quite good in c and c++ programming, but I'm quite a noob about filtering and video...
anyway, as everybody (I think) here I'm amazed by the quality of Didée's LimitedSharpen filter, but I also think that if it would be faster, I would be happier... so I was thinking: is there any way to create a simple (and fast) filter, not so rich of features, but which uses its main principle? (http://forum.doom9.org/showpost.php?p=559985&postcount=1)
what we would like is to enhance the steepyness of an edge without create overshooting. for example, in a one-dimensional world, if a sequence of three values is:
1 4 10
I could think that the central value is a blurred edge, and so I could try to enhance its steepyness moving it towards the nearest surrounding value, which is the 1, and so the processed output sequence could be:
1 1 10
or better:
1 2 10
but if the input sequence is:
1 10 4
I would not perform any modification because it would cause just 'overshooting', and so it would be useless to the sharpening of the image.
Trying to apply this in a two-dimensional environment, I think that two ways could be followed:
1 - use a simple 3*3 sharpening matrix like:
-1 -2 -1
-2 13 -2
-1 -2 -1
or any other (maybe customizable), but only when the central value is contained between the highest and the lowest of the surrounding values. maybe this happens too often, and maybe the mask could be applied just when the value is between the second or third highest and lowest of the surrounding pixels, or also this could be a customizable parameter. I also think that probably it's also quite easy to write it as a function (maybe using some masking), before trying developing it as a compiled filter...
2 - in a 3*3 window, I could look in all the 4 directions (horizontal, vertical and diagonals). in every direction I have a one-dimensional situation similar to the one showed above. so if the value of the central pixel is contained between the two neighbors it could be clipped to the nearest value of the neighbors, otherwise it would be unchanged. in this way, I obtain 4 new possible values for the pixel, and I could make a weighted average (also customizable) between the 4 new values and the old value. so the strength of the filter could be higher or lower choosing the values apllied for the average. don't know if the result could be nice-looking...
Both this ways could be developed using a variable radius, with the possibility of using more information than the one supplied by a 3*3 window, for example using a 5*5 one or a bigger one...
That's all!
So, what do you think? Comments, critics and suggestions are welcome!
Thanks,
Pippo
ambrotos
1st July 2005, 03:32
1 - use a simple 3*3 sharpening matrix like:
-1 -2 -1
-2 13 -2
-1 -2 -1
This kind of filter already exists and it's called Laplacian filter
http://homepages.inf.ed.ac.uk/rbf/HIPR2/unsharp.htm
beside the fact that the examples of Didée were 1-D, I think that very similar 2D kernel is already used in most sharpening filters and so in Limitedunsharpen().
So, what you propose is a new adaptative sharpener ? but what's the difference with the didée's limitedsharpen() ?
btw if you want to play with kernels : http://homepages.inf.ed.ac.uk/rbf/HIPR2/convolutiondemo.htm
(I think you can load your own monochromatic pictures)
2 - in a 3*3 window, I could look in all the 4 directions (horizontal, vertical and diagonals). in every direction I have a one-dimensional situation similar to the one showed above. so if the value of the central pixel is contained between the two neighbors it could be clipped to the nearest value of the neighbors, otherwise it would be unchanged.
Interesting variant of the min/max directional blur ^^ but for sharpening, it worth a try
Didée
1st July 2005, 09:13
Finally someone comes up with that ;)
In the wake of LimitedSharpen, I also built a function "SlopeBend()", which does something very similar.
Where LS is - basically - doing a simple trick (do normal sharpening, and just cut off the overshoot where it occurs ... hence the need for supersampling to avoid jaggy edges), SlopeBend shifts values inbetween local min/max values towards the closer-in-range extremum.
Main drawback of SlopeBend() is, that I never liked the results, and thus posted LS instead ...
To put the problem in simple words: when going to sharpen a pixel that's inbetween the local min. & max., it's hard to tell if the pixel actually
a) is part of a "blurry edge" (-> should be sharpened)
b) is part of a gradient (-> should be left alone, to avoid banding/posterization)
c) is part of the "antialiasing" of a hard edge (-> must not be sharpened, to not produce aliasing)
One could, of course, do more probability checks to process only the a)-pixels and bypass the b)- and c)-pixels. But then you leave the fast lane of 3x3 kernel operation. At least you'd have to look at the local 3x3 and 5x5 kernel (better even 7x7), and evaluate & compare their properties.
Read: slow operation :(
If you're interested, I'll post the function here this weekend.
ilpippo80
1st July 2005, 10:21
@ambrotos
That is a simple 2-D sharpening filter, there's nothing new with it, the only thing added would be that I would apply it just when there is an edge, which is also the main principle of LimitedSharpen.
Thanks for the links, I'll have a look! ;)
@Didée
c) is part of the "antialiasing" of a hard edge (-> must not be sharpened, to not produce aliasing)
I think I don't understand this fully, could you please explain it better?
and I would appreciate much if you could post that function, that would be a very good starting point... :)
Thanks,
Pippo
mg262
1st July 2005, 10:51
I've been meaning for some time to point people at this link on upsampling and retaining sharpening, and this seems like a decent place to do it:
http://www.avsforum.com/avs-vb/showthread.php?p=4767386&&#post4767386
In fact the whole thread is IMO very useful reading, especially the posts by dmunsil. Hope someone finds it interesting!
Didée
1st July 2005, 11:17
@ ilpippo80
Look closely at this picture: (Primitive showcase, but hey...)
http://img228.imageshack.us/img228/6250/2sharpenornot2sharpen0nf.png
In the middle there's the "blurry edge" for which shifting the pixels with inbetween values is OK.
On the left there's the "hard edge" where shifting the inbetween values produces aliasing.
On the right there's a smooth gradient getting destroyed by shifting inbetween values.
Didée
1st July 2005, 12:26
Oh, surprise. Found that once I had copied the script to this machine here, too.
SlopeBend_v01a (http://home.arcor.de/dhanselmann/_stuff/SlopeBend_v01a_avs.rar)
Warning: Fully experimental, do not use! Development version with more code commented out than code actually doing something ...
There're not even explaining comments in it (except for the header), but methinks you can figure the story from the variable names. If not, just ask.
ilpippo80
1st July 2005, 13:20
@Didée
many thanks for the clear examples and the code, this weekend I'll try experimenting with it...
and many thanks to everybody for your help! it's a big relief to be helped by so experienced filter programmers!
Pippo
kassandro
1st July 2005, 13:34
1 - use a simple 3*3 sharpening matrix like:
-1 -2 -1
-2 13 -2
-1 -2 -1
As already mentioned by ambrotos, this is a very standard way of sharpening. It easily leads to oversharpening, i.e. the edge halos, which are nicely displayed in a picture at the very beginning of the LimitedShapen thread.
2 - in a 3*3 window, I could look in all the 4 directions (horizontal, vertical and diagonals). in every direction I have a one-dimensional situation similar to the one showed above. so if the value of the central pixel is contained between the two neighbors it could be clipped to the nearest value of the neighbors, otherwise it would be unchanged. in this way, I obtain 4 new possible values for the pixel, and I could make a weighted average (also customizable) between the 4 new values and the old value. so the strength of the filter could be higher or lower choosing the values apllied for the average. don't know if the result could be nice-looking...
This idea is more interesting and I will probably implement two versions of it in the forthcoming version 1.0 of RemoveGrain. The first one will sharpen along the line, where the difference between the end points is minimal. This version is very conservative and doesn't destroy thin lines like most sharpeners. The second will choose the line with maximal difference. This is very aggressive and may easily destroy thin lines. Actually I have already implemented the first one. In fact, from any denoise mode of RemoveGrain one can derive a sharpening mode and I have already done this (the plugin is called RSharpen). Then RShapen(mode=9) is exactly the first very conservative version. Didee's SlopeBend corresponds to RSharpen(mode=1). However, my one dimensional sharpening method is more conservative much more sophisticated. The basic mistake in your sharpening technique can be shown by looking at four instead of three values 10, 30, 40, 60. Then your sharpening technique yields something like 10, 40, 30 , 60, which is terrible. Any good sharpener has to cope with this problem, that sharpening may occur from both sides. If it dose not it will not only be a noise amplifier but even a noise creator. Perhaps Didee made a similar mistake in his SlopeBend. My approach in RemoveGrain 1.0 takes care of this problem in a SSE friendly way.
Edit: My method of sharpening also tries to avoid aliasing as much as possible.
As I was locked out from this forum for 30 days because of my contribution to the Avisynth 2.6.6 beta thread, I will reduce my participation in this forum substantially. In particular, I will only announce new versions of my plugins here, but will no more support these filters. If you want support you have to go to my support forum (see the RemoveDirt, RemoveGrain web sites).
However, I will continue to critise neuron2, whenever this is justified. That is the last thing I will stop in this forum.
Didée
1st July 2005, 14:09
Hi, kassandro - may I say "welcome back" ...
No, Didée did not make a similar mistake in his script. ;) He just takes the absolute maxima and minima from a neighborhood of given radius, compares center pixel's value to (max+min)/2, and then does a normalized multiplication.
So no noise is ever generated. But the function can achieve very strong posterization, if 'strength' and 'radius' are set high enough ;)
BTW, the topmost link on the RemoveGrain page never worked for me - always gives "Whoops - Seite nicht gefunden!" ...
kassandro
1st July 2005, 14:43
Hi, kassandro - may I say "welcome back" ...
Probably I will be out again soon. As an immediate response I got the following PM
Warning
We have discovered that you have violated forum rule #4 in one of your posts. If you violate the forum rules 1 more times you will be suspended for 30 days.
BTW, the topmost link on the RemoveGrain page never worked for me - always gives "Whoops - Seite nicht gefunden!" ...
Fortunately mg262 just did point out this problem to me in a PM. It will be fixed soon.
Didée
1st July 2005, 15:23
@ ilpippo80
I just remembered that by that time, I had correspondence with a collegue (foreign, hence in English) about the operation, because I wasn't sure how to do some things. Have a look at this PDF (http://home.arcor.de/dhanselmann/_stuff/MyFormerMathProblem&Solution.pdf), could make things somewhat clearer. Perhaps.
BTW, right now I realize I never implemented that weigthened strength thingie - just forgot about it ...)
(kassandro - *don't* you look at that! You'll die for laughter ...)
ilpippo80
1st July 2005, 16:32
@mg262
thanks for the link! now I've understood something more, for example why we have to deal with an aliasing problem. indeed, it's not very natural to think about Nyquist frequency when you're watching an image...
by the way, some more ideas: using the values of the 8 surrounding pixels, maybe it would be possible to calculate (or probably just estimate) the minimum and maximum value that we could give to the central pixel without producing aliasing...
if this is possible, we could compare the central pixel values with this limits, and if it's contained we could push its value towards the nearest limit, otherwise we would leave it unchanged. so we would solve two problems:
1 - find a good way to apply the original 1-D idea in a 2-D world
2 - how not to create aliasing
now two questions:
Q: how could I calculate min and max?
A: dunnow. maybe some googling will help...
Q: what about gradients?
A: ...
...
(five minutes thinking)
ehm...
(five minutes thinking)
...
(five minutes thinking)
doh!
...
well, I think it's not good to try to solve too problems in the same time, is it?
Bye, Pippo
ilpippo80
1st July 2005, 18:04
@kassandro
About point 1 - I know that it's a very simple filter, but I was thinking that I could apply it just where needed to enhance blurred edges and not to apply it where it would produce just artifacts (easy to say, but difficult to do!)...
About point 2 - The truth is that the I 'stole' the basic idea from your way of denoising with RemoveGrain. BTW, great denoiser, and it's SO FAST...
Anyway, I know that you're completely right when you wrote that the use of such a way of sharpening could produce such problems, because a pixel value is not changed by its own, also its surroundings will be changed, I already knew it. Anyway, if the user is careful enough with the strength of the filter (see below), it should not produce relevant artifacts...
@Didée
I've read the pdf. I was thinking to something easier, just a weighted average between the actual value and the nearest limit, with a strength parameter used for the weight. if the strength is str and it's a value between 0 and 32, the actual value is act and the nearest limit is lim, the new value could be obtained in this way:
(lim*str+act*(32-str))/32
But I think that I'm thinking too much. :) Now I'd better start coding for a while!
You're really really really helping me a lot, Didée! Thanks again!
kassandro
1st July 2005, 18:17
@kassandro
Anyway, if the user is careful enough with the strength of the filter (see below), it should not produce relevant artifacts...
Exactly, if you at least halfen the strength, then the unpleasant phenomenon decribed above cannot occur.
By the way, I have corrected the links, but the old web pages may still be in Firefox cache. In my support forum I have discussed the problem in more detail and gave a precise description, how I sharpen between to points.
@mg262:
thanks for the interesting link to the avsforum. I didn't even know that there exists another advanced a/v forum besides that of doom9.
ilpippo80
2nd July 2005, 11:38
Here's some code.
function pSharpen(clip clp, int "strength", int "threshold")
{
strength=default(strength, 16)
strength=strength<0? 0 : strength>32? 32 : strength
threshold=default(threshold, 4)
threshold=threshold<4? 4 : threshold
temp=clp.greyscale()
# calculating the max and min between 9 pixels for every pixel
max=temp.expand()
min=temp.inpand()
# low-pass filtering max and min to avoid aliasing
conv=temp.dEdgeMask(0,255,0,255,"1 2 1 2 0 2 1 2 1",12)
th=string(threshold)
max=yv12lutxy(conv,max,"x 12 * y "+th+" * + 12 "+th+" + /")
min=yv12lutxy(conv,min,"x 12 * y "+th+" * + 12 "+th+" + /")
# where mask_mod is 0, the original value must be kept
mask_mod=logic (yv12lutxy(temp,max,"x y > 0 255 ?"),
\yv12lutxy(temp,min,"x y < 0 255 ?"),
\"and")
# where mask_hi is 0, the pixel must be rounded towards the max
avg=yv12lutxy(max,min,"x y + 2 /")
mask_hi=yv12lutxy(temp,avg,"x y > 0 255 ?")
# calculates the values for the rounding towards max or min
str=string(strength)
clp_hi=yv12lutxy(temp,max,"y "+str+" * x 32 "+str+" - * + 32 /")
clp_lo=yv12lutxy(temp,min,"y "+str+" * x 32 "+str+" - * + 32 /")
# merges the clips obtained
temp=MaskedMerge (temp,
\maskedMerge(clp_hi,clp_lo,mask_hi),
\mask_mod)
clp=clp.MergeLuma(temp)
clp
}
The strength is the wheight given to the min or max value averaged with the old pixel value. The threshold is how much the aliasing is allowed, the lower is this value and the higher is the low-pass filtering (a simple average filter) performed on the min and max values to avoid the aliasing but also the lower will be the effect of the strength.
The standard value do not produce artifacts but the sharpening is very (VERY) weak. You may try something like:
psharpen(20,6)
to have some more visible effect. anyway, the aliasing problem starts to show. it seems that there's no other choice than oversampling+filtering+subsampling to avoid it... :(
BTW, I'll make some more experimenting...
ilpippo80
2nd July 2005, 13:26
version with the upsampling and downsampling.
the threshold is disabled, as it seems it does not improves the quality and just slows down the filter...
function pSharpen(clip clp, int "strength", int "threshold", float "ss_x", float "ss_y")
{
strength = default(strength, 16)
strength = strength<0? 0 : strength>32? 32 : strength
threshold = default(threshold, 4)
threshold = threshold<4? 4 : threshold
ss_x = default(ss_x, 1.5)
ss_y = default(ss_y, 1.5)
temp = clp.greyscale()
ox = clp.width
oy = clp.height
temp = ss_x!=1.0 || ss_y!=1.0 ?
\temp.lanczosresize(round(ox*ss_x/8)*8,round(oy*ss_y/8)*8) :
\temp
# calculating the max and min between 9 pixels for every pixel
max = temp.expand()
min = temp.inpand()
# low-pass filtering max and min to avoid aliasing
#conv = temp.dEdgeMask(0,255,0,255,"1 2 1 2 0 2 1 2 1",12)
#th = string(threshold)
#max = yv12lutxy(conv,max,"x 12 * y "+th+" * + 12 "+th+" + /")
#min = yv12lutxy(conv,min,"x 12 * y "+th+" * + 12 "+th+" + /")
# where mask_mod is 0, the original value must be kept
#mask_mod = logic(yv12lutxy(temp,max,"x y > 0 255 ?"),
# \yv12lutxy(temp,min,"x y < 0 255 ?"),
# \"and")
# where mask_hi is 0, the pixel must be rounded towards the max
avg = yv12lutxy(max,min,"x y + 2 /")
mask_hi = yv12lutxy(temp,avg,"x y > 0 255 ?")
# calculates the values pushed towards min or max
str = string(strength)
clp_hi = yv12lutxy(temp,max,"y "+str+" * x 32 "+str+" - * + 32 /")
clp_lo = yv12lutxy(temp,min,"y "+str+" * x 32 "+str+" - * + 32 /")
clp_flt = maskedMerge(clp_hi,clp_lo,mask_hi)
# low-pass filtering the adjusted values to avoid the aliasing (?)
#conv = temp.dEdgeMask(0,255,0,255,"1 2 1 2 0 2 1 2 1",12)
#th = string(threshold)
#clp_flt = yv12lutxy(conv,clp_flt,"x 12 * y "+th+" * + 12 "+th+" + /")
# merges the clips obtained
#temp = MaskedMerge(temp, clp_flt, mask_mod)
temp = clp_flt
temp = ss_x!=1.0 || ss_y!=1.0 ? temp.lanczosresize(ox,oy) : temp
return clp.MergeLuma(temp)
}
Edit: I've added the return in the end, it's more elegant...
Didée
2nd July 2005, 13:45
function pSharpen
{
[...]
temp = clp.greyscale()
[...]
clp = clp.MergeLuma(temp)
clp
}
ermh ...
scharfis_brain
2nd July 2005, 13:50
what is wrong with that, Didèe?
temp = clp.greyscale()
return clp.MergeLuma(temp)
equals to:
temp = clp.greyscale()
return temp.Mergechroma(clp)
ilpippo80
2nd July 2005, 15:45
@Didée
agree with scharfis_brain, it should work correctly...
anyway, I've edited the last lines, the previous version:
clp=clp.MergeLuma(temp)
clp
was a bit awful to see... much better:
return clp.MergeLuma(temp)
has anybody tried the filter? what do you think about it?
it's quite slow, but it should not be a problem to use the same algorithm to write a plugin... the only thing I don't like is that resizing, I have to find some other anti-aliasing method...
Didée
2nd July 2005, 16:15
Oh, sh*t ... I had tomatoes on my eyes, and did read "MergeChroma" instead of "MergeLuma" :o
Sorry ...
Anti-aliasing is the only issue (artifact).
With LimitedSharpen, at ss_x/ss_y=1, strength>20 gives AA. (CPU 60-70%) ss_x/ss_y=1.1, strength>30 gives AA. (CPU 90-100%)
With the first version without over-sampling, pSharpen, strength>12 and threshold>4 gives AA.
With over-sampling, ss_x/ss_y=1.2, strength>6 gives AA. (CPU 90-100%)
My usage is for realtime DVD playback via FFDShow's Avisynth interfact. Initial impression seems LimitedSharpen gives a overall better picture quality.
System is P4 2.66G 1M Prescott 533M bus overclocked to 3.84G 768M bus.
regards,
Li On
ilpippo80
2nd July 2005, 17:41
@psme
thanks for the testing! anyway, this filter is thought to be written in c and compiled (I just wanted to see if it worths the effort of doing it) so my first problem is to try to reach the highest quality as possible, lower cpu usage is something that will eventually come...
anyway, some more adjusts, and another version:
function pSharpen (clip clp,
\int "strength", float "ss_x", float "ss_y",
\int "threshold", bool "th_enable")
{
strength = default(strength, 16)
strength = strength<0? 0 : strength>32? 32 : strength
threshold = default(threshold, 20)
threshold = threshold<4? 4 : threshold
th_enable = default(th_enable, true)
ss_x = default(ss_x, 1.5)
ss_y = default(ss_y, 1.5)
temp = clp.greyscale()
ox = clp.width
oy = clp.height
temp = ss_x!=1.0 || ss_y!=1.0 ?
\temp.lanczosresize(round(ox*ss_x/8)*8,round(oy*ss_y/8)*8) :
\temp
# calculating the max and min between 9 pixels for every pixel
max = temp.expand()
min = temp.inpand()
# low-pass filtering max and min to avoid aliasing
conv = temp.dEdgeMask(0,255,0,255,"1 2 1 2 0 2 1 2 1",12)
th = string(threshold)
max = th_enable ? yv12lutxy(conv,max,"x 12 * y "+th+" * + 12 "+th+" + /") : max
min = th_enable ? yv12lutxy(conv,min,"x 12 * y "+th+" * + 12 "+th+" + /") : min
# where mask_mod is 0, the original value must be kept
mask_mod = logic(yv12lutxy(temp,max,"x y > 0 255 ?"),
\yv12lutxy(temp,min,"x y < 0 255 ?"),
\"and")
# where mask_hi is 0, the pixel must be rounded towards the max
avg = yv12lutxy(max,min,"x y + 2 /")
mask_hi = yv12lutxy(temp,avg,"x y > 0 255 ?")
# calculates the values pushed towards min or max
str = string(strength)
clp_hi = yv12lutxy(temp,max,"y "+str+" * x 32 "+str+" - * + 32 /")
clp_lo = yv12lutxy(temp,min,"y "+str+" * x 32 "+str+" - * + 32 /")
clp_flt = maskedMerge(clp_hi,clp_lo,mask_hi)
# merges the clips obtained
temp = th_enable ? maskedMerge(temp, clp_flt, mask_mod) : clp_flt
temp = ss_x!=1.0 || ss_y!=1.0 ? temp.lanczosresize(ox,oy) : temp
return clp.MergeLuma(temp)
}
the strength of the sharpening goes from 0 (no sharpening) to 32 (highest sharpening). vales higher than 16 might produce artifacts.
the ss_x and ss_y are floats coefficients used for the upsampling and downsampling, which avoid the aliasing but cause some blurring.
After some tests, I decided to add the threshold again... as before, the highest is the threshold, the bigger is the possibility to have aliasing, but the picture will be more blurry. the lowest value for it is 4.
if th_enable is false, the prefiltering will not be done and so the threshold will not be used.
I believe the result is not horrible, and the further modifications I've got in my mind are almost impossible to be done in a function, so I think it's time for some c++ coding...
as ever, any (positive or negative) feedback is welcome!
Pippo
kassandro
4th July 2005, 06:51
Your pSharpen does the strongest sharpening, when "temp" is close to "avg". If temp = avg then it sharpens strongly towards the minimum and when temp = avg + 1, then it sharpens strongly to the maximum. Thus you get a massive discontinuity at "avg", which certainly results in poor compression and may even cause artifacts similar to the well known threshold artifacts.
The easiest way out of this problem, is to use a smooth mask instead of the brutal binary mask "mask_hi". More precisely mask_hi should contain the values
((temp - min)*255)/(max - min). Such a mask can be constructed by a threefold application of yv12lutxy. I am not lutmaniac and have always problems to read and write yvlutxy expressions. Hence you have to do this on your own. Also it has to be refined for proper rounding. I often see that user overuse poor binary masks instead of smooth masks although Masktools is really primarily made for the much better smooth masks.
Probably the better and faster way to fix the continuity problem is to use much more sophisticated yv12lutxy expressions in the definition of clp_hi and clp_lo.
ilpippo80
4th July 2005, 22:25
@kassandro
tweaking the formula to calculate the value of clp_hi and clp_lo was also what I was thinking of...
Anyway, I'm trying to substitute the wheighted average with something more sophisticated. Here's another version, here I'm using an equilateral hyperbola to perform the color adjustment (it's been a pain to write the formulas correctly). The sharpening is quite weak, and when the strength increases (26 - 30) the bright values become brighter but there's not an overall sharpening feeling. Anyway, for not very high values of the strength the aliasing is not showing. I think I could be on a good way, I'll try to make some more tweaking to the color adjustment formula...
I'd like not to complicate things too much with masking, I would like the algorithm to be as simple as possible. The way I use masking now is an adaptation of a simple if-then-else construct...
Pippo
ilpippo80
4th July 2005, 22:29
I was forgetting the code... :o
function pSharpen (clip clp,
\int "strength", float "ss_x", float "ss_y",
\int "threshold", bool "th_enable")
{
strength = default(strength, 16)
strength = strength<0? 0 : strength>32? 32 : strength
threshold = default(threshold, 20)
threshold = threshold<4? 4 : threshold
th_enable = default(th_enable, false)
ss_x = default(ss_x, 1.0)
ss_y = default(ss_y, 1.0)
val = clp.greyscale()
ox = clp.width
oy = clp.height
val = ss_x!=1.0 || ss_y!=1.0 ?
\val.lanczosresize(round(ox*ss_x/8)*8,round(oy*ss_y/8)*8) :
\val
# calculating the max and min between 9 pixels for every pixel
max = val.expand()
min = val.inpand()
# low-pass filtering max and min to avoid aliasing
conv = val.dEdgeMask(0,255,0,255,"1 2 1 2 0 2 1 2 1")
th = string(threshold)
max = th_enable ? yv12lutxy(conv,max,"x 12 * y "+th+" * + 12 "+th+" + /") : max
min = th_enable ? yv12lutxy(conv,min,"x 12 * y "+th+" * + 12 "+th+" + /") : min
# clipping to max and min the values of val
val = th_enable ? logic(max,logic(min,val,"max"),"min") : val
# where mask_hi is 0, the pixel must be rounded towards the max
#avg = yv12lutxy(max,min,"x y + 2 /")
#mask_hi = yv12lutxy(val,avg,"x y > 0 255 ?")
# calculates the values pushed towards min or max (weighted average)
#clp_hi = yv12lutxy(val,max,"y "+str+" * x 32 "+str+" - * + 32 /")
#clp_lo = yv12lutxy(val,min,"y "+str+" * x 32 "+str+" - * + 32 /")
#clp_flt = maskedMerge(clp_hi,clp_lo,mask_hi)
# normalizing the max and val to values from 0 to (max-min)
nmax = yv12lutxy(max,min,"x y -")
nval = yv12lutxy(val,min,"x y -")
mask_hi = yv12lutxy(nval,nmax,"x y 2 / > 0 255 ?")
# calculates the values pushed towards min or max
h=string(pow(2.0,(8.0-strength/4.0)))
a=string(1.0+sqrt(1.0+pow(2.0,(8.0-strength/4.0))))
clp_hi = yv12lutxy(nval,nmax,"y 0 = x y 2 / y 4 / "+a+" "+h+" 4 x * y / 4 - "+a+" + / - * + ?")
clp_lo = yv12lutxy(nval,nmax,"y 0 = x y 2 / y 4 / "+a+" "+h+" -4 x * y / "+a+" + / - * - ?")
clp_flt = maskedMerge(clp_hi,clp_lo,mask_hi)
clp_flt = yv12lutxy(clp_flt,min,"x y +")
# lowpass filtering the output (?)
#clp_flt = th_enable ? clp_flt.dEdgeMask(0,255,0,255,"1 2 1 2 "+th+" 2 1 2 1") : clp_flt
val = ss_x!=1.0 || ss_y!=1.0 ? clp_flt.lanczosresize(ox,oy) : clp_flt
return clp.MergeLuma(val)
}
Very nice! :)
Default pSharpen() gives an excellent picture. Still a bit AA especially on anime source but almost no artifact on live action.
I'll try to compare to LimitedSharpen more carefully tonight. CPU usage seems around the same or maybe 2-3% more.
If this can be made into a fast dll than more people can use it for realtime playback! Keep up the great work! :)
regards,
Li On
ilpippo80
6th July 2005, 23:22
@psme,
thanks for testing and support, it's always nice to listen to people appreciating what yo're doing...
ok, now back for a new (and I think it should be the last and stable) version of my script. this time the result is quite impressive, and with a moderate value of the strength it will not produce aliasing.
the value of every pixel is compared with the min and max of its neighbors, and its new value is calculated through the function showed in the following image:
http://img283.imageshack.us/img283/6857/grafico2og.png
I tried to optimize the speed of the script as I could, now without the oversampling it's much faster than LimitedSharpen, but if you want to increase the strength too much the oversampling will be necessary to avoid aliasing, and with oversampling it is as slow as LimitedSharpen. Anyway the output in my opinion is very good, LimitedSharpen seems to give a better 'feeling' of sharpness but psharpen seems to behave better on smooth gradients. what do you think?
see for example the background of the below images:
original:
http://img64.imageshack.us/img64/184/orig5jh.th.png (http://img64.imageshack.us/my.php?image=orig5jh.png)
limitedsharpen():
http://img64.imageshack.us/img64/8707/ls8wb.th.png (http://img64.imageshack.us/my.php?image=ls8wb.png)
psharpen():
http://img168.imageshack.us/img168/2146/ps5cm.th.png (http://img168.imageshack.us/my.php?image=ps5cm.png)
psharpen(70,70,1.5,1.5):
http://img168.imageshack.us/img168/5046/ps707015155ab.th.png (http://img168.imageshack.us/my.php?image=ps707015155ab.png)
I still haven't tried the compression loss of psharpen compared with ltdsharpen, I think I will do some tests tomorrow...
Here's the code:
function pSharpen (clip clp,
\int "strength", int "threshold",
\float "ss_x", float "ss_y",
\float "dest_x", float "dest_y")
{
# getting the input image size
ox = clp.width
oy = clp.height
# parsing the input values
strength = default(strength, 25)
threshold = default(threshold, 75)
ss_x = default(ss_x, 1.0)
ss_y = default(ss_y, 1.0)
dest_x = default(dest_x, ox)
dest_y = default(dest_y, oy)
strength = strength<0? 0 : strength>100? 100 : strength
threshold = threshold<0? 0 : threshold>100? 100 : threshold
ss_x = ss_x<1.0? 1.0 : ss_x
ss_y = ss_y<1.0? 1.0 : ss_y
# oversampling
clp = ss_x!=1.0 || ss_y!=1.0 ?
\clp.lanczosresize(round(ox*ss_x/8)*8,round(oy*ss_y/8)*8) :
\clp
# val is initialized with the input luma value
val = clp.greyscale()
# calculating the max and min in every 3*3 square
max = val.expand()
min = val.inpand()
# normalizing max and val to values from 0 to (max-min)
nmax = yv12subtract(max,min,0,false)
nval = yv12subtract(val,min,0,false)
# initializing the strings used to obtain the output luma value
s = string(strength/100.0)
t = string(threshold/100.0)
x0 = string((threshold/100.0)*(1.0-strength/100.0)/(1.0-(1.0-threshold/100.0)*(1.0-strength/100.0)))
expr = \
"x y / 2 * 1 - abs "+x0+" < " +\
s+" 1 = " +\
"x y 2 / = 0 y 2 / ? " +\
"x y / 2 * 1 - abs 1 "+s+" - / " +\
"? " +\
"x y / 2 * 1 - abs 1 "+t+" - * "+t+" + " +\
"? " +\
"x y 2 / > 1 -1 ? * 1 + y * 2 /"
# calculates the new luma value pushing it towards min or max
nval = yv12lutxy(nval,nmax,expr)
# normalizing val to values from min to max
val = yv12lutxy(nval,min,"x y +")
# applying the new luma value to the original clip
clp = clp.mergeluma(val)
# resizing the image to the output values
clp = ss_x!=1.0 || ss_y!=1.0 || dest_x!=ox || dest_y!=oy ? clp.lanczosresize(dest_x,dest_y) : clp
return clp
}
Now back to c++. I'm reading the lot of stuff about the mmx optimizations, I must confess that I'm a noob with it... Anyway, I've got some questions to the 'compiling' people:
- how much do you think that it will be (approximately...) the speed improvement of the above algorithm when compiled to a dll?
- what should I do for the oversampling? if there's a way to call an internal avisynth function from the c++ code, do you think it's a good idea? or should I look for an implementation of the lanczos resize algorithm somewhere and include it in the code? doing two lanczos is anyway a HUGE bottleneck, the speed will decrease very much anyway, no metter if the rest of the code is a script or an optimized function... should I tell the user: "if you want better quality or very high sharpening without aliasing, oversize it before and resize it after, but it will slow down"? what do you think?
Pippo
Edit: just some comments in the code...
Edit2: corrected error when using dest_x and dest_y and other comments...
Soulhunter
7th July 2005, 00:23
Looks nice, good work... ^^
Bye
Thanks Pipo!
The latest script use around 10% LESS CPU than LimitedSharpen without oversampling. Overall picture seems better too!
This may become my default sharpener! :)
regards,
Li On
PS:
System: DFI 865PE-TAG, P4 506 (2.66G 1M Prescott 533M bus non-HT) overclocked to 3.8G
Playback: ZoomPlayer, DScaler 5.006 decoder (YV12 out), 30fps NTSC DVD, ffdshow-20040801a_preview_SSE2.exe, Avisynth pSharpen, FFDShow Resize Lanczos 1280x720.
CPU usage: around 80-90%.
kassandro
8th July 2005, 08:43
Pippo,
your sharpener now fulfills all my conditions for a 2 point sharpener. Very nice picture of the graph of the sharpening function! Actually, without oversampling and with only one reasonable strength value (I have the maximal one, which avoids noise creation, i.e. the angle of the sharpening curve in the bottom left corner = 26.6 degree) your sharpener seems to be very similar to my RSharpen(mode=1). However, the steepness in the center point is somewhat different (in my case the angle of the sharpening curve in the center is always 63.4 degree), but both sharpening curves are of the same type and we both select the same two points for sharpening.
The more standard convolution based sharpener are always somewhat superior as far as continuity is concerned, i.e. the generate less aliasing. On the other hand for convolution based sharpeners it is virtually impossible to determine the right strength for sharpening, which results either in sharpening too little or creating edge halos and noise by sharpening adjacent pixels too much against each other. In this respect the two point sharpening method is far superior.
Chainmax
8th July 2005, 14:56
It would be interesting to see some comparisons between LimitedSharpen and pSharpen on animated content. I would provide the pics myself, but I don't have a DVD drive for now :(.
ilpippo80
8th July 2005, 18:09
@kassandro
glad we arrived to the same conclusion going through different ways: indeed the two point sharpening seems to carry very good results. But I think that my plugin will never be as famous as yours. yours is going to be much faster than mine, I had a look to the RemoveGrain source code and it's impressive, I think that I will never be able to reach your level of coding (at least in this life!) ;)
@chainmax
I don't have many anime movies, anyway I'll post some pics later this evening...
@psme
thanks for supporting! :)
@all
I've made some experiments with 'Harry Potter - Philosopher's Stone'. I took a couple of scenes and I encoded them with huffyuv to evaluate the size:
First scene:
original: 166922 KB
LimitedSharpen: 175778 KB
pSharpen: 168844 KB
pSharpen(70,70,1.5,1.5): 171934 KB
Second scene:
original: 238936 KB
LimitedSharpen: 252668 KB
pSharpen: 243424 KB
pSharpen(70,70,1.5,1.5): 248198 KB
Fizick
8th July 2005, 23:38
ilpippo80,
never say "never".
BTW, the good opimized code must also have a right approach.
You (and Kassandro) are on a good way, but this way is not single one. ;)
All,
do you want to try some really strange (but limited) sharpener?
So, try new fft3dfilter v.1.1 !
http://forum.doom9.org/showthread.php?p=684308#post684308
On my P4 3.8G for realtime DVD playback, 24fps NTSC source:
fft3dfilter(bt=-1, sharpen=0.4)
- CPU 100%, smooth playback, grid pattern (!)
pSharpen()
- CPU 40-50%
LimitedSharpen(ss_x=1.0,ss_y=1.0,Smode=3,strength=25)
- CPU 45-55%
PQ wise pSharpen and LimitedSharpen are very close. Some content I perfer LimitedSharpen and pSharpen seems better on others. fft3dfilter has more enhancement artifacts, even besides the "grid" pattern!
regards,
Li On
Soulhunter
9th July 2005, 18:57
@ ilpippo80
If I use the "Dest" params ya function throws a error...
MergeLuma: Images must have same width and height!
Bye
ilpippo80
9th July 2005, 22:49
@Soulhunter
yeah, I made a stupid mistake... :o
now correct!
Thanks for reporting!
Pippo
ilpippo80
15th July 2005, 12:35
Sorry if I've been so late, but I've been quite busy in these days.
Anyway, here's some animation screenshots:
Original
http://img302.imageshack.us/img302/2682/bear9ab.th.png (http://img302.imageshack.us/my.php?image=bear9ab.png)
LimitedSharpen():
http://img343.imageshack.us/img343/8953/bearls9mk.th.png (http://img343.imageshack.us/my.php?image=bearls9mk.png)
pSharpen():
http://img297.imageshack.us/img297/3668/bearps0zq.th.png (http://img297.imageshack.us/my.php?image=bearps0zq.png)
pSharpen(70,70,1.5,1.5):
http://img297.imageshack.us/img297/8167/bearps707015155xf.th.png (http://img297.imageshack.us/my.php?image=bearps707015155xf.png)
LimitedSharpen seems to create some artifacts, while pSharpen shows that it deals better with gradients...
Didée
15th July 2005, 13:57
LimitedSharpen seems to create some artifacts, while pSharpen shows that it deals better with gradients...
Sorry mate, but here I have to intervene. Looking at those screenshots, speaking of "artefacts" is quite an exaggeration. Or perhaps ... wishful thinking? ;)
Anyways ... if you do comparisons to LS, then you should use "comparable" settings. Note that with default parameters, LS is doing much stronger enhancement than pSharpen! This is evident by the PNG filesizes, and even by looking with the naked eye.
Possibilities are plenty ... reduce strength ... reduce overshoot ... use Smode=2 ... use soft=true ... use edgemode=1 ...
I mean, you could also use (Smode=3,strength=1000,overshoot=255), and then complain that LS is nothing but c.r.a.p. ;)
That having said, pSharpen seems to evolve pretty nice indeed. Well done, ilpippo80.
Next step: increase the working window from 3x3 to 5x5 ... ;)
Fizick
15th July 2005, 18:10
fft3dfilter uses 48x48 window ;)
mg262
15th July 2005, 18:33
On the subject of artefacts, were you referring to the halos (ringing, Gibbs phenomenon, whatever you call it) around the bear and other objects? These seem to be present in the source and are exacerbated by all the filters (to varying degrees)... but IMO this is the correct thing for them to do on that input.
ilpippo80
15th July 2005, 19:04
sorry Didée, I didn't mean any offence! :o
Your LimitedSharpen is the better sharpener I've ever tried, and having a result even slightly comparable with your filter's one is all that I'm looking for... It's true also that playing with the parameters can produce any kind of sharpening wanted... Indeed writing of "artifacts" is an exaggeration, I just wanted to point out that pSharpen seems to deal well with gradients... pSharpen's default values are generally quite soft because it doesn't use supersampling to reduce aliasing, but trying stronger values with some supersampling gives an effect quite comparable with LimitedSharpen's default values (at least to my eyes, but they're the only eyes I have! ;)). And in the end obtaining some nice result have made me become too hasty writing conclusions... I think you know the feeling when something you've done finally works the way you expected! :rolleyes:
I've already tried using the 5x5 working window (the modification to the code is very simple, you get it simply doubling the inpand and expand to obtain min and max), but I don't like the result very much, so I don't think I will use it. Maybe I could add the possibility to the function, so anyone could choose it... But indeed it would be quite more complicated (and would make it much slower) to put it in c++ code...
I'm coding the c++ version now, but I must admit that there's so much things that I'm learning (and still have to learn) about coding avisyinth filters that I don't know when I'm going to finish! :scared:
Didée, I will never thank you enough for all your help and support!
Pippo
mg262
15th July 2005, 19:35
From the size of the bands, it looks like it might be not the gradients per se but that sharpening (with high coefficients using whatever filter) exacerbates the discretisation of the gradients due to MPEG coding? I.e. the banding is already there and sharpening makes it worse? In which case perhaps apply a filter to reduce the MPEG artefacts before sharpening may be appropriate? I don't know... but it might be interesting to try different sharpeners on a synthesised smooth gradient.
ilpippo80
15th July 2005, 20:36
@mg262
The "artifacts" I was referring to weren't the ringing effect, were the horizontal stripes visible in the bear, if you see in the original they are already there, filtering the image with LimitedSharpen simply made them much more visible. Anyway trying to create "synthesized" input clips (not only gradients, maybe also several kinds of edges) to test the way a sharpener can deal with them might be a cool idea, I think I will try it.
Maybe it could show more objectively advantages and problems of the various sharpeners out there, it could be useful also to understand what kind of improvements could be made to obtain better results...
Didée
18th July 2005, 12:49
ilpippo80,
please don't you praise in such an obeisant manner - this reminds too much of tin god worship ...
In case you didn't realize - when writing my last post, I was not angry in any way, but rather amused :)
You noted LS' artefacting as 'a fact', where pSharpen doesn't. But, exactly those settings in pSharpen that give the advantage in this respect, were not activated in LS, although they *are* there, too.
I just stood up because you compared, a little bit, apples and oranges, and concluded that the apple has the smoother skin ... big surprise, when telling LS to produce oranges. Tell it to produce apples, and you'll get smooth-skinned fruit as well ;)
"Sharpening" is a term one can view from many different angles.
Loosely speaking, sharpening can be "edge-condensing" only, with WarpSharpening as a common example. This will improve the steepyness of edges, but hardly improves detail's contrast. And sharpening can be contrast improving, like in normal sharpen(), where increased edge steepiness is more like a (necessary) side-effect. And LS is, mostly, a mix of both methods.
Now, mg262 already told the important facts we're facing in this example. The source is not really "smooth", but already contains artefacts. Not only the edge enhancement (which should be reduced also, along with the sharpening), but also discontinuities from the former mpeg compression. That's the point we have to start from when evaluating what the sharpeners did.
Of course, both methods have different consequences, in particular in "flat" areas. Imagine a line of value "101" in a flat area of value "100". Pure edge condensing will not create any new values below 100 or above 101 - thus it'll be "artefact free", however it will not make the line "more visible". Contrast enhancing sharpening will create such new values ("overshoot"). Thus it possibly creates artefacts, but also makes the line "more visible".
The final valuation, however, is sort of random: if the line is an artefact, people cry "murder, there's artefacting!". If, however, the line actually was detail, people are :) that the detail was enhanced.
But ... how should the sharpener know if the line is an artefact, or if it's detail ...
Again, just like mg262 said: it's the correct thing for a sharpener to also enhance artefacts. At least for "plain" sharpeners like those we're talking about here. And I fully agree with mg262 that artefact protection is the job of additional filtering, and not the job of the sharpener. Of course we can include artefact protection into a sharpening routine. But then we're not really talking about a sharpener anymore, but about sort of an integrated solution.
Lastly, my comment about extending the evaluation from 3x3 to 5x5 was not meant in the simple way of just getting the extrema from a wider area. What I had in mind was more directed towards "slope evaluation". When looking at the extrema of a 3x3 neighborhood only, then there's not enough information to tell much about the "area context" of these values. But if one gets the extrema both from a 3x3 and a 5x5 neighborhood, then there is much more information available to get an idea about what the characteristics of the local area are, and what therefore should be done with and according to the actual local values.
Sidenote: While it seems obvious to look at the plain output of the filter, this is not fully practical. The amount of "overshooting" (and thus, possible artefacting) that is created by LS is rather small. If compares not the plain filter result, but the result after compression with a lossy codec, things look different again: Most of those weak artefacts will be dropped again by encoding, and the hype was for nothing.
Look at it this way: (very basically, and leaving out lossless encoding, obviously)
Encoding is lossy. If we start with our original source being 100%, then the encoding will come out at, say, 95%. Nothing to do against that, since the coding process is just lossy.
If, however, we enhance the source to 105% before encoding, then we will be much closer to 100% after encoding. That's why a certain (i.e. small enough!) amount of artefacting is pretty much tolerable: it will vanish again, anyways.
ilpippo80
18th July 2005, 14:38
please don't you praise in such an obeisant manner - this reminds too much of tin god worship ...
The truth is that you made a great original job while I've spent just a few weeks copying your ideas, it seems to me that I'm like a dwarf on a giant's shoulders... But I'm just a noob, I have the time to improve and I hope that I'll be able to create something useful!
In case you didn't realize - when writing my last post, I was not angry in any way, but rather amused :)
yeah, I know you weren't angry... :)
Again, just like mg262 said: it's the correct thing for a sharpener to also enhance artefacts. At least for "plain" sharpeners like those we're talking about here. And I fully agree with mg262 that artefact protection is the job of additional filtering, and not the job of the sharpener. Of course we can include artefact protection into a sharpening routine. But then we're not really talking about a sharpener anymore, but about sort of an integrated solution.
true, for a real encoding that source should have been denoised before sharpening...
Lastly, my comment about extending the evaluation from 3x3 to 5x5 was not meant in the simple way of just getting the extrema from a wider area. What I had in mind was more directed towards "slope evaluation". When looking at the extrema of a 3x3 neighborhood only, then there's not enough information to tell much about the "area context" of these values. But if one gets the extrema both from a 3x3 and a 5x5 neighborhood, then there is much more information available to get an idea about what the characteristics of the local area are, and what therefore should be done with and according to the actual local values.
it would be a great improvement, but I think it would come out with something completely different from what pSharpen is now. now the only information it uses is the maximum and minimum values in the neighborhood, and the result is quite good, if I manage to create a plugin fast enough to perform that kind of job I will be satisfied (at least now). I know that the information that pSharpen uses is quite poor, and anyway I think that I will try to create something more complicated (which uses more information) in a future!
Sidenote: While it seems obvious to look at the plain output of the filter, this is not fully practical. The amount of "overshooting" (and thus, possible artefacting) that is created by LS is rather small. If compares not the plain filter result, but the result after compression with a lossy codec, things look different again: Most of those weak artefacts will be dropped again by encoding, and the hype was for nothing.
That makes a comparison dependent by a lot of factors, like the encoder used, the matrix, the amount of compression and so on. anyway, the main purpose I've written pSharpen for is that I wanted a sharpener to use it for real-time playback filtering...
Mug Funky
18th July 2005, 16:05
wouldn't weak sharpening artefacts be low amplitude, high frequency noise? in this case, pretty much all encoders with all (sane) matrices will drop this detail through quantisation. at least all lossy DCT (or similar) based ones will, and probably wavelet ones as well - high frequencies take up the bulk of the space and are less important, so an efficient encoder will hit these harder.
mg262
18th July 2005, 18:56
wouldn't weak sharpening artefacts be low amplitude, high frequency noise?Which kind of sharpening artefacts are you referring to... the ringing or the gradient stuff? Assuming it's the gradient stuff, I can't see why it would be purely high-frequency stuff (i.e. decomposing to a sum of high-frequency sinewaves)... but that's probably me missing something.
Could it also depend on whether the blocks of the target encoder lineup with the blocks of the source?
At the end of the day, I guess we have to encode stuff and see...
psme
19th July 2005, 01:53
the main purpose I've written pSharpen for is that I wanted a sharpener to use it for real-time playback filtering...
This is the way I use the sharpen script! Realtime DVD (and other content) playback on a big projection system!
Thanks again for all the great work! :cool:
regards,
Li On
orion44
22nd July 2023, 17:11
This sharpener produces excellent results.
Is it ok to use the Soothe() function with pSharpen?
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.