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

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

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

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

2012年11月20日火曜日

FineSharpen for VapourSynth

VapourSynth用にFineSharpenを移植してみました。

https://gist.github.com/4111749

avs scriptの移植のサンプルにと考えて、規模的にちょうどよさそうだったのでこれを選んだわけですが、よく考えてみたらRemoveGrain、Repairというavisynthプラグインに依存してしまうので、あまりよくなかったかもしれません。

まあ、「masktoolsの代替をどうするか」の参考くらいにはなるということで。

これで使っているspline()は、こちらの方のものを参考にさせていただきました。
これだけのためにNumpyとか使うわけにもいかんからなぁ…

使い方:
gistからスクリプトをDLして、Python3.x\\Lib\\site-packages以下にfinesharp.pyという名前で保存して下さい。
import vapoursynth as vs
import finesharp

core = vs.Core()
core.std.LoadPlugin('path/to/the/ffms2.dll')
core.std.LoadPlugin('path/to/the/RemoveGrain.dll')
core.std.LoadPlugin('path/to/the/Repair.dll')
fs = finesharp.FineSharp(core)

clip = core.ffms2.Source('path/to/the/video.mp4')
clip = fs.sharpen(clip)
...

ちなみに 'print(fs.usage())' で、sharpen()の説明が読めます。

2012年11月18日日曜日

vsrawsource その3

更新しました。

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

* アルファチャンネルの読み込みに対応
* sarnum/sardenでsarを指定できるように機能追加

VapourSynth-r15以降専用です

vsavsreader その6

更新しました

vsavsreader-79a12496.7z
https://github.com/chikuzen/VS_AvsReader

* アルファチャンネルの読み込みに対応

VapourSynthはr15からAPIバージョンが3にあがりました。
それにあわせてこちらもAPIを上げたので、r15以降でないと動きません。

2012年11月16日金曜日

ある日の会話より

(Chikuzen) Doom9とかにたまに書き込んでる Mr.VacBobってastrange氏だったのか
(JEEB)     いぇす
(JEEB)     http://forum.doom9.org/showpost.php?p=1600803&postcount=501
(JEEB)     I have a new (NIH) resizer project in progress myself, but it's just for thumbnailing
           and not really worth trying to do much else with it.←リサイザー作ってるのか。まぁ、
           libavcodec等にはもうAppleのせいで触れられないんで、何か別のプロジェクトを作るのは時間の
           問題だった 笑
(Chikuzen) あら、あの人appleに就職したの?
(JEEB)     うん
(JEEB)     それであのQT用のlibavcodecうんたらが開発停止になったわけだし
(Chikuzen) なるほど、そーいうことだったのか
(Chikuzen) Perian開発終了はAppleのせいだった!
(JEEB)     あとlibavcodecの-mtに触れられないのもそれが原因だったりw
(Chikuzen) なんだ、最近ffmpeg-mt関連にあの人ほとんど触らなくなったと思ったら
(Chikuzen) GoogleなんかBBB氏を使ってガンガン進めてるのにねぇ
(Chikuzen) やっぱAppleはオープンソースの敵だよ
(JEEB)     オープンソースを敵にもなりそうな相手として扱ってるからねぇ・・・ 「外部で仕事でやった
           ような感じのプログラミングはしちゃダメダメよ(はーと)」という条件が仕事の契約に入ってるw

つくづく俺はAppleは嫌いだよ...

2012年11月2日金曜日

vsimagereader

というわけで(?)、これまで書いた4つの画像入力用プラグインを一つに統合しました。

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

いまのところ、これで読めるのはBMP、JPEG、PNG、TGAの4種類です。
いちおうそこそこ拡張しやすいように書いたつもりなので、ひょっとすると対応フォーマットが増えるかも知れません。
でも、殆どの場合、JPEGとPNGさえ読めれば、あとはどうとでもなるような気がするんですが…JPEG2000とかWebPとか、誰も使ってないでしょ?

なお、旧プラグイン4つはこれを持ちまして開発終了としますので、なにかバグをみつけても直しません。
不具合があった場合は、とりあえずこちらを試してみて、それでもダメだった場合のみ報告をお願いします。

2012年10月29日月曜日

vstgareader

結局libtgaをそのまま使うのは諦めて、コードを一部流用するほかは全部自分で書くことにしました。
まあ、そのほうがビルドしやすいし、使いやすいですからね。

vstgareader-0.1.0.7z
https://github.com/chikuzen/vstgareader

さて、これでBMP,JPEG,PNG,TGAとだいたい揃ってきたので、これらをなんとか一つに統合したいところですが…どうしようかなぁ。

2012年10月25日木曜日

libtga

PNGもなんとかなったので今度はTGAに挑戦です。

TGAを扱うライブラリにはlibtgaとその改造版のlibtga-exがあります。

で、使い方を確認するために、とりあえずこんなのを書いてみました。
/* TARGA2BGR */
#include <stdio.h>
#include <stdlib.h>

#include "tga.h"

typedef struct {
    TGA *tga;
    unsigned char *buff;
} tga_read_t;

int close(const char *msg, tga_read_t *tr, int ret)
{
    fprintf(stderr, "%s\n", msg);
    if (tr->buff) {
        free(tr->buff);
    }

    if (tr->tga) {
        TGAClose(tr->tga);
    }

    return ret;
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        return -1;
    }

    tga_read_t tr = {0};

    tr.tga = TGAOpen(argv[1], "rb");
    if (!tr.tga || tr.tga->last != TGA_OK) {
        return close("TGAOpen failed", &tr, -1);
    }

    if (TGAReadHeader(tr.tga) != TGA_OK) {
        return close("TGAReadHeader failed", &tr, -1);
    }

    fprintf(stderr, "width: %u\n", tr.tga->hdr.width);
    fprintf(stderr, "height: %u\n", tr.tga->hdr.height);
    fprintf(stderr, "depth: %d\n", tr.tga->hdr.depth);
    fprintf(stderr, "alpha: %d\n", tr.tga->hdr.alpha);

    size_t buf_size
         = tr.tga->hdr.width * tr.tga->hdr.height * tr.tga->hdr.depth >> 3;
    tr.buff = malloc(buf_size);
    if (!tr.buff) {
        return close("malloc failed", &tr, -1);
    }

    if (TGAReadScanlines(tr.tga, tr.buff, 0, tr.tga->hdr.height, TGA_BGR)
        != tr.tga->hdr.height) {
        return close("couldn't read all lines\n", &tr, -1);
    }

    char out_name[1024];
    sprintf(out_name, "%s.raw", argv[1]);
    FILE *out = fopen(out_name, "wb");
    if (!out) {
        return close("open output file failed", &tr, -1);
    }
    fwrite(tr.buff, 1, buf_size, out);
    fclose(out);

    return close("finish", &tr, 0);
}

