I realized having to host this potentially indefinitely might not be the best idea, so I am going to shut down this gitea instance eventually.
You’ll have time, at least until the end of 2022, probably longer, but please just get all your stuff somewhere safe in case we ever disappear.

initial commit

master
kageru 3 years ago
commit 033aed5884
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
  1. 253
      adaptivegrain.html
  2. 82
      aoc.html
  3. 153
      aoc_postmortem.html
  4. 116
      blogs.html
  5. 88
      dependencies.html
  6. 379
      edgemasks.html
  7. 62
      expediency.html
  8. 281
      grain.html
  9. 426
      matana.html
  10. 374
      mgsreview.html
  11. 475
      removegrain.html
  12. 343
      resolutions.html
  13. 20
      template.html
  14. 353
      videocodecs.html

@ -0,0 +1,253 @@
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<p class="heading">Adaptive Graining Methods</p>
<div class="content">
<ul>
<li><a href="#c_abstract">Abstract</a></li>
<li><a href="#c_demo">Demonstration and Examples</a></li>
<li><a href="#c_script">Theory and Explanation</a></li>
<li><a href="#c_performance">Performance</a></li>
<li><a href="#c_usage">Usage</a></li>
<li><a href="#c_outtro">Closing Words and Download</a></li>
</ul>
</div>
<div class="content">
<p class="subhead"><a href="#c_abstract" id="c_abstract">Abstract</a></p>
In order to remedy the effects of lossy compression of digital media files, dither is applied to randomize
quantization errors and thus avoid or remove distinct patterns which are perceived as unwanted artifacts. This
can be used to remove banding artifacts by adding random pixels along banded edges which will create the
impression of a smoother gradient. The resulting image will often be more resilient to lossy compression, as the
added information is less likely to be omitted by the perceptual coding algorithm of the encoding software.
<br>Wikipedia explains it like this:
<div class="code">
High levels of noise are almost always undesirable, but there are cases when a certain amount of noise is
useful, for example to prevent discretization artifacts (color banding or posterization). [. . .] Noise
added for such purposes is called dither; it improves the image perceptually, though it degrades the
signal-to-noise ratio.
</div>
<p>
In video encoding, especially regarding anime, this is utilized by debanding filters to improve their
effectiveness and to “prepare” the image for encoding. While grain may be beneficial under some
circumstances, it is generally perceived as an unwanted artifact, especially in brighter scenes where
banding
artifacts would be less likely to occur even without dithering. Most Blu-rays released today will already
have
grain in most scenes which will mask most or even all visual artifacts, but for reasons described <a
href="article.php?p=grain">here</a>, it may be beneficial to remove this grain.</p>
<p>
As mentioned previously, most debanding filters will add grain to the image, but in some cases this grain
might
be either to weak to mask all artifacts or to faint, causing it to be removed by the encoding software,
which in
turn allows the banding to resurface. In the past, scripts like GrainFactory were written to specifically
target
dark areas to avoid the aforementioned issues without affecting brighter scenes.</p>
<p>
This idea can be further expanded by using a continuous function to determine the grain's strength based on
the
average brightness of the frame as well as the brightness of every individual pixel. This way, the problems
described above can be solved with less grain, especially in brighter areas and bright scenes where the dark
areas are less likely to the focus of the viewer's attention. This improves the perceived quality of the
image
while simultaneously saving bitrate due to the absence of grain in brighter scenes and areas.
</p>
<p class="subhead"><a href="#c_demo" id="c_demo">Demonstration and Examples</a></p>
Since there are two factors that will affect the strength of the grain, we need to analyze the brightness of any
given frame before applying any grain. This is achieved by using the PlaneStats function in Vapoursynth. The
following clip should illustrate the results. The brightness of the current frame is always displayed in the top
left-hand corner. The surprisingly low values in the beginning are caused by the 21:9 black bars. <span
style="font-size: 70%; color: #aaaaaa;">(Don't mind the stuttering in the middle. That's just me being bad)</span>
<br><br>
<video width="1280" height="720" controls>
<source src="/media/articles/res_adg/adg_luma.mp4" type="video/mp4">
</video>
<br>
<div style="font-size: 80%;text-align: right">You can <a href="/media/articles/res_adg/adg_luma.mp4">download the video</a>
if your browser is not displaying it correctly.
</div>
In the dark frames you can see banding artifacts which were created by x264's lossy compression algorithm.
Adding grain fixes this issue by adding more randomness to the gradients.<br><br>
<video width="1280" height="720" controls>
<source src="/media/articles/res_adg/adg_grained.mp4" type="video/mp4">
</video>
<br>
<div style="font-size: 80%;text-align: right"><a href="/media/articles/res_adg/adg_grained.mp4">Download</a></div>
By using the script described above, we are able to remove most of the banding without lowering the crf,
increasing aq-strength, or graining other surfaces where it would have decreased the image quality.
<p class="subhead"><a href="#c_script" id="c_script">Theory and Explanation</a></p>
The script works by generating a copy of the input clip in advance and graining that copy. For each frame in the
input clip, a mask is generated based on the frame's average luma and the individual pixel's value. This mask is
then used to apply the grained clip with the calculated opacity. The generated mask for the previously used clip
looks like this:<br><br>
<video width="1280" height="720" controls>
<source src="/media/articles/res_adg/adg_mask.mp4" type="video/mp4">
</video>
<br>
<div style="font-size: 80%;text-align: right"><a href="/media/articles/res_adg/adg_mask.mp4">Download</a></div>
The brightness of each pixel is calculated using this polynomial:
<div class="code">
z = (1 - (1.124x - 9.466x^2 + 36.624x^3 - 45.47x^4 + 18.188x^5))^(y^2 * <span
style="color: #e17800">10</span>)
</div>
where x is the luma of the current pixel, y is the current frame's average luma, and z is the resulting pixels
brightness. The highlighted number (10) is a parameter called luma_scaling which will be explained later.
<p>
The polynomial is applied to every pixel and every frame. All luma values are floats between 0 (black) and 1
(white). For performance reasons the precision of the mask is limited to 8&nbsp;bits, and the frame
brightness is rounded to 1000 discrete levels.
All lookup tables are generated in advance, significantly reducing the number of necessary calculations.</p>
<p>
Here are a few examples to better understand the masks generated by the aforementioned polynomial.</p>
<table style="width: 100%">
<tr>
<td style="width: 33%"><img src="/media/articles/res_adg/y0.2.svg"></td>
<td style="width: 33%"><img src="/media/articles/res_adg/y0.5.svg"></td>
<td style="width: 33%"><img src="/media/articles/res_adg/y0.8.svg"></td>
</tr>
</table>
<p>
Generally, the lower a frame's average luma, the more grain is applied even to the brighter areas. This
abuses
the fact that our eyes are instinctively drawn to the brighter part of any image, making the grain less
necessary in images with an overall very high luma.</p>
Plotting the polynomial for all y-values (frame luma) results in the following image (red means more grain and
yellow means less or no grain):<br>
<img style="width: 100%; margin: -5em 0" src="/media/articles/res_adg/preview.svg">
<br>
More detailed versions can be found <a href="/media/articles/res_adg/highres.svg">here</a> (100 points per axis) or <a
href="/media/articles/res_adg/superhighres.7z">here</a> (400 points per axis).<br>
Now that we have covered the math, I will quickly go over the Vapoursynth script.<br>
<div class="spoilerbox_expand_element">Click to expand code<p class="vapoursynth">
<br>import vapoursynth as vs
<br>import numpy as np
<br>import functools
<br>
<br>def adaptive_grain(clip, source=None, strength=0.25, static=True, luma_scaling=10, show_mask=False):
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;def fill_lut(y):
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x = np.arange(0, 1, 1 / (1 << src_bits))
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;z = (1 - (1.124 * x - 9.466 * x ** 2 + 36.624 * x ** 3 -
45.47 * x ** 4 + 18.188 * x ** 5)) ** (
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(y ** 2) * luma_scaling) * ((1
<< src_bits) - 1)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;z = np.rint(z).astype(int)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return z.tolist()
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;def generate_mask(n, clip):
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;frameluma =
round(clip.get_frame(n).props.PlaneStatsAverage * 999)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;table = lut[int(frameluma)]
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return core.std.Lut(clip, lut=table)
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;core = vs.get_core(accept_lowercase=True)
<br>&nbsp;&nbsp;&nbsp;&nbsp;if source is None:
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;source = clip
<br>&nbsp;&nbsp;&nbsp;&nbsp;if clip.num_frames != source.num_frames:
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise ValueError('The length of the filtered and
unfiltered clips must be equal')
<br>&nbsp;&nbsp;&nbsp;&nbsp;source = core.fmtc.bitdepth(source, bits=8)
<br>&nbsp;&nbsp;&nbsp;&nbsp;src_bits = 8
<br>&nbsp;&nbsp;&nbsp;&nbsp;clip_bits = clip.format.bits_per_sample
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;lut = [None] * 1000
<br>&nbsp;&nbsp;&nbsp;&nbsp;for y in np.arange(0, 1, 0.001):
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lut[int(round(y * 1000))] = fill_lut(y)
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;luma = core.std.ShufflePlanes(source, 0, vs.GRAY)
<br>&nbsp;&nbsp;&nbsp;&nbsp;luma = core.std.PlaneStats(luma)
<br>&nbsp;&nbsp;&nbsp;&nbsp;grained = core.grain.Add(clip, var=strength, constant=static)
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;mask = core.std.FrameEval(luma, functools.partial(generate_mask, clip=luma))
<br>&nbsp;&nbsp;&nbsp;&nbsp;mask = core.resize.Bilinear(mask, clip.width, clip.height)
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;if src_bits != clip_bits:
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mask = core.fmtc.bitdepth(mask, bits=clip_bits)
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;if show_mask:
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return mask
<br>
<br>&nbsp;&nbsp;&nbsp;&nbsp;return core.std.MaskedMerge(clip, grained, mask)
</p></div><br>
<b>Thanks to Frechdachs for suggesting the use of std.FrameEval.</b><br>
<br>
In order to adjust for things like black bars, the curves can be manipulated by changing the luma_scaling
parameter. Higher values will cause comparatively less grain even in darker scenes, while lower
values will increase the opacity of the grain even in brighter scenes.
<!--
<p class="subhead"><a id="c_performance" href="#c_performance">Performance</a></p>
<p>Essentially, this script does two things. It analyzes the average brightness of every frame and generates a
mask. Luma analysis is done with one of the core functions and should have no impact on the encoding
performance.<br>
The mask generation should also have virtually no impact on the encode, as the lookup tables
are generated within seconds and then reused for all frames. I cannot time the involved core functions
(core.std.lut and core.std.MaskedMerge)
accurately, but neither of them is particularly expensive, so the performance cost should be negligible.<br>
Don't expect a notable difference in encoding performance.
-->
</p>
<p class="subhead"><a id="c_usage" href="#c_usage">Usage</a></p>
The script has four parameters, three of which are optional.
<table class="paramtable">
<tr>
<td>Parameter</td>
<td>[type, default]</td>
<td class="paramtable_main">Explanation</td>
</tr>
<tr>
<td>clip</td>
<td>[clip]</td>
<td>The filtered clip that the grain will be applied to</td>
</tr>
<!--
<tr>
<td>source</td>
<td>[clip, None]</td>
<td>The source clip which will be used for the luma mask. For performance reasons this should be the
unfiltered source clip. If you changed the length of your filtered clip with Trim or similar
filters, also apply those filters here, but do not denoise, deband, or otherwise filter
this clip. If unspecified, the clip argument will be used.
</td>
</tr>
-->
<tr>
<td>strength</td>
<td>[float, 0.25]</td>
<td>Strength of the grain generated by AddGrain.</td>
</tr>
<tr>
<td>static</td>
<td>[boolean, True]</td>
<td>Whether to generate static or dynamic grain.</td>
</tr>
<tr>
<td>luma_scaling</td>
<td>[float, 10]</td>
<td>This values changes the general grain opacity curve. Lower values will generate more grain, even in
brighter scenes, while higher values will generate less, even in dark scenes.
</td>
</tr>
</table>
<br>
<p class="subhead"><a href="#c_outtro" id="c_outtro">Closing Words</a></p>
<p>
Grain is a type of visual noise that can be used to mask discretization if used correctly. Too much grain
will
degrade the perceived quality of a video, while to little grain might be destroyed by the perceptual coding
techniques used in many popular video encoders.</p>
<p>
The script described in this article aims to apply the optimal amount of grain to all scenes to prevent
banding artifacts without having a significant impact on the perceived image quality or the required
bitrate.
It does this by taking the brightness of the frame as a whole and every single pixel into account and
generating an opacity mask based on these values to apply grain to certain areas of each frame. This can be
used to supplement or even replace the dither generated by other debanding scripts. The script has a
noticeable but not significant impact on encoding performance.
</p>
<div class="download_centered"><a href="https://github.com/Irrational-Encoding-Wizardry/kagefunc/blob/master/kagefunc.py">Download</a></div>
<br><br><span style="color: #251b18; font-size: 50%; text-align: left">There's probably a much simpler way to do this, but I like this one. fite me m8</span>
</div>
</div>

@ -0,0 +1,82 @@
<body>
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div class="wrapper_article">
<p class="heading" style="line-height: 1.1em">Challenges for the Advent of Code or<br>“How to Bite Off More Than You Can Chew, but not too much”</p>
<div class="content">
<p class="subhead"><a href="#c_introduction" id="c_introduction">Introduction</a></p>
<p>
Humans love challenges.<br>
We want to prove something to our friends, our coworkers, our peers, or even just ourselves.<br>
We want to push ourselves to our limits, maybe even beyond them, and grow into more than what we are.<br>
We want to show the world that we can do something, whatever that may be.<br>
We want to get just a tiny bit closer to the optimal version, to the person we could become.<br>
</p>
<p>
That’s one reason to do Advent of Code. The other — and much more common — is probably just boredom.<br>
For those unaware: Advent of Code is like an advent calendar where there’s a coding task hidden behind each door. You receive a randomized input (actually a randomly selected one from a precomputed set because they can’t store and solve a new puzzle for every user), and you have to enter your solution on the website. How you get that solution doesn’t matter. That should give you a basic idea. Onto the actual content of this.
</p>
<p class="subhead"><a href="#c_leaning" id="c_leaning">Learning From Past Experiences</a></p>
<p>
Last year, I failed the Advent of Code. And not just a little bit. I really mean I failed.
I wanted to use it as an opportunity to learn Haskell, so my plan was to implement all 24 tasks in Haskell.
This should, in theory, give me enough practice to understand the basics and become at least reasonably comfortable in the language.
</p>
<p>
Reality was… not quite that.<br>
Not only did I not finish even <em>a single</em> puzzle in Haskell, but it deterred me from even trying to learn any functional language for quite a while (that is, until today and ongoing).
I then switched to Nim because reasons, but I didn’t finish all 24 in that either. I ended up skipping ~70% of all days.
</p>
<p>
What do we learn from that?<br>
Well, for one, we shouldn’t just plan tasks of completely unknown complexity. “It will be alright” is not how you approach software. I’ve done that, and it works more often than it should, but this is really a case where it was just silly.
For two, (I know that’s not how english works; don’t @ me) I needed variety. Just picking one language may work for other people and it’s certainly useful if you want to learn that language, but it wasn’t enough to keep me motivated.
</p>
<p class="subhead"><a href="#c_forward" id="c_forward">Going Forward</a></p>
<p>
So what did I change this year? Simple.
<ul>
<li title="yes, I still can’t into functional programming">I will not use programming paradigms that are completely alien to me</li>
<li>I will use more than just one language to keep things interesting</li>
<li>I will brag as openly as possible about my plans so people can publicly shame me for not following them</li>
</ul>
I know that there are people who try “24 days, 24 languages”, but I’m not quite that insane. For me, 8 languages should be enough. I’m giving myself three tickets for each language. It is up to me which language I will use for any given day, as long as I have at least one ticket left for that language.<br>
I’ve created a git repo for this challenge <a href="https://git.kageru.moe/kageru/advent-of-code-2018">here</a>.<br>
The number of remaining tickets for each language can be tracked in the <code>tickets.md</code> file.<br>
Edit: looks like there are 25 Days, and I misjudged a few things, so I had to change my plans here on day one. Still keeping this section, just because.<br>
The languages I have chosen are (in alphabetical order):
<ol>
<li>C</li>
<li>Go</li>
<li>Java</li>
<li>Javascript</li>
<li>Kotlin</li>
<li>Nim</li>
<li>Python</li>
<li>Rust</li>
</ol>
That puts us at 6 compiled and 2 interpreted languages. Out of these, I would rate myself the most proficient in Python and the least proficient in C. Contrary to popular belief, you can be a software developer <span title="Just for the sake of the stupid pun, I just opened a terminal and ran “touch c”. Kill me pls.">without ever having touched C</span>.<br>
<br>I would like to add that I’m not necessarily a fan of all of these, especially Java. However, since I’m currently working on a ~1,000,000 loc Java project as my fulltime job, not including it here just felt wrong.<br>
To show my remorse and to give me a very early impression of the suffering that this decision will bring with it, I’m typing this paragraph in <a href="https://www.gnu.org/fun/jokes/ed.msg.html">ed, the default editor</a>.
It really is quite the experience. The creativity of the people who wrote it is admirable. You wouldn’t believe how many convenience features you can add to an editor that only displays one line at a time. (Let me also remind you that ed was used before monitors were a thing, so printing lines wasn’t free either.)
This is, unironically, a better editor than most modern default editors (referring mostly to Windows Notepad and whatever OS X uses).<br>
Oh, and ed supports regex. Everything is better if it supports regex. But I digress.
<br>
Apart from the JVM languages, I’ll write all of the solutions in vim, using only Syntastic and the language’s respective command line compilers.
Java is just too verbose to be used without introspection and autocompletion. At least for my taste.
vim can be really cool to use, and I love it for most languages, but Java is just not one of them. Still typing this in ed, btw. :^)
</p>
<p>
If I come across a solution that seems particularly interesting to me, I might share it here, but I doubt that will happen.
Let’s see how far I can get this year. Expect a recap of this year’s inevitable failure here around the end of the year.
</p>
<p>
Edit: after finding out that JS has basically no consistent way of reading files, I removed that from the list. It’s garbage anyway, and I’m not writing node.js. Since all of this doesn’t really work with 25 days either, I’ve also decided to drop two more languages to get me to 5x5.<br>
That left me with C, Go, Kotlin, Python, and Rust. 5 days each.
</p>
<span class="ninjatext">I thought about including Haskell in this list, but I decided not to repeat history… at least for now</span>
</div>
</div>
</body>

