ラベル VapourSynth の投稿を表示しています。 すべての投稿を表示
ラベル VapourSynth の投稿を表示しています。 すべての投稿を表示

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のバイナリも入ってます。

2013年7月31日水曜日

VapourSource

Avisytnh2.6用プラグインの新作を公開。

VapourSource-20130731.zip
https://github.com/chikuzen/VapourSource/

VapourSourceはVSScriptの使い方を調べるために書いてみたAvisynthプラグインです。
VapourSynth script(r19以降)をAvisynthで読み込みます。

書き始めたのはr19のtest1のリリース直後でしたが、r19の正式公開までに二度のAPIブレークがありました。
やっぱこういうのって、実際に色々使ってみないと足りないところとかわからないものだよね。

vsrawsource その4

更新

vsrawsource-0.3.2.zip
https://github.com/chikuzen/vsrawsource/

* アルファ付ソース読み込み時のメモリリーク修正
* ファイル名に非ASCII文字が含まれる場合の対応

vsimagereader その2

VS r19にあわせて更新

vsimagereader 0.2.1.zip
https://github.com/chikuzen/vsimagereader/

* アルファ読み込み時のメモリリーク修正
* ファイル名に非ASCII文字を含む場合の対応
* libpngを1.6.2に更新

2013年7月14日日曜日

LutとLut2を改良した

VapourSynthのissueリストにこんなのがありました。
http://code.google.com/p/vapoursynth/issues/detail?id=52
「LutとLut2なんだけど、いちいちlook up tableを作ってから渡すのってメンドイから、生成用関数渡したらフィルタ側で作るようにしたいんだけど、誰かやらんかね?」

たしかにあれはめんどくさい。
特にLut2の場合は二重ループ必須だから、余計に書くのがめんどくさい。
というわけで、パッチ書いて送ったのが昨日mergeされました。

core.std.Lut(clip:clip;planes:int[];lut:int[]:opt;function:func:opt;)
core.std.Lut2(clips:clip[];planes:int[];lut:int[]:opt;function:func:opt;bits:int:opt;)

例:クリップのすべてのYの値を50下げたい場合。
clip = something

#これまでの書き方
lut = [max(x - 50, 0) for x in range(2 ** clip.format.bits_per_sample)]
clip = core.std.Lut(clip, lut=lut, planes=0)

#改良後
def func(x):
    return max(x - 50, 0)
clip = core.std.Lut(clip, function=func, planes=0)

#または
clip = core.std.Lut(clip, function=lambda x: max(x - 50, 0), planes=0)

例:二つのクリップを平均化して新しいクリップを作りたい。
clipx = something
clipy = something

