2016年11月30日水曜日

まいとま1

最近GOG.comでMIGHT AND MAGIC® 6-PACK LIMITED EDITIONを買いました。

いや、なんか動画関連のコードをいじるモチベーションが特に理由もなく急速に衰えたので、別のことをやろうかと思いまして。
そういえば以前にも似たような感じで数年ほどネットから消えてたことがあったような…まあ、しばらくしたらまた何かしらフィルタ書いたりする気になるのではないかと思います。

さて、Might and Magicなんですが、1はX1版、2はPC98版をやったことがあるんですが、もう一度やりたくなったのですね。

スタークラフトは既に倒産して久しいうえに洋ゲーの移植なせいかProject Eggでも復活は無理そうだしどうしたものかと思ったのですが、探してみたらDOS版なら今でも買えることが分かったので(しかも1~6のセットで10USドル…安い)、もう英語でもいいやとすぐに購入しました。

で、今回は1のお話。


Might and Magic Book One - Secret of The Inner Sanctum(以下MM1)は1986年のクリスマスセールの頃に発売された栄光のシリーズ第1作。

作者のJon Van Caneghem(ジョン・ヴァン・キャネガン)はUCLAの学生だった20歳のときにNew World Computing,inc.を立ち上げ、それから3年かけてほぼ一人でこのゲームを作り上げました。
なんでも「やっとできたぜさあ売るぞ」と思ったが引受先が見つからなかったので、Activision社が手を上げるまでの数か月はパブリッシャーも自分でやる羽目になり、NWCのオフィス(兼ヴァン・キャネガンの自宅のアパート)にかかってきた800本余りの電話(注文やユーザーサポート)の応対をしたとか。

最初はApple//版しかなかったこのゲームはその後徐々に売れ出してコモドール64やIBM-PC/AT互換機(MS-DOS)等にも移植されます。
はたまた、まだそんなに売れてない頃にいきなり日本から押しかけてきたスタークラフトの社長と契約したので、1987年末には日本製PC版(以下スタクラ版)も発売されるなんてことも起こりました。
こうしてMM1は世界中に広まり、ヴァン・キャネガンはゲームクリエイターとしての確固たる地位を築いたわけです……が……2016年現在にやってみると、ほんとつらいわこのゲーム。

つらい理由その1:グラフィック
モンスターの絵なんかはヴァン・キャネガンが自分で書いたにしては結構雰囲気出てるなぁとは思うんですが、色数も少なく、なにより画面が暗すぎる。
あと戦闘中はテキストしか表示されないので非常に寂しい。
自分はスタクラ版の見た目が結構好きだったので、余計にそう感じるのかもしれません。


つらい理由その2:経験値稼ぎ
DOS版MM1はスタクラ版に比べると、実はちょっと優しくなっています。

たとえばスタクラ版では最初はどのキャラも棍棒1本持ってるだけの無一文で、ある程度の装備を揃えるまでが非常にきつい(慣れれば実はそれほどでもないんだけど)のですが、DOS版はデフォルトキャラのCRAG THE HACKが200GOLD所持しています。

この違いが生まれた理由は、スタークラフトが移植に当たって参考にしたのは初期のApple//版であるのに対し、DOS版は発売後しばらくしてからヴァン・キャネガンが手を加えたバージョンを移植したものだからでしょう。

"M&M is also one of the most difficult games in which to get started, particularly if you have one of the earlier versions. In those, your party starts out with no money, no armor, and only clubs for weapons. This does not make for a very effective fighting force, especially when monsters show up in large groups. Characters die with appalling frequency under those circumstances.
Fortunately, by the time you read this, the newer version of the game should be out. In that one, the pre-created party that comes on disk will have some money with them, and you can mug 'em to equip your own characters a bit more decently. That, of course, is no guarantee they will survive, but at least their chances will have improved slightly."

(Computer Gaming World 1987年4月号より)

多分、発売後に押し寄せた800件の電話のうちの結構な割合が「ゲーム開始後すぐに全滅する」とかの苦情で、ヴァン・キャネガンもさすがに自分がやり過ぎたことに気づいた(もしくは電話をこれ以上受けるのが嫌になった)のではないかと思われます。

これ以外にもDOS版は若干優しくなっているところがいくつかあります。