@ -0,0 +1,153 @@
<body>
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<p class="heading">Advent of Code: Postmortem</p>
<div class="content">
<p>
Looks like it’s that time of the year again. I’m on a train with nothing to do, and I’ve been procrastinating this for months.
</p>
<p>
Last year, I attempted a challenge for <a href="https://adventofcode.com/">Advent of Code</a>.
If you want a detailed explanation, check out the <a href="https://kageru.moe/blog/article/aoc/">blog post I wrote back then.</a>
tl;dr: I had a set of 5 programming languages for the 25 tasks. I had to use each of them 5 times.
</p>
<p>
Even though I failed, it didn’t turn out that bad.
I finished 14.5 days. 1-14 are fully implemented, and for 16, I did the first half. Then, I stopped.</p><p>
I’m not here to make excuses, but humor me for a paragraph before we get to the actual content of this.
</p>
<p>
I really disliked day 15.
Not because it was too hard, but because it would have been boring to implement while also being time-consuming due to the needless complexity.
I just didn’t feel like doing that, and that apparently set a dangerous precedent that would kill the challenge for me.</p><p>
So there’s that. Now for the interesting part:
</p>
<p class="subhead">The part you’re actually here for</p>
<p>
I tried out languages. Languages that I had never really used before. Here are my experiences:
</p>
<p class="subhead">C<p>
<p>
Days finished: 4</p><p>
Before the challenge, I knew absolutely nothing about C. I had never allocated memory, incremented a pointer, or used section 3 of <code>man</code>. That’s why I wanted to get this out of the way as quickly as possible.</p><p>
C is interesting.
It lets you do all these dirty things, and doing them was a kind of guilty pleasure for me.
Manually setting nullbytes or array pointers around.
Nothing about that is special, but other languages just don’t let you.
You know it’s bad, and that only makes it better.</p><p>
Would I use C for other private projects? No. Definitely not.
I just don’t see the point in $currentYear. But was it interesting? You bet.
</p>
<p class="subhead">Go<p>
<p>
Days finished: 3</p><p>
Allegedly, this language was made by some very smart people.
They may have been smart, but they may have created the most boring programming language in existence.
It feels bad to write, and it’s a terrible choice when dealing mostly with numbers (as is the case with most AoC puzzles).
It’s probably great for business logic, and I can say from personal experience that it also works quite well for web development and things like <a href="https://git.kageru.moe/kageru/discord-selphybot">discord bots</a>.
But to me, not having map/reduce/filter/etc. just makes a language really unenjoyable and verbose.</p><p>
Writing Go for AoC sometimes felt like writing a boring version of C (TL note: one that won’t segfault and memory leak your ass off if you don’t pay attention).
</p><p>
People say it’s more readable and all that, and that’s certainly great for huge projects, but for something like this… I wouldn’t ever pick Go voluntarily.</p><p>
And also not for anything else, to be perfectly honest. I mean… just look at this.
(Yes, I wrote this. Scroll down and use the comment section to tell me that I’m just too dumb to understand Go.)
</p>
<pre><code class="go">package main
import "fmt"
func main() {
// This declares a mutable variable.
var regularString = "asdf"
// This also declares a mutable variable.
// I have no idea why there are two ways of doing this.
unicodeString := "aä漢字"
for char := range(unicodeString) {
// You might expect that this prints the characters individually,
fmt.Println(char)
/*
* Instead, it compiles and prints (0, 1, 3, 6) -- the index of the first byte of each character.
* Very readable and very intuitive. Definitely what the user would want here.
*/
}
for _, char := range(unicodeString) {
/*
* Having learned from our past mistakes, we assign the index to _ to discard it.
* Surely this time.
*/
fmt.Println(char)
/*
* Or not because this prints (97, 228, 28450, 23383) -- the unicode indices of the characters.
* Printing a rune (the type Go uses to represent individual characters,
* e.g. during string iteration) actually prints its integer value.
*/
}
for _, char := range(unicodeString) {
/*
* This actually does what you’d expect.
* It also handles unicode beautifully, instead of just iterating over the bytes.
*/
fmt.Printf("%c\n", char)
}
/*
* So go knows what a character is and how many of those are in a string when iterating.
* Intuitively, this would also apply to the built-in len() function.
* However...
*/
fmt.Println(len(regularString)) // prints 4
fmt.Println(len(unicodeString)) // prints 9
}</code></pre>
<p>
Oh, and there are no generics. Moving on.
</p>
<p class="subhead">Kotlin<p>
<p>
Days finished: 3</p><p>
Oh Kotlin. Kotlin is great. A few weeks after AoC, I actually started writing Kotlin at work, and it’s lovely.
It fixes almost all of the issues people had with Java while maintaining perfect interoperability, and it’s also an amazing language just by itself.</p><p>
I like the way Kotlin uses scopes and lambdas for everything, and the elvis operator makes dealing with nullability much easier.
Simple quality of life improvements (like assignments from try/catch blocks), things let() and use(), proper built-in singletons, and probably more that I’m forgetting make this a very pleasant language. Would recommend.</p><p>
In case you didn’t know: Kotlin even compiles to native binaries if you’re not using any Java libraries (although that can be hard because you sometimes just need the Java stdlib).
</p>
<p class="subhead">Python<p>
<p>
Days finished: 2</p><p>
I don’t think there’s much to say here.
Python was my fallback for difficult days because I just feel very comfortable writing it.
The standard library is the best thing since proper type inference, and it supports all the syntactic sugar that anyone could ask for.
If a language is similar to Python, I’ll probably like it.</p><p>
Yes, I’ve tried nim.
</p>
<p class="subhead">Rust<p>
<p>
Days finished: 2</p><p>
Rust is… I don’t even know.
But first things first: I like Rust.<br>
I like its way of making bad code hard to write.<br>
I like the crate ecosystem.<br>
I like list operations and convenience functions like <code>sort_by_key</code>.<br>
I like immutability by default.<br>
I like generics (suck it, Go).</p><p>
Not that I didn’t have all kinds of issues with it, but Rust made me feel like those issues were my fault, rather than the fault of the language.
I also wouldn’t say I feel even remotely comfortable with the borrow checker -- it sometimes (or more often than I’d like to admit) still felt like <a href="https://i.redd.it/iyuiw062b1s11.png" _target=blank>educated trial and error.</a>
I’m sure this gets better as you grow more accustomed to the language, and so far I haven’t encountered anything that would be a deal breaker for me.</p><p>
Rust might even become my go-to language for performance-sensitive tasks at some point.
It definitely has all of the necessary tools.
Unlike <em>some languages</em> that leave you no room for optimization with intrinsics or similar magic. (Why do I keep going back to insulting Go? Why does Go keep giving me reasons for doing so?)</p><p>
The borrow checker will likely always be a source of issues, but I think that is something that is worth getting used to.
The ideas behind it are good enough to justify the hassle.
</p>
<p class="subhead">See you next year. Maybe.<p>
<p>
I underestimated just how little time and motivation I’d have left after an 8-hour workday that already mostly consists of programming.</p><p>
It was fun, though, and I’ll probably at least try something similar next year.<br><br>
Let’s see what stupid challenge I come up with this time.
</p>
<span class="ninja">Did anyone actually expect me to succeed?</span>
</div>
</div>
</body>