こいつにtgaファイルを食わせてやればBGRなrawデータとして出力するわけですが、試しにtgaではないファイルを食わせるとなぜかクラッシュします。
いちおうTGAReadHeaderのところでチェックはしているみたいなんですが…はて?

で、TGAReadHeaderの部分のコードを読んでみると
int TGAReadHeader (TGA *tga)
{
    tbyte *tmp;

    if (!tga) return 0;
    __TGASeek(tga, 0, SEEK_SET);
    if (tga->off != 0) {
        TGA_ERROR(tga, TGA_SEEK_FAIL);
        return 0;
    }
    /* 中略 */
    return TGA_OK;
}
と問題なしならTGA_OKを返し、問題有りなら0を返すようになっています。
ではTGA_OKの中身は何かとtga.hのほうを読んでみると…
/* error codes */
enum {
    TGA_OK = 0,  /* success */
    TGA_ERROR,
    TGA_OOM,  /* out of memory */
    TGA_OPEN_FAIL,
    TGA_SEEK_FAIL,
    TGA_READ_FAIL,
    TGA_WRITE_FAIL,
    TGA_UNKNOWN_SUB_FORMAT  /* invalid bit depth */
};

TGA_OKが全然OKになってねえよ…そりゃクラッシュもするわ。

なんで誰もこれを直してないんだ?

2012年10月23日火曜日

vsbmpreader/vspngreader

vsjpgreaderに続いて今度はbmpとpngです。

vsbmpreader-0.1.0.7z
vspngreader-0.1.0.7z

https://github.com/chikuzen/

TurboJPEG/OSSが非常に簡単だったので「じゃあ次はlibpngだぜ、ヘヘッ」てな感じでまずvspngreaderのほうに取り掛かったわけですが、これがなんともまあ厄介なシロモノでした。

前世紀から開発継続中の非常に歴史のあるライブラリなせいか、とにかくドキュメントの情報量はやたら充実してはおりますが、これが非常に読みにくい。

サンプルコード見てみても、そこら中に#ifdef/#elseifが散りばめられており、追うのが辛い。
void*をわざわざpng_voidpにしたり、unsigned charをpng_byteはいいとしてpng_byte*がpng_bytep、png_bytep*をpng_byteppと、わざと使いにくくしてるとしか思えないtypedefの乱発もまた辛い。
おまえらそこまでアスタリスク嫌いなのかよと小一時間…。

なんとか書き上げてはみたものの、動かしてみると原因不明のクラッシュで、もうすっかりお手上げです。

で、とりあえず気分転換にとbmpのほうを半日で書き上げてみてから、ふと気づいたのがlibpngのバージョン。

それまでは最新の1.5.13を使っていたわけですが、libpng.orgの説明書き(これも無駄に冗長でわかりにくい)をよくよく読んでみると、どうも自分の目的(48bitRGB/16bitGrayscaleの読み込み)は1.2.50でもよさそうです。
で、ものは試しと1.2.50に差し替えてみたらあっさりと動きました…。

2012年10月19日金曜日

vsrawsource その2

更新しました。

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

* 新オプション 'rowbytes_align'を追加
* Windows Bitmap(.bmp)の読み込みサポート

rowbytes_alignは、画像のstrideの境界指定用です(と書いて、分かる人は説明しなくてもわかる人だよなぁ)。
とりあえず世に出回ってるファイルの殆どはwidth==strideか、さもなくばDWORD(4バイト)境界のどちらかだと思いますので、1か4のどちらかにすればいけると思います。
まあ、中にはv210みたいに128バイトにアライメントされてるクソ仕様もありますが…どのみち対応する気もないから別にいいやってことで。ffms2で読めるし。

2012年10月16日火曜日

vsjpgreader その2

更新しました。

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

*デコード時の幅の計算を間違えていたのを修正。

ところでこれに関するバグ報告をDoom9でもらったついでにわかったのですが、どうも現時点ではVSFSはwidthがmod8でないと変になるようです。

2012年10月14日日曜日

vsjpgreader

VapourSynth用のJPEG入力プラグインを書きました。

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

追記:
JPEG圧縮時のYUV変換がYUV420/YUV440だった場合の高さが奇数の際の処理を忘れていたので修正しました。


JPEGの読み込みはffms2でも出来ますし、vsavsreaderを使えばavisynthのimagesourceも利用できますが、前者は読み込むファイルの数だけffindexが作られるのがウザすぎるし、後者はWindowsでしか動きません。
imagemagickとか使えばJPEGだけでなく色々な形式に対応できるんだろうけど、なんか巨大すぎてわけわからんし、「ぽーたびりてぃ」ってもんに欠けるような気がしたので、よさげなライブラリはないものかと探してみたら、TurboJPEG/OSSなるものが見つかりました。

いやぁ、これ、いいっすねぇ。
ヘッダも小さくすっきりしててほんの数時間で完読できるし、それでいて必要な機能はあらかた揃ってるじゃないですか。それにlibjpeg-turboのラッパーだから、SIMD化も進んでいてスピードも文句なし。

ヘッダ読みながらこんなコードを書いてみて感じもつかめたので、そのまま一気にVSプラグインに仕上げました。

2012年10月12日金曜日

HBVFWSource.dll その3

更新しました。

HighBitDepth_VFWSource-0.2.2.7z
https://github.com/chikuzen/HighBitDepth_VFWSource

マクロをinline関数に変更した際にとってもおバカさんなことをしておりました…orz

2012年10月11日木曜日

vsrawsource

rawsource.dllをVapourSynth用に一から書き直しました。

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

一応VSがサポートしている全色空間で使えるようになっています。

ところでMacOS X用にビルドする場合、configureはどう書けばいいんでしょうかね?
最近Wipple氏が音信不通なので、相談相手がおりません。

2012年10月7日日曜日

HBVFWSource.dll その2

更新しました。

HighBitDepth_VFWSource-0.2.1.7z
https://github.com/chikuzen/HighBitDepth_VFWSource

* Dither stacked formatでの出力をサポート

stacked formatでの出力はinterleaved formatよりも遅くなります。
特に必要でない場合はinterleaved formatで使って下さい。

追記:
0.2.1に更新。
stacked formatが少し速くなりました。

2012年10月6日土曜日

vsavsreader.dll その5

更新しました。

vsavsreader-d219012.7z
https://github.com/chikuzen/VS_AvsReader

* 入力がRGB24/RGB32の場合のplaneの順番をG->B->RからR->G->Bに変更。

