Log in

View Full Version : Contrast enhance in dark area


dplaton
28th March 2005, 10:32
This an adaptation from

http://www.gimpguru.org/Tutorials/ContrastMask/

The original methode (for photo)

1. duplicate layer
2. duplicate layer -> B&W
3. B&W -> negative
4. negative -> Mode = Overlay
5. Overlay -> Gaussian Blur 10-30

An AviSynth adaptation

a = AviSource()
b = a. GrayScale().Levels(0,1,255,255,0)Blur(0.2,0.2)
Layer(a,b,"add",10)

Happy testing

Seed
28th March 2005, 10:56
Great. Simple, yet clever. I love this type of scripts, as much as I like those mega-watts iPP or HybridFuPP. Actually I'd like to love iPP or HybridFuPP more, but my CPU (even though at 3.4 GHz) tell me to love them less :D

Dark-Cracker
28th March 2005, 14:37
give nice results :) but you should try to use YV12Layer to increase speed of your script and avoid to convert input source in YUY2.

++

Mug Funky
28th March 2005, 14:54
hey, cool. with a slight modification this will also make a fast and nice substitute for the virtualdub filter "windowed histogram equalization".

Didée
28th March 2005, 22:33
Sorry to spoil the fun, but this does not enhance contrast. In contrary, it is reducing contrast, shifting all outer levels towards 128. The pleasing effect is only the brightening of the dark parts. Also, the histogram shows loss of tonalities, indicated by "spikes" in the histogram. Please check yourself with

interleave(a,last).histogram(mode="levels")

Then, blur(0.2,0.2) is not a simulation of a radius 10 gaussian blur. To simulate that, you'd need to replace that blur() with resize(33%).resize(100%)

Backwoods
28th March 2005, 23:53
Originally posted by Didée
Then, blur(0.2,0.2) is not a simulation of a radius 10 gaussian blur. To simulate that, you'd need to replace that blur() with resize(33%).resize(100%)

EDIT: Nm, but this method seems to make an thick border around the image.

Didée
29th March 2005, 03:39
Backwoods: Yes. but you've hawk's eyes, and are probably trying on cartoon or animee. Try bicubic(25%, 0.33,0.33).bicubic(100%, 1.0,0.0) instead of lanczos ;)

I'm too tired right now to work it all through. But please, just try the following:

- raise strength in layer() from "10" to, say, "32" or "64"

- *do check* the result via interleave() & histogram(mode="levels") as I said.

Then you will see what really is going on. Nothing against the method. It is a simple method, working very fast. And so is the result: fast and, well, simple.
Put shortly, the method is nothing more than a combination of {reduction of dynamic range} plus {unsharp masking}.
In particular, one is loosing blacks and whites.


BTW, using MaskTools, one cane do exactly the same as one-liner:

without any blurring (ultrafast):
strength = 10

yv12lut(source, "x 128 - 128 x - "+string(strength)+" 128 / * + 128 +", U=2,V=2)

# interleave( source, last ) .histogram(mode="levels")

with custom blurring:
strength = 10

blurred = source.blur(0.2,0.2)
#blurred = source.bicubicresize(source.width/16*4,source.height/16*4)
# \ .bicubicresize(source.width,source.height, 1.0, 0.0)

yv12lutxy(source,blurred,"x 128 - 128 y - "+string(strength)+" 128 / * + 128 +",U=2,V=2)

# interleave( source, last ) .histogram(mode="levels")

That's exactly the same, with following exceptions:

- chroma is not touched (original script washes out colors, too)
- it is faster
- it works in YV12
- it is more precise: one-step-operation, hence no rounding errors.

Also, try both blurring versions of the 2nd scriptlet. You'll notice that the first one (weak blurring @ 0.2) destroys the homogenity of a frame's histogram, and the output also looses sharpness. With the gaussian blurring, the histogram stays smooth, and the output doesn't loose sharpness.

Good night then ... /*snoozes away*/

Backwoods
29th March 2005, 03:57
All on my to-do list for tomorrow, thanks. And actually it wasn't on anime but I was using Lanczos. Nothing gets past you.

dplaton
29th March 2005, 07:49
Dark-Cracker

but you should try to use YV12Layer to increase speed of your script and avoid to convert input source in YUY2.


I'm in YUY2 space and that's the point.


Didée

but this does not enhance contrast... The pleasing effect is only the brightening of the dark parts.



That's correct, if you want a pleasing effect for dark scenes you can use it.

The quality allways depend on shooting, but sometimes you can use some tricks, knowing that are tricks.