@ -0,0 +1,116 @@
<body>
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<p class="accent1 heading">
Writing Blogs out of Boredom
</p>
<div class="content">
<p>
Yes, blogs. Not blog posts. We’ll get into that in a second.<br>
This entire thing is also really unpolished, but I probably won’t ever find the motivation to make it better. Keep that in mind, and don’t expect too much.
</p>
<p class="subhead">
Introduction
</p>
<p>
More often than not, we find ourselves bored. The modern world, brimming with excitement, variety, colors, lights, and stimuli, fails to entertain us. We could have anything, but we want nothing.
While this is certainly a serious issue that merits pages upon pages of psychological and even philosophical discussion, it won’t be our topic for today.
Today, we’re looking at the other side of the digital age. Those rare occurences where you have nothing, but would take anything.<br>
Okay, maybe not quite, but let’s go with that.
</p>
<p>
A few weeks ago, I found myself on a rather lengthy train trip. No books, close to no reception, no one to talk to. Just me, a bag of clothes, and a laptop without internet.
<sup>
[1]
</sup>
<br>
So I did what every sane person would do. I programmed something. Not because I really needed it, but because it kept me busy, and hey, maybe someone someday might get some use out of it.
As has become the norm for me, I wanted to do something that was in some way new or unknown to me. In this case, template rendering and persistent storage in Go.
</p>
<div class="annotation">
[1] Incidentally, I’m on a train right now. No books, no reception, just a laptop with… you get the gist.<br>
<span class="ninja">
Although there is a moderately attractive girl sitting not too far from me this time.<br>
She just seems awfully immersed into her MacBook, and I don’t want to interrupt her.<br>Also &gt;Apple
</span>
</div>
<p>
The idea of the blog is quite simple, and I don’t feel bad admitting that I took inspiration from a friend who had designated channels on his discord server that were used as micro blogs by different users. One channel per user.
Mimicking that, I created a simple database schema that would store a message, an author, and a timestamp.<sup>[2]</sup>
The template would then query the last 50 entries from that database and display them on the website like an IRC log.<br><br>
And that was the frontend done.<br>
<a href="https://i.kageru.moe/kpGbwu.png">This</a> is what it looked like, in case you’re curious.
</p>
<div class="annotation">
[2] Of course, an auto-incrementing ID was also added, but that’s not really relevant here.
</div>
<p class="subhead accent1">
What, Why, How?
</p>
The reality is that the frontend was the very last part I implemented, but I already had a general idea, and explaining it first makes more sense to an outsider.
Some of you might also be thinking
</p>
<p>
<em>“What do you mean »the frontend was done«? You can’t publish anything on that blog.”</em>
</p>
<p>
You see, almost everything is optional. Sure, you might want to publish something, but do you really need a dedicated page for that? As long as the server knows who you are and what you want to write (and that you have the permissions to do so), it’s fine.
Implementing a login page in the frontend seemed a bit overkill, and requiring you to copy-paste a token into a password field for every line that you want to publish is also inconvenient at best.<br>
And why would we want that anyway?
</p>
<p class="subhead accent1">
The Best UI There Is…
</p>
<p>
There is <a href="https://brandur.org/interfaces">a very good article</a> about terminals and what we can learn from them when designing UIs.
However, in our efforts to make UIs that are _ at least in some regards _ <em>like</em> a terminal, we shouldn’t forget that some UIs might as well <em>be</em> terminals.<br>
And so I did the only logical thing. I implemented an endpoint that opens a port and listens for POST requests containing a JSON.<br>
That way, publishing can be automated with a simple shell script that uses curl to send the requests.<sup>[3]</sup>
</p>
<pre>
<code class="bash">function publish {
curl your_blog.com/add -d "{\"content\": \"$line\", \"Secret\": \"your_password\", \"author\": \"kageru\"}" -H "Content-Type: application/json"
}
read line
while [[ ! -z "$line" ]]; do
publish "$line"
read line
done
</code>
</pre>
<p>
This simple script will continue to read lines from STDIN (i. e. whatever you type before pressing return) and publish them as individual entries on the blog. It exits if you enter an empty line.
</p>
<p>
Now tell me, did you really need a website with a full HTML form for that?
</p>
<div class="annotation">
[3] Did I mention this only works on *nix? Though you could probably create a very similar script in PowerShell.
</div>
<p class="subhead accent1">
…Sometimes
</p>
<p>
I won’t deny that this is ugly in a few ways.<br>
Having your password stored in plain text in a shell script like this is certainly a huge security flaw, but since no real harm can be done here (all messages are HTML escaped, so no malicious content could be embedded in the website by an attacker), I consider this an acceptable solution for what it is _ a proof of concept that was created because I was bored on a train. Just like I am now, as I’m writing this.
The way the backend handles the requests is also anything but beautiful, but it does the job surprisingly well (if, like me, you only want a single user). You don’t even need a separate config file to store your password because the hash is in the source code and compiled into the binary.<br>
Isn’t this a great example of how time constraints and spontaneous solutions can make software terrible?
<em>
This is why, when a developer tells you he will need 3 months, you don’t force him to do it in 3 weeks.
</em>
</p>
<p>
Anyway, I think that’s it. I don’t know if this will be interesting, entertaining, enlightening, or whatever to anyone, but even if it doesn’t, it still kept me busy for the past hour. I still have almost two hours ahead of me, but I’ll find a way to keep myself entertained.
</p>
<span class="ninjatext">
The girl I mentioned earlier stashed away her MacBook, but it looks like she’s going to get off the train soon. Tough luck.
</span>
</div>
</div>
</body>