vsavsreaderは入力するavsがRGB24/32の場合、planar-RGBに変換します。
これはVSがpacked-RGBで展開することを禁止しているためです。

さて、RGB24/32をplanarに変換すること自体はとても簡単なのですが、問題はR,G,B各planeをどの順番でVSに渡すべきかです。
VapourSynth.hを読む限りでは特にどの順番で渡すのが既定なのか書かれていませんし、AviSynthのplanar-YUVのようにPLANAR_RとがPLANAR_Bのようなマクロも定義されていません。
で、とりあえずswscaleにあわせてG->B->Rの順番にしていましたが、つい先程vfw経由でプレビューしてみたら、どうやらR->G->Bで渡すのが標準のようでした。

ついでに今回から要求スペックをSSE2以上に上げました。
これはVSの大きな特徴であるマルチスレッド処理を活かすなら、最低でもCore2が要求されるからです。
と言っても、ビルド時のオプションで-msse2をつけただけなので、どうしてもPentium3やAthlonXPで使いたい人は、自分でオプションいじってビルドして下さい。

追記:
Myrsloik氏の公式発表により、VapourSynthはSSE2が使えるCPUを最低ラインとすることが決定しました。というわけで、Pen3/AthlonXPは完全にさようならです。

2012年10月5日金曜日

vsavsreader.dll その4

vsavsreaderは最初からdither hackのinterleaved formatに対応していたわけですが、readmeにはそのことについては何も書いていませんでした。
これは単にreadmeに書くのが非常にめんどかったので放置していただけなわけですが、この度Doom9のほうで奇特にも書いてくれた人がいましたので追加しました。

vsavsreader-8c6c94a.7z
http://github.com/chikuzen/VS_AvsReader

いやはや、ありがたいことです。

P216改めP21x VFW Reader for AviUtl

HBVFWSourceのほうをいろいろいじっているうちに、P210やP010といったフォーマットについて誤解していたことに気づきました。
P210は10bitYUVだと思い込んでいたのですが、こちらをよくよく読んでみると
The 10-bit formats also use 16 bits for each channel, with the lowest 6 bits set to zero,
as shown in the following diagram.
Because the 10-bit and 16-bit representations of the same YUV format have the same memory layout, it is possible to cast a 10-bit representation to a 16-representation with no loss of precision. It is also possible to cast a 16-bit representation down to a 10-bit representation.
と、まあ、P210/P010は10bitYUVを左に6bitシフトして16bitYUVにしたものでした。

たしかにそのほうが色々扱いやすいわけですな。

で、P210もP216と全く同じ計算式でYC48に変換できることになりますので、P210も読めるように変更しました。

P21x_VFW_Reader_for_AviUtl-0.2.0.7z
https://github.com/chikuzen/P216_VFW_Reader_for_AviUtl

P210も読めるのであれば名前がP216 Readerってのもなんかおかしいので、ついでに名前をP21x VFW Readerに変更しましたが、gitレポはP216のままと、だんだんカオスなことに…まあ、いいか。

2012年10月4日木曜日

HBVFWSource.dll

P216 VFW ReaderはAviUtl用ですが、こちらはAviSynth用です。

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

P010/P016/P210/P216なクリップをVFW使って読み込みます。
読み込まれたクリップはdither hackのinterleaved formatの状態になります。

色変換の手間が要らないのとplanarフォーマットをサポートしてる分、やっぱAviSynthのほうが書きやすかったです。

追記:
やはり色々まずいところがあったようです。
0.1.3を上げたのですでにDLしてしまった人は差し替えて下さい。

P216 VFW Reader for AviUtl

VapourSynthがVFWでP010/P016/P210/P216を出力するようになりました。
これをなるべく高品質でプレビューしたくなったので、AviUtl用の入力プラグインを書きました。

P216_VFW_Reader_for_AviUtl-0.1.0.7z
https://github.com/chikuzen/P216_VFW_Reader_for_AviUtl

使い方はreadmeのほうで。
色変換のコードをいろいろ書くのがメンドイので、P216専用です。
音声もVSが対応してないのでこっちも無視です。

それにしてもVFWって息が長いですね。
最後に仕様が改訂されたのはWindows95の時代ですよ…。

2012年10月1日月曜日

vsavsreader.dll その3

更新しました。

vsavsreader-4dafb1b.7z
https://github.com/chikuzen/VS_AvsReader

*vsavsreaderで作ったクリップをSplice()で結合出来なかったりしたのを修正

2012年9月30日日曜日

VFW module

VapourSynthのr10で新たにVFWモジュールが追加され、Video for Windows(又はDirectShow)を利用してAVIを読み込むソフトウェアでVSのスクリプトを読み込むことが出来るようになりました。
これによりAvsPmodやVirtualDub等でプレビューできるようになったので、この前書いたプレビュー用スクリプトは(Windowsでは)早くも用済みとなった次第です。いや、大変喜ばしい。

この記事はVapourSynth r18までを対象としています。
r19以降では使い方が一部変更されたので、この記事のとおりではうまくいきません。


VFWモジュールの使い方

1.スクリプトを書く

#sample.vpy
import vapoursynth as vs
core = vs.Core()
core.std.LoadPlugin('C:/foo/bar/ffms2.dll')
clip = core.ffms2.Source('D:/hoge/fuga/video.mp4')
clip = core.resize.Bicubic(clip, clip.width. clip.height, vs.YUV420P8)
last = clip

注意する点

・スクリプト名は普通のPythonのように拡張子".py"ではなく、".vpy"として保存すること。

・VFWモジュールを使う場合、出力先に渡すクリップのインスタンス名は必ず'last'とすること。
'last'以外の名前のクリップは渡せません。

・プラグインやソースファイルの読み込みは、ファイルパスを必ずフルパスで記述すること。
相対パスで扱うのは失敗のもとです。

・VFWで渡せるクリップのフォーマットは8bitのみで、10bitや16bitのものは渡せません。
これはVideo for Windowsが8bit以上のものを扱うことを想定していないことによる制限です。

なお、RGBでもVSのRGB24はplanarフォーマットなので、これも使えません。RGBの場合はCOMPATBGR32に変換する必要があります。

・VFWモジュールを使う際は、クリップのoutputメソッドは使えません。

2.読み込ませる


・AviSynthの場合

AviSynthにvpyを読みこませる場合はAVIFileSource(又はAVISource)を使います。


・VirtualDubの場合

通常のaviやavsと違ってVirtualDubではvpyをD&Dで読み込ませることはできません(拡張子vpyがVirtualDubには登録されていないため)。
VirtualDubのメニューから
File -> Open video file
-> 読み込ませるファイルをダイアログで指定 -> 'ファイルの種類'を'AVIFile input driver(compat)(*.avs,*vdr)'に変更 -> '開く’
と、現時点ではちょっと面倒な手順が必要になります。