例えばコーラック(ソーピガル地下)->アガール(エルキューン)->テルゴラン(ダスク)のスクロール運搬は、スタクラ版(&Apple//初期版)では1セットあたり500GOLD+経験値1人1500ptだったのが、DOS版では1500GOLD+経験値1人3500ptに増えています(ちなみにこれがDOS版におけるもっとも効率の良い稼ぎ)。
まあ、スタクラ版はスタクラ版でバグ(PIRATES MAPが一度にたくさんもらえるとか、木登りクエストはどれか1本登るだけでOKとか)がある分、Apple//初期版よりはラクなんですが。

でもやはりきついのですね。

ハードは80186のTandy2000とかではなくi7上のDOSBoxなので1セット2分程度しかかからないのですが、それでも何百回も繰り返すとなるとさすがに御免被りたい。
でも繰り返さないとなかなかゲームは進まない……だってこのゲーム、DOS版でもモンスターとエンカウントしちゃうと逃げるのはまず無理なんだもの……orz


そしてチートに走る
昔は自分もこの程度では音を上げたりせず嬉々としてやりこんだものですが、さすがに年なのか気力がさっぱりわきません(つーか昔の俺は色々頭がおかしかったのでは)。

MM1といえば有名なセーブデータを2つ用意する所持金&アイテム増殖チートもありますが、あれは昔やったしねぇ……。

で、どーしたもんかと思いつつ試しにセーブデータ(ROSTER.DTA)をバイナリエディタで弄ってみたところ、どうやらこのゲーム、データ改変チェック等は一切行っていないようなので(まあ、スタンドアローンのゲームでそんな手間かけても意味ないとも思いますが)、こんなツールを作りました。

mm1cheat

データ解析しながらあれこれやってたら結局一日潰れましたが、(主にコード書くのが)楽しかったので、これはこれで元は十分に取れた(10USドルの6分の1だから190円くらい?)と思います。

あ、まだ一度もやったことのない人はこんなの使ったらダメですよ。
ファミコン版はバランス調整も結構いい感じになってるらしいので、未経験の方はそちらをやることをお勧めいたします。

2016年9月1日木曜日

MPEG2DecPlus その3

MPEG2DecPlus更新しました。バージョンは0.1.1になります。

修正:
- RFFフラグアリのソースでupConv > 0のとき出力が壊れてたのを修正。
- 不要になったバッファの確保を削除。

JEEBサーバにある最新版バイナリへのリンクを右側に追加しましたので、ほしい方はそちらからDLしてください。
なお、今回のバグは0.0.0からすでにあったようなので、旧バイナリはすべて削除されました。

2016年8月31日水曜日

画像端処理

以前書いたCombMaskの画像端処理についての質問が来ていたので記事にして解説することにしました。

例えば縦方向のみで半径2の平均化フィルタを書くとします。
このとき各サンプルの配置は下図のようになりますが、
ここで問題になるのは画像の上端及び下端をどうするかです。
参照する値が五つ揃わなければ(sa+sb+sc+sd+se)/5は出来ないので、足りない分をどうにかして補う必要があります。

ここで考えられるのは以下のようなものとなります。

パターン1:画像端は処理しない。
面倒なことは避けるという極めて常識的な方法です。
上のような場合は画像の上下各2ラインはそのまま入力値をコピーし、3行目から下端-2行目までの計算を行います。
この方法は半径が小さい場合は特に悪くはないのですが、半径が大きくなるとフィルタがかからない部分も大きくなりますし、重ね掛けをすると未処理のままの画像端の値の影響が大きくなっていく欠点があります。

パターン2:足りないところは0として計算する。
「ないものはないんだから0でいいだろう」という身も蓋もない方法です。
たしかにないものはないのだから、ある意味正しいのかもしれませんが、これはパターン1以上に問題があります。
特に重ね掛けをすると画像端の劣化は激しく、RGBなら真っ黒、YUVなら緑色に染まっていくことになります。
なんでもOpenCVのガウシアンブラーには、まさにこれが起こるのがある(あった?)みたいですね。

パターン3:画像端の色でパディング
存在しないからと言って0として扱うのは問題があり過ぎなため、画像端も処理したい場合にはよくとられる方法です。
存在しないところは境界の色がそのまま続いていると考えるわけですね。
これならばとりあえず全体を処理することが可能ですし、真っ黒になったりもしません。
ただし、半径が大きくなればその分だけ画像端部が参照されることも多くなるため、そこら辺の考慮は必要になります。

パターン4:ミラーリング
パディングの欠点を補うため、存在しない部分は上下(または左右)が逆になった画像がくっついているものとして処理する方法です。
これであれば画像端も処理できますし、上端だけが何度も参照されるということもそこそこ避けることができます。
今回来た質問は
> const uint8_t* sc = srcp;
> const uint8_t* sb = sc + spitch;
> const uint8_t* sa = sb + spitch;
> const uint8_t* sd = sc + spitch;
> const uint8_t* se = sd + spitch;
この部分で、sbとsdは同じものになってしまうのではないでしょうか。
というものでしたが、これも一種のミラーリングを行っているのですね。
CombMaskはインタレ映像のコーミングを見つけるわけですが、画像上端では次のような状態になりますので、saとsbはそれぞれseとsdと同一の点を参照するようにしているのです。

と、ざっと画像端処理について書いてみました。
画像端処理の問題は、多くの空間軸処理において起こります。面倒ではありますがなにかしら対策しないわけにもいかないので、どれにするのかよく考えて行いましょう。

2016年8月30日火曜日

MPEG2DecPlus その2

MPEG2DecPlusを更新しました。

変更内容
・idct=5(IEEE1180 reference)を倍精度浮動小数点処理に戻した(より遅くなった)。
・idct=4として単精度浮動小数点処理のLLMアルゴリズムを実装(SSE2またはAVX2使用)

0.0.0においてはとりあえず64bit化を目標にしていたため、iDCT関連は3のみをintrinsicで書き換え、5は単精度浮動小数点処理として新規で書き直し、他は全部削除という少々寂しい感じでしたが、これで少しはましになったように感じます。

今回追加した単精度LLMは従来のidct=4(64bit floating point)の替わりになるものとして追加したつもりです(コード自体はDCTFilterで書いたものとほぼ同じ)。
品質は単精度のためほんのわずかに落ちますが、それでもほぼ最高品質と言っても問題はないですし、速度は向上しています。
そしてIEEE1180 referenceは単精度にしていたのをやめて倍精度に戻しましたので、出力も従来のものと完全に一致するようになりました。(ただし、一応SIMD化はしていますが、従来のIEEE1180 referenceよりも遅いです)。

ところでDGDecodeのidctはたくさんありますが、どれを使うべきなのかをちゃんとわかっている人って、現在だとどのくらいいるのでしょうか?
かつて、idct=5に丸め込みバグがあったころは「普通は6を除いた中で一番速いもの、とことん品質重視なら4」というのが答えでしたが、5のバグが直ってからは、よくわからないという人が増えてしまったのではないかと思ったのでついでに解説しておきたいと思います。

まず1、2、3の三つは使っているCPUの命令セット以外は同じなので、スピード以外はなにも変わりません。
7(simple MMX)は1、2、3よりも少しだけ質が良いですが、スピードを落としてまで選ぶほどのものかどうかは疑問があります。
6(skal)はスピードは3に並ぶほど速いですがIEEE1180 compliantではない(実用上はともかく、規格的にはダメな実装である)ため、3が使えるCPUでは選ぶ意義はまったくありません。
4と5は品質的にほぼ等価なので、もし品質をとことん重視したいならばより速い4を選ぶべきでしょう。
以上によりSSE2がまともになったmerom(core2duo)以降のCPUであれば、3か4のどちらか好きな方ということになります。

で、品質の違いを確認する方法なんですが、ググってみた限りではなぜか「これだ」と思えるようなわかりやすいものがみあたらなかったのでこれも解説します。
src = "xxxxxxxxx.d2v" #d2vであればなんでもよい
ref = MPEG2Source(src, idct=5) #とりあえず最高品質とされる5
target = MPEG2Source(src, idct=4) #5と比較したいもの
d0 = mt_lutxy(ref, target, "x y - abs 0 > 255 0 ?", chroma="process").SUbtitle("diff>0")
d1 = mt_lutxy(ref, target, "x y - abs 1 > 255 0 ?", chroma="process").SUbtitle("diff>1")
w = BlankClip(d0, width=4, color_yuv=$FFFFFF)
StackVertical(StackHorizontal(ref, w, target), StackHorizontal(d0, w, d1))
これでこんな感じの画像が得られるはずです。今回はffmpegでエンコードした640x480のmpeg2を使いました。
下半分の左側はidct=5と比較して出力が一致するサンプルは0、1でも違う場合は255に変換したもの、右側は出力が2以上違う場合のみ255で他は0に変換したものです。
自分的には出力の違いが1を超えない(右側は全て単色)で、かつ違いの出るピクセルの数が0.01%未満であれば最高品質と言っていいのではないかと思います。

出力1の違いを肉眼で判別できる人間はいませんし(もし出来るという人がいれば、それは実は人間ではないか、頭がおかしいか、ただの嘘つきのどれかです)、なにかしら軽くフィルタを一つかけるか非可逆圧縮でもしようものならば簡単に吹き飛んでしまいますので、違いなんてでようがないのです。

品質とコストの兼ね合いをどうするかは人類普遍のテーマです。
安易に遅い方が質が良いとか考えないよう注意してください。

2016年8月29日月曜日

MPEG2DecPlus

最近、MPEG2DecPlusというプロジェクトを始めました。

MPEG2DecPlus

事の起こりはavs2pipemodのgithubのissue trackerにmaki_rxrz氏からtypoの指摘が来ていたことです。
で、typo自体はすぐ直したのですが(バイナリはリリースしてないけど)、ついでにちょっと覗いてみたmaki氏のDGMPGDecレポジトリ(国内放送TS向け修正版)に興味が出たのですね。

「そういや64bit用のDGDecodeって、けっこう昔にJossyD氏がビルドしたやつしかないんだっけ」とか思いながら試しにビルドしてみたら、32bit用バイナリでも結構大変な目にあいました。
まずコードにいくつかNASM用のアセンブラが入っているため、VisualStudioだけではビルドできない。
さらにMASM用のアセンブラもVC6のころのMASM用に書かれているため、古いMASMを拾ってくる必要があるというものでした(数日後には最新のMASMに合わせたコードに書き換える修正が入りましたが)。

ものがMPEG2デコーダだけにソフトウェア特許絡みで面倒そうなためか、maki氏はバイナリは配布していません。
でも自ビルドするにはいささか敷居が高そうだし(もし5年前の自分だったら15分ほどでほっぽりだしてます)、こりゃ何とかしといたほうがよさそうだなぁとか思ったわけです。

で、やったことは
・アセンブラ(MASM、NASM、インラインASM)を削除(x87FPU用のASMなんて読みたくないし、MMX、MMX2、3DNow!とかもいい加減古すぎ)
・削除したASMの替わりにintrinsic(最低ラインはSSSE3以上を目安)で書き直し
・VFAPI用コードの削除(AviUtlやTMPGEnc2.5はこれなしでもavs読めるし、もういらんでしょ)
・YV16、YV24出力対応(YUY2やRGB24で出力するのはavs2.6以降ではデメリットしかない)
・MPEG2Source以外のフィルタの書き直し
といったところです。

さてここで一番問題になるのがMPEG2Sourceのポストプロセス処理(cpuとかcpu2とか)とBlindPPです。
なにが難しいのかといえば、これはTrbarry氏がメイン処理部分をすべてインラインアセンブラのみで書いていてCのコードを残していないため、なにをやっているのかさっぱりわからないからです。
DctFilterもそうでしたが、あの人のコードはいつも参考になりません。
とりあえず半日ほど格闘してみましたがどうにもCに書き直すだけでも大変そうで埒があかないので、諦めて全部捨てることにしました。

かくして64bitでビルドするための障害はすべて消え去ったので、ついに昨日、0.0.0としてファーストリリースについにこじつけることができました。

で、バイナリですが、JEEB氏が再配布してくれることになったため、右のほうのリンク先から入手できます。

現状、ソフトウェア特許を認めていないEU圏のフィンランド人がドイツだかフランスだかにあるサーバで配布してるので、特許料請求とかも関係なくって安心ですね。

まあ、その特許もあらかた切れてるらしいので日本やアメリカで配っても大丈夫なのかもしれないけど、本当に大丈夫なのか調べるのも面倒ですし…。

2016年8月18日木曜日

DCTFilter その4

DCTFilterを更新しました。ver.0.5.0です。

バイナリ

追加・変更等
・avisynth2.6用のコードのバグを修正。
・avisynth+の追加フォーマットに対応。

avs2.6のみで使われるコードにバグがあったのを修正するついでにavs+の10/12/14bitフォーマットにも対応させました。
もともと内部処理はこれを見越してのfloat演算なので、入力クリップのbit深度とRGBかYUVかを判定するコードを追加するだけでした。


さて、このフィルタはMT_NICE_FILTERとして使えるように書いたつもりです。
そしてユーザー側で特にMT modeを設定しない限りはMT_NICE_FILTERとして設定されるようにもしています。
ところがDeblock_QEDにこれを使ってマルチスレッドで実行すると映像が壊れることがあるという報告を一件受けました。
で、試しに手元のサンプル映像に掛けてみたのですが、特にそんな様子もありません。

そもそもMT_NICE_FILTERというのは、avisynth+がマルチスレッドで動かそうとする際に特に障害となるような要素を持っていないだけの、ただのシングルスレッドで動くフィルタのことです。
マルチスレッドで動かしているのはavisynth+のほうなんですから、バグるなら報告先が違うような気がするんですが…?

あと、不具合が出るソースのサンプルもスクリプトも実行環境の説明も何もなしにただバグると言われても正直困ります。
再現可能な情報の提供がなければ動きようがないのです。
逆に再現率が高く再現方法も手軽なものであれば、そのバグは大抵すぐに直ります

というわけで、バグを見つけられた方はなるべく再現方法も教えてください。お願いします。

追記:報告者によると、これのほかにavs+やらいろいろ更新したら再現しなくなったそうです…まあ、そんなところだろうとは思ってたけどね。

2016年8月17日水曜日

最近のAvisynth+を使う場合の色々について

前回の記事についてたコメントあたりから察するに、どうもAvisynth+の基本的な使い方を書いておいた方がいいように感じたので書いておくことにします。
なお、この記事の内容はあくまでも現時点(r2161)でのものですから、今後どのように変わるか(あるいは変わらないか)はわかりません。

SetLogParams
SetLogParamsはr2064で追加されたログ機能です。
avisynth+を実行中に発生した色々な情報を出力します。
SetLogParams(string file, int log_level)
fileはログの出力先の指定で、"stderr"だと標準エラー出力、"stdout"だと標準出力、 "D:\foo\bar\avslog.txt"のようにファイル名にすればファイルに出力されます。
log_levelは出力するログの範囲の指定で、LOG_DEBUGなら詳細なすべてのログ、LOG_INFOならば普通のログ、LOG_WARNINGならば警告及びエラーのみ、LOG_ERRORならばエラーのみを出力します。

この機能によりAvisynth+本体及びプラグインによって発生した疑わしい挙動や不具合が大変わかりやすくなり、バグレポートしやすくなりました。
今後コマンドラインのツールを使う場合はSetLogParams("stderr", LOG_INFO)、AviUtlやVirtualDubのようなGUIツールと併用する時はSetLogParams("出力ファイル名", LOG_INFO)をスクリプトの先頭になるべくつけるようにすることをお薦めします。

SetMemoryMax
こちらはavisynth2.5の頃からのお馴染みですが、どれくらいに設定しているでしょうか?
SetMemoryMax(int mem)
avisynth+は規定値として32bitの場合(より正確には物理メモリより仮想メモリのほうが少ない場合、つまり64bitOS上で32bit版を使う場合)は128MB、64bitの場合(というか物理メモリより仮想メモリのほうが多い場合)は1GBをavisynth本体が使うメモリ量の上限として物理メモリを確保しようとします。
よってこれ以上を使うことが予想される場合、もしくはもっと少なくてもよい場合を除けば、これはスクリプトに書く必要はありません。

また重要なのは、このメモリ上限はあくまでもavisynth+本体が使用するメモリ量であり、プラグインフィルタが使うメモリ量ではないということです。
もしあるプラグインが中間バッファの確保にmallocやstd::vectorを使っても、その分はカウントされません。
Avisynth+には新たにバッファプール機能が追加されたため、この機能を使えばプラグインのメモリ使用量もavisynth+本体で管理することが可能になりましたが、実際に使っているプラグイン作者は現時点では私くらいしかいません(特に問題は見当たらないのですが、一応実験的な機能ってことになってますし)。
よって下手に大きな上限値を設定すると、プラグインのほうでメモリ不足を起こす可能性が出てきます。

で、あるスクリプトの実行時にどれくらいメモリを必要とするかは、使用するフィルタ及びソースの解像度等に依存するわけですが、もし設定された上限値では足りない、若しくはもう少しあったほうがより円滑に処理出来る場合は、先ほどのSetLogParamsによるログで教えてくれるようになりました。

というわけで、決め打ちに走るのもまあ悪くはないかもしれませんが、適切な量を調べてみるのもいいのではないかと思います。
とくにAviUtlと併用する場合はアプリケーション側も32bitなのにメモリ食いまくりますんで、絞れるところは絞るべきでしょう。

SetFilterMTMode
Avisynth+の目玉機能の一つはフレームレベルでのマルチスレッドですが、皆さん使っていますでしょうか?
私は(ここ数年はエンコはせずにコード書くだけの人なので)あまり使ってません。

とりあえずこのフィルタはMT_NICE_FILTERだ、これはMT_MALTI_INSTANCEじゃないとダメだ、とあれこれやるわけですが、ここで重要なのは"DEFAULT_MT_MODE"をどれに設定するかです。

現時点ではDEFAULT_MT_MODEは3(MT_SERIALIZED)以外ありえません。

"DEFAULT_MT_MODE"は、MT modeが設定されていないすべてのフィルタに影響するため、下手に2(MT_MALTI_INSTANCE)などにしようものなら安全な実行はまったく保証されなくなります。

そもそもいままで書かれてきたavisynthプラグインのほとんどはシングルスレッドでの実行を前提としているものが多く、また処理内容的にもマルチスレッド化はスライスレベルでしか実現不可能なものもあります。
このページには"mode 3 is evil."なんて書かれていますが、時間軸IIRフィルタを邪悪呼ばわりするとか、正直頭がおかしいんじゃないかと思います。単にちょっと前までのavisynth+MTにとって都合が悪かったものをそうやってくさしてるだけです。

本当に邪悪なのはmode 2です。
mode 2で動かせるフィルタは、すべてmode 1で動かせるように改変可能なフィルタです。
でも実際にこれまで書かれてきたフィルタを全部改造していくのはちょっと無理なんで、フィルタクラスを複数インスタンス化しそれらを同時に動かすという力技でごまかしているのがmode 2の実態です。
ちなみにmode 2というのは、avisynthMTのSetMTMode(2)と基本的に同じものです。Avisynth+ではキャッシュ機能も改良されたのでメモリ使用量も減って、かなり安定度も上がりました(が、それでも安全とは言いきれない)。

前回記事のコメントで私がなかなかScriptClipの不具合に気づかなかったのは、SetFilterMTMode("DEFAULT_MT_MODE", MT_SERIALIZED)と書かれたavsをオートローディングフォルダに突っ込んでるのを忘れてたからなんですね。


と、だらだらと書いてみました。
もし参考になった方がいましたら幸いです。

2016年8月14日日曜日

avs2pipemod その22

更新しました。

ver.1.1.0になります。
追記:バグ1件見つけたので1.1.1を上げました。

バイナリ

変更/追加
- avisynth+の追加色空間のサポート
- y4mbitsオプションの削除
- filtersオプションの追加

昨晩、avisynth+にあらたに10/12/14bitおよびPlanarRGBが追加されました。
これを見てもらえば、だいたいどんな感じになったのかなんとなくわかってもらえるのではないかと思います。

いや、もう、なんというか、やりすぎじゃありませんかね?
14bitとかYUV420でアルファに対応とか、基地外沙汰というかFFmpeg脳というか、まじで勘弁してほしいんですが。

2016年8月10日水曜日

alloca vs vector

avisynthプラグインの書き方は人それぞれですが、一つの指標として次のようなものがあります。

AviSynth Plugin Writing Tips

これはAviSynth Wiki内に置かれたAvisynth+特設ページ内の記事ですが、特に書き慣れていない人、これからなにかしら書いてみたい人は一度は目を通しておくことをお勧めします。

軽く要約すると、
  • 例外は基本的に使わない。
  • 使う場合は発生時の処理もプラグインdll内で完結させる。上流(avisynth本体)に投げる場合はIScriptEnvironment::ThrowError()を必ず使うこと。

  • 自分でマルチスレッド処理を実装しないこと。
  • 特に最近のAvisynth+は単にGetFrame()を同時に複数実行するレベルではなく、全体的なバランスをとりつつキャッシュコントロールするレベルまで進歩しています(AvisynthMTとThreadRequestを両方実装したような感じかな)。 追加フィルタレベルでマルチスレッド化なんてやってもオーバーヘッドが増えるかデッドロック起こすだけで無駄に終わりますし、特にOpenMPの使用は(Avisynth+では)最悪手です。 シングルスレッドだとC++のみでは性能が出ない場合はSIMDを使うかGPUプログラミングに行くしかありません。

  • 使用するヘッダ(avisynth.h)はAvisynth+の最新のものを使うこと。
  • Avisynth+のヘッダはAvisynth本家の最新ヘッダと完全互換を謳っていますし、実際にそうなっています。特に64bit化を考えるならAvisynth+のものを使うしかありません。

で、それに続いてWriting better AviSynth pluginsという記事があります。


  • YUY2用のコードは書かない
  • メイン処理用関数はクラスメンバーにしない
  • memcpyの再実装はしない
  • Y,U,V各プレーンで同じ処理をする場合にコピペはしない

こちらも概ね正しいですが、一番最後のDon’t be afraid of allocaだけは参考にしない方がよいようです。

最近のコンパイラ(確認した限りではVS2013以降)であればstd::vectorを使うべきあり、もはやallocaを使う意義はまったくありません。

実際に私はallocaをstd::vectorに置き換えてみましたが、スピードはほぼ等速か、むしろstd::vectorのほうがほんの少し速かったです。

わざわざ初期化したり最後に個々の要素を開放したりは手間になるだけです。


ちなみにstd::vectorではなくnew/deleteで配列の確保/解放をしてみると、これも変わらないか若干vectorのほうが速い感じになりました。
どうやらC++においては動的配列の場合、_aligned_malloc/_mm_malloc以外はstd::vectorにしておくのが間違いがないようですね。



2016年8月4日木曜日

DCTFilter その3

また更新しました。
もうリンクはバイナリだけでいいかな。
右のほうにgithubへのリンク付けておいたので、ソースコードに興味のある物好きな方は適当に漁ってください。

バイナリ

0.4.0です。
4x4DCTも出来るようにしました。
あとplaneのWidth/Heightが8の倍数でない時でも、4の倍数であれば8x8DCTができるようにしました。
単に余った右端及び下端は4x4DCTで済ませてるだけです。

いや、1920x1080のYUV420でベンチマーク取ろうとしたところで出来ないことを思い出したので、つい勢いでやってしまいました。

これだと都合が悪い場合は、これまでのようにpadding/cropでもして下さい。

2016年8月2日火曜日

DCTFilter その2

DCTFilterを更新しました。
いろいろ細々と最適化とかやってたら0.3.0になりました。

DCTFilter

バイナリ

- 最適化を進めた結果、単精度浮動小数点処理でもSSE4.1(int32->uint16の高速変換に必要)が使えれば、オリジナルのDctFilter(MMX/ISSE/SSE2?で整数処理)とほぼ同等のスピードで動くようになりました。AVX2/FMA3が使えるCPUならこっちのほうが速いです。
- DCTFilterD()を追加しました。
- avs2.6もサポートするようになりました。

書いてみてから改めて調べてみると、このフィルタは結構面白い使い方が出来るようですね。
なんだかお気に入りになりそうです。

2016年7月31日日曜日

DCTFilter

数か月前にmukenさんから「DctFilterの64bit版書いてよ」と頼まれました。
DCT/iDCTはよくわからんのでその時は「後でね」とごまかしてたんですが、数日前に今度はよく知らない外国の人からメールで「64bit版書いてよ」とリクエストが来てしまいました。

ちょうどAvisynth+のレポに送ったPRが3つも貯まったまま放置されてる状態なので(ultimははよコミットなりリジェクトなりしろよ)、気分を変えようと書いてみることにしました。

とりあえずアルゴリズムは簡単に実装できそうでサンプルも豊富なLLMで、いろんなbit深度にいちいち対応するのもめんどいので整数近似はやらずに浮動小数点で処理することに決定。
(オリジナルのコードはxvidから持ってきたわけのわからんASM使ってるのでパス)
で、実際に書き始めたら二日ほどでなんとか出来上がりました。

ソースコード

バイナリ

なお、これはAvisynth+MT専用です。
Avisynth2.6では動きません。

それにしても毎度のことながら、一番時間かかるのがREADME書くことなのはなんとかならないものかしら…。

2016年7月24日日曜日

avs2pipemod その21

また更新です。
バージョン1.0.3になります。

ソースコード

バイナリ

- メモリーリーク?の修正
- バージョンリソースを追加

つい先日のAvisynth+の更新でログ出力機能が追加されました。
スクリプトにSetLogParams("stderr", LOG_INFO)と書いておけば、標準エラー出力にログが出るようになります。
"stderr"を"stdout"にすれば標準出力に、"ファイル名"とすればファイルに保存されます。
で、さっそく使ってみたところ"WARNING: A plugin or the host application might be causing memory leaks."とか表示されやがるんですな。
どうやらプラグインと違ってアプリケーションでは、PClipのインスタンスをクラスメンバーにしている場合は自分でデストラクタを呼ばないとこうなるらしい。
どのみちすぐexitしてしまうので実害はないとは思いますが、気持ちいいものでもないので修正しました。

あと、今回からバージョンリソースを追加したので、Explorerでバージョンが分かるようになりました。
VS2015だとタダでリソースエディタが使えるのを思い出したので付けてみたわけですね。
プラグインとかはどーするかな…。

2016年7月22日金曜日

avs2pipemod その20

1件バグがみつかったので更新しました。
バージョンは1.0.2になります。

ソースコード

バイナリ

- infoで表示されるv:durationの計算が間違ってたのを修正。
- infoでavisynthのVersionNumberの他にVersionStringも表示されるようにした。

本来durationの計算は num_frames * fps_denominator / fps_numerator ですが、これを num_frames * fps_numerator / fps_denominator に間違えてました。ごめん。

もう一つの変更点について
その18で書き忘れてたのですが、1.0.0からavs2pipemodには'-dll'というオプションが追加されました。
これは複数のavisynth.dllを使い分けるために用意したものです。
たとえば
avs2pipemod -info -dll="C:/hoge/fuga/avisynth.dll" script.avs
とやれば、avs2pipemodは'C:\hoge\fuga\'にあるavisynth.dllを使います。
あと、うちのC:\Windows\SysWOW64には'AviSynth.dll'の他に'AviSynth.dll_official260.esc'とか'AviSynth.dll_official261a.esc'なんてファイルも存在したりするのですが、これも
avs2pipemod -benchmark -dll="avisynth.dll_oficial261a.esc" test.avs
のようにすれば、そちらのDLLを使うようになります。
-dllを未指定の場合は今まで通り、Pathの通ったところにあるAviSynth.dllを使います。
で、VersionNumberだけだとどれも2.600としか表示されないのでもうちょっとわかりやすくする必要があったのですね。
まあ、そういうことです。

2016年7月18日月曜日

avs2pipemod その19

avs2pipemodにバグがあったので更新しました。

ソースコード

バイナリ

変更点
- rawvideo及びy4m出力の際のチェックが間違っていたのを修正

staxripの中の人からバグレポ貰ったので直しました。
なんつーかたいへん間抜けかつありがちなバグでした。
1.0.0を使ってる人は更新してください。

2016年7月10日日曜日

avs2pipemod その18

avs2pipemodを更新しました。

ソースコード

バイナリ

変更点
- avisynth_c.hを使うのをやめてavisynth.hを使うようにした。
- C99をやめてC++で書きなおした。
- VisualStudio2015以降(または新しめのインテルのコンパイラ)でしかビルドできなくなった。
- avisynth2.5xのサポート終了。
- WindowsXPのサポート終了。
- x264bdとx264rawが使えなくなった。
- Avisynth+の16bit/32bitフォーマットに対応した。
- Y4M出力でffmpegの拡張を使えるようにした。

上記のようになんかガラッと変わったように見えますが、Avisynth+MTの最新版を使っている人以外にはどうでもいい変更ばっかでもあります。
まあ、すべてはavisynth_c.hが悪いんです。

というわけで、Avisynth+MTの新しいの使ってるよって人は更新してみたらいかがでしょうか。

もしx264bdとx264rawもまだいるよって人はパッチ書いて送ってね!

2016年7月6日水曜日

いろいろ更新

Avisynth+MTの開発が数日前に再開され、現在r2003がテスト用として公開されています。

で、いろいろ変更が入ったんですが、本家2.6.1の更新を取り込んだ分でAvisynth+のほうだけAPIブレイクが起こり、自分が公開しているプラグインのうちの大部分が影響をもろに受けてしまうことがわかりました。
なんとロードするだけでavisynth全体がまともに動かなくなってしまうという…。

というわけで以下のプラグインを更新しました。

RawSource26
PlanarTools
yadifmod2
TMM2
CombMask
TCannyMod
VapourSource

それぞれリリースページに飛びますので、Avisynth+MTを使ってる人は最新のやつに更新してください。
なお、本家Avisynth2.6しか使ってない人は別に更新する必要はありません。

それにしてもとうとうAvisynth+も8ビットの枠から飛び出してしまいました。
プラグイン書く人としては10bitだの16bitだのfloatだのはどうやって出力を検証したもんだかで色々頭が痛いのですががが…。

2016年5月27日金曜日

CombMask

Avisynth2.6/Avisynth+用のプラグイン、CombMask.dllの配布を開始しました。

ソースコード

バイナリ

このフィルタ、元々は3年くらい前に書いてそのままバイナリ配布とかせずにほっぽってたものなのですが、誰か別の人がビルドして配布していたようです。

書いたきっかけはたしかvapoursynthのVIVTCがオリジナルのTIVTCと違ってedeintオプションを使えないし、かといってTDeintとの連携もできない残念な仕様だったので、少しはましになるかと思ったんでしたかね。

なんでavisynth版も書いたかといえば、avisynthプラグインのほうがテストしやすく、サポートしているフォーマットも少なくて書きやすいからです。
まずavisynthプラグインを書いてみて、処理の全体的な流れや具体的な内容等のロジックを決めてある程度テストしてからでないと、なかなかvapoursynthプラグインは書けません。

で、結局作りこまずにそのまま…というのが真相なんですが、このたび誰かさんが配布してたバイナリが消えちゃったので自分のGithubレポのほうでバイナリ配布してくれないかとお願いメールが来まして、正式に配布することにしたついでにコードの見直しや機能追加もしました。

具体的には
・Avisynth+MT対応で、SetFilterMTModeが使える環境では自動的にMT_NICE_FILTERとして登録されます。
・AVX2も使えるようにしました(メモリアライメント等の都合上、Avisynth+MTのみ)。
・縞検出にTIVTC等のmetric1も使えるようにしました。
・コードがちょっと綺麗になりました(と、自分では思っている)。
といった感じです。

縞検出のmetric1は、そもそもTIVTC以前のdecombパッケージやMaskTools1のCombMask()で使われているものなんですが、はっきりいってインタレ縞検出用だと誤爆しまくって使えないんじゃないかと思います。
しかし、世の中には単に縦方向のみのノイズ検出フィルタとしていまだにMaskTools1を使っている人がいるようなので追加しました。
MaskTools1のはSIMD化されてないのでクソ遅いですし。

MaskTools版の
CombMask(clip, thY1=x, thY2=x, Y=3, U=1, V=1)
と、こっちの
CombMask(clip, cthresh=x, mthresh=0, chroma=false)
で、同じになります。
UやVだけに使いたい場合はUToY8/VToY8を併用してください。

それにしてもAVX2、なかなか速くなりませんね(HaswellではSSE2比120%弱くらいかな)。
SSE2がPen4で登場してCore2でまともになるまでに6年くらいかかったので、5年後くらいには180%くらいにはなってるんじゃないかとは思うんですが…。

2016年5月21日土曜日

TMM2

TMMをavisynth2.6/avisynth+用に書き直しました。

ソースコード

バイナリ

※ TMM2_avx2.dllはAVX2が使えるバージョンです。Nehalemとかだと多分動きません。
※ aviisynth+ではMT_NICE_FILTERとして使えます。なお、FilterModeはスクリプト初期化時に自動的に登録されますのでSetFilterMTMode()は必要ありません。

書き始めたのは結構前なのですが、8割くらいできたところで飽きてほっぽってたら某所に催促らしきものが書かれてたのでとりあえず動くようにしてみました。

元はC++(というかほぼC)のみのコードだったので、SSE2/AVX2を追加してみたら4倍弱速くなりました。
ほんとはもうちょっと早くなると思ってたんですが、フィールド別に作られた2つのモーションマスクを一つに組み立てる最終工程で大幅にスピードダウンしてしまうので、それほどでもなかったようです。
lengthを10固定にすれば多少は改善の余地はある?

あと、最後の工程で出力がオリジナルのTMMとちょっと変わってしまっているようです。
どこで変わってるのか何度もコード見直してみたんですがよくわからないし、そもそもそれでどれくらい影響が出るのかもさっぱりわかりません(エンコとかもう数年やってないし…)。

とりあえず公開しておきますので、もし不具合見つけたら教えてください。
つーか、だれかどこで違いが出てしまうのか教えてお願い…。

2016年5月11日水曜日

vsavsreader その7

ひさびさにvsavsreaderを更新しました。

Gthubページ

バイナリ

VapourSynthもプラグインがそこそこ増えたりavisynth2.6用プラグインも使えるようになったりと色々変化してるので、今では誰が使ってるのかよくわかりませんが、今回から64bit版のdllも配布することにしました。

avisynth_c.hもavisynth+の最新のやつにしようと思ったんですが、ちょっと一部が壊れてるんですな、これが。

で、替わりにavisynth.hを使うことにしたので、C++で書き直しました。

2016年5月7日土曜日

VapourSource その2

VapourSourceを更新しました。

githubページ

バイナリ

前回が2013年7月31日ですから、約3年ぶりの更新のようです。
VapourSynthもいつの間にやらr32になってますな。
使い方自体は全く変わってませんが、VS2010からVS2015になったのでコードもちょっと改善しました。
といってもスピードは変わらないと思いますが…。

あと、今回は64bitのバイナリも入ってます。

2016年5月5日木曜日

ReduceFlicker

ReduceFlickerをavisynth2.6/avisynth+用に書き直してみました。

Githubページ
バイナリ

これまでにフリッカリングが起こってるようなソースを扱ったことがないので興味もなかったのですが、muken氏がわざわざVapourSynthに移植したりしているのを見るに、需要が全くないわけではないらしい。
ひょっとすると時間軸NRフィルタとして使っている人でもいるのかしら?とも思ったわけですが、動きが大きいとけっこうアーティファクトも発生しますしなんとも微妙なフィルタです。

とりあえずオリジナルのReduceFlicker.dllに入ってる3つのフィルタ(ReduceFlicker,ReduceFluctuation,LockClense)のうち、AvsRecursionが必要ないReduceFlickerだけを実装しています。

それにしてもKassandroプラグインのわけわからなさったらほんと凄まじいわ…

2016年4月9日土曜日

Avisynth+のビルド

最近ローカルのAvisynth+のレポをいったん削除してクローンし直したので、ついでにビルド方法を書いておく。
というかCMakeほとんど使わないので書いておかないと(また)忘れそう。

用意するもの
Git for Windows(最新版は2.8.1)
CMake(最新版は3.5.1)
VisualStudio Community 2015
・githubのアカウント(パッチ書いてPull Request送りたい人だけ)

インストール
1. Git for Windowsをインストール
インストールの際は必ずPATHを通しておくこと(Use Git from Windows Command Promptを選択すればよい)。
ここでUse Git from Git Bash onlyを選んでるといろいろCMakeのほうでメンドクサイことになる。
Configure the line ending conversionは必ずCheckout as is, commit as isにしましょう。他を選ぶやつは何考えてるのか理解できんわ。
ちなみに筆者は普段はMSYS2のGitを使っているのですが、それでも別にGit for Windowsをこのためだけに入れております。
Git for WindowsがないとAvisynth+のビルドに必要なファイルが生成できないのです。

2. CMakeのインストール
特に注意するところはなかったはず。
デフォルトの選択肢のままでよかったと思うが、一応ちゃんと選択肢等の説明は読むこと。
こちらはPATHを通したりする必要は特にありません。

3. VisualStudio Communityのインストール
VC++が使えればあとはお好きなようにしてください。
ただし調子こいてあれこれ入れるとサイズは40GB超えて、かかる時間もとんでもなく増えるそうですよ。

ローカルにGitレポをcloneする
最近はAvisynth+の公式のほうは動きがぱったり止まってるので、pinterf氏のほうをcloneしたほうがよさそうです。
あなたがなにかしらパッチ書きたいならhttps://github.com/pinterf/AviSynthPlusを自分のアカウントでforkしてからsshでcloneしましょう。
sshの鍵作成とかgithubへの登録とかは自分でググって調べてね。
自ビルドだけなら、適当なフォルダに移動して右クリック -> Git Bash Here->
'git clone https://github.com/pinterf/AviSynthPlus.git' でいいです。

cloneが終わったらpinterf氏の変更を取り込みます。
以下の順にコマンドを打ちましょう
'cd AviSynthPlus'
'git remote add pinterf https://github.com/pinterf/AviSynthPlus.git'
'git fetch pinterf'
'git merge --no-ff pinterf/MT-pfmod'
これで手元の'master'ブランチに全変更がmergeされました。

ソリューション/プロジェクトの作成
ソースコードの準備が出来たら次にVisualStudio用のソリューション/プロジェクトファイルの作成です。
まずスタートメニューからcmake-guiを起動します。
次に'Where is the source code:'にcloneしたレポジトリを指定し、'Where to build the binaries:'に好きな場所を指定します。
自分はこんな感じです。
'Where to build the binaries:'で指定するフォルダは存在しなければcmakeが自分で作ります(作るか別の場所にするかあとで聞いてくる)。

上記2つの指定をしてから左下のほうのConfigureボタンを押すとこんなウィンドウが出てくるので
'Specify the generator for this project' のところを 'VisualStudio 14 2015' にします。
あとはそのままで'Finish'を押しましょう。
すると
こんな画面になるので
CMAKE_GENERATOR_TOOLSETのところをクリックしてv140_xpをv140に変更します(XPはさっさと滅ぶべきです)。
そしてもう一度Configureボタンを押して赤いのが白くなったらGenerateボタンを押せば終わりです。

VisualStudioでビルド
さきほど'Where to build the binaries:'で指定したフォルダを開くと中に色々ファイルやフォルダが出来ています。
その中のavs_coreフォルダを開き、中にあるAvsCore.slnをダブルクリックすればVisualStudioが起動します。
あとはソリューションエクスプローラーでAvsCoreのビルドを実行すればいいです。
なにかしらビルド中にやたら警告が出るとは思いますが、エラーになっていなければ大丈夫です。
64bit用もほしければ構成マネージャでプラットフォームを追加してください。

もし64bit用バイナリをビルドしようとしてLNK1112のエラーが出て失敗した場合は
ソリューションエクスプローラー -> AvsCore -> 'プロパティ' -> 'プラットフォーム(p):'がx64になっているのを確認 -> リンカー -> コマンドラインと進みます。
で'追加のオプション'のところに/machine:x86とあったら、それを消して適用 -> もう一度ビルドすればいいです。

大雑把ですが、はまりどころはそれほどはないはず…CMakeめんどくさいんだよバカ…

2016年4月5日火曜日

TCannyMod その3

また更新です。

TCannyMod

TCannyMod-1.1.1.zip

・32bitもAVX2を使えるようにした。

エッジ検出/エッジ勾配計算の処理を書き換えてみたところ、32bitでも問題なくAVX2を使えるようになりました。
なんで同じコードが64bitならよくて32bitだとダメなのかはいまだによくわかっておりませんが。

計算そのものは全く同じなので出力は以前と変わりません。


2016年3月28日月曜日

TCannyMod その2

TCannyModをまた更新しました。


TCannyMod

TCannyMod-1.1.0.zip

mode0(つまり普通のcannyフィルタ)が30%~50%速くなりました。
あとガウシアンブラーとかも少し速くなってます。
ついでにmode1のaliasとしてEMask()を作りました。

ちなみに手元のベンチマークでは、64bitだと1080pのグレイスケール(Y8)なcrowd_runをmode0のデフォルト設定で27.5fpsで処理できます。
1.0.0からはAvisynth+のMT_NICE_FILTER対応にした(つもり)ので、32bitでも4スレッドもあればリアルタイムでプレビューできますな。
50fpsでうごくcrowd runの線画ってなんか奇妙に思えます。

2016年3月26日土曜日

TCannyMod

3年くらい前に書いたTCannyModを久々に更新しました。

TCannyMod

TCannyMod-1.0.0.zip

自分が昔書いたコードをいま読み直してみると、色々と訳が分からなくなることが多々あります。

あれー? なんで俺、こんな変なことやってんのかしら?

そういうのを見つけるたびに書き直したくなって、いざ手を付けてみるとすぐに飽きてやめることがほとんどなのですが、今回はリリースまでもったようです。

具体的な変更内容としては
1. 64bitのみAVX2/FMA3対応
 32bitで/arch:AVXを付けてビルドするとなぜか真っ黒になってしまうので64bitだけです。
 intrinsicってこういうときはダメだな。理由がさっぱりわからん。
2.SSE4.1も対応
 こちらは32/64両方ですが、少なくともHaswellではSSE2とスピードは全く変わりません。
 まあ、ほとんど使ってないしね。
3.VS2015に開発環境を変更
 C++11の機能くらいは普通に使いたいです。
 といってもこれもほとんど使ってないですが。

くらいでしょうか。

Cannyではなく普通のエッジ検出(mode=1)やガウシアンブラー(mode=4 or GBlur())として使う分には、少し高速化してます。
Cannyとしてはhysteresis処理がどうやっても速くならないので、従来と変わりません。

2016年3月20日日曜日

yadifmod2 その3

またまたFizick氏からレポートいただきまして、0.0.3に更新しました。

もともとのyadifのコードの処理が1行抜けてたようです…すまぬorz

yadifmod2
ダウンロード

edeintなしの時の出力がyadifと(ほぼ)同じになりました。
画像端のみ違うことがありますが、もともとyadif自体が画像端の処理はほぼ無視の状態なので、これは仕様ということにします。

ちなみにyadifmodとして使う分には0.0.0から何も変わっていません。

2016年3月19日土曜日

Clense 応用

前々回にClenseの解説をしました。

Clenseは時間軸ノイズ除去フィルタとしてはなかなか扱いが難しいですが、しかし3枚のフレームによるメディアンフィルタというのは、ちょっと工夫すると面白い使い方ができます。

例えば本来は同一の映像なのに、ノイズの出方が異なる3つのクリップV0,V1,V2があるとします。

で、この3つのクリップのメディアンを求めてやると
V0 = something
V1 = something
V2 = something
median = median3(V0, V1, V2)

StackVertical(StackHorizontal(v0, v1), StackHorizontal(v2, median))

function median3(clip c, clip p, clip n, bool "gray")
{
    gray = default(gray, false)
    c = c.DuplicateFrame(0)     #Clenseはpreviousとnextを前後に1フレームずつずらしてしまうので
    n = n.DuplicateFrame(0, 0)  #DuplicateFrameで位置合わせをする
    return Clense(c, p, n, gray).DeleteFrame(0)
}

ノイズというのは本来そうであるべき値より高い又は低い状態ですから、ノイズの出方がそれぞれ違っていればメディアンを取ってやれば消えるわけです。

ただしノイズが同じ場所に重なっていると、それは消えません。

アニメ/ドラマのOPとか、あるいは放送局違いで3種類のソースが手に入ったとかいう場合には使えるかもしれませんね。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

yadifmod2 その2

Fizick氏からバグレポート頂いたので修正しました。

yadifmod2
version 0.0.2

edeintなしでoptが0以外のときの出力が誤っていたのが直りました。
あと、なんか前より速くなった気がする。

2016年3月18日金曜日

Clense

RemoveGrain、Repairと解説したのでついでにClenseも解説しときます。

思えばClenseは自分にとってRemoveGrainやRepair以上に謎の多いフィルタでした。
ドキュメントやコードは理解できないうえに、名前にしてもclense(清潔にする)って、あいまいすぎます。
avisynth.infoには

RemoveDirtと同じシンプルなテクニックを使った時間軸クリーナー。ただし、人工物(artifacts)の検出を行わない。Repair(もしくは他の人工物(artifacts)除去フィルタ)とセットで使用する。

とありますが、これでなにかが分かる人なんているとも思えません。

果たしてClenseの正体とはなんなのか?
その答えは3つのフレームによる時間軸メディアンフィルタでした。

Clense(clip c, clip "previous", clip "next", bool "gray")

Clenseは3つのクリップを引数にとります。
previousとnextのデフォルト値はcで、処理にあたってはcのn番フレーム、previousのn-1番フレーム、nextのn+1番フレームを一組とし、各フレームの同一座標上のサンプルのメディアンを返します。
grayのデフォルト値はfalseで、これをtrueにするとフィルタ後に出力されるフレームの色差は全く処理されてない状態(RemoveGrainにおけるmodeU=-1、modeV=-1と同じ)になります。

・使用例
Clenseは時間軸ノイズ除去フィルタです。例えば
src = something
src.ApplyRange(100, 100, "subtitle", "noise", 100, 100) #100フレーム目のみnoiseの文字を入れる 
next = last.DuplicateFrame(0)
prev = last.deleteframe(0)
StackHorizontal(prev, last, next)
こんな感じで、あるフレームだけポツンとノイズがでているソースにClense()をかけてやると
src = something
src.ApplyRange(100, 100, "subtitle", "noise", 100, 100)
Clense()
next = last.DuplicateFrame(0)
prev = last.deleteframe(0)
StackHorizontal(prev, last, next)
けっこういい感じでノイズが消えたりします。
そもそもノイズというものは、周囲に比べて極端に値が高いか低いかのどちらかなので、前後のフレームとのメディアンを求めれば消えてしまうのですね。
ただし、ちょっと動きが大きくなると
このように残像が出てしまうので使い勝手はあまりよいとはいえません。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

2016年3月17日木曜日

Repair

前回までRemoveGrainの解説をやったので、Repairもやっておくことにします。

Repairはあるフレームにフィルタをかけた場合に発生したアーティファクトを低減するためのフィルタです。
名前もRepair(修復)で、ストレートなところがいいですね。

・シンタックス
シンタックスは
Repair(clip A, clip B, int "mode", int "modeU", int "modeV", bool "planar")
です。
RemoveGrainと違ってクリップ2つを入力にとります。

・Repairの基本と実例
Repairの基本的な考え方は
・任意のフィルタをかける前のクリップBeforeと、フィルタをかけたあとのクリップAfterを用意する。
・After上のあるサンプルXaと同一座標にあるBefore上のサンプルXb及びXbの近傍8点N1~N8を用意する。
・mode及びXb、N1~N8の値に応じてXaを書き換える。
となります。

modeは例によってたくさんありますが、mode1であれば
minimum = min(N1, N2, N3, N4, N5, N6, N7, N8, Xb); // Xb及びN1~N8のうちの最小値
maximum = max(N1, N2, N3, N4, N5, N6, N7, N8, Xb); // Xb及びN1~N8のうちの最大値
Xa = clamp(Xa, minimum, maximum); // Xaをminimum~maximumの範囲内にclamp
という処理を行います。

こうすることで、なんらかのフィルタをかけた際に出力値が極端に変化しすぎてしまったもの(=アーティファクト)を、許容範囲内に収めてしまおうというわけです。

では実際にやってみましょう。

まず、あるクリップにアンシャープマスクをかけてみます。
src = something
blur = src.RemoveGrain(19).RemoveGrain(19)
sharpen = src.mt_adddiff(src.mt_makediff(blur, chroma="process"), chroma="process")
StackHorizontal(src.Subtitle("source"), sharpen.Subtitle("sharpen"))

で、ちょっとこれだと強すぎかな?帽子周りとか肩あたりとかhaloでてるし~ という場合にこうする。
src = something
blur = src.RemoveGrain(19).RemoveGrain(19)
sharpen = src.mt_adddiff(src.mt_makediff(blur, chroma="process"), chroma="process")
repair = Repair(sharpen, src, 1)
StackHorizontal(src.Subtitle("source"), sharpen.Subtitle("sharpen"), repair.Subtitle("repair"))
はい、「ちょっとシャープ強すぎ?」が「そこそこシャープ」になりました。便利ですね。

・Repairのmode
RepairもRemoveGrain同様、modeは1~24(+mode-1とmode0)が用意されています。
しかし、mode1以外を使っているスクリプトを自分は知りませんし、自分でも1以外を使った記憶がありません。
まあ、ものによっては1以外が有効なこともあるんでしょうが…。
興味のある方は色々なソースやフィルタで試してみてください。

ではRepair解説はこれで終了とします。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrain mode23/mode24 及び総評

さて、RemoveGrainのmode解説も残すところあと2つとなりました。
ちなみにオリジナルにはmode25もありますが、RgToolsでは削除されています。

・mode23/mode24
mode23とmode24はエッジ周りを少し削ることを目的としたフィルタです。
ぼかしフィルタであるmode11/mode12/mode19/mode20やインタレ補間のmode13~mode16を除いた他のmodeは、基本的にエッジを保護するように働きますが、23と24はあえてエッジを削るのです。
これは、シャープフィルタや圧縮時のDCT等の影響で発生したhalo(オーバーシュート)を低減することが目的だからです。


たしかに帽子の輪郭とかのhaloが消えてはいますね…肩のあたりのラインが削れ過ぎてる気もするけど。
mode24でmode6+エッジ削りくらいかな?

スピード的にはmode9 = mode23 > mode5 = mode24 となります。

・総評
さて、以上でmode1~mode24の解説が終わりましたが、実際のところ、RemoveGrainはどのような感じで使われているのでしょうか?
Avisynth wikiのexternal filtersのページにはプラグインだけでなくユーザースクリプトも色々登録されていますので、使用にあたっての傾向がある程度わかります。

で、どんな感じかといえば…実際に使われているのはmode4、mode11/12、mode19/20ばっかりでした。

つまり、色々と工夫を凝らしてたくさんのmodeを用意したのに、現実にはただの高速blur/medianフィルタとしての需要しかないということです。

たしかにエッジ周りやラインの保護をしたければmasktools2等でエッジマスク作って色々やるほうが汎用性も高く仕上がりも調整しやすいですし、そもそも2005年と2016年では扱うソースも大幅に変わっていますし…。

とオチらしきものがついたところでRemoveGrain解説はおひらきといたします。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RemoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrain mode17~mode22

今回はmode17からmode22まで。


・mode17/mode18
mode17とmode18はmode5~mode9までと同じように、Cをclampする際の組み合わせをCを通る直線上の2点の組み合わせを元に選びます。
mode5~mode9同様、細い線を保護しつつ処理するのが主目的なようですね。

mode9までが実装、公開された後にDoom9あたりでディスカッションが起こり、それらを元に追加実装されたもののようですが、当時の自分はDoom9どころかエンコ自体知らなかったんで、そこら辺は憶測にすぎません。

傾向としては、mode17はmode1と同じかそれ以上に速くノイズの取れ具合もmode4なみですが、細い線以外への影響が大きい。
mode18はスピードも効き具合もmode5とmode6の中間ぐらいです。

・mode19/mode20
mode19とmode20はmode11/mode12のような3x3のconvolutionによるぼかしフィルタです。

mode19のカーネルは[1, 1, 1, 1, 0, 1, 1, 1, 1]で、mode20は[1, 1, 1, 1, 1, 1, 1, 1, 1]となっています。
どちらもmode11/mode12よりも強いblurがかかりますが、特にmode19は処理対象となる中央のサンプルCを含まないため、効きが一番強くなります。

・mode21/mode22
mode21とmode22は処理時の端数処理の違いはありますがどちらも同じフィルタです。
mode5等のようにCを通る直線状の2点の組み合わせ4組を使いますが、このmodeではそれらの組み合わせの平均値をまず求め、4つの平均値のうちの最小値と最大値でclampします。
コードっぽく書くなら
m1 = (N1 + N8) / 2;
m2 = (N2 + N7) / 2;
m3 = (N3 + N6) / 2;
m4 = (N4 + N5) / 2;
minimum = min(m1, m2, m3, m4);
maximum = max(m1, m2, m3, m4);
output = clamp(C, minimum, maximum);
といった感じです。
特にmode22はこれまでのどの処理よりも軽く、全24mode中最速となっています。

フィルタの傾向としてはmode1~mode10、mode17、mode18と違ってエッジ保護をしませんが、細い線は保護します。
というか細い線の保護以外は投げやっているような感じです。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

2016年3月16日水曜日

RemoveGrain mode13~mode16

今回はmode13からmode16までです。

これまでのmode1~mode12はRemoveGrainの名にふさわしくいずれもdenoiserでしたが、13~16はdenoiserではなくなんとdeinterlacerです。

インタレ解除においては失われてしまったフィールド情報をいかにして補間するかが重要になるわけですが、補間方法にも線型補間とか三次補間とか色々あります。
上図において失われたフィールド上の点Xを補間する場合、線型(linear)補間なら(F0 + F1) / 2、三次(cubic)補間なら(F0 + F1*3 + F2*3 + F3) / 8を行いますが、他に有名どころではELA(Edge Line Average)法というものがあります。
ELA法は補間自体は線型補間と同様の計算をしますが、参照する2点の組み合わせを上図の場合だと(Fu0,Fl4)、(Fu1,Fl3)、(Fu2,Fl2)、(Fu3,Fl1)、(Fu4,Fl0)の5つのどれか一つから選びます。
これは三次補間のような垂直方向のみのデータの組み合わせでは、エッジ部においてジャギるからです。
もしエッジが斜め方向に走っている場合は参照する点も斜め方向からとらないといけないってわけですね。

RemoveGrainが書かれた当時(2005年頃?もっと前?)、avisynthにはすでにELAを使ったdeinterlacerとしTomsmocompが存在しており、なかなかに高い評価を受けていました。
しかしTomsmocompには「アーティファクトが発生しやすい」という弱点もありました。

で、Kassandro氏はELAにおける参照点として(Fu0,Fl4)か(Fu4,Fl0)が選ばれてしまった場合アーティファクトが特に発生しやすいことから、組み合わせを(Fu1,Fl3)、(Fu2,Fl2)、(Fu3,Fl1)の3つに減らし、さらにTemporalRepairフィルタを併用することで、よりアーティファクトの少ない(しかも高速な)deinterlacerとしてmode13~mode16を実装したのです!

と、ここまで書きましたが現在これを使う人はまずいません。
なぜならアーティファクト低減機能付きELA deinterlacerとして2007年にYadifというフィルタが登場したからです。

Yadifで満足できないならこれを使っても満足できるということはまずありませんので、これでmode13~mode16の解説は終了とします。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrain mode5~mode12

今回はmode5からmode12までです。

・mode5~mode9
mode1~mode4ではCの近傍の8点N1~N8のうちのどれか2点を選んでclampしていましたが、mode5~mode9ではこの2点をCを通る直線上に限定するようになっています。
つまり、2点(a,b)の組み合わせが(N1,N8)、(N2,N7)、(N3,N6)、(N4,N5)のいずれかに限定されるということです。
mode3やmode4では組み合わせを限定していなかったため、細い直線やその終端が壊れやすかったのが、組み合わせを直線状に限定することで壊れにくくなっています。

具体的な処理内容については言葉で説明するのは非常に難しいため、もうソースコードを読んでもらうしかありません。
よって、ここでは傾向だけを説明します。

ノイズの取れ具合はmode5 < mode6 < mode7 < mode8 < mode9 とmodeが上がるほど強力になりますが、その分映像に与える悪影響(エッジの角が丸まったり、ボケたり)も大きくなります。

そして処理スピードは mode2 > mode9 > mode5 > mode7 > mode6 = mode8 です。

一般に細い線を壊さない処理というものは、YUV4:2:0な映像の色差にフィルタをかける場合に効果を発揮します。(なんせ色差は輝度の4分の1しかありませんから)
よって5~9はmodeU、modeVで指定することがほとんどでしょう。

・mode10
mode10はclampを行わず、N1~N8のうち最も近い値(C-Nxの絶対値が最も低いもの)にCを置き換えたものを出力します。
一見簡単に思えますが、実はこの処理はmode1~mode9のどれよりも比較の回数が増えるため、処理スピードはmode6 > mode10です。
しかもノイズの取れ具合はmode1と同程度と、もはや何のためにあるのかよくわかりません。

これが効果的な映像とは果たして…?

・mode11/mode12
mode11とmode12は3x3のconvolution filterです。
使用するカーネルは[1, 2, 1, 2, 4, 2, 1, 2, 1]で、これは内蔵フィルタのBlur(1.0)に相当しますがより速くて正確です。
オリジナルのほうのドキュメントの該当部分を読んでみると(処理内容があらかじめ理解できているとドキュメントも理解できるようになりました…なんという本末転倒…)
「mode12はmode11よりもさらに速いよ」ってなことが書かれていますが、tp7氏はどちらも同じ処理にしてしまっています。
そりゃ出力が同じなら、速い方しか要らんよね…なんで遅い方をわざわざ残してたんでしょ?

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrain mode1~mode4

まずはmode1からmode4の説明です。

・mode1
mode1ではN1からN8までの8点のうちの最小値minNと最大値maxNを求め、clamp(C, minN, maxN)を出力します。
このように、ポツポツと不連続なドット状ノイズが出ているような画像にかけると、ノイズ以外の部分にはほぼ影響を出さずにノイズだけ消すことができます。
ちなみにこの処理はtrbarry氏のundotフィルタと同じものです。

・mode2
mode2ではN1からN8までの8点のうちの2番目に小さい値min2Nと2番目に大きい値max2Nを求め、clamp(C, min2N, max2N)を出力します。
1よりもちょっと大きなノイズも取れますが、エッジの終端部分が少し壊れます。


・mode3
mode3ではN1からN8までの8点のうちの3番目に小さい値min3Nと3番目に大きい値max3Nを求め、clamp(C, min3N, max3N)を出力します。
mode2よりもさらに大きなノイズが取れますがエッジの終端により大きな影響を与えます。また、細い線が結構壊れます。

・mode4
mode4ではN1からN8までの8点のうちの4番目に小さい値min4Nと4番目に大きい値max4Nを求め、clamp(C, min4N, max4N)を出力します。
少し考えればわかると思いますが、これはN1~N8とCの9点におけるmedian(中央値)と同じです。
つまりmode4はごく一般的な3x3のメディアンフィルタです。
1~3に比べて結構ボケるし、エッジの角が丸くなったりしますので注意が必要です。使用の際はエッジマスク等の併用を常に考える必要があります。

ちなみに処理スピードはmode1 > mode4 >= mode2 = mode3 です。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrainの基本事項

RemoveGrainのシンタックスは以下の通りです。

RemoveGrain(clip clip, int "mode", int "modeU", int "modeV", bool "planar")

今回はまず、フィルタの具体的な処理内容以外のことについて解説します。

RemoveGrainにおける基本事項


・参照サンプルの配置
RemoveGrainはmode=1からmode=24までの24種類のフィルタを一つにまとめたものですが、これらは全て処理対象となるサンプルとその近傍8点を参照するspatial(空間軸)フィルタです。
図にするとこんな感じ。
中央のCが処理対象となるサンプルで、N1~N8はその周囲のサンプルです。
次回から説明するすべてのmodeにおいて、処理対象となるプレーンの上下左右の画像端1ラインを除くすべてのサンプルに対して処理を行います。
なお画像端の1ラインはそのままコピーされます。

・mode、modeU、modeV
RemoveGrainではYプレーン、Uプレーン、Vプレーンそれぞれに異なったmodeでフィルタをかけることができます。
RemoveGrain(mode=10, modeU=2, modeV=5)なら、Yプレーンはmode10、Uプレーンはmode2、Vプレーンはmode5のフィルタがかかります。
modeは1~24までの24種類がありますが、それ以外に-1(なにもしない)と0(そのままコピー)もあります。
-1の場合、フィルタリング後のデータを書き出すために用意されたメモリには何も手を加えられませんので、どのような出力になるかは誰にもわかりません(avisynthは確保したメモリのゼロクリア等はしません)。
0ならば、フィルタ前のデータがそのままフィルタ後のメモリにコピーされます。
mode、modeU、modeVのデフォルト値はmode=1、modeU=mode、modeV=modeUです。
よって、もしYのみにフィルタをかけたい場合は、RemoveGrain(mode=1~24のどれか, modeU=0)とします。

・planar
本家RemoveGrainはYV12とYUY2/RGB(一応)をサポートしていましたが、RGToolsではYUY2やRGBのサポートはなくなり、代わりに全てのplanar format(YV24, YV16, YV12, YV411, Y8)をサポートするようになりました。
YUY2なクリップに対してフィルタをかけたいなら事前にConvertToYV16()をかけ、フィルタ後にConvertToYUY2()で元に戻しましょう。
RGBにかけたいならPlanarToolsでも使ってください。
planarパラメータはもともと本家RemoveGrainではSSE2Toolsを利用したYUY2/RGBサポートのために用意されていたものですが、RGToolsでは単にavisynth2.5用に書かれたユーザースクリプトへの互換性のためだけに用意されたもので、これをtrueにしてもいいことはまったくありません。
よってこのパラメータは一切弄らないようにしましょう。

・clamp()
modeの内容を説明するにあたり、clamp(x, a, b)という表現を使用することになると思います。
clamp(x, a, b)はxがa以下の場合はa、xがb以上の場合はb、a < x < b の場合はxの値そのままの意味になります。(なお常にa <= bの関係が成り立つものとします)
この処理はclippingとかsaturateなどと呼ばれることもありますが、ここではとりあえずclampで統一します。

では次からいよいよmodeの説明にはいります。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

RemoveGrain解説 導入

RemoveGrainはkassandroことRainer Wittmann氏が書いた有名なavisynthプラグインです。
Didée氏のscriptなんかではほぼ確実に使われていたりと使用頻度は極めて高いプラグインなのですが、その使い方は自分にとって極めて難解なものでした。

難解な理由その1: ドキュメントが極めて読みにくい。
RemoveGrainのドキュメントはすべて英語で書かれています。
まあ英語だけならある程度気合いを入れれば読めないことはないのですが、なんと改行がほとんどありません。

数行読むだけでも息が詰まってくる上に、すぐにどこまで読んだのかわからなくなってしまうことの連続でいつも10分経たぬうちに挫折してしまいます。
まったく読みづらいったらありゃしねえ…。
RTFM(Read The Fine/F○○kin' Manual)なんて言葉がありますが、ここまでF○○kin'なマニュアルも珍しいんじゃないかと思います。

難解な理由その2: コードが極めて読みにくい。
「マニュアルがダメならソースコードを読んで理解すればいいじゃない」
そんな風に考えていた時期が俺にもありました。
CやPythonを少し覚えてちょぼちょぼとは書けるようになった頃だったと思います。
しかしその考えが甘かった。

RemoveGrainのバイナリパッケージにはRemoveGrain,Repair,DenoiseSharpen,RSharpenという4種類のDLLがCPUアーキテクチャやMSVCRTのスタティックリンク等の別に合計で13個(!)入っているわけですが、これらをビルドするためのソースコードはなんと約8900行からなるRemoveGrain.cppというたった一つのファイルしかありません。
いや、普通は複数のファイルで一つのバイナリなのに、一つのファイルで複数のバイナリっておかしいやろ…はじめて中身を覗いたときはそりゃもうびっくりしましたよ。

数行おきに現れる#ifdef~#elif~#endif、inline asm、そしてCPPマクロの嵐。
もはや狂気を感じるレベルです。
まあ、自分が書いたコードも(特に数年くらい前のやつとか)いま見直すと結構アレなんですが、このレベルになるともう、わざと書こうと思っても書けるものではありません。
実はわたくし、ドイツ人というものになんというかちょっとネガティブな偏見を持っているのですが、そのうちの半分くらいはこのRemoveGrainのドキュメントとコードに起因するものだったりします。

その後の展開
で、RemoveGrainのドキュメントとコードの解読はすっかり諦めていたわけですが、そのうちにFiresledge(cretindesalpes)氏がDitherPackage用に改造したりVapourSynthに移植されたりといったことが起こり、avisynthのほうでもtp7氏がRgToolsとしてrewrite版を出したおかげで大変わかりやすいソースコードが手に入るようになりました。
でもあいかわらずドキュメントは…。
で、自分用の備忘録も兼ねて解説でも書いてみようかなーとか思ったわけです。

tp7氏のRGToolsにはRemoveGrain、Repair、Clense、FowerdClense、BackwordClense、VerticalCleanerの計5つのフィルタが入っていますが、これらはすべてKassandro氏が書いたもののrewrite versionです。
解説はRGToolsのRemoveGrainをもとに行いますので、オリジナル(Kassandro版)とは違うところもあるかもしれません。

では次回よりRemoveGrainの解説を行います。

目次:
RemoveGrain解説 導入
RemoveGrainの基本事項
RemoveGrain mode1~mode4
RemoveGrain mode5~mode12
RemoveGrain mode13~mode16
RemoveGrain mode17~mode22
RmoveGrain mode23/mode24 及び総評
Repair
Clense
Clense 応用

2016年3月5日土曜日

yadifmod2

前回yadifmodを書き直したものを公開しましたが、結局yadifそのものもavs2.6用に書き直すことにしました。

というわけでできたのがyadifmod2です。

yadifmod2

バイナリはここ

YadifMod2 = Yadif + YadifMod

とあるように、edeintなしならyadif、edeintありならyadifmodになります。

2016年3月1日火曜日

YadifMod for Avisynth2.6 / Avisynth+

tritical氏のyadifmodをAvisynth2.6/Avisynth+用に書き直してみました。

ソースコード:
yadifmod26

バイナリ(32bit/64bit同梱):
https://github.com/chikuzen/yadifmod26/releases

tritical版はSIMDはMMXしかありませんでしたが、今回SSE2とAVX2に対応させました。
それなりに速くはなったようです。

なおバイナリにyadifmod.dllとyadifmod_avx.dllの2種類がありますが、AVXマシン(SandyBridge以降だっけ?)はyadifmod_avx.dllを、
Nehalemとかの場合はyadifmod.dllを使ってください。

追記:これとは別にyadifとyadifmodを統合したyadifmod2もリリースしました。

2016年1月20日水曜日

mt_yrangemask

お久しぶりです。

potatosubさんのブログ読んでたら、yrangemaskなるフィルタを使っているのに気づきました。

わたくし、このフィルタはこれまで知らなかったんですが、ちょっと気になったことがあったので実験してみました。

#test.avs
LoadPlugin("yrangemask.dll")
LoadPlugin("masktools2.dll")

#1920x1080のグラデーション画像10000フレーム
BlankClip(length=10000, width=1920, height=1080, pixel_type="YV12")
mt_lutspa(mode="relative", expr="x 256 *", chroma="128")

#yrangemask(16, 4, 80, 16, true)
mt_yrangemask(16, 4, 80, 16, true)

function mt_yrangemask(clip clip, int "min_y", int "fade_min_y", int "max_y", int "fade_max_y", bool "invert")
{
    assert(clip.IsPlanar(), "clip is not planar format")
    min = default(min_y, 0)
    max = default(max_y, 0)
    assert(min >= 0 || min < 256 || max < 256, "Specify in the range from 0 to 255")
    assert(min <= max, "min_y must be less than or equal to max_y")
    fmin = default(fade_min_y, 0)
    fmax = default(fade_max_y, 0)
    assert((fmin + fmax) <= (max - min), "'fade_min_y + fade_max_y' should be less than or equal to 'max_y - min_y'")

    min = string(min - 1)
    max = string(max + 1)
    fmin = string(fmin + 1)
    fmax = string(fmax)
    invert = default(invert, false)

    #expr = 255 / (x < max - fmax ? fmin / (x - min) : (fmax + 1) / (max - x))
    #expr = invert ? 255 - expr : expr
    expr = "255 / (x < " + max  + " - " + fmax + " ? " + fmin + " / (x - " + min + ") : (" + fmax + " + 1) / (" + max + " - x))"
    expr = (invert ? "255 - " : "") + expr

    return clip.mt_lut(mt_polish(expr), chroma="0")
}
で、
$ for i in {1..3}; do avs2pipemod -benchmark test.avs; done

     yrangemask   mt_yrangemask 
 1   303.556fps    889.047fps
 2   300.232fps    890.155fps
 3   301.298fps    891.027fps
------------------------------------
avg  301.695fps    890.076fps

やはり気のせいではなかったようです。

mt_lutは起動時にあらかじめxが0から255までの256パターンの計算を行いLUT(ルックアップテーブル)を作るため、実行中の処理スピードは式がどれだけ複雑になっても変わりません。
LUTのサイズは256バイトですから、L1キャッシュに収まるため高速です。
逆ポーランド記法もmt_polishを使えば覚えなくて済むので、積極的に使ってみることをお勧めします。

追記:2016/01/21 mt_lutの式を簡略化 & invert=trueの場合をちょっと高速化