@ -0,0 +1,88 @@
<body>
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div class="wrapper_article">
<p class="heading">Vapoursynth: Are We Too Afraid Of Dependencies</p>
<div class="content">
<p class="subhead">Introduction</p>
<p class="content">
Now, if you’re anything like me, you’ll probably react to that title with “wtf, no, we have way too many of them”.
But hear me out. While it is true that most Vapoursynth “funcs” have dozens of dependencies that are sometimes poorly (or not at all) documented,
we might be cutting corners where it matters most. Most as measured by “where it affects most users”.
</p>
<p class="content">
Fundamentally, there are two groups of developers in the Vapoursynth “community” (and I use that term very loosely):
<ul>
<li> People who write plugins</li>
<li> People who write Python functions</li>
</ul>
Myrsloik, the head developer of Vapoursynth, has set everything up in a way that facilitates a structured plugin ecosystem.
Every plugin can reserve a namespace, and all of its functions have to live there.
You can’t have a denoiser and a color shift in the same namespace without making things really confusing, both for you as a dev and for the users.<br>
This is good. It should be like this. But here’s an issue:
</p>
<p class="subhead">Functions are a separate ecosystem</p>
<p class="content">
Funcs (and I’ll use that term to collections of functions such as havsfunc or mvsfunc) are fundamentally different from plugins.
Most importantly for the user, they need to be imported manually. The namespacing is handled by Python. But even Python can’t save you if you don’t let it.
</p>
<p class="content">
Probably the most popular func out there is <a href="https://github.com/HomeOfVapourSynthEvolution/havsfunc/blob/master/havsfunc.py">havsfunc</a>.
At the time of writing, it consists of 32 main functions and 18 utility functions. The other big funcs paint a similar picture.
For some reason, the convention has become to dump everything you write into a single Python file and call it a day.
When I started using Vapoursynth, this trend was already established, so I didn’t really think about it and created my own 500-line monstrosity with no internal cohesion whatsoever.
We don’t care if our func depends on 20 plugins, but God forbid a single encoder release two Python modules that have to be imported separately.
This is what I mean by “we’re afraid of dependencies”. We want all of our code in one place with a single import.<br>
It is worth pointing out that not everyone is doing this. People like dubhater or IFeelBloated exist, but the general consensus in the community (if such a thing even exists) seems to be strongly in favor of monolithic, basically unnamed script collections.
This creates multiple problems:
</p>
<p class="subhead">The Barrier of Entry</p>
<p class="content">
I don’t think anyone can deny that encoding is a very niche hobby with a high barrier of entry.
You won’t find many people who know enough about it to teach you properly, and it’s easy to be overwhelmed by its complexity.<br>
To make matters worse, if you’re just starting out, you won’t even know where to look for things.
Let’s say you’re a new encoder who has a source with a few halos and some aliasing, so you’re looking for a script that can help with that.
Looking at the documentation, your only options are Vine for the halos and vsTAAmbk for the aliasing.
There is no easy way for you to know that there are dehalo and/or AA functions in havsfunc, fvsfunc, muvsfunc, … you get the point.
We have amazing namespacing and order for plugins, but our scripts are a mess that is at best documented by a D9 post or the docstrings.
This is how you lose new users, who might have otherwise become contributors themselves.<br>
But I have a second issue with the current state of affairs:
</p>
<p class="subhead">Code Duplication</p>
<p class="content">
As mentioned previously, each of these gigantic functions comes with its own collection of helpers and utilities.
But is it really necessary that everyone writes their own oneliner for splitting planes, inserting clips, function iteration, and most simple mask operations?
The current state, hyperbolically speaking, is a dozen “open source” repositories with one contributor each and next to no communication between them.
The point of open source isn’t just that you can read other people’s code. It’s that you can actively contribute to it.
</p>
<p class="subhead">The Proposal</p>
<p class="content">
So what do I want? Do I propose a new system wherein each function gets its own Python module? No. God, please no.
I accept that we won’t be able to clean up the mess that has been created. That, at least in part, <em>I</em> have created.
But maybe we can start fixing it a least a little bit to make it easier for future encoders.
Actually utilizing open source seems like it would benefit everyone. The script authors as well as the users.
Maybe we could start with a general vsutil that contains all the commonly-used helpers and utilities.
That way, if something in Vapoursynth is changed, we only have to change one script instead of 20.
This should particularly help then-unmaintained scripts which won’t break quite as frequently.
The next step would be an attempt to combine functions of a specific type into modules, although this might get messy as well if not done properly.
Generally, splitting by content rather than splitting by author seems to be the way.
</p>
<p class="content">
I realize that I am in no real position to do this, but I at least want to try this for my own <a href="https://github.com/Irrational-Encoding-Wizardry/kagefunc/blob/master/kagefunc.py">kagefunc</a>, and I know at least a few western encoders who would be willing to join.
We’ve been using <a href="https://github.com/Irrational-Encoding-Wizardry">a GitHub organization</a> for this for a while, and i think this is the way to go forward.
It would also allow some sort of quality control and code review, something that has been missing for a long time now.
</p>
<p class="content">
I’ll probably stream my future work on Vapoursynth-related scripts (and maybe also some development in general) <a href="https://www.twitch.tv/kageru_">on my Twitch channel</a>.
Feel free to follow if you’re interested in that or in getting to know the person behind these texts. I’ll also stream games there (probably more games than coding, if I’m being honest), so keep that in mind.
</p>
<p class="content">
Edit: It has been pointed out to me that <a href="http://vsdb.top/">vsdb</a> exists to compensate for some of the issues described here.
I think that while this project is very helpful for newcomers, it doesn’t address the underlying issues and just alleviates the pain for now.
</p>
<span class="ninja">I just had to plug my Twitch there, didn’t I?</span>
</div>
</div>
</body>