追記:
こちらの最新テスト版から、vpyを直で開けるようになりました。

・AviUtlの場合

AviUtl0.99k以降であれば、内蔵の'AVI File Reader(Video for Windows)'、または'DirectShow File Reader'でvpyを読み込むことができます。
ただし、AviUtlですので、クリップのフォーマットはCOMPATYUY2に変換しておきましょう。
なお、vpyになんらかの誤りがあった場合(ファイルパスを相対パスで書いたため、読み込みで不備がおこったとか)、AviUtlが固まってエクスプローラごと落ちて一旦Ctrl+Alt+Delでログオフしなければならなくなったりますので、くれぐれも注意しましょう。


・avconv/ffmpegの場合

avconv/ffmpegはAVIの読み込みにはVFWを使用しませんが、avsの読み込みはVFWを使用します。
よって、これを利用すればvpyをpipeを使わずに渡すことができます。
avconv.exe -f avs -i "d:/herp/derp/sample.vpy" -c:v utvideo out.avi
もっとも、こういったコマンドラインツールはPythonスクリプトから操作するなりしたほうがラクだと思います。
10bitYUVとかの入力にも対応してるし。

追記:
2013年3月以降、ffmpeg.exeではこの方法は使えなくなりました(avs読み込みにVFWを使わなくなったため)。
avconv.exeならばまだ使えます。

2012年9月27日木曜日

vsavsreader.dll その2

更新

vsavsreader-10c62ad.7z
https://github.com/chikuzen/VS_AvsReader

更新といっても中身は変わってないのですが、VapourSynthがr9でAPIのバージョンを上げたのでいままでものは使えなくなりました。
いろいろとバグがとれたりしてるようなので、使ってる人はVSと一緒に更新して下さい。

追記:
ちょっとまずいところがあったようなので、修正版に差し替えました。

2012年9月21日金曜日

VapourSynthでプレビュー

とりあえず現時点ではVapourSynthでクリップのプレビューが出来るエディタ等はないので、間に合わせで書いてみました。

#vsshow.py

import vapoursynth as vs
from ctypes import *
from PIL import Image

def show(core, clip, frame_number, vflip=1):
    format = clip.format.id
    width = clip.width
    height = clip.height
    if format == vs.GRAY16:
        clip = core.resize.Point(clip, width, height, vs.GRAY8)
    if format != vs.GRAY8 and format != vs.RGB24:
        clip = core.resize.Bicubic(clip, width, height, vs.COMPATBGR32)

    format = clip.format.id
    planes = range(clip.format.num_planes)
    frame = clip.get_frame(frame_number)
    data = [(frame.get_read_ptr(p), frame.get_stride(p)) for p in planes]
    buff = [b'\0' * data[p][1] * height for p in planes]
    for p in planes:
        memmove(buff[p], data[p][0], data[p][1] * height)

    if format == vs.COMPATBGR32:
        mode, src = 'RGBA', 'BGRA'
    else:
        mode, src = 'L', 'L'
    img = [Image.frombytes(mode, (width, height), buff[p], 'raw', src,
                           data[p][1], vflip) for p in planes]
    if len(planes) != 1:
        img = Image.merge('RGB', (img[2], img[0], img[1]))
    else:
        img = img[0]

    img.show()

使い方
>>> import vapoursynth as vs
>>> import vsshow
>>> core = vs.Core()
>>> core.std.LoadPlugin('vsavsreader.dll')
>>> clip = core.avsr.Eval('ColorBars()')
>>> vsshow.show(core, clip, 1234)

こんな感じで指定したフレーム番号のフレームがプレビューできます。
vsshow.pyはvapoursynth.pydと同じ場所に置いておけばいいです。
ビューワーにはPIL(Python Imaging Library)の仕様上、.bmpファイルに関連付けされているアプリケーションが使われます(MSペイントだったりPicasaだったりMangameeyaだったり)。
なお、Python3用のPILは、こちらからDLしてインストールして下さい。
フレームがYUVの場合のRGB変換はVS内蔵のswscaleが使われる都合上、BT601のlimitted-range限定です。
色々ひどいと我ながら思いますが、ないよりはマシくらいのおおらかな気持ちで使いましょう。

だれかPySideあたりでもっといいの書いてくれないかしら。


Pythonはいろいろ不慣れでこれだけでも苦労しました(おかげでPythonの学習自体ははかどりましたが)。
Twitterでnu774氏(qaacの中の人)に色々アドバイス頂いたおかげで随分助かりました。

vsavsreader.dll

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

vsavsreader-8e7f46c.7z
https://github.com/chikuzen/VS_AvsReader

使い方
readmeに具体例を書いてあるので、そちらを読んで下さい。

必要なもの
VapourSynth-r8以降
AviSynth2.6alpha3以降

VapourSynthはAviSynth用プラグインをVapourSynthプラグインとして使えるようになってはいますが、対応しているものは2.5x用のもので、2.6以降専用のプラグインは使えません。
また、2.5x用のプラグインでもAviSynthの内蔵フィルタを利用しているものは出力が若干違ったり(DGDecodeで読み込むと、縦1088が1080にCropされないとか)します。
これを使うことにより、RawSource26.dllのような2.6以降用のプラグイン等もVapourSynthで活用できるようになりますし、DGDecodeを使った入力でもCropを改めて掛ける必要はなくなります。

あくまでもVapourSynthでavsを読み込めるようにするだけなので、勘違いはしないよう注意して下さい。

2012年9月12日水曜日

VapourSynth

巷で話題沸騰中?のVapourSynthを、ここ数日ちょこちょこいじっております。

VapourSynthとはffms2でお馴染みのMyrsloikことFredric Mellbin氏によって開発されているもので、
・動画を扱うためのPython3用モジュール。
・AviSynth2.5のプラグインをマルチスレッドで利用できる。
といった特徴があります。

早い話がAviSynthの開発が過去との互換性やらinline-asmやらでMT化も64bit化も一向に進まないので、新しいAviSynthを作ってしまったわけですな。

とりあえず現時点ではまだまだ試作段階ですが、MVTools2やTIVTC(mode次第では)がマルチスレッドで安定して動くあたりを見るに、かなり期待できそうです。

というわけで、試しにこんなのを書いてみました。

#!/bin/env python3
#coding: utf-8

import vapoursynth as vs
import subprocess as sp

class RawSource(object):
    def __init__(self, core, path='C:\\AviSynth2\\plugins\\rawsource_2.5x.dll'):
        self.avs = core.avs
        if core.list_functions().find('RawSource') == -1:
            self.avs.LoadPlugin(path=path)

    def rawsource(self, file):
        return self.avs.RawSource(file=file)


