View Full Version : Enlarging then shrinking
pfxz
14th April 2011, 16:46
I've seen some people enlarging a video, do some postprocessing and then shrinking it to the intended output size.
When should I do this? How should I pick the intermediate size?
Didée
14th April 2011, 17:41
When should I do this?
Preferably during winter time, to heat the house. :)
jmac698
15th April 2011, 19:15
One reason *I* do this is work work around the limitations of YV12 sampling. In this color format, a whole set of 4 pixels (2x2) are operated on as a group. For this reason you can't resize to smaller than 2x2 and have to resize to even amounts. When I want to do things to just 1 pixel, I enlarge everything to work on it, then resize it back. And yes you can change individual pixels in the luma, however the color pixels share a common block.
Another reason is for better quality. If I want to shift 1.5 pixels to the left, there's an implied interpolation going on there. You should resize with a good resizer, shift by an integer amount of pixels, then resize back (and now your integer shift gets divided so it's a floating point shift).
But to generally answer your question, I can't really - if a particular need comes up you might benefit or even need to resize, but in general I don't think it will do much for you to just blindly resize everything, operate, and size back.
Gavino
15th April 2011, 19:50
Another reason is for better quality. If I want to shift 1.5 pixels to the left, there's an implied interpolation going on there. You should resize with a good resizer, shift by an integer amount of pixels, then resize back (and now your integer shift gets divided so it's a floating point shift).
But when using Avisynth, it makes no sense to do it this way, since you can do a subpixel shift directly in a single operation with any of the resizers. Doing an upsize and downsize will produce a worse quality result.
jmac698
16th April 2011, 06:31
Yes but there's external resizers which are even better. And I believe the operations are equivalent, though I'd have to see the code (and I guess you would know the code better than I).
Ghitulescu
16th April 2011, 07:44
One reason *I* do this is work work around the limitations of YV12 sampling. In this color format, a whole set of 4 pixels (2x2) are operated on as a group. For this reason you can't resize to smaller than 2x2 and have to resize to even amounts. When I want to do things to just 1 pixel, I enlarge everything to work on it, then resize it back. And yes you can change individual pixels in the luma, however the color pixels share a common block.
Another reason is for better quality. If I want to shift 1.5 pixels to the left, there's an implied interpolation going on there. You should resize with a good resizer, shift by an integer amount of pixels, then resize back (and now your integer shift gets divided so it's a floating point shift).
But to generally answer your question, I can't really - if a particular need comes up you might benefit or even need to resize, but in general I don't think it will do much for you to just blindly resize everything, operate, and size back.
Why would do one all these operations? :confused:
Gavino
16th April 2011, 10:22
Yes but there's external resizers which are even better. And I believe the operations are equivalent, though I'd have to see the code (and I guess you would know the code better than I).
They are only equivalent if you have a perfect resizer, which is mathematically impossible. Just try upsizing and downsizing and see if you can recover the original image.
I suppose your method might win out if the external resizer was so much better ('twice as good'?) that even with two resize operations it still came out ahead, ie lost less quality than the best internal one does in a single resize.
jmac698
16th April 2011, 23:43
Ok, so how would you do a bilinear .5 pixel shift?
3 5 7 shifted left .5 is 4 6 4 (with implied black border)
If you doublesized it first you get
3 4 5 6 7 4
If you resize back in half, with pointresize you get
4 6 4
Therefore the operations are equivalent.
l=blankclip(width=16,height=32,pixel_type="YV12",color_yuv=$648080)
r=blankclip(width=16,height=32,pixel_type="YV12",color_yuv=$C88080)
stackhorizontal(l,r)
w=width
h=height
resize2(w,h)
function resize1(clip v, int w, int h) {
v
bilinearresize(w*2,h)
pointresize(w,h)
}
function resize2(clip v, int w, int h) {
v
bilinearresize(w,h,.5)
}
For resize2, I'm getting exactly what I expect, the pixel values are 100,150,200. It's an average at the transition.
Resize1, however, is giving me 100,125,175,200, which isn't what I expected.
So, I review bilinear and find that I was thinking of linear. Bilinear is not actually linear but quadratic.
Therefore the subpixel shift must be wrong?
I also find that the resize2 doesn't assume a black edge.
Of course you can do two resizes and keep the original image, as most resizers keep the original pixels as anchors and create new values between them, so you can always pointresize to get the anchors again.
With subpixel shifting, you never get the original pixels, so all pixels are calculated. My method of resizing twice effectively deletes the anchors leaving only the new pixels, so it should be equivalent. But the avisynth resize doesn't hold to this principal.
So is it the case that resizers are supposed to destroy all the original pixels?
Ghit, as to why I need to do all these operations, try making a test image of exactly 3,5,7,0. You can do it in YUV, but you need to construct it in doublesize first and resize down.
jmac698
17th April 2011, 00:50
Here you can see the effects of various resizers
l=blankclip(60,16,32,pixel_type="YV12",color_yuv=$648080)
r=blankclip(l,color_yuv=$C88080)
stackhorizontal(l,r)
ScriptClip("""
resizen("bilinear",current_frame)
""")
function resizen(clip v, string resizer, int n){
resizen2(v,resizer,n,n)
}
function resizen2(clip v, string resizer, int n, int i){
i==0?v:resizen2(resize(v,resizer),resizer,n,i-1)
}
function resize(clip v, string resizer){
v
w=width
h=height
eval(resizer+"resize("+string(w*2)+","+string(h)+")")
eval(resizer+"resize("+string(w)+","+string(h)+")")
}
Gavino
17th April 2011, 01:53
OK, I see what you're getting at now - I thought you meant to use the same resizer for the second step, rather than PointResize. But how would you do a shift of 0.3, say, with your method?
So, I review bilinear and find that I was thinking of linear. Bilinear is not actually linear but quadratic.
No, it is linear. Bilinear means linear in two dimensions. Quadratic would involve the square of the distance.
Of course you can do two resizes and keep the original image, as most resizers keep the original pixels as anchors and create new values between them, so you can always pointresize to get the anchors again.
With subpixel shifting, you never get the original pixels, so all pixels are calculated. My method of resizing twice effectively deletes the anchors leaving only the new pixels, so it should be equivalent. But the avisynth resize doesn't hold to this principal.
So is it the case that resizers are supposed to destroy all the original pixels?
The Avisynth resizers preserve the image centre, not the corner point, so in general original pixels are not preserved. (For example, see this thread, or this one.) To avoid this, you need to use a subpixel offset, so your resize1 could be done with bilinearresize(w*2,h, 0.25). But then you might as well use bilinearresize(w,h,.5) (ie resize2), which gives the same results with half the work.
Here you can see the effects of various resizers ...
resizen2 is redundant - resizen can be written as:
function resizen(clip v, string resizer, int n){
n==0 ? v : v.resize(resizer).resizen(resizer,n-1)
}
The last two lines of resize can be simplified to:
apply(resizer+"resize", w*2, h)
apply(resizer+"resize", w, h)
jmac698
17th April 2011, 06:04
bug in apply
Interesting - thanks, I learn a lot from you. Notice I used a template with blankclip for the first time :) I even tried looking it up this time, but for some reason my eyes are immune to the clip parameter.
However, I found a bug? This code gives me an error:
Apply("colorbars",720,480)
Apply("bilinearresize", 720,480)
Script error: Invalid arguments to function "Apply" (line 2)
I can't see anything obviously wrong here.
Centring of resize
bilinearresize(w*2,h, 0.25)
Wow, I needed to know that a year ago. That also explains the thread about dividing a picture into a grid. I wish this stuff was documented! I need to do very precise work. Finally I can rewrite my TBC without all that pointresize stuff in the first place. However I still need another way to shift lines that includes black instead of replicating at the ends. Generalconvolution won't work.
I can't understand why you use .25 (which works) as opposed to .5, which would seem to indicate mid-pixel.
Utility of pre/post resizing with external resizer
So you agree then that using an external resizer, shifting, and downsizing again should give good results?
Linear resize formula
how would you do a shift of 0.3
If a,b are two pixels, the new pixel ab is:
a+(b-a)*(1-shift)
So for example, a=3, b=5, shift=.5, ab=3+(5-3)*.5=4
In your question, shift=.3 we have
ab=3+(5-3)*(1-.3)=4.4
I guess on way to do it with my "method" is to pointresize*10, shift (by integer amount) then average the result, like this:
5 5 5 5 5 5 5 5 5 5 =>3 pixel right is
3 3 3 5 5 5 5 5 5 5
when resized down to 1 pixel should be
3*3+7*5=9+35=44 or 4.4
I guess you see how I'm thinking here, I haven't filled in all the details.
So how would you write a true subpixel shift script? It has to have black borders added I guess. Something like
resizew=1.3
resizeh=1
fracw=resizew-floor(resizew)
frach=resizeh-floor(resizeh)
addborders(2,0,2,0).resize(width,height,.25+fracw/2).crop(2+intw,0,2+inth,0)
Gavino
17th April 2011, 12:32
This code gives me an error:
Apply("bilinearresize", 720,480)
Sorry, my mistake, it should be Apply("bilinearresize", last, 720,480) - see the example (and following comment) in the wiki.
I wish this stuff was documented! [...] I can't understand why you use .25 (which works) as opposed to .5, which would seem to indicate mid-pixel.
This is to preserve the image centre.
When scaling by a factor k, the new first pixel is located at -0.5*(1 - 1/k).
Hence to get the original first pixel instead, you need to use an offset of +0.5*(1 - 1/k).
Setting k=2 gives an offset of 0.25.
For documentation, see http://avisynth.org/mediawiki/Resampling.
So you agree then that using an external resizer, shifting, and downsizing again should give good results?
In principle yes it could, using a 'better' external resizer, and with simple fractions like 0.5.
I guess on way to do it with my "method" is to pointresize*10, shift (by integer amount) then average the result, like this ...
Yes, but that's a different method now, using PointResize up and an averaging function. And it only does a bilinear interpolation - I thought the idea was to get a more accurate result using a 'better' interpolator.
So how would you write a true subpixel shift script? It has to have black borders added I guess.
To shift left/right by an arbitrary float x pixels (x<0 for right shift), using black beyond original edge, you can do this:
w = width
AddBorders(t, 0, t, 0)
xxxResize(w, height, src_left=x+t, src_width=w)
where t is no less than the number of 'taps' of the resizer used (eg 1 for Bilinear, 3 for Lanczos) - or just fix it at some suitable even number (to cope with YUV constraints), say t=4.
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.