@ -0,0 +1,379 @@
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<style scoped>
convolution {
display: flex;
flex-direction: column;
font-size: 250pt;
width: 1em;
height: 1em;
}
convolution > * > * {
flex-grow: 1;
flex-shrink: 1;
border: 1px #7f7f7f solid;
font-size: 30pt;
display: flex;
align-content: space-around;
}
convolution.c3x3 > * > * {
flex-basis: 33%;
}
convolution.c5x5 > * > * {
flex-basis: 20%;
}
convolution.c7x7 > * > * {
flex-basis: 14.28%;
}
convolution > * > * > span {
margin: auto;
}
convolution > * {
display: flex;
flex-direction: row;
height: 100%;
}
convolution > * > transparent {
background-color: transparent;
}
convolution > * > *[data-accent="1"] {
border-color: #e17800;
}
convolution > * > *[data-accent="2"] {
border-color: #6c3e00;
}
</style>
<p class="heading">Edge Masks</p>
<div class="content">
<p class="subhead">Table of contents</p>
<ul>
<li><a href="#c_intro">Abstract</a></li>
<li><a href="#c_theory">Theory, examples, and explanations</a></li>
<li><a href="#c_deband">Using edge masks</a></li>
<li><a href="#c_performance">Performance</a></li>
<li><a href="#c_end">Conclusion</a></li>
</ul>
<a id="c_intro" href="#c_intro"><p class="subhead">Abstract</p></a>
<p>
Digital video can suffer from a plethora of visual artifacts caused by lossy compression, transmission,
quantization, and even errors in the mastering process.
These artifacts include, but are not limited to, banding, aliasing,
loss of detail and sharpness (blur), discoloration, halos and other edge artifacts, and excessive noise.<br>
Since many of these defects are rather common, filters have been created to remove, minimize, or at least
reduce their visual impact on the image. However, the stronger the defects in the source video are, the more
aggressive filtering is needed to remedy them, which may induce new artifacts.
</p>
<p>
In order to avoid this, masks are used to specifically target the affected scenes and areas while leaving
the
rest unprocessed.
These masks can be used to isolate certain colors or, more importantly, certain structural components of an
image.
Many of the aforementioned defects are either limited to the edges of an image (halos, aliasing) or will
never
occur in edge regions (banding). In these cases, the unwanted by-products of the respective filters can be
limited by only applying the filter to the relevant areas. Since edge masking is a fundamental component of
understanding and analyzing the structure of an image, many different implementations were created over the past few
decades, many of which are now available to us.</p>
<p> In this article, I will briefly explain and compare different ways to generate masks that deal with the
aforementioned problems.</p>
<a id="c_theory" href="#c_theory"><p class="subhead">Theory, examples, and explanations</p></a>
<p>
Most popular algorithms try to detect abrupt changes in brightness by using convolutions to analyze the
direct
neighbourhood of the reference pixel. Since the computational complexity of a convolution is
<em>0(n<sup>2</sup>)</em>
(where n is the radius), the the radius should be as small as possible while still maintaining a reasonable
level of accuracy. Decreasing the radius of a convolution will make it more susceptible to noise and similar
artifacts.</p>
<p>Most algorithms use 3x3 convolutions, which offer the best balance between speed and accuracy. Examples are
the operators proposed by Prewitt, Sobel, Scharr, and Kirsch. Given a sufficiently clean (noise-free)
source, 2x2 convolutions can also be used<span class="source"><a
href="http://homepages.inf.ed.ac.uk/rbf/HIPR2/roberts.htm">[src]</a></span>, but with modern
hardware being able to calculate 3x3 convolutions
for HD video in real time, the gain in speed is often outweighed by the decreased accuracy.</p>
<p>To better illustrate this, I will use the Sobel operator to compute an example image.<br>
Sobel uses two convolutions to detect edges along the x and y axes. Note that you either need two separate
convolutions per axis or one convolution that returns the absolute values of each pixel, rather than 0 for
negative values.
<table class="full_width_table">
<tr>
<td style="width: 40%;">
<convolution class="c3x3 accent1">
<row>
<transparent><span>-1</span></transparent>
<transparent><span>-2</span></transparent>
<transparent><span>-1</span></transparent>
</row>
<row>
<transparent><span>0</span></transparent>
<transparent><span>0</span></transparent>
<transparent><span>0</span></transparent>
</row>
<row>
<transparent><span>1</span></transparent>
<transparent><span>2</span></transparent>
<transparent><span>1</span></transparent>
</row>
</convolution>
</td>
<td style="width: 40%;">
<convolution class="c3x3 accent1">
<row>
<transparent><span>-1</span></transparent>
<transparent><span>0</span></transparent>
<transparent><span>1</span></transparent>
</row>
<row>
<transparent><span>-2</span></transparent>
<transparent><span>0</span></transparent>
<transparent><span>2</span></transparent>
</row>
<row>
<transparent><span>-1</span></transparent>
<transparent><span>0</span></transparent>
<transparent><span>1</span></transparent>
</row>
</convolution>
</td>
</tr>
</table>
<p>
Every pixel is set to the highest output of any of these convolutions. A simple implementation using the
Convolution function of Vapoursynth would look like this:</p>
<pre><code class="python">def sobel(src):
sx = src.std.Convolution([-1, -2, -1, 0, 0, 0, 1, 2, 1], saturate=False)
sy = src.std.Convolution([-1, 0, 1, -2, 0, 2, -1, 0, 1], saturate=False)
return core.std.Expr([sx, sy], 'x y max')</code></pre>
Fortunately, Vapoursynth has a build-in Sobel function
<code>core.std.Sobel</code>, so we don't even have to write our own code.
<p>Hover over the following image to see the Sobel edge mask. </p>
<img src="/media/articles/res_edge/brickwall.png"
onmouseover="this.setAttribute('src', '/media/articles/res_edge/brickwall_sobel.png')"
onmouseout="this.setAttribute('src', '/media/articles/res_edge/brickwall.png')"><br>
<p>Of course, this example is highly idealized. All lines run parallel to either the x or the y axis, there are
no small details, and the overall complexity of the image is very low.</p>
Using a more complex image with blurrier lines and more diagonals results in a much more inaccurate edge mask.
<img src="/media/articles/res_edge/kuzu.png" onmouseover="this.setAttribute('src','/media/articles/res_edge/kuzu_sobel.png')"
onmouseout="this.setAttribute('src','/media/articles/res_edge/kuzu.png')"><br>
<p>
A simple way to greatly improve the accuracy of the detection is the use of 8-connectivity rather than
4-connectivity. This means utilizing all eight directions of the <a
href="https://en.wikipedia.org/wiki/Moore_neighborhood">Moore neighbourhood</a>, i.e. also using the
diagonals of the 3x3 neighbourhood.<br>
To achieve this, I will use a convolution kernel proposed by Russel A. Kirsch in 1970<span class="source"><a
href="https://ddl.kageru.moe/konOJ.pdf">[src]</a></span>.
</p>
<convolution class="c3x3 accent1">
<row>
<transparent><span>5</span></transparent>
<transparent><span>5</span></transparent>
<transparent><span>5</span></transparent>
</row>
<row>
<transparent><span>-3</span></transparent>
<transparent><span>0</span></transparent>
<transparent><span>-3</span></transparent>
</row>
<row>
<transparent><span>-3</span></transparent>
<transparent><span>-3</span></transparent>
<transparent><span>-3</span></transparent>
</row>
</convolution>
<br>
This kernel is then rotated in increments of 45° until it reaches its original position.<br>
Since Vapoursynth does not have an internal function for the Kirsch operator, I had to build my own; again,
using the internal convolution.
<pre><code class="python">def kirsch(src):
kirsch1 = src.std.Convolution(matrix=[ 5, 5, 5, -3, 0, -3, -3, -3, -3])
kirsch2 = src.std.Convolution(matrix=[-3, 5, 5, 5, 0, -3, -3, -3, -3])
kirsch3 = src.std.Convolution(matrix=[-3, -3, 5, 5, 0, 5, -3, -3, -3])
kirsch4 = src.std.Convolution(matrix=[-3, -3, -3, 5, 0, 5, 5, -3, -3])
kirsch5 = src.std.Convolution(matrix=[-3, -3, -3, -3, 0, 5, 5, 5, -3])
kirsch6 = src.std.Convolution(matrix=[-3, -3, -3, -3, 0, -3, 5, 5, 5])
kirsch7 = src.std.Convolution(matrix=[ 5, -3, -3, -3, 0, -3, -3, 5, 5])
kirsch8 = src.std.Convolution(matrix=[ 5, 5, -3, -3, 0, -3, -3, 5, -3])
return core.std.Expr([kirsch1, kirsch2, kirsch3, kirsch4, kirsch5, kirsch6, kirsch7, kirsch8],
'x y max z max a max b max c max d max e max')</code></pre>
<p>
It should be obvious that the cheap copy-paste approach is not acceptable to solve this problem. Sure, it
works,
but I'm not a mathematician, and mathematicians are the only people who write code like that. Also, yes, you
can pass more than three clips to sdt.Expr, even though the documentation says otherwise.<br>Or maybe my
limited understanding of math (not being a mathematician, after all) was simply insufficient to properly
decode “Expr evaluates an expression per
pixel for up to <span class="accent1">3</span> input clips.”</p>
Anyway, let's try that again, shall we?
<pre><code class="python">def kirsch(src: vs.VideoNode) -> vs.VideoNode:
w = [5]*3 + [-3]*5
weights = [w[-i:] + w[:-i] for i in range(4)]
c = [core.std.Convolution(src, (w[:4]+[0]+w[4:]), saturate=False) for w in weights]
return core.std.Expr(c, 'x y max z max a max')</code></pre>
<p>Much better already. Who needed readable code, anyway?</p>
If we compare the Sobel edge mask with the Kirsch operator's mask, we can clearly see the improved accuracy.
(Hover=Kirsch)<br>
<img src="/media/articles/res_edge/kuzu_sobel.png" onmouseover="this.setAttribute('src','/media/articles/res_edge/kuzu_kirsch.png')"
onmouseout="this.setAttribute('src','/media/articles/res_edge/kuzu_sobel.png')"><br>
<p> The higher overall sensitivity of the detection also results in more noise being visible in the edge mask.
This can be remedied by denoising the image prior to the analysis.<br>
The increase in accuracy comes at an almost negligible cost in terms of computational complexity. About
175 fps
for 8-bit 1080p content (luma only) compared to 215 fps with the previously shown sobel
<span title="You know why I'm putting this in quotes">‘implementation’</span>. The internal Sobel filter is
not used for this comparison as it also includes a high- and lowpass function as well as scaling options,
making it slower than the Sobel function above. Note that many of the edges are also detected by the Sobel
operator, however, these are very faint and only visible after an operation like std.Binarize.</p>
A more sophisticated way to generate an edge mask is the TCanny algorithm which uses a similar procedure to find
edges but
then reduces these edges to 1 pixel thin lines. Optimally, these lines represent the middle
of each edge, and no edge is marked twice. It also applies a gaussian blur to the image to eliminate noise
and other distortions that might incorrectly be recognized as edges. The following example was created with
TCanny using these settings: <code>core.tcanny.TCanny(op=1,
mode=0)</code>. op=1 uses a modified operator that has been shown to achieve better signal-to-noise ratios<span
class="source"><a href="http://www.jofcis.com/publishedpapers/2011_7_5_1516_1523.pdf">[src]</a></span>.<br>
<img src="/media/articles/res_edge/kuzu_tcanny.png"><br>
Since I've already touched upon bigger convolutions earlier without showing anything specific, here is an
example of the things that are possible with 5x5 convolutions.
<pre><code class="python"
>src.std.Convolution(matrix=[1, 2, 4, 2, 1,
2, -3, -6, -3, 2,
4, -6, 0, -6, 4,
2, -3, -6, -3, 2,
1, 2, 4, 2, 1], saturate=False)</code></pre>
<img src="/media/articles/res_edge/kuzu5x5.png">
This was an attempt to create an edge mask that draws around the edges. With a few modifications, this might
become useful for
halo removal or edge cleaning. (Although something similar (probably better) can be created with a regular edge
mask, std.Maximum, and std.Expr)
<a id="c_deband" href="#c_deband"><p class="subhead">Using edge masks</p></a>
<p>
Now that we've established the basics, let's look at real world applications. Since 8-bit video sources are
still everywhere, barely any encode can be done without debanding. As I've mentioned before, restoration
filters
can often induce new artifacts, and in the case of debanding, these artifacts are loss of detail and, for
stronger debanding, blur. An edge mask could be used to remedy these effects, essentially allowing the
debanding
filter to deband whatever it deems necessary and then restoring the edges and details via
std.MaskedMerge.</p>
<p>
GradFun3 internally generates a mask to do exactly this. f3kdb, the <span
title="I know that “the other filter” is misleading since GF3 is not a filter but a script, but if that's your only concern so far, I must be doing a pretty good job.">other popular debanding filter</span>,
does not have any integrated masking functionality.</p>
Consider this image:<br>
<img src="/media/articles/res_edge/aldnoah.png">
<p>
As you can see, there is quite a lot of banding in this image. Using a regular debanding filter to remove it
would likely also destroy a lot of small details, especially in the darker parts of the image.<br>
Using the Sobel operator to generate an edge mask yields this (admittedly rather disappointing) result:</p>
<img src="/media/articles/res_edge/aldnoah_sobel.png">
<p>
In order to better recognize edges in dark areas, the retinex
algorithm can be used for local contrast enhancement.</p>
<img src="/media/articles/res_edge/aldnoah_retinex.png">
<div style="font-size: 80%; text-align: right">The image after applying the retinex filter, luma only.</div>
<p>
We can now see a lot of information that was previously barely visible due to the low contrast. One might
think
that preserving this information is a vain effort, but with HDR-monitors slowly making their way into the
mainstream and more possible improvements down the line, this extra information might be visible on consumer
grade screens at some point. And since it doesn't waste a noticeable amount of bitrate, I see no harm in
keeping it.</p>
Using this newly gained knowledge, some testing, and a little bit of magic, we can create a surprisingly
accurate edge mask.
<pre><code class="python">def retinex_edgemask(luma, sigma=1):
ret = core.retinex.MSRCP(luma, sigma=[50, 200, 350], upper_thr=0.005)
return core.std.Expr([kirsch(luma), ret.tcanny.TCanny(mode=1, sigma=sigma).std.Minimum(
coordinates=[1, 0, 1, 0, 0, 1, 0, 1])], 'x y +')</code></pre>
<p>Using this code, our generated edge mask looks as follows:</p>
<img src="/media/articles/res_edge/aldnoah_kage.png">
<p>
By using std.Binarize (or a similar lowpass/highpass function) and a few std.Maximum and/or std.Inflate
calls, we can transform this edgemask into a
more usable detail mask for our debanding function or any other function that requires a precise edge mask.
</p>
<a id="c_performance" href="#c_performance"><p class="subhead">Performance</p></a>
Most edge mask algorithms are simple convolutions, allowing them to run at over 100 fps even for HD content. A
complex algorithm like retinex can obviously not compete with that, as is evident by looking at the benchmarks.
While a simple edge mask with a Sobel kernel ran consistently above 200 fps, the function described above only
procudes 25 frames per second. Most of that speed is lost to retinex, which, if executed alone, yields about
36.6 fps. A similar, <span title="and I mean a LOT more inaccurate">albeit more inaccurate</span>, way to
improve the detection of dark, low-contrast edges would be applying a simple curve to the brightness of the
image.
<pre><code class="python">bright = core.std.Expr(src, 'x 65535 / sqrt 65535 *')</code></pre>
This should (in theory) improve the detection of dark edges in dark images or regions by adjusting their
brightness
as shown in this curve:
<img src="/media/articles/res_edge/sqrtx.svg"
title="yes, I actually used matplotlib to generate my own image for sqrt(x) rather than taking one of the millions available online">
<a id="c_end" href="#c_end"><h2 class="subhead">Conclusion</h2></a>
Edge masks have been a powerful tool for image analysis for decades now. They can be used to reduce an image to
its most essential components and thus significantly facilitate many image analysis processes. They can also be
used to great effect in video processing to minimize unwanted by-products and artifacts of more agressive
filtering. Using convolutions, one can create fast and accurate edge masks, which can be customized and adapted
to serve any specific purpose by changing the parameters of the kernel. The use of local contrast enhancement
to improve the detection accuracy of the algorithm was shown to be possible, albeit significantly slower.<br><br>
<pre><code class="python"># Quick overview of all scripts described in this article:
################################################################
# Use retinex to greatly improve the accuracy of the edge detection in dark scenes.
# draft=True is a lot faster, albeit less accurate
def retinex_edgemask(src: vs.VideoNode, sigma=1, draft=False) -> vs.VideoNode:
core = vs.get_core()
src = mvf.Depth(src, 16)
luma = mvf.GetPlane(src, 0)
if draft:
ret = core.std.Expr(luma, 'x 65535 / sqrt 65535 *')
else:
ret = core.retinex.MSRCP(luma, sigma=[50, 200, 350], upper_thr=0.005)
mask = core.std.Expr([kirsch(luma), ret.tcanny.TCanny(mode=1, sigma=sigma).std.Minimum(
coordinates=[1, 0, 1, 0, 0, 1, 0, 1])], 'x y +')
return mask
# Kirsch edge detection. This uses 8 directions, so it's slower but better than Sobel (4 directions).
# more information: https://ddl.kageru.moe/konOJ.pdf
def kirsch(src: vs.VideoNode) -> vs.VideoNode:
core = vs.get_core()
w = [5]*3 + [-3]*5
weights = [w[-i:] + w[:-i] for i in range(4)]
c = [core.std.Convolution(src, (w[:4]+[0]+w[4:]), saturate=False) for w in weights]
return core.std.Expr(c, 'x y max z max a max')
# should behave similar to std.Sobel() but faster since it has no additional high-/lowpass or gain.
# the internal filter is also a little brighter
def fast_sobel(src: vs.VideoNode) -> vs.VideoNode:
core = vs.get_core()
sx = src.std.Convolution([-1, -2, -1, 0, 0, 0, 1, 2, 1], saturate=False)
sy = src.std.Convolution([-1, 0, 1, -2, 0, 2, -1, 0, 1], saturate=False)
return core.std.Expr([sx, sy], 'x y max')
# a weird kind of edgemask that draws around the edges. probably needs more tweaking/testing
# maybe useful for edge cleaning?
def bloated_edgemask(src: vs.VideoNode) -> vs.VideoNode:
return src.std.Convolution(matrix=[1, 2, 4, 2, 1,
2, -3, -6, -3, 2,
4, -6, 0, -6, 4,
2, -3, -6, -3, 2,
1, 2, 4, 2, 1], saturate=False)</code></pre>
<div class="download_centered">
<span class="source">Some of the functions described here have been added to my script collection on Github<br></span><a href="https://gist.github.com/kageru/d71e44d9a83376d6b35a85122d427eb5">Download</a></div>
<br><br><br><br><br><br><span class="ninjatext">Mom, look! I found a way to burn billions of CPU cycles with my new placebo debanding script!</span>
</div>
</div>

