Home_en

Kickaha

English version / Versione italiana



Welcome to the wiki of QMtrim!
(This document is a free adaptation of the original HoHoHo's guide. --KICKAHA)

Introduction

Optimizing video compression

One of the aims of video compression is to achieve the best quality with minimum size. To this end, different software tools were developed to be used during compression. Some of those are:

  • the decoder, to decompress the original stream for it to be processed,
  • the frame server, handling the stream process throughout the whole pipeline,
  • the filters, processing frames before re-compression,
  • the codecs, that compress the stream into the destination file.

AviSynth is a frame server with a powerful scripting language that allows video processing with highly effective logic. AviSynth has also a well developed set of filters enabling many type of image processing.

You can use AviSynth to split the video in different sequences with the trim function, process each sequence with a different filter chain and merge back the sequences just before compressing the video. This type of work-flow let you apply the best possible filter for each sequence.

QMtrim was thought to further enhance this work-flow by applying "rougher" filters (dropping details for best compression) to sequences with more motion (where the eyes don't catch the details) and "lighter" filters to more static sequences (where eyes have a more fine perception). This way you lose detail where is less needed leaving space for images that will be seen more accurately and achieving a higher perceived quality.

Motion measurement and troubles

To realize this kind of video processing, you need at first to measure motion. There are filters for AviSynth doing this job: one of them is MVAnalyse from Manao & Fizick's MVtools. Comparing each frame with the next and applying a motion-compensation algorithm just link some codecs, it returns the sum of motion of every block of pixels.
As filter system, you could think of an ideal algorithm probing every frame for motion and applying a filter just fit for it, but this means that in a sequence becoming faster and faster you would apply rougher and rougher filters in a very short time and your eyes would see the quality dropping.
Also, motion measurement is not an exact data, a continuous line; values are likely affected by error (like a white noise) and there are motion "peaks" that should be ignored as too short to be noticed by the eyes: it's typical of the scene change case, measured as big motion in a single frame maybe immediately followed by a static sequence.

QMtrim

In using QMtrim you have to think to a discrete set of filters, each a little "rougher" that the previous: the first filter will be used for almost static images, the last for sequence with the most motion. If the number of filters is high enough you will not notice the difference between a filter and the next and you will achieve the same effect an infinite set of filter would have, with each one perfectly fit for each image motion.
Another problem is to split the video in sequences where every image has roughly the same motion to apply the right filter to each one. AviSynth uses the trim function to split video, but as the number of filters grows, the sequences become shorter and too many to be handled manually. And last, you must avoid to use rougher and rougher filters too fast.
QMtrim tries to solve this issues.

Step #1: statfile generation

First of all you need to measure motion for every frame to feed QMtrim with. With respect to this, QMtrim was thought as independent to the measured quantity and the filter logic; i.e. given a filter that measures image redness, you could use QMtrim to choose the filter to be used to reduce the red component or anything else. Within this guide the motion approach will be discussed: how to apply more or less rough filters depending on the motion of every frame.
What matters to QMtrim is to have an input file to process, with this format:

  • text format;
  • two number for each line: frame number and motion measurement (or other quantity);
  • every line, starting from frame 0 and proceeding orderly, describes a single frame;
  • the two values must be separated by a space or tab;
  • the frame number must be integer;
  • the second value can have decimal digits (the decimal separator is the dot).

You need to get MVtools at this address (http://avisynth.org.ru/mvtools/mvtools.html) and install them with other AviSynth libraries so that it can be used within you scripts (in the following examples I'll suppose you installed the DLL in this path: "C:\Programs\AviSynth 2.5\plugins\").
To ease your job a script for AviSynth (QMlib.avsi) is provided: it generates the statfile using MVtools libraries.
To create the statfile you need to write an AviSynth script that decodes the video stream and apply minimum filters to measure motion calling the statfile generator function.
From MVtools website you can read that only progressive stream can be analyzed and those must use YV12/YUY2 color format. Also QMlib.avsi allows only YV12, then the minimum filtering usually is:

  • deinterlace (when needed);
  • crop/resize;
  • possible conversion to YV12.

At this step is not necessary to apply other filters, unless you really need a more precise motion measurement (and usually you don't).
A script example:

LoadPlugin("DGDecode.dll")
LoadPlugin("C:\Programs\AviSynth 2.5\plugins\MVtools.dll")
Import("QMlib.avsi")
Mpeg2Source("VTS_01.d2v")
Spline16Resize(576,432,10,4,-20,-2)


On the script tail you add the call to the functions provided in QMlib.avsi: QMwritesub(), QMstack() e QMwritefile().

  • QMwritesub shows a subtitle with motion value, useful to check correct measurement;
  • QMstack generates a frame of two images: on top the original frame with motion subtitle (as with QMwritesub), on the bottom the motion vector map (the longer the lines the faster the motion);
  • QMwritefile generates the statfile for QMtrim.

The analysis is made with respect to the next frame: for the frame #0 the motion is measured between frame #0 and frame #1.

The functions have this syntax:

  • QMwritesub(clip c, bool "chroma", int "Ysc", int "thSCD1", int "thSCD2")
  • QMstack(clip c, bool "chroma", int "Ysc", int "thSCD1", int "thSCD2")
  • QMwritefile(clip c, string "statfile", bool "chroma", int "Ysc", int "thSCD1", int "thSCD2")

The parameters are:

  • chroma (default true) set to false for monochromatic video;
  • Ysc (default 255) motion value to return when two frames are too different (e.g. scene change); ideally should be more than video maximum measured motion, but it doesn't matter since QMtrim cuts peaks;
  • thSCD1 (default 600) when looking for a block of pixels in the next frame, this is the maximum difference allowed to use the block as candidate to motion analysis; if no block is found within this limit, the original block is marked as uncorrelated;
  • thSCD2 (default 130) is the portion (of 255) of uncorrelated blocks, beyond which two subsequent frames are decided to be too different and Ysc is returned (the default 130 means 51% of the image);
  • statfile (mandatory) is the file name of the statfile for QMtrim.

For most videos, the default values for Ysc, thSCD1 e thSCD2 are adequate. Please refer also to MVtools documentation for more information about these parameters.

Our script producing the statfile for QMtrim will become something like:

LoadPlugin("DGDecode.dll")
LoadPlugin("C:\Programs\AviSynth 2.5\plugins\MVtools.dll")
Import("QMlib.avsi")
Mpeg2Source("VTS_01.d2v")
Spline16Resize(576,432,10,4,-20,-2)
QMwritefile(statfile="StatFile.txt")


To generate the statfile you'll have to allow AviSynth to process the whole video in a single step, starting from the first frame to the last. Then, if you use VirtualDubMod, play the video from the beginning and leave it complete the playback until the end; it's easy to corrupt the statfile by seeking and playing as much as a few frames. When done, close the application and get the statfile.
Now QMtrim has the information needed to generate the trim sequence.

Step #2: using QMtrim

QMtrim was initially written as a command line tool and can still be used as such. Moreover now you can use its GUI. The parameters used when you launch it, will be proposed in the GUI to be used or modified; if you launch QMtrim without parameters, the GUI will have default values.

Command line

Below the parameter list:

  • -v or --version show QMtrim version and exit
  • -h or --help show command line syntax and exit
  • -i statfile or --input statfile specify statfile name
  • -o trimfile or --output trimfile specify the file name to save the trim sequence into
  • -u unsaw_val or --unsaw unsaw_val frame width for the window to be used to cut peaks (default 0)
  • -c cxrest_val or --cxrest cxrest_val parameter to slow down filter roughness increment, in frame (default 50)
  • -t or --thresholds distribution is given as a list of thresholds, instead of quotas
  • -f levels or --flat-distrib levels flat distribution of levels filters (each with the same quota)
  • -q or --quiet included for compatibility with previous version of QMtrim, ignored
  • -d or --debug shows additional debug information in the GUI
  • distribution sequence of values, one for each filtering level, to be read as quota or threshold (default: 1,1,1,1,1,1,1,1,1,1)

First of all you have to define how many levels of filtering you want: 3-4 levels manage most videos, AvsOptimizer uses 10 levels, for more precise optimization you can use 32 levels of filtering. QMtrim support up to 250 levels.

The number of levels defined, you decide which type of distribution you want to start with: by quotas or by thresholds.

The distribution by thresholds is a growing sequence of values that correspond directly to those you have in the statfile, that is: motion values. Each frame will be assigned to the corresponding level depending on its motion value. Each level n will have frames with motion between the n-1-th value of the distribution and the n-th value. Specifically the first level will have motion below the first value of the distribution and frames with motion above the last values will be still assigned to the last level. Below is an example:

Level Threshold Frame assigned to this level
0 10 Those with motion between 0 and 10 included
1 20 Those with motion above 10 and up to 20
2 50 Those with motion above 20 and up to 50
3 80 Those with motion above 50

The distribution by quotas requires a set of values indicating which quota of frames will be assigned to each level. It will be QMtrim job to find the right threshold values to use to achieve this result. It does not matter what absolute values you use as quota, just what value is each with respect to the others in the distribution. Below are three examples of equivalent quota:

Level Ex. 1 Ex. 2 Ex. 3 Frame assigned to this level
0 1 0.125 3 12.5% of frames
1 2 0.25 6 25% of frames
2 3 0.375 9 37.5% of frames
3 2 0.25 6 25% of frames

Then the distribution on the command line will define motion value thresholds if the flag -t (or --thresholds) is present and quotas otherwise.
The values must be specified in growing level order and must be separated by comma (,) semicolon (;) or space, using the dot (.) as decimal separator.
The default distribution (1,1,1,1,1,1,1,1,1,1) has 10 identical quotas that will assign 10% of frames to each filtering level. It's equally possible to achieve a flat distribution with an arbitrary number of levels with option -f (or --flat-distrib) followed by the number of levels.

Other two parameters are used to refine the analysis: unsaw e cxrest.

The unsaw parameter modifies the statfile sequence of values to cut peaks. QMtrim analyzes a window of unsaw_val frames and define a limit as the maximum value found on the window edges; every values inside this window must be less that this limit, otherwise they will be set to the limit itself. The higher unsaw_val the wider the window that will cut the peaks. A similar algorithm will be applied anyway, also with unsaw_val = 0: every value above both the previous and the next frame will be reduced to their average.

The cxrest parameter, instead, modifies the trim sequence by delaying too fast filter change. Basically cxrest_val define for how many frames the filtering level cannot increase, to avoid the eyes notice an image is becoming more rough. Decreasing the filtering level, instead, will be freely allowed.

GUI

The GUI displays every parameter described above and shows more useful information about the distribution and frame assignment, and allow the user to operate a set of functions.

Screenshot thumbnail
QMtrim prototype
 

The statfile name is shown, followed by the button used to open another file and to reload the file whether it has been modified (QMtrim avoid to read it every time, and you must explicitly reload it).
The parameters unsaw and cxrest follow with the check box thresholds indicating whether you are providing a set of thresholds or quotas.

The distribution table looks like the one printed by the previous version of QMtrim. You can add/remove levels, setup a flat distribution with a number of levels of your choice (option -f or --flat-distrib) or redistribute quotas with an automatic algorithm(*).
The table shows the following information:

  • Cx, the level number (to the level n corresponds section Cn of the trim sequence);
  • Weight, the distribution value for the level (quota or threshold);
  • Frames, the number of frames actually assigned to the level; even tough you set up a flat distribution, the number of frames assigned is not predictable, since the parameters unsaw, cxrest and peak cutting will unbalance the levels at the end;
  • Quote, the percentage of frames assigned to the level;
  • Threshold, the threshold actually used to assign the frames; this let you know the threshold used by QMtrim to achieve the required quotas;
  • Density, the frame percentage divided by the percentage of possible motion values for the level; this value measures how much "crowded" a level is and gives and idea of how fast or slow a video is; this value is meaningful with unsaw_val and cxrest_val set to zero.

Under the distribution you have the buttons for: copying the table values in a sequence of tab-separated fields, copying in the clipboard the command line to use to setup the current GUI, generating the trim sequence.
The trim sequence follows with fields to save the result: trimfile name, button to choose the trimfile, save button.

The status bar on the bottom will provide information on the execution, missing data needed to proceed and suggestions about how to use the dialog.

(*) The automatic redistribution of quotas let you achieve a better assignment of levels and can be done as follows:

  1. setup the parameters to have a flat distribution with the desired number of levels;
  2. (the trim sequence is automatically generated;)
  3. use the "Auto-redistribute quotes" button that will copy thresholds into the column "Weight" to be used as quotas;
  4. (the trim sequence is automatically generated;)
  5. proceed to refine the first and last level, because usually they will still be unbalanced.
Step #3: the final script

The trim sequence will be something like (usually much longer):

C0.Trim(0,10)+C1.Trim(11,16)+C2.Trim(17,23)\
+C3.Trim(24,29)+C4.Trim(30,32)+C3.Trim(33,33)\
+C4.Trim(34,39)+C5.Trim(40,45)+C6.Trim(46,49)


To use it you should have defined the filter for each level and called the result Cn where n is the level number. The trim sequence can be manually inserted into your script after copying it from the text box or simply by including the trimfile in your script. An example with filter definition followed by the trim sequence is:

# filters
C0 = clip.Spline36Resize(XRES,YRES,X0,Y0,X1,Y1)
C1 = clip.Spline16Resize(XRES,YRES,X0,Y0,X1,Y1)
C2 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=80)
C3 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=70)
C4 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=60)
C5 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=50)
C6 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=40)
C7 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=30)
C8 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=20)
C9 = clip.GaussResize(XRES,YRES,X0,Y0,X1,Y1,p=10)

# trim generated by QMtrim
Import("C:\qmtrim\QMtrim\trim.avsi")


That's all!

Support

On SourceForge you can use:

  • the ticketing service to notify bugs by describing and attaching everything you think can be used to reproduce them (e.g. the statfile);
  • this wiki as a user reference manual;
  • the forum to ask for help, report your experience and discuss improvements.

Road map

The aim of the prototype (0.7.7) was to wrap the original tool in a GUI and fix issues without too much care about how the code was developed.

The next version (0.8) will be a real beta to start with.
At the moment I'm rewriting everything to have:
- a standard GUI;
- every function in v0.7.7;
- (possibly) a GNU/Linux release.

In the future (it's not a promise!) some of the following could be added:
- internationalization;
- support for processing different range of frames with different distribution (static filter included);
- save/load configuration;
- help/documentation;
- hints to guide first time users.

--KICKAHA


Related

Wiki: Home_en
Wiki: Home_it