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もサポートするようになりました。

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