結婚式準備で活躍したプログラミングまとめ(Javascript, python)
しまじろう大好き、夫です。
今回は、理系の端くれとして僕が身に着けた雀の涙程度のプログラミング技術を用いて、結婚式準備のために書いたコードを紹介してきたいと思います。(完全に当時のままではなく、あとで思いついた改善点も反映されたコードになっています)
1.カウントアップ
披露宴で流したオープニングムービーでは妻と付き合ってからの日数をカウントアップしながら思い出の写真を流すようなものをつくりました。
そのために、少しずつ加速(減速)しながら数字を1ずつカウントアップさせて表示するWebページをJavascriptで書きました。
こだわりポイントはカウントアップの速度を最終的に収束させるために、抵抗力のある落下運動を意識したコーディングをしたことです。もともとビデオの素材として使う上で、無限に速くなりすぎても困るので、ある程度加速しきったら等速になってほしいと考えていました。そのような運動としてまず思い浮かんだのが、空気中での自由落下運動でした。
空気中で落下する物体には速度に比例した抵抗力がかかるので、その抵抗力が重力と釣り合ったところで速度が一定になります。こうした運動は自然界にありふれているため、私たちが目にする機会が多く、したがって私たちが違和感なく受け入れやすい加速運動であると期待できます。
そこで、加速度(acceleration)と速度に比例する抵抗力の比例係数(resistance)をパラメータとして立てた式に基づいてカウントアップの速度を変化させるプログラムを書きました。
ソースコード全体はここ(GitHub)においておきます。
速度変化の計算に対応する部分が下記です。スクリプトタグ冒頭で定義したacceleration, resitance変数を用いて、抵抗力のある落下運動の式に基づいて各ステップごとに速度を求めています。
//各数字ごとのカウントアップの速度を計算し、リストに格納する
const velocitylist = [velocity];//ステップごとの速度のリスト
const initsign = (acceleration-velocity*resistance);//加速する運動か減速する運動か(正/負値であれば加速/減速)
for(let i=countMin; i<countMax-1; i++){
//速度の変化量(1ステップにかかる時間はおよそ1/velocityなのでそれを乗じている)
const diff = (acceleration-velocity*resistance)/velocity;
//速度の変化が初期状態と逆方向になったら(e.g.加速から減速に転じたら)、
//終端速度に達したとみなしてループを抜ける
if(initsign*diff<0)break;
//変化後の速度が非正値であればその時点で運動が停止したと見なせるのでループを抜ける
if(velocity + diff <= 0)break;
//上記以外の状況であれば速度を変化させリストに加える
velocity += diff;
velocitylist.push(velocity);
}
注意点として、速度変化がなめらかに0になって速度が収束する現実世界と違って、カウントアップ世界は離散値のため、ステップごとの速度変化が0付近で正値と負値を振動することが予測されます。これを防ぐため、初期状態の加速方向をinitsignとして求めておき、これと各ステップごとの加速方向が逆(initsignとdiffの積が負)になった時点でループを抜けるようにしています。また減速運動を想定して、速度が負になった時点でループを抜ける処理も入れています。
ほかは特にひねりのないコードなので、以下、主要な部分だけ簡単に解説していきます。
html部分でbodyとclass名"count"を与えたdivを書きます。
<body>
<div class="count">0</div>
</body>
bodyの下にscriptタグをおき、javascriptでカウントアップの動作を記述していきます。冒頭で、速度変化のパラメータ、カウントアップの範囲、ページの見た目に関する値を定義しています。
//各種変数の設定
//カウントアップの速度を変化させる式に関する部分
let velocity = 1; //カウントアップの速度の初期値(1/s)。この数字が大きいほど速く、小さいほど遅くカウントアップする
const acceleration = 4, //加速度(1/s^2)。この値が大きいほどカウントアップの速度が急激に早くなっていく。負の場合、減速する
resistance = 0.1 //抵抗力(1/s)。この値に速度を乗じた値が加速度の絶対値を小さくするように加速度に加算される
;
//カウント開始と終了の数
const countMin = 0, //カウント開始の数
countMax = 2000//カウント終了の数
;
//見た目に関する部分
const textColor = "gray",//文字色
backgroundColor = "black",//背景色
fontSize = "100px"//フォントサイズ
;
定義した変数をページの見た目に反映させるなどします。
//背景(body)要素を取得
const body = $("body");
//背景色の指定
body.css("background-color",backgroundColor);
//スクロールバーを隠す
body.css("overflow","hidden");
//count表示領域要素の取得
const count = $(".count");
//領域をwindowいっぱいに表示する
count.height($(window).height());
count.width($(window).width());
//文字色の指定
count.css("color",textColor);
//文字を真ん中に表示するための指定
count.css("text-align","center");//左右中央寄せ
count.css("display","table-cell");//上下中央寄せのために表示方法を表形式する
count.css("vertical-align","middle");//上下中央寄せ
//フォントサイズの指定
count.css("font-size",fontSize);
//windowをリサイズしたときに文字が中央に表示され直すようにする
$(window).resize(function(){
count.height($(window).height());
count.width($(window).width());
});
//初期値の表示
count.html(countMin);
速度のリストを作り(前述)、そこから、カウントアップの時間間隔を計算したあと、setTimeoutでcountクラスのhtml要素の数字を1ずつ増加させていきます。
//速度をinterval (ms) になおす
const intervallist = velocitylist.reduce((prev,v)=>{
if(v>0){
const last = prev[prev.length-1];
prev.push(last+(1/v)*1000);
return prev;
}
},[0]);
//最初の0は作業用の値なので削除する
intervallist.shift();
//計算したintervalにしたがって数字を変化させる
for(let waittime of intervallist){
setTimeout(()=>{
count.html(Number(count.html())+1);
},waittime);
}
以上でソースコードの記述は完了です。
あとは適当なhttpサーバーをたてて、ソースをブラウザから読み込んで、動画のスクリーンショットでカウントアップの様子を記録し、動画編集用の素材とすればOKです。
ただここまで説明しておいて恐縮ですが、実際には細かい速度調整などは動画編集ソフトのほうでやってしまったため、プログラム上で自然な速度変化を再現することにこだわる意味は、実はそんなになかったです。
参考にさせていただいたサイト:
http://black-flag.net/jquery/20161108-6221.html
2.画像の回転とトリミング
こちらのノートでも少し触れたようにプロフィールムービーはコマ撮りアニメにしました。その際、撮影の間にどうしても三脚が微妙に動いてしまうことがあったので、遠くから大きめに撮影して必要な部分だけあとでトリミングするプログラムをpythonで書きました。
1枚の画像だけであればパワポやMacのプレビューなどで事足りるのですが、100枚以上の画像を同じサイズでトリミングする必要があったことから、プログラムでやることにしました。
ソースコードは下記のような感じです。
# -*- coding: utf-8 -*-
import sys
import cv2
import os
import numpy as np
if __name__ == '__main__':
#左上が原点
startX = 963
startY = 736
width = 2270
height = 1450
# 回転させたい角度(正の値は反時計回り)
angle = -0.1
# 拡大比率
scale = 1.0
files = os.listdir()
for val in files:
if val.startswith("IMG_"):
src_mat = cv2.imread(val,1)
# 画像サイズの取得(横, 縦)
size = tuple([src_mat.shape[1], src_mat.shape[0]])
# dst 画像用意
dst_mat = np.zeros((size[1], size[0], 4), np.uint8)
# 画像の中心位置(x, y)
center = tuple([int(size[0]/2), int(size[1]/2)])
# 回転変換行列の算出
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# アフィン変換
img_dst = cv2.warpAffine(src_mat, rotation_matrix, size, dst_mat, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT)
dst = img_dst[startY:height+startY, startX:width+startX]
cv2.imwrite('cuttest/'+val, dst)
print('cut '+val)
トリミング用のライブラリにOpenCVを使いました。
プログラムの実行ディレクトリと同じ場所にある"IMG_"から始まるファイルに対してトリミングを行い、ディレクトリcuttest以下に保存します。ディレクトリcuttestは予め作成しておく必要があります。
元の画像の重心を回転中心としてangleで指定された角度だけ回転したあとに、左上頂点のxy座標と幅、高さで指定された矩形領域をトリミングします。今回は使いませんでしたが、scaleで拡大や縮小をすることもできます。
参考にさせていただいたサイト:
https://axa.biopapyrus.jp/ia/opencv/image-crop.html
3. 画像の一部の入れ替え
ある画像から矩形領域を切り出して、別の画像に重ねるプログラムです。コマ撮り撮影中にマウスポインタを映り込ませてしまうという凡ミスをやらかしたため、これを修正するために書きました。また動画中に含まれるコメントの差し替えのためにも活躍しました。言語はpythonです。ソースコードは以下のような感じです。
# -*- coding: utf-8 -*-
import cv2
import os
startX = 1289
startY = 1049
width = 100
height = 100
dx = -27
dy = 3
img_origin = cv2.imread('origin.JPG')
files = os.listdir()
for val in files:
if val.startswith('IMG_'):
img_src = cv2.imread(val)
img_src[startY+dy:startY+dy+height, startX+dx:startX+dx+width] = img_origin[startY:startY+height, startX:startX+width]
cv2.imwrite('changed/changed_'+val,img_src)
print(val)
左上頂点の座標(startX, startY)と幅(width)、高さ(height)で指定された矩形領域をorigin.JPGから切り取り、修正先の画像の同じ大きさの領域と入れ替えます。切り取り領域と、差し替え領域の位置をずらしたい場合はdx、dyで指定できます。origin.JPGと修正先の画像で光加減が違うためよく見ると境界はわかるのですが、会場ではほとんど気づかれないレベルで修正できました。
おわりに
簡単ですが結婚式準備で活躍したプログラミングについてまとめてみました。前述したように1のカウントアップのプログラムは結局、動画編集ソフトでの微調整のほうが有用だったのであまり活躍しなかったのですが、2,3の画像切り抜きプログラムは手動だと面倒な作業を肩代わりしてくれたのでかなり役に立った感じがしました。
もし似たようなことを考える方がいれば参考になれば幸いです。
ここまでお読みいただきありがとうございました。
この記事が気に入ったらサポートをしてみませんか?