今回は、拙作Olive+を使って、動画ファイル(AVIファイル)を昔の映画スクリーン風に加工するスクリプトを作ってみます。
Olive+が対応するAVIファイルは、Motion-JPEGもしくはDIB形式に限られますので、適切な変換ソフトウェア等を使って、Olive+で読み込み可能なAVIファイルを用意してください。AVIファイルをOlive+で開くと、読み込み可能な形式だった場合には、動画の簡易再生ウインドウが開きますので、その方法で確認してください。
「昔の映画スクリーン風」とは?どんな感じでしょうか?ざっと分析してみて、以下の様な特徴をリストアップしてみました。
この4事象を個別にモデリングしていきたいと思います。
映画館のスクリーン(特に昔の)は、スクリーン中央部と周辺部で明るさが異なっている印象があります。具体的には、中央部は明るく、周辺部はそれに比べて暗いという感じです。これは、投影ランプの性能だけではなくて、レンズの性能とかにも影響しているのかもしれませんね。
いずれにしても、最近のスマホなどで撮影した動画は、中央部から周辺部まで、一様な明るさで撮影されるので、周辺部を暗くすることで、投影ランプの光度分布を再現することとします。
以下、光度分布パタンを生成するスクリプトの該当部分を示しながら、コードの下に、その説明を追加します。
// lamp pattern
ginit 512,512,32;
var win_lmp;
win_lmp=win_id;
グラフィック画面を初期化します。後で、動画像のサイズに合わせてリサイズするので、ここでは、適当に、加工しやすいサイズ(小さめ)でOKです。
// draw pattern
box 0,0,fwid - 1,fhei - 1, cblack, solid;
circle fwid/2,fhei/2, fwid/4, cwhite, solid;
画面全体を黒く(暗く)して、中心に白い(明るい)円を描画します。これが、中央が明るくて、周辺が暗い、投影ランプモデリングの基礎となります。
// filter
filter sqrdif, fwid/4,fwid/4, true;
画面全体をぼかして、明暗の変化(白と黒の境目)を連続的にします。
filter zoom, fwid/2,fhei/2, 200,true;
中心部へズームして、暗すぎる周辺部分を画面の外に追い出し、明るさを調整します。
filter resize, avi_fwid,avi_fhei, true;
動画の画像サイズに合わせてリサイズします。
// close temporary window
window copy_id, win_lmp,win_id;
window front_id, win_lmp;
while (win_cnt>1)
{
window close, 1;
}
filter命令は、実行する度に新しいウインドウを1枚開くので、不要なウインドウを閉じて、終了です。
「シャッタ」と聞くと、カメラ(撮像機)の方を想像するかもしれませんが、実は、映写機の方にもシャッタがついています。昔の、フィルムで配信されるタイプの映画は、大きなリールにフィルムが巻き付けられていて、一方のリールから他方のリールへフィルムが流れて行きます。
映写機の画像検索(別ウインドウが開きます)
この途中、フィルムが投影ランプとレンズの間を通り抜けて、流れて行くのですが、そのままだと、スクリーンに映る映像も流れてしまって、映画どころではなくなってしまいます。
そこで、フィルムが流れるのと同期して、開閉するシャッタが付いていて、フィルムの各コマが投影ランプとレンズのちょうど良い位置に来た時だけ、シャッタが開いて、スクリーンにその映像が映し出されるような原理になっています。
このため、シャッタの開閉によって、スクリーンの映像は瞬いて見えるのです。この「瞬き」をモデリングします。
たぶん、普通に乱数を使って、適切に画像全体の明るさを制御してやると、それらしくは見えるはずです。今回は、もう少し踏み込んで、乱数に1/fゆらぎ乱数を使います。
1/fゆらぎ乱数については、以下の記事で解説していますので、そちらをご覧ください。
今回は、後者のプログラムから抜き出した、1/fゆらぎ乱数テーブルをOlive+で読み込んで使用します。以下、読み込んだ1/fゆらぎ乱数テーブル(rn1f)を参照しながら、ガンマ補正フィルタ(filter命令のgammaサブコマンド)を使って、明るさを制御する部分のコードです。
// 瞬き(1/fゆらぎ)
window front_id, win_lmp;
filter gamma, lum, rn1f#(cnt%n_rn1f);
本当は、普通に係数を掛けるようなフィルタが欲しかったのですが、適切なフィルタが無かったので、ガンマ補正フィルタを流用しました。ガンマ補正の場合、一番明るい(真っ白)部分と、一番暗い(真っ黒)部分の明るさは変化しないので、前節で作った光度分布パタンの中央付近の明るさは変わらず、周辺部の明るさだけが変化するような状態になるはずです。まあまあ、それらしく見えているので、良しとしましょう。
長い年月をかけ、何度も映写したフィルムには、ごみやキズが付いてきます。
前節で検索したように、1回の上映で、フィルムは一方のリールから他方のリールに巻き取られます。上映後、元のリールに戻すため、巻き戻しまで実施されることになります。
この時、フィルムは、映写機の中を通って、そこで擦れたり、発生した静電気でごみを吸いつけて巻き込んでしまったりします。このようなごみやキズがフィルムには蓄積していくものと考えられます。
ごみであれば、各コマ毎に異なるごみが付くでしょうから、こちらは、適当にごみに見立てた小さな図形を画面にランダムに散らせば良いでしょう。
一方、フィルムが一方向に流れる時、どこかに擦れて付いたキズであれば、縦方向に流れるキズになるはずです。
そこで、ごみとして楕円図形、キズとして縦線を映像に加えます。以下、該当処理部分のスクリプト抜粋です。
var N_DUST=16; // ごみの数/1コマ
var N_DUMG=8; // キズの数/1コマ
// ごみ
n_dust=n_dust + N_DUST;
while (n_dust>=1)
{
pc=rnd (1);
col2pix pc,pc,pc, pc;
circle rnd (fwid),rnd (fhei), rnd (4), pc, solid, rnd (1) + 0.5;
// 継続
n_dust=n_dust - 1;
}
// キズ
n_dumg=n_dumg + N_DUMG;
while (n_dumg>=1)
{
pc=rnd (1);
col2pix pc,pc,pc, pc;
ix=rnd (fwid);
iy=rnd (fhei);
line ix,iy,ix,iy + rnd (fhei/4), pc, 1 + rnd (1);
// 継続
n_dumg=n_dumg - 1;
}
スクリプト先頭付近の変数N_DUST、N_DUMGで、1コマ当たりのごみ・キズの数を個別に指定できるようになっています。実数で指定できるので、例えば、N_DUMG=0.5と設定すると、2コマに1つ、キズをつけることができるようになっています。
デフォルトで設定してある数値は、ごみ・キズの効果を解り易くするため、少し大きめに設定してあります。演出したいフィルムの「古さ」に応じて、数値を調整して使ってください。
最近の映写機では、こういう現象は起きないのでしょうけど、昔の映写機だと、フィルムを送る機構と、シャッタを開閉する機構が完全に同期しきれないケースが頻発したみたいです。この現象が、スクリーンを見ている観衆からどのように見えるか?と言うと、スクリーンに映った画像が、時々縦ブレする様に見えます。
そこで、読み込んだ動画データのコマ毎に、ランダムに縦ブレを加えることにします。縦ブレは、スクロールフィルタ(filter命令のscrollサブコマンド)を使います。
var P_BLUR=0.01; // 同期ズレ量[%]/100
filter scroll, 0,(rn1f#(cnt%n_rn1f) - avmv_ave)*avi_fhei*P_BLUR;
上のコードでお分かりの様に、ここにも1/fゆらぎ乱数列(rn1f)を使っています。ただし、ちょっと工夫しています。今回使った1/fゆらぎ乱数列は、繰り返し回数が4,096回となっています。これを、1/30秒単位の1コマ毎に割り当てて行くと、1周期が4,096/30≒137秒となります。この乱数列をそのまま縦ブレ量に使ってしまうと、周期が長すぎて、今回の縦ブレモデルと合いません。
そこで、1/fゆらぎ乱数列の移動平均(avmv_ave)を取って、移動平均との差分で縦ブレ量を算出しています。1/fゆらぎ乱数列の中の、高周波数成分だけを使っているイメージです。
これによって、ランプの瞬きと、縦ブレに使った乱数列が、全く同じものであるにもかかわらず、別な乱数列を使ったのと同じ様に見えるという効果も期待できます。
下のコードは、移動平均の算出箇所です。
// 1/fゆらぎ乱数の移動平均
var avmv_siz=10;
avmv#(avmv_ptr)=rn1f#(cnt%n_rn1f);
avmv_ave=0;
idx=0;
while (idx<avmv_siz)
{
avmv_ave=avmv_ave + avmv#(idx);
// 継続
idx=idx + 1;
}
avmv_ave=avmv_ave/avmv_siz;
avmv_ptr=avmv_ptr + 1;
if (avmv_ptr>=avmv_siz)
{
avmv_ptr=0;
}
同期ズレ量は、スクリプト先頭付近のP_BLURで制御できるようになっています。P_BLURは、動画の縦方向ドット数からの比率なので、P_BLUR=0.01で、縦ドット数が1,080ドットの場合には、最大で±1,080×P_BLUR≒10ドットブレることになります。ただし、乱数列の移動平均からの変位量を使っているため、この計算より、ずっと少ないブレしか発現しないものと思われます。何度か試してみて、しっくりくるブレ量になるように調整してみてください。
今回作成したスクリプト(cinematic.olv)で処理すると、元のAVIファイルに入っていた音声トラックは失われてしまいます。元のAVIファイルに入っていた音声トラックを引き継ぎたい場合は、拙作sPeachを使用して、以下の手順で音声トラックを付加します。
これで、cinematic.olvを実行した動画に元の音声を付加することができます。元のAVIファイルに音声トラックが含まれていない場合には、この手順は必要ありませんが、イージーリスニングなどの音楽を追加して、昔の無声映画風に仕上げるのもよろしいかと。
実は、sPeachで実行した機能は、Olive+のスクリプトでも実現可能な様にaviwav命令を整備してあります。よって、スクリプトで実行することも可能なのですが、エラー処理とかI/Fを作り込むのが面倒だったので、sPeachを使ってしまいましたm(1__)m。
スクリプトで一貫して処理したい方は、Olive+のマニュアルをご覧の上、作り込みしてください。
完成したOlive+スクリプトをリンクしておきます。
1) | Olive+スクリプト | cinematic.olv |
2) | 1/fゆらぎ乱数テーブル | cinematic.txt |
1)がスクリプト本体です。Olive+をインストールしたPCで、ダブルクリックして起動してください。2)は、「#4-6 LEDキャンドル」で作成した1/fゆらぎ乱数テーブルです。1)から読み込んで使用していますので、1)と同じディレクトリに配置してください。
1)を実行して生成した動画サンプルをご覧に入れます。
1コマづつエフェクトをかけて行くため、一度変換を開始すると、終了するまでしばらくかかります。途中経過のウインドウが開いたり閉じたりしながら、CPUにも負荷をかけますので、PCを使わない時間帯に実行することを推奨します。
元となった動画の内容についての解説は、「立体に見える印刷物」をご覧ください。