@ -0,0 +1,62 @@
<body>
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<p class="heading">Do What Is Interesting, Not What Is Expedient</p>
<p>
If you’re just here for the encoding stuff, this probably isn’t for you. But if you want to read the thoughts of a Linux proselyte (and proselytizer), read on.
</br>I’m sure this also applies to other things, but your mileage may vary.
</p><p></p><p>
Also, I wrote this on a whim, so don’t expect it to be as polished as the usual content.
</p>
<div class="content">
<h2 id="#c_introduction">Introduction</h2>
Once upon a time (TL note: about 4 months ago), there was a CS student whose life was so easy and uncomplicated he just had to change something. Well that’s half-true at best, but the truth is boring. Anyway, I had been thinking about Linux for a while, and one of my lectures finally gave me a good excuse to install it on my laptop. At that point, my only experience with Linux was running a debian server with minimal effort to have a working website, a TeamSpeak server, and a few smaller things, so I knew almost nothing.</p><p>
I decided to “just do it™” and installed <a href="https://manjaro.org/">Manjaro Linux</a> on my Laptop. They offer different pre-configured versions, and I just went with XFCE, one of the two officially supported editions. Someone had told me about Manjaro about a year earlier, so I figured it would be a good choice for beginners (which turned out to be true). I created a bootable flash drive, booted, installed it, rebooted, and that was it; it just worked. No annoying bloatware to remove, automatic driver installation, and sane defaults (apart from VLC being the default video player, but no system is perfect, I guess). I changed a few basic settings here and there and switched to a dark theme—something that Windows still doesn’t support—and the system looked nice and was usable. So nice and usable that I slowly started disliking the Windows 10 on my home PC. (You can already tell where this is going.)</p><p>
I wanted full control over my system, the beauty of configurability, a properly usable terminal (although I will add that there are Linux distros which you can use without touching a terminal even once), the convenience of a package manager—and the smug feeling of being a Linux user. You’ll understand this once you’ve tried; trust me.</p><p>
And so the inevitable happened, and I also installed Manjaro on my home PC (the KDE version this time because “performance doesn’t matter if you have enough CPU cores”—a decision that I would later revisit). I still kept Windows on another hard drive as a fallback, and it remains there to this day, although I only use it about once a week, maybe even less, when I want to play a Windows-only game.
<h2 id="#wabbit">Exploring the Rabbit Hole</h2>
No one, not even Lewis Carroll, can fully understand what a rabbit hole is unless they experienced being a bored university student who just got into Linux. Sure, my mother could probably install Ubuntu (or have me install it for her) and be happy with that. But I was—and still am—a young man full of curiosity. Options are meant to be used, and systems are meant to be broken. The way I would describe it is “Windows is broken until you fix it. Linux works until you break it. Both of these will happen eventually.”</p><p>
So, not being content with the stable systems I had, I wanted to try something new after only a few weeks. The more I looked at the endless possibilities, the more I just wanted to wipe the system and start over; this time with something better. I formatted the laptop and installed Manjaro i3 (I apparently wasn’t ready to switch to another distro entirely yet). My first time in the live system consisted of about 10 minutes of helpless, random keystrokes, before I shut it down again because I couldn’t even open a window (which also meant I couldn’t google). This is why you try things like that in live systems, kids. Back at my main PC, I read the manual of i3wm. How was I supposed to know that <span style="font-family: Hack, monospace">$super + return</span> opens a terminal?</p><p>
Not the best first impression, but I was fascinated by the concept of tiling window managers, so I decided to try again—this time, armed with the documentation and a second PC to google. Sure enough, knowing how to open <span style="font-family: Hack, monospace">dmenu</span> makes the system a lot more usable. I could even start programs now. i3 also made me realize how slow and sluggish KDE was, so I started growing dissatisfied with my home setup once again. It was working fine, but opening a terminal via hotkeys took about 200ms compared to the blazing 50ms on my laptop. Truly first-world problems.</p><p>
It should come as no surprise that I would soon install i3 on my home PC as well, and I’ve been using that ever since. <a href="/media/articles/res_exp/neofetch_miuna.png">Obligatory neofetch</a>.</p><p>
I also had a home server for encoding-related tasks which was still running Windows. While it is theoretically possible to connect to a Windows remote desktop host with a Linux client (and also easy to set up), it just felt wrong, so that had to change. Using Manjaro again would have been the easiest way, but that would have been silly on a <a href="https://en.wikipedia.org/wiki/Headless_computer">headless system</a>, <span title="Btw I use Arch Linux :^)">so I decided to install <a href="https://www.archlinux.org/">Arch Linux</a> instead.</span> It obviously wasn’t as simple as Manjaro (where you literally—and I mean <em>literally</em>—have to click a few times and wait for it to install), but I still managed to do it on my first attempt. And boy, is SSH convenient when you’re used to the bloat of Windows RDC.</p><p></p><p>
I would later make a rule for myself to not install the same distro on two systems, which leads us to the next chapter of my journey.
<h2 id="#descent">The Slow Descent Into Madness</h2>
Installing an operating system is an interesting process. It might take some time, and it can also be frustrating, but you definitely learn a lot (things like partitioning and file systems, various GNU/Utils, and just basic shell usage). Be it something simple like enabling SSH access or a bigger project like setting up an entire graphical environment—you learn something new every time.</p><p>
As briefly mentioned above, I wanted to force myself to try new things, so I simply limited each distro to a single device. This meant that my laptop, which was still running Manjaro, had to be reinstalled. I just loved the convenience of the AUR, so I decided to go with another arch-based distro: <a href="https://antergos.com/">antergos</a>. The installer has a few options to install a desktop environment for you, but it didn’t have i3, so I had to do that manually.</p><p>
With that done, I remembered that I still had a Raspberry Pi that I hadn’t used in years. That obviously had to be changed, especially since it gave me the option to try yet another distro. (And I would find a use case for the Pi eventually, or I at least told myself that I would.)</p><p>
I had read <a href="https://blog.quad.moe/moving-to-void-linux/">this</a> not too long ago, so I decided to give Void Linux a shot. This would be my first distro without systemd (don’t worry if you don’t know what that is).</p><p></p><p>
I could go on, but I think you get the gist of it. I did things because they seemed interesting, and I definitely learned a lot in the process. After the Void Pi, I installed Devuan on a second Pi (remind you, I already had Debian on a normal server, so that was off-limits).</p><p>
The real fun began a few days ago when I decided to build a tablet with a RasPi. That idea is nothing new, and plenty of people have done it before, but I wanted to go a little further. A Raspberry Pi tablet running Gentoo Linux. The entire project is a meme and thus destined to fail, but I’m too stubborn to give up (yet). At the time of writing, the Pi has been compiling various packages for the last 20 hours, and it’s still going.</p><p>
As objectively stupid as this idea is (Gentoo on a Pi without cross-compilation, or maybe just Gentoo in general), it did, once again, teach me a few things about computers. About compilers and USE flags, about dependency management, about the nonexistent performance of the Pi 3… you get the idea. I still don’t know if this will end up as a working system, but either way, it will have been an interesting experience.</p><p>
And that’s really what this is all about. Doing things you enjoy, learning something new, and being entertained.</p>
<p>
Update: It’s alive… more or less.
</p>
<!--span class="code">//TODO: update this when/if the Pi is done and usable</span></p><p-->
<h2 id="conclusion">Conclusion</h2>
<p>
So this was the journey of a former Windows user into the lands of free software.</p><p></p><p>
Was it necessary? No.</p><p>
Was it enjoyable? Mostly.</p><p>
Was it difficult? Only as difficult as I wanted it to be.</p><p>
Does Linux break sometimes? Only if I break it.</p><p>
Do I break it sometimes? Most certainly.</p><p>
Would I do it again? Definitely.</p><p>
Would I go back? Hell, no.</p><p>
Do I miss something about Windows? Probably the way it handles multi-monitor setups with different DPIs. I haven’t found a satisfying solution for UI scaling per monitor on Linux yet.</p><p>
</p><p>
I’m not saying everyone should switch to Linux. There are valid use cases for Windows, but some of the old reasons are simply not valid anymore. Many people probably think that Linux is a system for nerds—that it’s complicated, that you need to spend hours typing commands into a shell, that nothing works (which is still true for some distros, but you only use these if you know what you’re doing and if you have a good reason for it).</p><p>
In reality, Linux isn’t just Linux the way Windows is Windows. Different Linux distros can be nothing alike. The only commonality is the kernel, which you don’t even see as a normal user. Linux can be whatever you want it to be; as easy or as difficult as you like; as configurable or out-of-the-box as you need.</p>
<p>
If you have an old laptop or an unused hard drive, why don’t you just try it? You might learn a few interesting things in the process. Sometimes, being interesting beats being expedient. Sometimes, curiosity beats our desire for familiarity.</p>
</br></br></br>
<span class="ninjatext">Btw, I ended up not attending the lecture that made me embark upon this journey in the first place. FeelsLifeMan</span>
</div>
</div>
</body>

