Blockbuster - An Avisynth filter for eliminating DCT blocks
formerly known as AddNoise
By Ross Thomas <email@example.com>
Blockbuster is an Avisynth filter designed to reduce or eliminate DCT blocks from an enocode. DCT blocks, also known as "dark blocks" when they appear in low-luma areas of a frame, are ugly, distracting artifacts that MPEG encoders like to scatter liberally over our otherwise flawless encodes.
While the severity of the problem varies between the different versions of MPEG, with MPEG-1 exhibiting the most DCT blocks and MPEG-4 the least, they do occur with all versions. MPEG-1 produces them almost anywhere there is a flat surface with a low detail level (such as a wall), and MPEG-2 shows them in the same areas but less frequently (they're particularly visible at lower bitrates, as anyone familiar with digital satellite can testify). MPEG-4 has signficantly less of a problem with low detail levels, but does still produce DCT blocks, especially in dark areas (see this Doom9 thread
for more on these blocks and MPEG-4).
(Incidentally, DCT stands for "discrete cosine transform", and is one of the techniques MPEG uses to do its compression. They differ from macroblocks, which are most noticible in high-motion areas when the bitrate is insufficient to describe the motion accurately, and also from mosquito noise, which is an artifact that tends to appear around edges and areas of high contast.)
As hinted at above, the cause of these blocks seems to be a lack of detail in areas of the picture, which the encoder fails to "see" and so applies too much compression. In the process what details were there are smoothed away and the area turns into a DCT block.
The aim of this filter is to attempt to make those areas more "noticible" to the encoder so that it allocates more bits and thus doesn't need to compress so much. The astute amongst you will realise this filter is, basically, designed to increase the bitrate (and decrease the compressibility) of your clip. While this is considered a cardinal sin by most, there are at least two valid reasons for doing so.
First, while the highest possible compression ratio is of course desirable, one has to consider the cost. No-one would accept a movie with a resolution of 120x90, even though everyone knows it would have an excellent compression ratio. A similar effect could be achieved by smoothing the clip until it looks like you smeared your monitor screen with vasoline, but again no-one would accept this kind of image.
It could be argued that DCT blocks are similarly unacceptable in a good-quality encode. Peoples' opinion on this differs greatly, but an increasing number, myself included, are looking for a sure-fire way to get rid of them. This filter is my contribution to the effort.
Second, as compression technology evolves and improves it is sometimes necessary to re-encode old movies into new, more advanced formats. Removing artifacts from existing movies is much more difficult than stopping them appearing in the first place. Thus removing DCT blocks from your encodes now will make it easier to re-compress your movies later.
How it works
The filter splits each frame into a series of blocks (of user-configurable dimensions) and checks each block in turn for the amount of detail it contains. If the detail level is within the defined range then the block is processed according to the method parameter (see the Usage section below).
There are several methods that can potentially reduce or eliminate DCT blocks: adding noise, sharpening, and blurring. These different approaches are accessed with the method parameter, combined with a set of parameters shared by all methods as well as parameters specific to each one. There is an additional method called "show" which highlights each block that will be affected by the filter.
The general form of usage is
Blockbuster([clip], method="...", shared parameters, method-specific parameters)
The method parameter can be either "noise", "dither", "sharpen", "blur" or "show". Parameters specific to each method will be listed in the Methods section below. Parameters common to all methods are:
|block_size||Determines the size of the blocks into which each frame is split before processing. The value represents both the width and height of the block, and cannot be less than 3.||8|
|detail_min, detail_max||Determines the amount of detail that must be present in a block for it to be processed. This value is a percentage, and can be between 1 and 100. It represents the percentage of unique brightness levels within the block. For example, if block_size is set to 8, each block contains 64 pixels. If detail_min is 1 and detail_max is 50, a block will only be processed if it contains between 1 and 32 unique brightness levels. A setting of detail_min=1, detail_max=100 will process the entire frame.||detail_min=1, detail_max=10|
|luma_offset, luma_threshold||Luma pixels in the range 0-luma_threshold will be offset by luma_offset within processed blocks. For example, if luma_threshold is 30 and luma_offset is -2, dark pixels (those with a luma between 0 and 30 inclusive) will have 2 subtracted from them, making them slightly darker still. (Rationale: It was discovered that Marc FD's mpeg2dec plugin's lumoff parameter seems to reduce the appearance of MPEG-4 "dark blocks" -- DCT blocks in dark areas of the frame -- further. These parameters are designed to do the same thing, but constrain the brightness change only to areas that seem to need it.)||luma_offset=0, luma_threshold=25|
| || || |
This method adds normally distributed -- also known as Gaussian -- noise to the clip. Testing has shown that Gaussian noise is far more suitable for this filter's purposes than uniformly distributed noise. With uniformly distributed noise, each possible value is as likely to occur as any other. That is, if you generate a sequence of numbers in the range 1-100, at any point in the sequence you are as likely to generate a 5 as a 95.
"Normally distributed" means the chance of each value occurring is not equal. Let's say you generate normally distributed numbers with a mean of 0 and a variance of 1 (for an explanation of these terms see the links at the end of the paragraph). The generator can in theory spit out any number that can fit into a double-precision floating point, but taken as a whole the numbers will average out to zero (that's what mean=0, er, means). With these parameters about 68% of the values will be between -1 and 1, about 95% between -2 and 2, and about 99% between -3 and 3. The probability of generating numbers that are significantly higher or lower is very small indeed, with the probability getting smaller the further away from zero you get. You'd need to make billions of Gaussian random numbers with mean 0, variance 1 before you saw, for example, the value 9 being generated. You can read more about normal distribution here
Gaussian noise very much tends to concentrate around the specified mean, and is thus more "natural" than uniformly distributed noise. Most things in nature (including spring precipitation, calorific intake, and, of course, noise) cluster around a "normal" value, with progressively less frequent occurrances as you get further from that norm.
You can read more about adding noise to eliminate DCT blocks in this ArsTechnica thread
(Link now 404.)
Blockbuster([clip], method="noise", common parameters, float mean, float variance, int cache, int seed)
|mean, variance||The mean and variance of the generated random noise.||mean=0, variance=1|
|cache||Because the generation of Gaussian numbers is very slow, to achieve reasonable performance Blockbuster creates a cache of random numbers at startup. This parameter spcifies the size of the cache in kilobytes. While you can set this value to any positive number, it's best to keep it fairly large in order to maintain properly random noise. While the default may seem quite big, consider this: If you have a block size of 8, then each block has an area of 64 pixels. Each pixel requires 2 bytes to describe it, so a processed block will consume 128 bytes of random data. If 10% of the blocks in a 640x480 frame are processed, this will require nearly 62kb of random numbers.||256|
|seed||By default the pseudo-random number generator will use the system's current time as its seed value. For certain applications where predictable results are desired, the seed parameter can be used to override this default, thus providing the same "random" noise each time the filter is run. The seed can be any number from 0 to 2,147,483,647. When zero, the system's current time is used.||0|
| || || |
This method is very similar to the "noise" method, with the only difference being this method will add the same noise to each frame of the clip, whereas the "noise" method will add different noise to each frame. The effect of this is hard to describe, but easy to see, so try it for yourself with a high variance. The closest comparison I can think of is that this method produces an effect similar to watching the movie through speckled glass. The reason I added this method is because I found when using method="noise" that the constantly changing nature of the noise produced motion in otherwise static areas of the frame, particularly when using a low bit rate. It is my hope that using the same noise for each frame will prevent artificial motion in static areas. This should be considered experimental.
Blockbuster([clip], method="dither", common parameters, float mean, float variance, int seed)
For a description of this method's parameters, see the "noise" method above. Note however that the "dither" method has no cache parameter since it always generates only enough noise for one frame.
This method applies a basic (and fast) sharpening filter to processed blocks. In this way it "amplifies" detail already present in the block rather than adding new noise.
Blockbuster([clip], method="sharpen", common parameters, int strength)
|strength||Specifies the strength of the sharpening, from 1-100.||25|
| || || |
This method applies a 3x3 blur to processed blocks. It is currently experimental, since in theory reducing the frequency of already low-frequency blocks will not reduce the appearance of DCT blocks. It's worth a try, though.
Blockbuster([clip], method="blur", common parameters, int strength)
|strength||Specifies the strength of the blurring, from 1-100.||25|
| || || |
This method highlights blocks that will be processed using the specified block-related common parameters (block_size, detail_min, and detail_max). It is mainly useful as a visual aid in setting the detail thresholds to the desired range. This method has no additional parameters.
- MMX/SSE optimizations (I have to learn x86 assembler first...).
- Unsharp mask method.
- 0.7 - Added dither method.
- 0.6 - Added seed parameter. Fixed a bug that might have been responsible for the access violations when using with high resolutions and the default cache size.
- 0.5 - Remove chroma noise since no-one seemed to use it, so cm and cv are gone, and lm and lv have been renamed mean and variance respectively. Some small optimizations here and there. First Avisynth 2.5/YV12 version.
- 0.4 - Added luma_offset and luma_threshold common parameters. Split detail_threshold into detail_min and detail_max and was thus able to remove invert mode.
- 0.3a - Forgot to clamp noise method to [0, 255]. Oops!
- 0.3 - Name changed to Blockbuster (thanks to Doom9's MaTTeR for the suggestion). Major restructuring and modularizing of code to make maintenance easier; also resulted in overall performance gains. Algorithmic optimizations everywhere (all methods now run in real-time at 720x480 on my system). Added concept of methods. Implemented sharpen and blur methods in addition to noise. Redesigned noise generation to be faster and simpler -- no need for non-portable "fast mode" now. Removed some useless parameters (e.g. rounding is now always to the nearest integer, merged um, uv, vm, vv into cm and cv). Reimplemented show mode as a method. Almost totally rewrote documentation. All in all a pretty minor update.
- 0.2 - Implemented detail detection code. Fixed bug that could cause an access violation.
- 0.1 - First release. Alpha code. Danger, Will Robinson!
Download here: SansGrip's Avisynth Filters [DOWNLOADS]