2012年8月28日火曜日

あぷこん考

そこのあなた、アプコンやったことありますか?
そう、アニメDVDとかを拡大してからわざわざ再エンコードするあれですよ、あれ。

私? ええ、やったことありますよ。何年前になるのかなぁ、エンコ始めたばっかの頃。
わけも分からずに、やたら糞重たいフィルタを何重にも掛けて、10数時間がかりでCPUぶん回した挙句、途中でクラッシュとかね。

だいたい1週間くらいで飽きてやめたんだけど、あの頃やったのは今でもとってあります。
なんつーか、見返すたびに当時の自分の精神状態を疑いたくなりますな。

で、若気の至り?の残骸を久しぶりに見ながら、「今だったらせめてこうやるよなぁ」みたいなことを考えたのでちょっと書いてみることにします。

1.まずは素材

拡大するんですからインタレ解除は必須です。
DVD素材なら、まあこんな感じでいいでしょう。
MPEG2Source("video.d2v", idct=3, cpu=0)

#1pass目
#TFM(d2v="video.d2v", output="matches.txt")
#TDecimate(mode=4, output="metrics.txt")

#2pass目
TFM(d2v="video.d2v", input="matches.txt")
TDecimate(mode=5, hybrid=2, vfrDec=1, input="metrics.txt", tfmIn="matches.txt",
\         tcfv1=false, mkvOut="timecodes.txt")
ここ数年はDVDでもソフトプルダウンのものが増えましたので、force filmで済むものも多くなりましたな。

2.拡大

DVDサイズ(720x480)を拡大するなら、とりあえず16:9なら1280x720、4:3で960x720くらいが妥当でしょう。それ以上はさすがに無理がある。
リサイザは色々ありますが、スピードとか考えたらSpline36Resizeあたりでいいんじゃないですかね。
あと、周囲に黒ベタがあるなら、それは綺麗に削ります。多少のアス比の狂いなんてキニシナイキニシナイ。
src = last
resized = src.Spline36Resize(1280, 720, 4, 2, -3, 0) #Crop値は適当です。

3.いろいろ

アプコンする人って、大抵線が濃いほうが好きですよね。まあ、拡大したせいで結構ボケてるでしょうし、それもいいでしょう。
resized = resized.FastLineDarkenMod()

え? 線が太いのがいや? ならWarpSharpで削りますか。
resized = resized.WarpSharp()

ここまでやると、ある程度エンコ歴のある人は顔をしかめますな。「油絵かよ」って。
WarpSharpは特にエンコ始めたばっかの人に人気のあるフィルタですが、線が細くなる以外に色の変化がキツくなるという作用もあります。ボケた感じの背景に掛けると油絵の具で塗ったみたいになるのです。

そもそもアニメなんてのは、エッジを除けばあとはベタ塗りとグラデーションとボケた背景の塊です。
こういった映像を拡大するなら、SplineやLanczosよりもBilinearResizeのほうが遥かに向いています。
ボケてるものはボケたままに拡大するほうが綺麗です。

じゃあ、どううすればいいかなって考えてみると
mask = resized.mt_edge().mt_binarize()
background = src.BilinearResize(1280, 720, 4, 2, -3, 0)
mt_merge(background, resized, mask)
つまり、FastLineDarkenやらWarpSharpやらかけたものはエッジだけ使って、その他の部分はBilinearResizeで拡大したものにしてやる。
これで完璧ってわけにはいきませんが、やらないよりはマシでしょう。

あとは画面が広くなった分バンディングが目立つようになるだろうから、低減フィルタをかければオシマイ。

4.まとめ

LoadPlugin("DGDecode.dll")
LoadPlugin("mt_masktools-26.dll")
LoadPlugin("warpsharp.dll")
Import("FastLineDarkenMod.avs")
#その他フィルタの読み込み

MPEG2Source("video.d2v", idct=3)
#インタレ解除
#必要ならデノイズ

bg = last.BilinearResize(1280, 720, left, top, right, bottom)
edge = last.Spline36Resize(1280, 720, left, top, right, bottom).
\      FastLineDarkenMod().WarpSharp()
mask = edge.mt_edge().mt_binarize()
mt_merge(bg, edge, mask)

#バンディング/暗部対策等

こんなもんじゃないですかね。
あくまでも考えただけで、実際に何本かやってみようとかは全然思わないんですけど。

2012年8月26日日曜日

AreaResize

AviSynth用に面積平均法(平均画素法)のリサイザを書きました。
AreaResize-0.1.0.zip
https://github.com/chikuzen/AreaResize