@ -0,0 +1,281 @@
<a href="/blog">
{% load static %}
<div class="bottom_right_div"><img src="{% static '2hu.png' %}"></div>
</a>
<div id="overlay" aria-hidden="true" onclick="removefull()"></div>
<div class="wrapper_article">
<p class="heading">Grain and Noise</p>
<div class="content">
<ul>
<li><a href="#c_introduction">Introduction</a></li>
<li><a href="#noisetypes">Different Types of noise and grain</a></li>
<li><a href="#remove">Different denoisers</a></li>
<li><a href="#c_encoding">Encoding grain</a></li>
</ul>
</div>
<p class="subhead"><a href="#c_introduction" id="c_introduction">Introduction</a></p>
<div class="content">
There are many types of noise or artifacts and even more denoising algorithms. In the following article, the
terms noise
and grain will sometimes be used synonymously. Generally, noise is an unwanted artifact and grain was added to
create a certain effect (flashbacks, film grain, etc) or to prevent banding. Especially the latter may not
always
be beneficial to your encode, as it increases entropy, which in turn increases the bitrate without improving the
perceived
quality of the encode (apart from the gradients, but we'll get to that).<br>
Grain is not always bad and even necessary to remove or prevent banding artifacts from occuring, but studios
tend to
use dynamic grain which requires a lot of bitrate. Since you are most likely encoding in 10 bit, banding isn't
as
much of an issue, and static grain (e.g. f3kdb's grain) will do the job just as well.<br>
Some people might also like to denoise/degrain an anime because they prefer the cleaner look. Opinions may vary.
</div>
<p class="subhead"><a id="noisetypes" href="#noisetypes">Different types of noise and grain</a></p>
<div class="content">
This section will be dedicated to explaining different types of noise which you will encounter. This list is not
exhaustive, as studios tend to do weird and unpredictable things from time to time.<br>
<div>
<p class="subhead">1. Flashbacks</p>
<img class="img_expandable" src="/media/articles/res_dn\shinsekaiyori.jpg">
<div style="font-size: 80%; text-align: right">Image: Shinsekai Yori episode 12 BD</div>
Flashbacks are a common form of artistic film grain. The grain is used selectively to create a certain
atmosphere and should
not be removed. These scenes tend to require quite high bitrates, but the effect is intended, and even if
you
were
to try, removing the grain would be quite difficult and probably result in a very blurred image.<br>
Since this type of grain is much stronger than the underlying grain of many sources, it should not be
affected by
a normal denoiser, meaning you don't have to trim around these scenes if you're using a denoiser to remove
the general background noise from other scenes.
</div>
<div>
<p class="subhead">2. Constant film grain</p>
<img class="img_expandable" src="/media/articles/res_dn\no_cp.jpg">
<div style="font-size: 80%; text-align: right">Image: Corpse Party episode 1, encoded by Gebbi @Nanaone
</div>
Sometimes all scenes are overlaid with strong film grain similar to the previously explained flashbacks.
This type of source is rare, and the only way to remove it would be a brute force denoiser like QTGMC. It is
possible to
get rid of it, however, generally I would advise against it, as removing this type of grain tends to change
the mood of a given scene. Furthermore, using a denoiser of this calibre can easily destroy any detail
present, so you will have to carefully tweak the values.
</div>
<div>
<br>
<p class="subhead">3. Background grain</p>
This type is present in most modern anime, as it prevents banding around gradients and simulates detail by
adding random information to all surfaces. Some encoders like it. I don't. Luckily, this one can be removed
with relative ease, which will notably decrease the required bitrate. Different denoisers will be described
in a later paragraph.
</div>
<div>
<br>
<p class="subhead">4. TV grain</p>
<img class="img_expandable" src="/media/articles/res_dn\kekkai_crt.jpg"><br>
<div style="font-size: 80%; text-align: right">Image: Kekkai Sensen episode 1, encoded by me</div>
This type is mainly used to create a CRT-monitor or cameraesque look. It is usually accompanied by
scanlines and other distortion and should never be filtered. Once again, you can only throw more bits at the
scene.
</div>
<div>
<p class="subhead">5. Exceptional grain</p>
<img src="/media/articles/res/bladedance_grain_src.jpg" class="img_expandable">
<div style="font-size: 80%; text-align: right">Image: Seirei Tsukai no Blade Dance episode 3, BD</div>
Some time ago, a friend of mine had to encode the Blu-rays of Blade Dance and came across
this scene. It is about three minutes long, and the BD transport stream's bitrate peaks at more than
55mbit/s, making the Blu-ray non-compliant with the current Blu-ray standards (this means that some BD
players may just refuse to play the file. Good job, Media Factory). <br>
As you can see in the image above, the source has
insanely strong grain in all channels (luma and chroma). FFF chose to brute-force through this scene by
simply letting x264 pick whatever bitrate it deemed appropriate, in this case about 150mbit/s. Another (more
bitrate-efficient solution) would be to cut the scene directly from the source stream without re-encoding.
Note that you can only cut streams on keyframes and will not be able to filter (or scale) the scene
since you're not re-encoding it. An easier solution would be using the --zones parameter to increase the crf
during the scene in question. If a scene is this grainy, you can usually get away with higher crf values.
</div>
</div>
<p class="subhead"><a id="remove" href="#remove">Comparing denoisers</a></p>
<div class="content">
So let's say your source has a constant dynamic grain that is present in all scenes, and you want to save
bitrate in your encode or get rid of the grain because you prefer clean and flat surfaces. Either way, what
you're looking for is a denoiser. A list of denoisers for your preferred frameserver can be found <a
href="http://avisynth.nl/index.php/External_filters#Denoisers">here
(Avisynth)</a> or <a href="http://www.vapoursynth.com/doc/pluginlist.html#denoising">here (Vapoursynth)</a>.
To compare the different filters, I will use two scenes – one with common "background grain" and one with
stronger grain. Here are the unfiltered images:<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/mushishi_src22390.png"><br>
An image with "normal" grain – the type you would remove to save bitrate. Source: Mushishi Zoku Shou OVA
(Hihamukage) Frame 22390. Size: 821KB<br>Note the faint wood texture on the backpack. It's already quite blurry
in the source and can easily be destroyed by denoising or debanding improperly.<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/erased_src5322.png"><br>
A grainy scene. Source: Boku Dake ga Inai Machi, Episode 1, Frame 5322. Size: 727KB<br>I am well aware that
this is what I
classified as "flashback grain" earlier, but for the sake of comparison let's just assume that you want to
degrain this type of scene.<br>
Furthermore, you should note that most denoisers will create banding which the removed grain was masking
(they're technically not creating the banding but merely making it visible). Because of this, you will usually
have to deband after denoising.
<p class="subhead">1. Fourier Transform based (dfttest, FFT3D)</p><br>
Being one of the older filters, dfttest has been in development since 2007. It is a very potent denoiser
with good detail retention, but it will slow your encode down quite a bit, especially when using Avisynth due to
its lack of multithreading.
The Vapoursynth filter is faster and should yield the same results. <br>FFT3DGPU is
hardware accelerated and uses a similar (but not the same) algorithm. It is significantly faster but
less precise in terms of detail retention and possibly blurring areas. Contra-sharpening can be used to
prevent the latter. The filter is available for Avisynth and Vapoursynth without major differences.<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/mushishi_dft0.5.png"><br>
sigma = 0.5; 489KB
<img class="native720 img_expandable" src="/media/articles/res_dn/erased_dft4.png"><br>
sigma = 4; 323KB
<p class="subhead">2. Non-local means based (KNLMeans, TNLMeans)</p><br>
The non-local means family consists of solid denoisers which are particularly appealing due to their
highly optimized GPU/OpenCL implementations, which allow them to be run without any significant speed
penalty.
Using the GPU also circumvents Avisynth's limitation to one thread, similar to FFT3DGPU.<br>Because of
this, there is no reason to use the "regular" (CPU) version unless your encoding rig does not have a GPU. K
NL can remove a lot of noise while still retaining quite a
lot of detail (although less than dft or BM3D). It might be a good option for older anime, which tend
to have a lot of grain (often added as part of the Blu-ray "remastering" process) but not many fine
details. When a fast (hardware accelerated) and strong denoiser is needed, I'd generally recommend using
KNL rather than FFT3D.<br>One thing to highlight is the Spatio-Temporal mode of this filter. By
default, neither the Avisynth nor the Vapoursynth version uses temporal reference frames for denoising. This can
be
changed in order to improve the quality by setting the d parameter to any value higher than zero.
If your material is in 4:4:4 subsampling, consider using "cmode = true" to enable denoising of the
chroma planes. By default, only luma is processed and the chroma planes are copied to the denoised
clip.<br>Both of these settings will negatively affect the filter's speed, but unless you're using a
really old GPU or multiple GPU-based filters, your encoding speed should be capped by the CPU rather than
the GPU. <a href="https://github.com/Khanattila/KNLMeansCL/blob/master/DOC.md">Benchmarks and documentation
here.</a><br>
<img class="native720 img_expandable" src="/media/articles/res_dn/mushishi_knl0.2cmode1.png"><br>
h = 0.2, a = 2, d = 3, cmode = 1; 551KB<br>
<span class="fakelink"
onclick="fullimage('/media/articles/res_dn/mushishi_knl0.2cmode0.png')">cmode = 0 for comparison; 733KB</span><br>
<img class="native720 img_expandable" src="/media/articles/res_dn/erased_knl0.5.png"><br>
h = 0.5, a = 2, d = 3, cmode = 1; 376KB
<p class="subhead">BM3D</p><br>
This one is very interesting, very slow, and only available for Vapoursynth. Avisynth would probably die
trying to run it, so don't expect a port anytime soon unless memory usage is optimized significantly.
It would technically work on a GPU, as the algorithm can be parallelized without any issues <a
href="https://books.google.com/books?id=xqfNBQAAQBAJ&pg=PA380&lpg=PA380&dq=bm3d+GPU+parallel&source=bl&ots=MS9-Kzi-8u&sig=fMcblGOrD-wCUrZzijmAdQF2Tj8&hl=en&sa=X&ei=wljeVI-LKcqAywPVyILgDQ&ved=0CDQQ6AEwBA#v=onepage&q=bm3d%20GPU%20parallel&f=false">[src]</a>,
however no such implementation exists for Avisynth or Vapoursynth. (If the book doesn't load for you,
try scrolling up and down a few times and it should fix itself)<br>
BM3D appears to have the best ratio of filesize and blur (and consequently detail loss) at the cost of
being the slowest CPU-based denoiser on this list. It is worth noting that this filter can be combined
with any other denoiser by using the "ref" parameter. From the <a
href="https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D">documentation</a>:
<div class="code">Employ custom denoising filter as basic estimate, refined with V-BM3D final estimate.
May compensate the shortages of both denoising filters: SMDegrain is effective at spatial-temporal
smoothing but can lead to blending and detail loss, V-BM3D preserves details well but is not very
effective for large noise pattern (such as heavy grain).
</div>
<img class="native720 img_expandable" src="/media/articles/res_dn/mushishi_bm3d_1511.png"><br>
radius1 = 1, sigma = [1.5,1,1]; 439KB<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/erased_bm3d.png"><br>
radius1 = 1, sigma = [5,5,5]; 312KB<br>Note: This image does not use the aforementioned "ref" parameter
to improve grain removal, as this comparison aims to provide an overview over the different filters by
themselves, rather than the interactions and synergies between them.<br>
<p class="subhead">SMDegrain</p><br>
SMDegrain seems to be the go-to-solution for many encoders, as it does not generate much blur and the
effect seems to be weak enough to save some file size without notably altering the image.<br>The
substantially weaker denoising also causes less banding to appear, which is particularly appealing when
trying to preserve details without much consideration for bitrate.<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/mushishi_smdegrain_luma.png"><br>
Even without contra-sharpening, SMDegrain seems to slightly alter/thin some of the edges. 751KB<br>
<img class="native720 img_expandable" src="/media/articles/res_dn/erased_smdg.png"><br>
In this image the "sharpening" is more notable. 649KB<br>
One thing to note is that SMDegrain can have a detrimental effect on the image when processing with
chroma. The Avisynth wiki describes it as follows:
<div class="code">Caution: plane=1-4 [chroma] can sometimes create chroma smearing. In such case I
recommend denoising chroma planes in the spatial domain.