class MVTools(object):
    def __init__(self, core, path='C:\\AviSynth2\\plugins\\mvtools2.dll'):
        self.avs = core.avs
        if core.list_functions().find('MSuper') == -1:
            self.avs.LoadPlugin(path=path)

    def superclip(self, clip, pel=2, chroma=True, sharp=2):
        return self.avs.MSuper(c1=clip, chroma=chroma, sharp=sharp)

    def analyseclip(self, super, delta, blksize=8, overlap=4):
        return [[self.avs.MAnalyse(
            c1=super, delta=d, blksize=blksize, isb=b, overlap=overlap)
            for b in (True, False)]
            for d in range(1, delta + 1)]

    def mdegrain1(self, clip, pel=2, chroma=True, sharp=2, blksize=8,
                  overlap=4, thSAD=400):
        super = self.superclip(clip, pel, chroma, sharp)
        vec = self.analyseclip(super, 1, blksize, overlap)
        return self.avs.MDegrain1(
            c1=clip, c2=super, c3=vec[0][0], c4=vec[0][1], thSAD=thSAD)

    def mdegrain2(self, clip, pel=2, chroma=True, sharp=2, blksize=8,
                  overlap=4, thSAD=400):
        super = self.superclip(clip, pel, chroma, sharp)
        vec = self.analyseclip(super, 2, blksize, overlap)
        return self.avs.MDegrain2(
            c1=clip, c2=super, c3=vec[0][0], c4=vec[0][1], c5=vec[1][0],
            c6=vec[1][1], thSAD=thSAD)

    def mdegrain3(self, clip, pel=2, chroma=True, sharp=2, blksize=8,
                  overlap=4, thSAD=400):
        super = self.superclip(clip, pel, chroma, sharp)
        vec = self.analyseclip(super, 3, blksize, overlap)
        return self.avs.MDegrain3(
            c1=clip, c2=super, c3=vec[0][0], c4=vec[0][1], c5=vec[1][0],
            c6=vec[1][1], c7=vec[2][0], c8=vec[2][1], thSAD=thSAD)

    def mflowblur(self, clip, pel=2, chroma=True, sharp=2, blksize = 8,
                   overlap=4, blur=15):
        super = self.superclip(clip, pel, chroma, sharp)
        vec = self.analyseclip(super, 1, blksize, overlap)
        return self.avs.MFlowBlur(
            c1=clip, c2=super, c3=vec[0][0], c4=vec[0][1], blur=blur)


if __name__ == '__main__':
    core = vs.Core(threads=4)
    rs = RawSource(core)
    mvt = MVTools(core)
    clip = mvt.mdegrain2(rs.rawsource("D:\\source\\derf\soccer.y4m"))
    cmd = ('D:\\tools\\x264.exe - --demuxer y4m --frames %i'
           ' --preset ultrafast -o output.mp4' % clip.num_frames)
    clip.output(sp.Popen(cmd.split(), stdin=sp.PIPE).stdin, y4m=True)

for文やif文、リスト内包表記、そして様々な他のPythonモジュール等が使えるあたり、可能性はAviSynthよりも高そうです。
それにPythonですから、avsと違って仕事や学業等にも汎用的に使えます。
憶えて損はないでしょうね。

2012年9月2日日曜日

avs2pipemod その17

更新しました。

avs2pipemod-0.4.2.7z
https://github.com/chikuzen/avs2pipemod

*エラーチェックを少し強化

avisynthからフレームを受け取った際に-benchmark以外のモードなら、エラーチェックをするようにしました。

追記:
どうも解凍時に不具合が出る人がいるようなのでzip版も上げてみました。
avs2pipemod-0.4.2.zip

2012年9月1日土曜日

RawSource.dll その7

久しぶりに更新

rawsource_26_dll_20120831.zip
https://github.com/chikuzen/RawSource_2.6x

*読み込み用バッファサイズを最大1フレーム分に拡張しスピードアップ。

これまで1ライン分のデータをファイルから読み込んではメモリに書き出していたのをやめて、なるべくBitBltでまとめて処理するようにしました。
手元の計測では201109版に比べてPlanarフォーマット及びBGR/BGRA/YUY2の場合、4~7倍くらい速くなりました。

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で済ませている四隅の部分もしっかり鏡像にしている芸の細かさを見せてくれます。

2012年5月11日金曜日

Benchmark plugin

AviUtl用のベンチマークプラグインを書きました。

benchmark_for_aviutl-0.1.0.zip
https://github.com/chikuzen/benchmark_for_aviutl

1. 出力プラグインです
2. 基本的な挙動はあじ氏のNULL出力プラグインと似ていますが、ベンチマークの結果をテキストファイルとして出力します。
3. aviutl.exeと同じディレクトリに作られるbenchmark.iniをいじることで、出力色空間と繰り返し回数を指定できます。

例えばbenchmark.iniを
output_format=1
repeat=3
としておけば
date                      : Fri May 11 22:16:45 2012
output colorspace         : YUY2
resolution(width x height): 720 x 480
frames                    : 13805
total proc time(msec)     : 45996
average proc rate(fps)    : 300.135


date                      : Fri May 11 22:17:31 2012
output colorspace         : YUY2
resolution(width x height): 720 x 480
frames                    : 13805
total proc time(msec)     : 45767
average proc rate(fps)    : 301.637


date                      : Fri May 11 22:18:17 2012
output colorspace         : YUY2
resolution(width x height): 720 x 480
frames                    : 13805
total proc time(msec)     : 46080
average proc rate(fps)    : 299.588
みたいなテキストが出力されます。

なお、出力先のファイルは追記モードで開くようにしています。

2012年5月7日月曜日

AvsReader その9

更新しました。

AvsReader-0.7.1.zip
https://github.com/chikuzen/AvsReader/

* DGDecode.dllのパスの指定を設定に追加。
* DGDecode.dllがオートローディングフォルダになく、パスの指定もない場合は、DGVfapi.vfpと同じ場所を自動で検索するように変更。
* d2v_cpu2のデフォルト値を'指定なし'に変更。

DGDecode.dllの検索に関してmaki-rxrz氏のアイデアを拝借しました。
レジストリのVFAPIプラグイン登録情報を頼りに捜すので、DGIndex.exeのVFAPI登録を無効にしている場合はこれは効きません。
また、DGMPGDecのパッケージ内からDGDecode.dllを動かしている場合も、自動検索はできません。

更新するたびにもうネタ切れだろうと思ってるんですが、けっこう出てくるもんなんだな…。

追記: バグが一つ見つかったので、0.7.1に更新しました。

2012年5月3日木曜日

