Log in

View Full Version : Descale


Frechdachs
2nd May 2017, 23:07
https://github.com/Frechdachs/vapoursynth-descale

This plugin undoes upscaling.

Included Filters: Debilinear, Debicubic, Delanczos, Despline16, and Despline36

The code can probably be improved. This is literally babby's first C++ program.

Look at the GitHub page for an explanation of how the algorithm works.

Currently works only on GrayS, YUV444PS, and RGBS, but for convenience I've included a python wrapper which does conversion automatically and accepts YUV (every subsampling), Gray, and RGB of every bitdepth. Chroma is scaled with a normal kernel, since it almost never makes sense to use this on chroma.
As of now, if you want to use this on subsampled non center-aligned chroma, you would have to do the shifting manually with src_left.

Although it works pretty well, this is still work in progress. Currently, the vertical pass limits the speed a bit on some systems. I have to figure out how to access columns on a plane efficiently. Hints are appreciated.

TODO:

- Improve the vertical pass
- Decide wether to do the horizontal or vertical pass first depending on cost
- Clean up this mess of a code
- Maybe handle subsampled chroma

feisty2
3rd May 2017, 02:45
Currently, the vertical pass limits the speed a bit on some systems. I have to figure out how to access columns on a plane efficiently. Hints are appreciated.


probably generate a pre-transposed version of the entire frame before the actual processing,,, if u don't mind about memory usage

Frechdachs
9th May 2017, 10:24
https://github.com/Frechdachs/vapoursynth-descale/releases

r2


Fixed a small but nasty bug in the python wrapper of Debicubic that caused c to always equal b
Added native support for YUV444PS and RGBS to the plugin
Added some sanity checks

ChaosKing
9th May 2017, 12:03
Is there a way to find out the original resolution programmatically?

jackoneill
9th May 2017, 14:01
Hi.

Parameters passed by reference but not modified in the function should be const. It helps readability and keeps you from accidentally modifying the parameter later.

You can sprinkle "static" over every single function except VapourSynthPluginInit. That way the compiler won't generate a public symbol. See: https://gcc.gnu.org/wiki/Visibility

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L350
What is this for?

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L440
You can pass NULL instead of src here. This frame doesn't need the frame properties from src. dst needs them so they're passed on to the next filter in the chain, but intermediate doesn't make it out of your filter, so it's an unnecessary allocation and copying.

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L482
You can free intermediate before this if-else.

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L501
The vi_dst member doesn't have to be a pointer. If you make it a plain struct and get rid of DescaleData's width and height members, vi_dst will actually be what its name suggests.

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L631-L633
This code never runs. Parameters not marked with "opt" will always be available. The VapourSynth core checks their presence before calling debilinear_create. You can safely pass NULL instead of &err.

https://github.com/Frechdachs/vapoursynth-descale/blob/d6263ea9dc915d62e30aa8a9ec92e91ea1ac9fbd/descale.cpp#L960
You can avoid the code duplication in all those *_create functions by using just one create function and passing the appropriate DescaleMode here instead of that nullptr. You will receive the value in the function's userData parameter.

You can make the parameter lists more readable like so:

"src:clip;"
"width:int;"
"height:int;"
"src_left:float:opt;"
"src_top:float:opt"

Frechdachs
11th May 2017, 02:41
@jackoneill

Thank you! This helped a lot.

What is this for?
That was a left-over of code that was previously there.

You can avoid the code duplication in all those *_create functions by using just one create function and passing the appropriate DescaleMode here instead of that nullptr.
Thanks, this was really bugging me. I hope I'm doing it right™ now.



@ChaosKing

Is there a way to find out the original resolution programmatically?

Yes. The simplest and in my opinion most reliable way is looking at the luma average of the diff between the source frame and the downscaled and upscaled version of that frame.

You can use this script for that: https://gist.github.com/kageru/549e059335d6efbae709e567ed081799

Usage example:
python getnative.py --input C:/path/to/file.m2ts --frame 15000 --kernel bicubic --bicubic-b 0 --bicubic-c 0.5 --min-height 700 --max-height 1000

Make sure you read the header before trying to use it. This script uses fmtconv's resample with invks=True which is not as accurate as this plugin, but it's good enough for resolution guessing.