#これまでの書き方
lut = []
for y in range(2 ** clipy.format.bits_per_sample):
    for x in range(2 ** clipx.format.bits_per_sample):
        lut.append((x + y) // 2)
clip = core.std.Lut2([clipx, clipy], lut=lut, planes=[0, 1, 2])

#改良後
clip = core.std.Lut2([clipx, clipy], planes=[0, 1, 2], function=lambda x, y: (x + y) // 2)

functionの引数名はmasktools2のmt_lut/mt_lutxyに合わせて、xとyで固定です。
今までのようにlutをPython側で作って渡すこともできますが、lutとfunctionの両方を渡したらfunctionは無視されます。

次のリリース(多分r19test5)から使えるようになります。

2013年7月13日土曜日

MultiByteToWideCharToMultiByte その4

5.VapourSynthと文字エンコード

VapourSynthはr19でいろいろと大きな変更が入ることが見えたきたが、そのなかでも特に大きなものはVSScriptの導入である。

r18までのVSはvapoursynth.dll(Linuxならlibvapoursynth.so)と、vapoursynth.pydの二つに分かれていた。
vapoursynth.dllはメイン処理を担当するC++で書かれたライブラリであり、vapoursynth.pydはvapoursynth.dllをPythonで操作するためのcython製モジュールである。
この構成は全てをPython上で実行するだけなら特に問題はなかったが、いざPython以外の外部プログラムと連携させようとするとどうやったらいいものなのかが非常にわかりにくかった。
それこそコードを全部読みこんでMyrsloik氏自身と同程度の理解を要求されるレベルなのだ。
Windowsならばvsvfwとvsfsがあるのでまだ良いが、LinuxやMacではどうしたらいいものかさっぱりだった(筆者自身、半年ほど前にavs2yuvのようなツールを作ってみようとして、半日で挫折している)。

VSScriptはこのような状況を改善し外部プログラムとの連携を容易にするために作られたAPIである。
現段階ではまだ未完成ではあるが、既にAvsPmod開発者のvdcrim氏などは非常に強い関心を寄せている。
LinuxやMacでもAvsPmodを使ってVS用スクリプトを編集できるようになる日もそう遠くはないかもしれない。

さて、VSScriptのコードを追っかけてみると、vapoursynth.pydを構成するコード(vapoursynth.pyx)に次のような記述が追加されていた。
cdef public api int vpy_evaluateScript(VPYScriptExport *se, const char *script, const char *errorFilename) nogil:
...
        comp = compile(script.decode('utf-8'), errorFilename.decode('utf-8'), 'exec')
        exec(comp) in evaldict
...
このコードから察するに、VSScriptを使ってメモリ上に展開されたスクリプトのバイト列をPythonに渡すと、Pythonはバイト列をUTF-8でデコードしてユニコード文字列に変換し、しかる後にバイトコードにコンパイルすることになる。
これは事実上、r19以降はスクリプトの記述はUTF-8(BOMなし)で統一されるというこどだ。

そもそもPythonでは、スクリプトはASCII文字のみで書くか、それがダメならばUTF-8にすることとPEP8で決められている。
これは絶対の掟ではないが、今時Shift_jisやEUC-JPでPython書きたがるのはただの馬鹿だ(過去に書かれた負債のため嫌々書くならば理解できる)。
また、Avisynthでも前回で述べたようなファイル名の問題等を解決するため、スクリプトはUTF-8で書くように出来ないかとか、ユニコード対応ソフトウェアに改修しようといった議論が行われたことがあった(結局互換性重視の方針により実現はしなかったが)。
この変更は、大いに歓迎すべきものだろう。

VapourSynthはまだまだ若いプロジェクトであり、プラグイン作者もそれほど多くはない。
しかもほぼ全員が#darkhold(IRCチャンネル)に常駐しているため、舵取りも容易な状態である。
あきらかに自分のコードの書き方が悪かっただけなのに、プラグインの互換性を損ねたと駄々をこねる困った子も今のところはいない。
変更を追いかけるのはキツイかもしれないが、あと1年くらいは色々と変わるのではないかと思う。

2013年6月30日日曜日

VapourSynth r19(テスト版)

VapourSynthがr19で色々変わるようです。
とりあえずテスト版でのr18までとの違いを簡単にまとめておきます。

*この記事は現時点でのテスト版を元に書かれており、今後の展開によっては特に断りなく修正されます*

変更1:Coreクラスがsingletonになり、Core()がget_core()になった。

singletonは、デザインパターン(オブジェクト指向プログラミングのノウハウ)の一種ですが説明はメンドイので省きます。
具体的には、いままでこう書いていたのが
import vapoursynth as vs
core = vs.Core(threads=2, add_cache=False, accept_lowercase=True)
c0 = core.std.BlankClip(format=vs.YUV420P8)
c1 = core.std.BlankClip(format=vs.YUV422P9)
c2 = core.std.BlankClip(format=vs.YUV444P16)
こうなります。
import vapoursynth as vs
core = vs.get_core(threads=2, add_cache=False, accept_lowercase=True)
c0 = core.std.BlankClip(format=vs.YUV420P8)
c1 = core.std.BlankClip(format=vs.YUV422P9)
c2 = core.std.BlankClip(format=vs.YUV444P16)
もっと言えばこう書いてもいいです(あまりオススメはしませんが)。
import vapoursynth as vs
c0 = vs.get_core(threads=2, add_cache=False, accept_lowercase=True).std.BlankClip(format=vs.YUV420P8)
c1 = vs.get_core().std.BlankClip(format=vs.YUV422P9)
c2 = vs.get_core().std.BlankClip(format=vs.YUV444P16)
これまでは
Note that the loaded plugins are per core instance and not global so if you do create
several processing cores at once (not recommended) you will have to load the plugins
you want for each core.
と、Coreオブジェクトは何個でも作れましたが、r19以降は1プロセスに付き1個しか作れなくなりました(最初にget_core()した時に作られます)。
これにより、いままではユーザー定義のクラスや関数に引数でCoreオブジェクトを渡す必要がありましたが、今後はなくなりました(必要なところでget_core()すれば、どこで呼んでもプロセス内で共通のものが使われます)。

変更2:output()がなくなり、set_output()とvspipe.exeが出来た。またlastというクリップ名に特別な意味がなくなった。

これまでは
#sample_pre.vpy
import vapoursynth as vs
core = vs.Core()
last = core.std.BlankClip(format=vs.YUV420P8)

if __name__ == '__main__':
    last.output(sys.stdout, y4m=True)
このスクリプトをコマンドラインで使う場合は
$ python sample_pre.vpy | x264 - --demuxer y4m -o out.264
という感じで実行し、VirtualDub等で読みこめばlastクリップがプレビュー出来ましたが今後はこうなります。
#sample_r19.vpy
import vapoursynth as vs
core = vs.get_core()
clip = core.std.BlankClip(format=vs.YUV420P8)
clip.set_output()
このスクリプトをVirtualDub等VfWを利用するソフトウェアで読みこめば、set_output()したクリップ(変数名はなんでもよい)がプレビューできます。
そしてx264等に入力するにはvspipe(インストールすればおまけでついてきます)を使います。
$ vspipe sample_r19.vpy - -y4m | x264 - --demuxer y4m -o out.264 
output()がなくなってしまったので、これまでのようにスクリプト内でsubprocessつかってコマンド実行が困難(出来ないことはないけど)になりましたが、かわりに外部プログラムから使うためのAPIがかなり整備されたので、そのうちx264等にAVS入力同様、VS入力が実装されるでしょう。

変更3:get_plugins()とget_functions()が追加された。

r18までは、core.list_functions()で現在使えるすべてのフィルタ及びその引数の一覧を文字列として取得出来ましたが、r19でさらにcore.get_plugins()とcore.***.get_functions()が追加されました。
get_plugins()は、list_functions()では文字列として取得できた情報がdictとして取得出来ます。
>>> import vapoursynth as vs
>>> vs.get_core().get_plugins()
{'com.vapoursynth.resize': {'namespace': 'resize', 'identifier': 'com.vapoursynt
h.resize', 'functions': {'Bicubic': 'clip:clip;width:int:opt;height:int:opt;form
at:int:opt;yuvrange:int:opt;', 'Sinc': 'clip:clip;width:int:opt;height:int:opt;f
ormat:int:opt;yuvrange:int:opt;', 'Bilinear': 'clip:clip;width:int:opt;height:in
t:opt;format:int:opt;yuvrange:int:opt;', 'Spline': 'clip:clip;width:int:opt;heig
ht:int:opt;format:int:opt;yuvrange:int:opt;', 'Gauss': 'clip:clip;width:int:opt;
height:int:opt;format:int:opt;yuvrange:int:opt;', 'Lanczos': 'clip:clip;width:in
t:opt;height:int:opt;format:int:opt;yuvrange:int:opt;', 'Point': 'clip:clip;widt
h:int:opt;height:int:opt;format:int:opt;yuvrange:int:opt;'}, 'name': 'VapourSynt
h Resize'}, 'com.vapoursynth.avisynth': {'namespace': 'avs', 'identifier': 'com.
vapoursynth.avisynth', 'functions': {'LoadPlugin': 'path:data;'}, 'name': 'Vapou
rSynth Avisynth Compatibility'}, 'com.vapoursynth.std': {'namespace': 'std', 'id
entifier': 'com.vapoursynth.std', 'functions': {'Loop': 'clip:clip;times:int:opt
;', 'PropToClip': 'clip:clip;prop:data:opt;', 'StackVertical': 'clips:clip[];',
'Transpose': 'clip:clip;', 'FlipVertical': 'clip:clip;', 'PEMVerifier': 'clip:cl
ip;upper:int[]:opt;lower:int[]:opt;', 'Splice': 'clips:clip[];mismatch:int:opt;'
, 'ClipToProp': 'clip:clip;mclip:clip;prop:data:opt;', 'PlaneDifference': 'clips
:clip[];plane:int;prop:data:opt;', 'Lut2': 'clips:clip[];lut:int[];planes:int[];
bits:int:opt;', 'AssumeFPS': 'clip:clip;src:clip:opt;fpsnum:int:opt;fpsden:int:o
pt;', 'Cache': 'clip:clip;size:int:opt;fixed:int:opt;', 'FlipHorizontal': 'clip:
clip;', 'Expr': 'clips:clip[];expr:data[];format:int:opt;', 'LoadPlugin': 'path:
data;forcens:data:opt;', 'MaskedMerge': 'clips:clip[];mask:clip;planes:int[]:opt
;first_plane:int:opt;', 'ShufflePlanes': 'clips:clip[];planes:int[];format:int;'
, 'CropRel': 'clip:clip;left:int:opt;right:int:opt;top:int:opt;bottom:int:opt;',
 'SeparateFields': 'clip:clip;tff:int;', 'Reverse': 'clip:clip;', 'StackHorizont
al': 'clips:clip[];', 'Trim': 'clip:clip;first:int:opt;last:int:opt;length:int:o
pt;', 'SelectEvery': 'clip:clip;cycle:int;offsets:int[];', 'Interleave': 'clips:
clip[];extend:int:opt;mismatch:int:opt;', 'ModifyFrame': 'clips:clip[];selector:
func;', 'Turn180': 'clip:clip;', 'Lut': 'clip:clip;lut:int[];planes:int[];', 'Pl
aneAverage': 'clip:clip;plane:int;prop:data:opt;', 'BlankClip': 'clip:clip:opt;w
idth:int:opt;height:int:opt;format:int:opt;length:int:opt;fpsnum:int:opt;fpsden:
int:opt;color:float[]:opt;', 'DoubleWeave': 'clip:clip;tff:int;', 'CropAbs': 'cl
ip:clip;width:int;height:int;x:int:opt;y:int:opt;', 'SelectClip': 'clips:clip[];
src:clip[];selector:func;', 'AddBorders': 'clip:clip;left:int:opt;right:int:opt;
top:int:opt;bottom:int:opt;color:float[]:opt;', 'Merge': 'clips:clip[];weight:fl
oat[]:opt;'}, 'name': 'VapourSynth Core Functions'}}
はい、dictのなかにさらにdictがたくさんあって、なにがなにやらさっぱりわかりません……。
get_functions()は、もう少しマシです。
>>> vs.get_core().std.get_functions()
{'Loop': 'clip:clip;times:int:opt;', 'PropToClip': 'clip:clip;prop:data:opt;', '
StackVertical': 'clips:clip[];', 'Transpose': 'clip:clip;', 'FlipVertical': 'cli
p:clip;', 'PEMVerifier': 'clip:clip;upper:int[]:opt;lower:int[]:opt;', 'Splice':
 'clips:clip[];mismatch:int:opt;', 'ClipToProp': 'clip:clip;mclip:clip;prop:data
:opt;', 'PlaneDifference': 'clips:clip[];plane:int;prop:data:opt;', 'Lut2': 'cli
ps:clip[];lut:int[];planes:int[];bits:int:opt;', 'AssumeFPS': 'clip:clip;src:cli
p:opt;fpsnum:int:opt;fpsden:int:opt;', 'Cache': 'clip:clip;size:int:opt;fixed:in
t:opt;', 'FlipHorizontal': 'clip:clip;', 'Expr': 'clips:clip[];expr:data[];forma
t:int:opt;', 'LoadPlugin': 'path:data;forcens:data:opt;', 'MaskedMerge': 'clips:
clip[];mask:clip;planes:int[]:opt;first_plane:int:opt;', 'ShufflePlanes': 'clips
:clip[];planes:int[];format:int;', 'CropRel': 'clip:clip;left:int:opt;right:int:
opt;top:int:opt;bottom:int:opt;', 'SeparateFields': 'clip:clip;tff:int;', 'Rever
se': 'clip:clip;', 'StackHorizontal': 'clips:clip[];', 'Trim': 'clip:clip;first:
int:opt;last:int:opt;length:int:opt;', 'SelectEvery': 'clip:clip;cycle:int;offse
ts:int[];', 'Interleave': 'clips:clip[];extend:int:opt;mismatch:int:opt;', 'Modi
fyFrame': 'clips:clip[];selector:func;', 'Turn180': 'clip:clip;', 'Lut': 'clip:c
lip;lut:int[];planes:int[];', 'PlaneAverage': 'clip:clip;plane:int;prop:data:opt
;', 'BlankClip': 'clip:clip:opt;width:int:opt;height:int:opt;format:int:opt;leng
th:int:opt;fpsnum:int:opt;fpsden:int:opt;color:float[]:opt;', 'DoubleWeave': 'cl
ip:clip;tff:int;', 'CropAbs': 'clip:clip;width:int;height:int;x:int:opt;y:int:op
t;', 'SelectClip': 'clips:clip[];src:clip[];selector:func;', 'AddBorders': 'clip
:clip;left:int:opt;right:int:opt;top:int:opt;bottom:int:opt;color:float[]:opt;',
 'Merge': 'clips:clip[];weight:float[]:opt;'}
たとえば「Lut2の引数ってどんな感じだったっけ?」と思ったら、
>>> core.std.get_functions()['Lut2']
'clips:clip[];lut:int[];planes:int[];bits:int:opt;'
このようにcore.name.get_functions()['フィルタ名']とタイプすれば確認できます。
状況によってlist_functions()と使い分けましょう。

変更4:スクリプトに使用する文字セットをUTF-8で統一

VSScript(外部アプリケーションとの連携用API)の導入に伴い、スクリプトはUTF-8で書くように統一されたようです。
スクリプトはPythonに渡された後、UTF-8決め打ちで一旦Pythonのバイトコードにコンパイルされます。
このためUTF-8以外で日本語用文字等を使っている場合は、内部で文字化けを起こしてデコードエラーになります。
これまでは日本語ファイル名等を扱う場合はCP932で保存したほうがよかったですが、今後はUTF-8で保存するようにしましょう(もちろんBOMなしで)。
そしてもうひとつ大事なことですが、Windowsの場合、日本語ファイル名が扱えるかどうかはプラグインの実装次第となりました。
とりあえず見た感じ、avisource.dllはちゃんと対応してたけどd2vsourceはどうも現状ではダメっぽいですな。
ちなみに拙作のソースフィルタ群も軒並みダメです。ああ、直すのメンドイorz

2013年2月22日金曜日

EasyVFR for VapourSynth

なんかEasyVFRのVS版を欲しがってる人たちがいるようなので、やってみました。

easyvfr.py

使い方

まずeasyvfr.pyをPython3/Lib/site-packagesに置きます。

あとはこんな感じ
#!/usr/bin/env python3

import vapoursynth as vs
import easyvfr

vs.get_core().std.LoadPlugin('/path/to/d2vsource.dll')
d2vsrc = vs.get_core().d2v.Source

def ivtc(clip, offset, cycle=10, tff=True):
    sf = clip.std.SeparateFields(tff)
    dw = sf.std.DoubleWeave(tff)
    return dw.std.SelectEvery(cycle, offset)

def bob(clip, tff=True):
    sf = clip.std.SeparateFields(tff)
    return sf.resize.Bicubic(height=clip.height)


src = d2vsrc('/path/to/the/source.d2v')

# 適当にTrimして、デインタレ/IVTC/Bobしたりする
av0 = ivtc(src[100: 2000], [0, 3, 5, 8]) # 24fps
op0 = ivtc(src[2000: 3456], [0, 2, 5, 7]) # 24fps
op1 = bob(src[3456: 3501]) # 60fps
op2 = ivtc(src[3501: 6541], [1, 4, 6, 9]) # 24fps
a00 = ivtc(src[8000: 12000], [1, 3, 6, 8]) #24fps
a01 = src[12000: 12151] # 30fps
a02 = ivtc(src[12151: 20000], [0, 2, 5, 7]) #24fps

# av0, op0, a00の先頭フレームにチャプタを打ち、IDRフレームにしたい
av0 = easyvfr.ChapterClip(av0, 'アバンA')
op0 = easyvfr.ChapterClip(op0, 'OP')
a00 = easyvfr.ChapterClip(a00, 'Aパート')

# 各クリップをひとつのリストにまとめる
clips = [av0, op0, op1, op2, a00, a01, a02]

'''
22行目以降はこういう書き方もあり
cc = easyvfr.ChapterClip
clips = [
    cc(ivtc(src[100: 2000], [0, 3, 5, 8]), 'アバンA'),
    cc(ivtc(src[2000: 3456], [0, 2, 5, 7]), 'OP'),
    bob(src[3456: 3501]),
    ivtc(src[3501: 6541], [1, 4, 6, 9]),
    cc(ivtc(src[8000: 12000], [1, 3, 6, 8]), 'Aパート'),
    src[12000: 12151],
    ivtc(src[12151: 20000], [0, 2, 5, 7]),
]
'''

# timecodeファイル、chapterファイル、x264用QPファイルの出力、及び各クリップを結合
vfr = easyvfr.EasyVFR(clips)
vfr.write_tcq('/path/to/the/files')
#vfr.write_timecode('/path/to/the/timecode.txt')
#vfr.write_chapter('/path/to/the/chapter.txt')
#vfr.write_qpfile('/path/to/the/qpfile.txt')
vfr.splice_clips().set_output()

timecodeのフォーマットはv2のみです。
chapterファイルのエンコーディングはUTF-8になります。

追記(20160402):最近のVapourSynthにあわせてちょっと書き直しました。

2013年1月17日木曜日

GenericFilters その5

更新しました。

GenericFilters-0.4.1.7z
https://github.com/chikuzen/GenericFilters

* Convolution/ConvolutionHV/Blur:フレームが16bitだった場合の処理を修正

uint16_tとint16_tの乗算が出来る組み込み命令がないのって面倒すぎると思うんですが…。

2013年1月13日日曜日

GenericFilters その4

更新しました。

GenericFilters-0.4.0.7z
https://github.com/chikuzen/GenericFilters

* 新関数'Binarize2'を追加

Binarize2はBinarizeと同じくクリップを二値化するフィルタですが、閾値によって分けるのではなく、Sierra-2-4Aという誤差拡散法(いわゆるディザリングで使われるアルゴリズム)の一種によって処理します。

具体的には、これが
こうなります。
簡単に出来そうだったからやってみただけですので、実用性とかは気にしてはいけません。


2013年1月12日土曜日

vsimagereader その2

更新しました。

vsimagereader-0.2.0.7z
https://github.com/chikuzen/vsimagereader

* VapourSynthのAPIバージョンを3に更新
* アルファチャンネルの読み込みに対応/'alpha'オプションの追加
* 幅/高さ/出力フォーマットがバラバラな画像の読み込みに対応

以前も書きましたが、VapourSynthはavisynth等と違って各フレームの解像度やフォーマットが異なっていても扱えるようになっています。
今回の更新で、「解像度や保存形式がバラバラなものを一度に読み込んで、解像度を揃えて出力」なんてことが出来るようになりました。

一度にプラグイン4つも更新したので、大変疲れました。

GenericFilters その3

更新しました。

GenericFilters-0.3.0.7z
https://github.com/chikuzen/GenericFilters

* 関数に'Blur'を追加。

普通の3x3のぼかしフィルタです。
単なるConvolution()のaliasですな。

vsrawsource その4

更新しました。

vsrawsource-0.3.1.7z
https://github.com/chikuzen/vsrawsource

* アルファチャンネルのサポート方法を変更

vsavsreader その7

更新しました。

vsavsreader-0.1.0.7z
https://github.com/chikuzen/VS_AvsReader

* アルファチャンネルのサポート方法を変更
* 'alpha'オプションを追加

今回からこれにもバージョン番号を付けることにしました。

2013年1月6日日曜日

GenericFilters その2

更新しました。

GenericFilters-0.2.2.7x
https://github.com/chikuzen/GenericFilters

* Sobel/Prewitt: rshiftオプションを追加
* いろいろバグフィックス

ちょっとだけ内部処理の説明を書いておきます。

画像処理を行う場合、各ピクセルの値は、そのフォーマットによって扱える範囲が限定されます。
8bitフォーマットであればピクセルは0~255、16bitフォーマットであれば0~65535の範囲内に存在する値をとらなければなりません。
しかし、いろいろな計算を行った場合、出力値はしばしばこの下限/上限を超えることがあります。

この対処法として、最終的に出力する前に次のようなことが行われます。
1. 単純に、下限値/上限値で切り捨て。
2. 絶対値で扱う(値が下限値の0を超えた場合の対策)
3. 値を割ることで範囲内に収める(値が上限を超えた場合の対策)

GenericFiltersのConvolution(HV)はsaturateオプションをFalseにすれば処理2(絶対値化)が行われます。
divisorオプションによって処理3が行われます。
最後に処理1(値の切り捨て)を行うことで、出力値は範囲内に収められます。

さて、SobelとPrewittも多少工程が複雑であることを除けば単なる畳み込み演算なのですが、これらはエッジ検出に特化したアルゴリズムなので、自由度はConvolutionよりも低くなっています。
まず、処理2の絶対値化は必ず行われます。
そして、処理3の割る数は(主に処理速度の都合により)2の整数乗(rshiftで指定)に限定されています。

では今回はこのへんで

2013年1月5日土曜日

GenericFilters

VapourSynth用のプラグインを書きました

GenericFilters-0.2.0.7z
https://github.com/chikuzen/GenericFilters

名前のとおり、デジタル画像処理で一般的によく知られているフィルタ(畳み込み演算、二値化、膨張、収縮、メディアン等)の詰め合わせです。
avisynthで言えばmasktoolsにあたります。
VapourSynthは標準フィルタとして画像合成用のLut/Lut2/Expr/MaskedMergeを装備しているので、これらと組み合わせれば大幅に出来ることが増えます。

これを書くためにまず試作品としてConvo2Dを書き、ついでこれを書いたわけですが、最初はC言語オンリーだったので遅いったらありません。
とりあえず色々チュ-ニングしては見ましたが、やはり根本的な解決には至らず、今回とうとうSIMD(SSE2)に手を出してしまいました。

intrinsicなので多少はラクなのでしょうが、いや疲れたのなんのって、頭の中がバイト列の組み合わせパズルで占領されてしまいました。

UtVideoの梅澤さんに「よくアセンブラとか書けますね」って聞いてみたら、intrinsicよりMASMやNASMのほうが彼にはとってはラクなのだとか…怖いなぁ。

2012年12月14日金曜日

convo2d その3

更新しました。

convo2d-0.1.3.7z
https://github.com/chikuzen/convo2d

* 5x5マトリクスが効かなくなっていたのを修正
* ちょっと高速化

追記:
本フィルタはGenericFiltersに統合されましたので、開発/配布を終了しました。
今後はGenericFiltersをお使い下さい。

2012年12月13日木曜日

convo2d その2

更新しました。

convo2d-0.1.2-2.7z
https://github.com/chikuzen/convo2d

・入力クリップの各フレームのフォーマットが一定でない場合、及びフレームのサンプルがfloatの場合の処理を追加。

VapourSynthはAviSynthと違って、クリップの各フレームの解像度や色空間がすべて同じであるとは限りません。フレーム0は720x480のYUV420P8なのに、フレーム1は1920x1080のRGBということもありえます。
そこらへんを忘れていたので処理を追加しました。


さて、せっかく書いたプラグインなので、なにかやってみようと思います。
とりあえず簡単そうなやつで、アンシャープマスクでもいってみましょうか。

一般にアンシャープマスクと呼ばれる処理は次のようなことを行います。
1. 元画像と、それをぼかした画像を用意する。
2. 元画像とぼかした画像の差分をとる。
3. 差分をそのまま、もしくは何かしら手を加えてから元画像にかぶせる。
この結果として、なんかコントラストがきつくなったような画像が得られます。

まずは元画像を読み込みます。
import vapoursynth
core = vapoursynth.Core()
core.std.LoadPlugin('/path/to/vsimagereader.dll')
clip = core.imgr.Read('/path/to/lena.bmp')
次にぼかした画像を用意する。
core.std.LoadPlugin('/path/to/convo2d.dll')
blur = core.convo2d.Convolution(clip, [1, 1, 1, 1, 1, 1, 1, 1, 1])
二つの画像の差分をとります。
def clamp(value):
    if value < 1: return 0
    if value > 254: return 255
    return value

lut_diff = []
for y in range(256):
    for x in range(256):
        lut_diff.append(clamp(x - y + 128))

diff = core.std.Lut2([clip, blur], lut_diff, [0, 1, 2])
とりあえず今回はそのままかぶせます。
lut = []
for y in range(256):
    for x in range(256):
        lut.append(clamp(x + y - 128))
result = core.std.Lut2([clip, diff], lut, [0, 1, 2])
では元画像と結果を並べてみましょう。
last = core.std.StackHorizontal([clip, result])

差分のクリップに閾値をつけたり、値に倍率を掛けたり、あるいはぼかし方を変えてみたりしてみるのも面白いかもしれません。

しかし、これではconvo2dの使い方というよりLut2の使い方みたいですな…。

追記:
0.1.2では修正が不十分だったので、0.1.2-2に更新しました。

2012年12月10日月曜日

convo2d

VapourSynth用に空間convolutionフィルタを書きました。

convo2d-0.1.1.7z
https://github.com/chikuzen/convo2d

マトリクスを変更することによって様々な処理が行えることから、カスタムフィルタと呼ばれたりするものです。

単純な平滑化/先鋭化からエッジ検出、エンボス、ピクセルシフトといろいろ出来るので、ググってマトリクスを探してみるのもいいかもしれません。

追記:
メモリリーク、及び画像端の処理が間違ってたのを直しました。