AvsReader その8

更新しました

AvsReader-0.6.1.zip
https://github.com/chikuzen/AvsReader/

* d2v読み込み時の設定をいろいろ追加
* DVD2AVIのd2vの読み込みを、デフォルトで無効化

maki-rxrz氏が面白そうなブランチを作っていたので取り込んでみました。
d2vを読み込んだ場合は、AviUtlでキーフレームのシークが出来ます。
ただし、あくまでもavisynthを通して読み込んでいるので、MPEG1/2クリップの直接編集が出来るわけではないことに注意して下さい。

あと、DVD2AVIのd2vは弾くようにしました(自環境で試しに使ってみたところ、システム例外発生でMPEG2Dec3が動かなかったため)。
DVD2AVI用のd2v読み込みもコード自体は存在しますので、使ってみたい人は自ビルドしてみてください。

2012年5月2日水曜日

AvsReader その7

更新しました

AvsReader-0.5.1.zip
https://github.com/chikuzen/AvsReader/

AviUtl0.99lがリリースされ、その4に書いたAviUtl0.99k2のバグが直ったので、こちらも右端のピクセルのコピーを外しました。
AviUtlを0.99lに更新しない場合は、引き続き0.5.0を使って下さい。

2012年5月1日火曜日

AvsReader その6

更新…

AvsReader-0.5.0.zip
https://github.com/chikuzen/AvsReader/

* プラグイン設定に d2v_upconv と yuy2converter を追加。

なんというか、なにか思いつくたびに更新するのはそろそろ控えたほうがいいような気はしますが…。
今回の更新で、DVD2AVIのd2vでもいけるようになりました(要MPEG2Dec3k)。
でも、いまどきあれを使ってる人は、知っている中ではTheRyuu氏くらいだな…。
なんでもDVDエンコにはDGよりもいいんだとかなんとか…ホントかね?

使い方はreadmeを読んで下さい。
読んでもわからない人は、freenodeの#L-SMASH まで来てくれれば、直接いろいろ教えます。
(午後8時以降~0時くらいなら多分います)

2012年4月30日月曜日

AvsReader その5

更新しました。

AvsReader-0.4.1.zip
https://github.com/chikuzen/AvsReader/

*新機能: d2vの読み込みに対応。

だいたい終わりとか言いながら、同じ日に二度目の更新…。
早い話がavsを書かなくてもd2vをそのまま読めるようにしました。
DVD2AVI/MPEG2Dec3はupConvオプションがないので、非対応です。

追記:奇数解像度のRGB入力を壊してたので上げ直しました。

AvsReader その4

更新しました。

AvsReader-0.3.0.zip
https://github.com/chikuzen/AvsReader/

* 新機能: adjust_audio_length
自動的に内部でTrim(0, 0)を追加することで、映像と音声の尺を合わせます(デフォルトは有効)。

* widthが奇数なYV24/Y8の読み込みを変更
その2で書いたBT.709での映像崩壊を避けるため、右端のピクセルをコピーして偶数になるようにした。

これで、このプラグインでやれそうなことは、だいたい終わったかな。

追記:
widthが奇数なYC48での問題はAviUtl側の不具合ということでした(KENくん氏のお返事より)。
0.99k3か0.99lで直るそうなので、直ったらもとに戻すつもりです。

AvsReader その3

更新しました。

AvsReader-0.2.0.zip
https://github.com/chikuzen/AvsReader/

* dither hack(interleaved format/16bit)のサポートを追加

使い方等はreadmeを読んで下さい。

16bitYUV->YC48変換は結構適当ですが、
 輝度は4096(16<<8) -> 0、60160(235<<8) -> 4096
 色差は4096(16<<8) -> -2048、61440(240<<8) -> 2048
になるようにしています。

2012年4月29日日曜日

AvsReader その2

更新しました。

AvsReader-0.1.1.zip
https://github.com/chikuzen/AvsReader/

* widthが奇数なRGBの読み込みを修正。
* YV24/Y8なクリップはYC48に変換するように変更。
これにより奇数解像度にも対応(したつもり)。

YV24/Y8->YC48はBT.601限定です。
widthが奇数のYV24なクリップを読み込んで、AviUtlの色変換設定の入力をBT.709にすると、何故か表示が崩れます。
入力設定がBT.601だと問題ないので、多分AviUtl側の問題だとは思うのですが…どうなんだろ?

2012年4月28日土曜日

AvsReader

AviUtlは0.99k以降、内蔵のAVI File Reader(vfw) でavsを読めるようになりましたが、それとは別に新しく入力プラグインを作ってみました。

AvsReader-0.0.2.zip
https://github.com/chikuzen/AvsReader/

VFWを使わないでavsを読める入力プラグインは他にDirectShow File ReaderとDGMPGDecのD2V/AVS Readerがありますが、avisynth.dllを直接叩くものは多分これだけでしょう。
まあ、中身はavs2pipemodでやってることとたいして変わりませんけど…。

追記
無音のavsを読ませるとAviUtlが固まるのを修正しました。
0.0.1をすでにDLしてしまった物好きな方(5名ほどいるらしい)は、0.0.2を落としなおして下さい。

2012年4月8日日曜日

xorshift

アルゴリズムの勉強とかしてると、試しに書いてみたコードを走らせるためにたくさんのサンプルデータが必要になる。
で、標準関数のrand()を使って要素数1000個とか10000個とかの配列をでっち上げたりしていたわけですが、ここで問題にぶち当たりました。
C言語のrand()は0以上RAND_MAX以下の擬似乱数を発生させるわけですが、mingw-gccだとこのRAND_MAXの値が0x7fff(32767)しかありません。
これではもっと大きい数を扱いたい場合に色々と不便なので、「もっとよい乱数生成関数はないかいな」と探してみたところ、xorshiftというのがなかなか良さそうです。

参考:良い乱数・悪い乱数

なんでもやたら速いうえに周期が128bitもあるそうなので、uint64_tな範囲でも問題なく扱えそう。

というわけで書いてみました。
/* xorshift.h*/
#ifndef XOR_SHIFT_H
#define XOR_SHIFT_H

#include <stdint.h>

uint64_t *srand_xs64(void);
uint64_t rand_xs64(uint64_t *x);
void close_xs64(uint64_t *x);

#endif
/* xor128.c*/
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include "xorshift.h"

uint64_t *srand_xs64(void)
{
    uint64_t *x = (uint64_t *)malloc(sizeof(uint64_t) * 4);
    if (!x)
        return NULL;

    uint64_t s;
    do {
        s = (uint64_t)(time(NULL));
    } while (!s);

    *x = (s << 32) | s;
    *(x + 1) = (*x << 8) | ((*x & 0xff00000000000000) >> 56);
    *(x + 2) = (*(x + 1) << 8) | ((*(x + 1) & 0xff00000000000000) >> 56);
    *(x + 3) = (*(x + 2) << 8) | ((*(x + 2) & 0xff00000000000000) >> 56);

    return x;
}