After the script finishes, you will get a graph like this:

https://i.imgur.com/CQqtoZ2.png

You are basically looking for negative spikes.
In this example it is very obvious that the show was produced in 873p.

Keep in mind that the graph does not show the actual error, since very low values in the diff image are set to zero.

Also, this will NOT help you with finding the right kernel, even if a wrong kernel is used, these spikes are usually still there.

Myrsloik
11th May 2017, 10:45
If there's (sometimes?) such a clear spike, why can't we have a function that helps with the guessing? Or a function that can make such a nice plot at least? Seems like a useful thing to have easily available.

Frechdachs
12th May 2017, 11:23
What do you mean exactly? The script that I've linked does try to guess the resolution. It outputs a text file with the guessed resolution and a plot image.

Or do you mean having this script's functionality as a simple function instead of a callable script?

MonoS
12th May 2017, 20:10
How does it compare against punedtree's debicubic/debilinear and firesledge's invks?

Myrsloik
12th May 2017, 20:11
What do you mean exactly? The script that I've linked does try to guess the resolution. It outputs a text file with the guessed resolution and a plot image.

Or do you mean having this script's functionality as a simple function instead of a callable script?

Never mind, I didn't read stuff.

Frechdachs
13th May 2017, 10:26
How does it compare against punedtree's debicubic/debilinear and firesledge's invks?

It should work the same way as prunedtree's Debilinear and Debicubic.

I'm not sure how fmtconv's resample with invks=True does it, but it's less accurate than this plugin.

MonoS
14th May 2017, 19:22
It should work the same way as prunedtree's Debilinear and Debicubic.

I'm not sure how fmtconv's resample with invks=True does it, but it's less accurate than this plugin.

Thanks for your response :)

ChaosKing
16th May 2017, 22:05
Does it make any sense to Descale in 16bit since the upscale was done in 8bit?

import descale as ds
clip = mvf.Depth(clip, 16)
clip = ds.Debilinear(clip, 1600,900)

jackoneill
18th May 2017, 13:01
Does it make any sense to Descale in 16bit since the upscale was done in 8bit?

import descale as ds
clip = mvf.Depth(clip, 16)
clip = ds.Debilinear(clip, 1600,900)

It does not. You now have two bit depth conversions. First from 8 to 16, then from 16 to float. If you call ds.Debilinear directly, you get a single conversion, from 8 to float.

lansing
26th August 2019, 02:49
I came to an anime site where I want to save their high quality screenshots, but the image url that they use are a resizer that split out resized images from the input arguments. For example, this (https://image2.b-ch.com/tool/resize2.php?pp=ttl2/533/533028a.jpg&ww=1920&hh=1440), I can input any dimension in the url and its will just give me the image in that resolution. I couldn't find the original dimension.

I am using the python script getnative (https://github.com/Infiziert90/getnative) to find the original resolution, but I don't know which is the right kernel to use. For the image above for example, I'm getting different result

$ python getnative.py test8.jpg -k bilinear
Script exceeded memory limit. Consider raising cache size.
Using ffms2 as source filter
501/501
Kernel: bilinear AR: 1.33
Native resolution(s) (best guess): 720p, 900p


$ python getnative.py test8.jpg -k bicubic
Script exceeded memory limit. Consider raising cache size.
Using ffms2 as source filter
501/501
Kernel: bicubic AR: 1.33 B: 0.33 C: 0.33
Native resolution(s) (best guess): 900p, 992p, 638p


I'm suspecting the original resolution to be 720p or 900p, but I don't know which one is the right one.

jackoneill
26th August 2019, 15:07
Bilinear does not look very good, so you can tell by looking at the result.

1. Ask for 720p and save that image for testing.

2. Upscale that image yourself with bilinear and with bicubic to something like 3x larger.

3. Ask for 3x larger and compare what you receive with the images from 2.

lansing
26th August 2019, 23:09
Bilinear does not look very good, so you can tell by looking at the result.

1. Ask for 720p and save that image for testing.

2. Upscale that image yourself with bilinear and with bicubic to something like 3x larger.

3. Ask for 3x larger and compare what you receive with the images from 2.

I upscaled the 720p image 3x to 2880 x 2160 using photoshop. The 3x one from the site is a lot sharper and has more details in comparison.