M7S
31st March 2005, 08:55
If you want a large-radius Gaussian blur, I recommend chaining several copies of Blur(1.0) together. (Anybody remember Pascal's triangle?)
From Avisynth.org (http://www.avisynth.org/Blur). Is this a sollution?

Regards,
M7S

Manao
31st March 2005, 12:43
M7S : it will indeed make a large radius blur, but it will be slower than the downsizing / upsizing combo. Another way ( in YV12 only ) would be to use YV12Convolution with the horizontal and vertical vectors equal to something like "1 2 1" or "1 4 6 4 1" or "1 6 15 20 15 6 1" and so on... It should be faster than the blurs combo on very high radius, but it'll be slower ( i think )than the down/upsize combo.

Didée
31st March 2005, 18:41
Indeed. Chaining several blur()'s theoretically is the right thing. But in practice, there will be an accumulation of rounding errors, which lead to a) inefficiency for big radii and b) sometimes nasty artefacts, especially in flat areas.

For doing a pretty precise gaussian blur, yv12convolution is just it. However for most tasks, the resizing combo is precise enough, and it's really so much faster. It's probably not so awfully important for a single operation, but if you need several of them ... I'm fiddling with a script that internally uses a good bunch of different gaussian-blur operations. Doing all of them through BicubicResize, speed is slow, but acceptable. Exchanging all of'em with yv12convolution, Vdub's processing window is too small to completely hold the numbers for "estimated time" ... ;)

This one is fast, precise enough, and also allows different radii for x-axis and y-axis:

function FastGaussBlur(clip clp, float rx, float "ry")
{
ry = default(ry, rx)
rx = rx+1.0
ry = ry+1.0
ox = clp.width
oy = clp.height
oxs1 = int(round(ox/4.0/sqrt(rx))*4)
oys1 = int(round(oy/4.0/sqrt(ry))*4)
oxs2 = int(round(sqrt(ox*oxs1)/4.0)*4)
oys2 = int(round(sqrt(oy*oys1)/4.0)*4)
oxs1 = oxs1<16 ? 16 : oxs1
oys1 = oys1<16 ? 16 : oys1
clp.bicubicresize(oxs1,oys1)
rx>9.0||ry>9.0 ? bicubicresize(oxs2,oys2,0.5,0.25) : last
return bicubicresize(ox,oy,1,0)
}

tsp
31st March 2005, 19:16
what about variableblur (http://forum.doom9.org/showthread.php?s=&threadid=88645&highlight=variableblur). I haven't seen any rounding artifacts yet with it.

Didée
31st March 2005, 21:05
tsp:
Holy sh** :eek:, somehow I had totally forgotten about your filter! :o
Sorry, and thanks for reminding.

Okay, just a quick test about precision, artefacts and speed:

http://img220.exs.cx/img220/4571/gaussianblur23jg.th.jpg (http://img220.exs.cx/my.php?loc=img220&image=gaussianblur23jg.jpg)

As can be seen clearly, chaining blur()'s is practically unusable. Not only the color degrades, but note the banding artefacts on the wall, and the overall degradation for radii 24 and 64.
YV12convolution and VariableBlur seem very precise both.
FastGaussBlur is less precise, but IMHO sufficient for most "normal" requirements of blurring.


And the speed:

For each filter method, I let compute 10 chained instances of Gaussian Blurring with radius=16. In case of yv12convolution, I cheated a little: since I was too lazy ;) to create Pascal's Triangle up to the 34th line, I made radius=16 by applying 4 times radius=4 ("1 8 28 56 70 56 28 8 1"). Dunno whether or not this put any penalty on yv12convolution, but still ... look below:

Results:
FastGaussBlur: 11.2 fps
VariableBlur: 6.9 fps
chained blur(): 1.8 fps
yv12convolution: 1.0 fps

Objections, anyone?

akupenguin
31st March 2005, 23:35
Originally posted by Didée
In case of yv12convolution, I cheated a little: since I was too lazy to create Pascal's Triangle up to the 34th line, I made radius=16 by applying 4 times radius=4 ("1 8 28 56 70 56 28 8 1"). Dunno wether or not this put any penalty on yv12convolution, but still ...
That's a radius=2 gaussian, and convolving 4 of them gives you a radius=4, not 16.
Likewise, you have to chain 256 blur(1)'s to get a radius=16 gaussian.

Didée
1st April 2005, 00:44
Then you catched me on notation & terminology. So this means, the diameter of the used convolution kernel represents the radius of the gaussian? Doesn't seem obvious to me, but if it is like that, well, let it be ...
Means the sqrt's should be taken out of above function to make it "correct" - and tsp should adjust his plugin too, since it seems to interpret the gaussian radius wrongly as well.


Back to topic:

Originally posted by dplaton
The quality allways depend on shooting, but sometimes you can use some tricks, knowing that are tricks.
Sure. I only wanted to point out that blurring with a large radius is an *essential* part of the proposed method. Your used blurring with blur(0.2) has so little of the wanted effect, that the effect of the initial script is almost the same as

levels( 0,1.0,255, 10,245 )

which isn't that revolutionary to be used as a trick. It has to be a big gauss, else you're only doing this simple levels squishing, but in a more complicated way.

akupenguin
1st April 2005, 01:41
Originally posted by Didée
Then you catched me on notation & terminology. So this means, the diameter of the used convolution kernel represents the radius of the gaussian? Doesn't seem obvious to me, but if it is like that, well, let it be ...
It should be more evident if you look at a larger radius. The radius of the gaussian is not a function of the number of coefficients in the kernel (they're all infinitely wide if you calculate them to sufficient precision), but rather of the number of nontrivial coefficients. Your rx=256 would be equivalent to a kernel like
".01 .01 .01 .01 .02 .02 .03 .04 .05 .06 .07 .09 .11 .13 .15 .18 .21 .24 .28 .32 .37 .42 .47 .52 .57 .62 .68 .73 .78 .83 .87 .91 .94 .97 .98 1.00 1.00 1.00 .98 .97 .94 .91 .87 .83 .78 .73 .68 .62 .57 .52 .47 .42 .37 .32 .28 .24 .21 .18 .15 .13 .11 .09 .07 .06 .05 .04 .03 .02 .02 .01 .01 .01 .01"
I call that radius=16.

<edit> To be exact, radius = the distance from the center to the coefficient with value 0.37 = 1/e (assuming the center coeff is 1)</edit>

Didée
1st April 2005, 04:26
but rather of the number of nontrivial coefficients
Ah! You mean the radius refers to the number of unique coefficients present in the kernel? (Seems I've done my homework rather bad in the past) ;)

edit: Wait. Does that make sense? Because, if one would scale the values of above kernel to, say, [0,1024] range, then those several "0.1" coefficients would get different, unique numbers. Thus the "radius" would change, despite the fact that still a kernel with the same shape is sampling over the same area ... no, I don't get it yet. /edit.

Well, however ... feeding yv12convolution with above kernel, a single instance runs @ 2.1 fps. VariableBlur /w radius=256 runs @ 6.2 fps. The resizer thingie runs at ... ~65 fps.

One has to think twice if one's actual need for applying gaussian blurring really requires "maximum precision". Perhaps a pretty close approximation is sufficient, especially if it runs 10x - 30x faster.

tsp
2nd April 2005, 17:12
remember that a gaussian blur is based on the gaussian function:
1/sqrt(2*pi*SD)*exp(-(x^2+y^2)/(2*SD^2))
radius 1 is then equal to 2 SD(95% of the blurred pixel value is based on the pixels within the radius). An ordinary 1 2 1 kernel is about the same as 0.5 SD^2 1 4 6 4 1 = 1 SD^2 1 8 28 56 70 56 28 8 1 = 2 SD^2 that is for step down on pascals triangle is equal to 1 SD^2 increase.

radius=1 in variable blur is the same as 1/2 SD^2 radius 2 = 1 SD^2 etc. the bigger the radius is the better the binomial blur approaches the true gaussian blur. Also note that variable blur uses a 5x5 kernel for the blur and repeats it to produces higher radius. Due to the greater kernelsize (compaired to a 3x1 kernel in Blur()) rounding error isn't a problem.

Wilbert
2nd April 2005, 17:30
1/(SD*sqrt(2*pi)) * exp(-x^2/(2*SD^2))

and

1/(2*pi*SD^2) * exp(-(x^2+y^2)/(2*SD^2))

A typo I hope :)

tsp
2nd April 2005, 19:45
1/(SD*sqrt(2*pi)) * exp(-x^2/(2*SD^2))

and

1/(2*pi*SD^2) * exp(-(x^2+y^2)/(2*SD^2))

A typo I hope

um yes thats right. Maybe we should just use SD or variance(=SD^2) instead of radius. Less confusing.

PeaceAnt
29th November 2008, 21:58
An AviSynth adaptation

a = AviSource()
b = a. GrayScale().Levels(0,1,255,255,0)Blur(0.2,0.2)
Layer(a,b,"add",10)

Happy testing

you should use "overlay" instead of "add" but it isn't implemented in "layer" i think. i use this method (with my "overlay" plugin) and it works like shadow/highlight in photoshop. maybe someone should implement "overlay" in avisynth layer function? it's a combination of multiply and screen methods:

*(dstp +w) = A < 128 ? (B*A)/127 : 255-(2*(255-B)*(255-A))/255;

pozdrawiam

Leak
30th November 2008, 00:36
you should use "overlay" instead of "add" but it isn't implemented in "layer" i think.
Are you sure you don't mean "Soft light" or "Hard light" as supported by the built-in Overlay (http://avisynth.org/mediawiki/Overlay) filter?

Also, do you really think the poster you replied to is going to need your answer after 3 and a half years? :confused:

np: Gas - Track 2 (Pop)

PeaceAnt
1st December 2008, 09:54
soft & highlight does't seem to work. as i sad i use my own plugin with "overlay" method (dplaton:"4. negative -> Mode = Overlay") and it works as i expected:
http://img139.imageshack.us/img139/2585/torttc4.jpg
here's my code:
a = directshowsource(".AVI")
b = a.bilinearresize(120,68).GreyScale().gaussResize(720,576,p=10).levels(80,1,255,210,120)
overlay = imagesource("overlay.bmp").converttorgb32
GHRCompound(a, b, overlay)
try to do it with built-in filter instead of GHRC.

oh, 3 and a half years? well... better later than never. ;)

pozdrawiam