uint64_t rand_xs64(uint64_t *x)
{ 
    uint64_t t = (*x ^ (*x << 11));
    *x = *(x + 1);
    *(x + 1) = *(x + 2);
    *(x + 2) = *(x + 3);
    *(x + 3) = (*(x + 3) ^ (*(x + 3) >> 19)) ^ (t ^ (t >> 8));
    return *(x + 3);
}

void close_xs64(uint64_t *x)
{
    if (x) {
        free(x);
        x = NULL;
    }
}
#include <stdio.h>
#include <inttypes.h>
#include "xorshift.h"

int main(void)
{
    uint64_t *seed = srand_xs64();
    if (!seed)
        return -1;

    for (int i = 0; i < 100000)
        printf("%"PRIu64"\n", rand_xs64(seed));

    close_xs64(seed);
    return 0;
}

とりあえず10万個程度の乱数を発生させてみてもダブりがひとつも出ないし(当たり前なんだろうけど)、スピードも速いみたい。
素晴らしい!

2012年4月7日土曜日

BucketMedian その2

更新。
パラメータを追加しました。

BucketMedian-0.2.0.zip
https://github.com/chikuzen/BucketMedian/

新パラメータ int "thresh"

ただのメディアンフィルタだと、radiusをちょっと高くしただけでも酷いことになるので、しきい値をもう一つ追加しました。
ソースのピクセル値と算出したメディアンの差がこの値以下の場合のみ、ピクセル値をメディアンに変更します。

これを5くらいにしておくと、なんかエッジをぼかさずにゴミがよく取れるようです。

2012年4月4日水曜日

BucketMedian

ここ一週間ほど基本的なアルゴリズムとかを勉強していたらなにか書きたくなったので、基本中の基本なメディアンフィルタを書いてみました。

BucketMedian-0.1.0.zip
https://github.com/chikuzen/BucketMedian/

メディアンを求めるのにバケツソートをちょっと応用してみましたが、あまり速くないですな。

使い方:
AVISource("herpderp.avi")
BucketMedian(clip c, int "radius", int "min", int "max")
clip:
 planar format(YV12,YV16,YV24,YV411,Y8)のみ対応。
 処理は輝度のみで、色差はまったくいじりません。

radius (1~255, デフォルト1): 参照する近傍ピクセルの半径。
 処理するピクセルを中心として、1だったら3x3、nだったら(2n+1)x(2n+1)の
近傍ピクセルのメディアンを求め、入れ替えます。
 大きくすればするほどボケて、遅くなります。

min (0~254, デフォルト0): しきい値の下限。
 これよりもピクセルの輝度が低い場合、そのピクセルはメディアンを求めず、そのままコピーします。

max (1から255, デフォルト255): しきい値の上限。
 これよりもピクセルの輝度が高い場合、そのピクセルはメディアンを求めず、そのままコピーします。

必要なもの:
 avisynth2.5.8以降
 Microsoft Visual C++ 2010 再頒布可能パッケージ

2012年3月14日水曜日

avs2pipemod その16

更新しました。
avs2pipemod-0.4.1.7z
https://github.com/chikuzen/avs2pipemod/

*'x264raw(tc)'のエラーハンドリングを修正。

たくあん氏からバグ報告いただきました。ありがとうございました。

しかしなんですな
http://k4095-takuan.blogspot.com/2012/03/batbash.html
やっぱ、batって色々と難しすぎると思うのですよ。
コマンドラインが苦手という人の理由の8割は、無理にコマンドプロンプト/batを使おうとしているからだと思います。

2012年3月4日日曜日

avs2pipemod その15

更新しました。
avs2pipemod-0.4.0.7z
https://github.com/chikuzen/avs2pipemod

* 'dumpyuv'を'dumptxt'に変更し、YUY2やRGBに対応。

やっぱ、どうせやるならpacked formatもサポートしとくべきだよなぁとか思ったので。

2012年2月18日土曜日

avs2pipemod その14

更新しました。

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

*新機能 "dumpyuv" を追加

これを読んでたら自分も欲しくなったので、機能を追加しました。
RGBデータを扱いたい場合は
ColorBars()
Y=ShowGreen("Y8").ConvertToYV24()
U=ShowBlue("Y8")
V=ShowRed("Y8")
YToUV(U, V, Y)
とでもして下さい。

2012年2月16日木曜日

avs2pipemod その13

更新しました。

avs2pipemod-0.2.2.7z
https://github.com/chikuzen/avs2pipemod

*x264raw/x264bdの出力内容に"--frames"を追加

rawvideo出力等で"-trim=*,*"を使っている場合は、こちらも同様にtrimを追加して下さい。
両方で同じ指定をしないと、フレーム数の整合がとれなくなります。

2012年2月5日日曜日

Pythonで関数の引数の順序を入れ替える

メモ替わり

L-SMASHのコードで使われている関数のいくつかについて、引数の順序を入れ替える必要が発生した。

こういった作業はIDEを使っていれば簡単にできるのかも知れないが、何かしら書くときはいつもテキストエディタを使い、コンパイルはCLI操作で済ませているので、使い方がいまだにさっぱり分からない。
かといっていちいち関数名で検索しては手作業で修正するのも面倒なので、久々にPythonを書いた。


制限事項:
一つの関数の記述が複数行にまたがる場合
int var = ItIsNoNeedToCareAboutTheLengthOfFunctionNamesBecauseEverybodyUsesIDE(foo,
                                                                               bar,
                                                                               fizz,
                                                                               buzz,
                                                                               bleh,
                                                                               fuck);
は処理できないので、warningを頼りに手動で修正すること。

2012年1月30日月曜日

avs2pipemod その12

更新しました。

avs2pipemod-0.2.1.7z
https://github.com/chikuzen/avs2pipemod

*新機能'x264raw','x264rawtc'を追加

最近はdither hackやらx264がRGB圧縮に対応したりやらのおかげで、y4m出力の代りにrawvideo出力を使う頻度が結構高くなりました。
YUV4MPEG2は仕様で「使えるフォーマットは8bitのplanarYUVのみ」と決められているので、これらのためには使えないのです。
で、仕方がないので
$ avs2pipemod -rawvideo=vflip input.avs | x264_x64 - --demuxer raw --input-csp bgr --input-res 1280x720 --output-csp rgb --fps ...
とかやるわけですが、長いコマンドがいい加減めんどうなので省力化することにしました。