*面積平均法なので縮小専用です。
*YUY2はサポートしてません。
*遅いのは仕様です(SIMDコードなんて書けませんから)。

2012年8月22日水曜日

BucketMedian その3

前回の記事を書きながら考えたわけですが、そもそもAviSynthは便利な内蔵フィルタを持っているわけですから、画像端の処理をいちいちプラグイン側でやるというのは無駄な分岐を増やして自らスピードを落としているということになります。
gradfun2dbのように画像端の処理をしないのは非常に合理的な選択なのです。

というわけで拙作BucketMedianもこれにならい、画像端は処理しないことにして、かわりにヘルパー関数を用意することにしました。

BucketMedian-0.3.1.zip

*画像端を処理しないようにした。
*BMBorderProc.avsiを追加した。

少しだけど、ホントに速くなったよ…画像端の処理って結構悩んだような覚えがあるんだけど…orz


使い方
LoadPlugin("BucketMedian.dll")
Import("BMBorderProc.avsi")
AVISource("video.avi")
BMBorderProc(clip c, int "r", int "th", int "min", int "max")

r : BucketMedianのradiusと同じ。
th : BucketMedianのthreshと同じ。
min: BucketMedianのminと同じ。
max: BucketMedianのmaxと同じ。

BMBorderProcを使えば、0.2.0と同じ出力が得られます。

追記:
rが奇数の場合、BMBorderProcがエラーになるので修正しました。

2012年8月21日火曜日

FastGradFunkMirror

一見同じようなavsでも書き方一つでパフォーマンスは結構変わっちゃうかもよ、というお話。

GradFunkMirrorという有名なスクリプトがあります。
function GradFunkMirror(clip c, float "strength")
{
    strength = default(strength, 1.2)
    w = c.width()
    h = c.height()
    vflip = c.FlipVertical()
    hflip = c.FlipHorizontal()
 
    StackHorizontal(hflip.crop(w-16, 0, 16, h).AddBorders(0, 16, 0, 16),
    \ stackvertical(vflip.crop(0, h-16, w, 16), c, vflip.Crop(0, 0, w, 16)),
    \ hflip.Crop( 0, 0, 16, h ).AddBorders(0, 16, 0, 16))
    gradfun2db(strength)
    Crop(16, 16, -16, -16)

    return last
}
これはGradFun2DBは上下左右の端16pixの部分を処理しないようになっているので(画像端の処理って面倒ですからねぇ)、それを補うために用意されているスクリプトです。
原理は非常に簡単で、端16pixを処理できないなら周囲に16pix分の鏡像をくっつけてやることで元画像の端まで処理範囲を拡大し、フィルタ後にくっつけた画像をCropしてしまえというもの。

さて、昨日AviSynthの内蔵フィルタのソースを眺めていてふと気づいたので、こう書き直してみました。
function GradFunkMirror2(clip c, float "strength")
{
    strength = default(strength, 1.2)
    w = c.width()
    h = c.height()
    top = c.Crop(0, 0, w, 16).FlipVertical()
    bottom = c.Crop(0, h-16, w, 16).FlipVertical()
    left = c.Crop(0, 0, 16, h).FlipHorizontal().AddBorders(0, 16, 0, 16)
    right = c.Crop(w-16, 0, 16, h).FlipHorizontal().AddBorders(0, 16, 0, 16)

    StackHorizontal(left,
    \               StackVertical(top, c, bottom),
    \               right)
    gradfun2db(strength)
    Crop(16, 16, -16, -16)
    
    return last
}

違いは「鏡像を作る際に、画像を反転してからCropするか、それともCropしてから反転するか」だけです。
出力されるもの自体は同じですがベンチマークを取ってみると
#sample.avs
AVISource("1440x1080_ULY0.avi")
GradFunkMirror()
#GradFunkMirror2()

$ for i in {1..3}; do avs2pipemod -benchmark sample.avs; done

      GradFunkMirror   GradFunkMirror2
1st     50.600fps        57.665fps
2nd     50.566fps        58.214fps
3rd     50.308fps        57.566fps
---------------------------------------
avg     50.491fps        57.815fps
「大きな画像を反転させるよりも小さな画像を反転させるほうがコストは低い」という、まあ当たり前な話なわけですが、結構な差が出るものですな。

追記:
ちなみにGradfun2DBModの場合はここに書いたようなロスはなく、GradFunkMirrorがAddBordersで済ませている四隅の部分もしっかり鏡像にしている芸の細かさを見せてくれます。