使い方
$ avs2pipemod -x264raw 1280x720_30fps_rgb24.avs
てな感じのコマンドを実行すると
- --demuxer raw --input-csp bgr --input-depth 8 --input-res 1280x720 --fps 30/1 --output-csp rgb
と標準出力に出力されます。

dither packageやflash3kyuu_debandのinterleaved出力を使う場合は、引数でinput-depthを指定するとwidthを1/2にしたコマンドラインが出力されます。
$ avs2pipemod -x264raw=16 2560x719_ntsc-film_16bit_yv24.avs
- --demuxer raw --input-csp i444 --input-depth 16 --input-res 1280x719 --fps 24000/1001 --output-csp i444
'x264rawtc'は、--tcfile-inを使う場合のためのもので、"--fps fpsnum/fpsden"は出力されません。
$ avs2pipemod -x264rawtc 640x480_yv411.avs
- --demuxer lavf --input-fmt rawvideo --input-csp yuv411p --input-depth 8 --input-res 640x480 --output-csp i422
筆者のようにCLIにCygwinやMSYSのBash/shell scriptを使っている人なら
$ avs2pipemod -rawvideo input.avs | x264 $(avs2pipemod -x264raw=16 input.avs) --crf 22 --preset slower ...
てな感じで、コマンド置換を利用すれば結構ラクになると思います。

cmd.exeな人の場合は…あいにく筆者はdosコマンドやbatは数年前に憶えることを放棄したので、さっぱりわかりません。
まあ、某ペンギン様も「batを憶えるくらいならPerlをインストールしろ」とか言ってますし、それでいいかと思っています。

2012年1月28日土曜日

avs2pipemod その11

更新しました。
avs2pipemod-0.1.2.7z
https://github.com/chikuzen/avs2pipemod

エラーの出るavsを使用した際にクラッシュしていたのを修正。

生まれて初めてバグレポート頂きました。
これまでずっとレポートする側だったので、なんか新鮮な気分です。

2012年1月7日土曜日

ConvertToRGB

avcodecに10bit-AVCデコーダがcommitされて随分経つのにいまだによく解らない人のための乱暴な解説。

まずはこちらの方の解説を読みましょう。
自分が知るかぎりでは、もっともシンプルでわかりやすい説明をされています。
DTVかくし味 - YUVとRGBの比較

特にこの図、
いかに暗部の色数が少ないか、なぜ暗部にシルバーグレインを撒く(=Yの値だけまばら且つ適当に上げる)とマシに見えるようになるかが一発で理解できます。

さて、「YUVはRGBで表現できる範囲をすべてカバーしています。(が、細かい諧調はRGBに分があります。)」とまとめられていますが、これをちょっと具体的に計算してみましょうか。
/* yuv2rgb.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SATURATE(X) X = (X < 0) ? 0 : (X > 255) ? 255 : X

int main(void)
{
    const struct {
        char *name;
        int coef[6];
    } matrix[] = {
        {"rec601", {76309, 104597, 25675, 53279, 76308, 132201}},
        {"pc601",  {65536,  91881, 22553, 46801, 65536, 116129}},
        {"rec709", {76309, 117504, 13954, 34903, 76308, 138453}},
        {"pc709",  {65536, 103219, 12257, 30659, 65536, 121621}},
        {NULL}
    };

    int count = 256 * 256 * 256;
    int *rgb = (int*)calloc(count, sizeof(int));
    if (!rgb) {
        fprintf(stderr, "malloc failed\n");
        return -1;
    }
    for (int m = 0; matrix[m].name; m++) {
        int coef0 = matrix[m].coef[0], coef1 = matrix[m].coef[1],
            coef2 = matrix[m].coef[2], coef3 = matrix[m].coef[3],
            coef4 = matrix[m].coef[4], coef5 = matrix[m].coef[5];
        int x = !strncmp(matrix[m].name, "rec", 3) ? 16 : 0;

        for (int y = 0; y < 256; y++) {
            for (int u = 0; u < 256; u++) {
                for (int v = 0; v < 256; v++) {
                    /* 下記計算式は茂木和博氏のm2v.vfpのreadmeより引用しました */
                    int r = (coef0 * (y - x) + coef1 * (v - 128)) >> 16;
                    int g = (coef0 * (y - x) - coef2 * (u - 128) - coef3 * (v - 128)) >> 16;
                    int b = (coef4 * (y - x) + coef5 * (u - 128)) >> 16;
                    SATURATE(r);
                    SATURATE(g);
                    SATURATE(b);
                    rgb[((r << 16) | (g << 8) | b)]++;
                }
            }
        }

        int num = 0;
        for (int i = 0; i < count; i++) {
            num += !!rgb[i];
            rgb[i] = 0;
        }
        printf("%s: %d\n", matrix[m].name, num);
    }
    free(rgb);
    return 0;
}
このコードでBT.601とBT.709で8bitYUV->8bitRGBに変換した際、RGBで使われる色数がわかります。 で、結果は
BT.601 TVレンジ(伸張)      2,956,082色
BT.601 PCレンジ(ストレート) 4,261,687色
BT.709 TVレンジ(伸張)      3,048,157色
BT.709 PCレンジ(ストレート) 4,400,772色
となります。
いやぁ、ほんと色数少ないですね。TrueColorRGBの16,777,216色のうち、最大でも4分の1程度しか使われていません。
バンディングが発生するのも当たり前です。
これが10bitYUV->8bitRGBだとどの程度になるのかは、適当な計算式がとりあえず見当たらなかったのでやってませんが、8bit->10bitでY,U,Vの精度がそれぞれ4倍になることを考えれば、色数もかなり良い感じに増えそうです。

てなわけで、10bitエンコードの力を確認するためにバカ高い業務用モニタ等は別に必要ありません。
民生品でもそこそこの品質があれば違いは確実にわかります(もちろん、良いものであればもっとよく分かるでしょうが)。

つーか、AviUtlのプレビューがやたら綺麗に見える理由って、まさにこれだよね。

2012年1月5日木曜日

avs2pipemod その10

更新しました。

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

コードの大部分を書き直しました。

* avisynth.libが要らなくなった。
これまではビルドするためにavisynth.libを使う必要がありましたが、これをやめてLoadLibrary()を使うようにしました。
これにより64bit用もビルド出来るようになりました。

* avisynth.dllのバージョンを検出し、挙動を切り替えるようにした。
てなわけでavs2pipe26modはなくなりました。

* おまけ
SetMTModeに応じてDistributor()を自動追加する機能もコードには存在します。
もし、この機能が欲しい人は、make時に'XCFLAGS=-DBLAME_THE_FLUFF'を追加して自ビルドして下さい。