librosaで楽曲のクロマグラムが類似のYouTube動画を再生してみる
前回、音楽動画に含まれる楽曲テンポをlibrosaで推定し、楽曲テンポが類似したYouTube音楽動画表示してみましたが
今回は、楽曲に含まれる和音特徴量であるクロマグラムを以前librosaで求めてみた感じで
色々な楽曲のクロマグラムを求め、クロマグラムをEMDに突っ込んでみたら数ある曲の中から似たような感じの曲が集めることができないか試してみることに。
で、クロマグラムとは何?ということで
楽曲に含まれる和音特徴量として、スペクトログラムを音名ごとにオクターブ間で足し足し合わせたもののことで
ピアノの鍵盤を見ると一番低い「ラ」の音から7と1/4オクターブ上の「ド」の音までの白鍵、黒鍵 1オクターブ12個の88鍵キーからなってますが
一番低い「ラ」の音は周波数27.5Hz、その上の「ラ」は55Hzと1オクターブ上がるとオクターブ毎にが周波数が2倍の「倍音」になる鍵盤から構成されています。(隣合うキー同士は倍の周波数が等比数列になるキーからなっています)
クロマグラムとは、相異なる音名(クロマ)に対応する要素が音響信号にどのように含まれているか、簡単に言えば、オクターブ違いの音も含めて上の鍵盤のどのキー音がどのぐらいの分量で含まれているかをグラフ化したもので、パワースペクトルにバンドパスフィルタをかけて時間方向に積分。オクターブ違いの同じ音名は全て積算して求まるそうです。
とりあえず、
何かの楽曲がないと始まらないので、安室ちゃんの『TSUKI』という曲の途中30秒分の楽曲データでクロマグラムを
こんな感じのソースでlibrosaで求めてみると
import matplotlib.pyplot as plt import librosa import librosa.display import numpy as np # mp3読み込み y, sr = librosa.load('./center_mp3/center_0000000123.mp3') # ビートの抽出 tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr) # クロマベクトルを推定 chromagram = librosa.feature.chroma_cqt(y=y, sr=sr) # ビート間の平均値を用いてクロマベクトルをビートに同期 beat_chroma = librosa.util.sync(chromagram, beat_frames, aggregate=np.mean) print chromagram.shape print beat_chroma.shape # chromagramのプロット plt.figure(figsize=(12,8)) plt.subplot(2,1,1) librosa.display.specshow(chromagram, y_axis='chroma') plt.title('chromagram') plt.colorbar() # beat_chromaのプロット plt.subplot(2,1,2) librosa.display.specshow(beat_chroma, y_axis='chroma') plt.title('beat_chroma') plt.colorbar() plt.tight_layout() plt.show()
こういう感じのグラフになります。
下図の上段のグラフはlibrosaで求めた生のクロマグラムのグラフで、サンプリングレート 22,050の曲を30秒間分析したので、22,050×30=661,500個あるデジタルデータを512個ずつクロマグラムを求めているので、1292個×1オクターブ12個の配列になるのですが
これだと、結構なデータ量なので、同じ拍数内は同じような音が続いているんじゃない(?)ということで、同じ拍内はビート間の平均値を用いてクロマベクトルをビートに同期して45個×1オクターブ12個に圧縮したものが下のグラフになります。
それで、上記を踏まえて、
取り敢えず手元にある5,000曲の音楽データーを使い、ビート間のクロマグラムの平均値を用いて圧縮したクロマベクトルを、画像のヒストグラムの類似度から似た色合いの画像を抽出してみたときと同じ方法で
クロマグラムをOpenCVのEarth Mover’s Distance (EMD)関数に突っ込んでみて、出てきた数値順に安室奈美恵ちゃんの『TSUKI』を起点にYouTube楽曲動画を表示してみると以下になります。
(クロマグラムをEMDにそのまま突っ込んで、これって、意味あるのって感じではあるのですが^^; クロマグラムの類似を求めるなら、クロマグラムをk平均法とかでクラスタリングして、同じクラスタに含まれる要素の数も加味してクラスター間の距離みたいのを計算しないといけない気がするんですけど、ま、お遊びということで、タイトルには偽りアリですが^^; )
安室奈美恵『TSUKI』にテンポとクロマグラムが近いYouTube楽曲動画を連続再生する
曲目リスト
EMD(0.000000) BPM(89) 安室奈美恵 / TSUKI
EMD(0.098299) BPM(89) 高橋優 / 卒業
EMD(0.125635) BPM(95) 和楽器バンド / 起死回生
EMD(0.136257) BPM(89) 星野 源 / Snow Men
EMD(0.140311) BPM(86) Rie fu & the fu / PRE-LOVE SONG
EMD(0.144603) BPM(89) 平井 大 / 小さな世界
EMD(0.145897) BPM(86) ARIA / SHINE ON feat. K DUB SHINE
EMD(0.148280) BPM(89) 日南響子 / Save me
EMD(0.151992) BPM(89) SMOOTH ACE / CHRISTMAS TREE
EMD(0.153015) BPM(89) Tiara / さよならをキミに... ~キミがいた夏~
EMD(0.156589) BPM(89) The ROOTLESS / One day
EMD(0.156787) BPM(89) 藤田麻衣子 / 君が手を伸ばす先に
EMD(0.161237) BPM(86) Milky Bunny / ナミダソラ
EMD(0.162171) BPM(80) シェネル / let go feat. Matt Cab
EMD(0.162261) BPM(83) かりゆし58 / 会いたくて
EMD(0.163944) BPM(92) Blue Bird Beach / 星のシャンデリア
EMD(0.164251) BPM(92) nao / オレンジの空
EMD(0.165368) BPM(95) 藤田麻衣子 / 二度目の恋
EMD(0.165808) BPM(89) MIHIRO ~マイロ~ / さよならの前に
EMD(0.166222) BPM(86) 一穂 / キミはボクの世界
EMD(0.166530) BPM(89) 今井美樹 / やさしさに包まれたなら
EMD(0.167777) BPM(89) 藤田麻衣子 / 瞬間
EMD(0.168131) BPM(86) 渡 watary / 涙
EMD(0.168289) BPM(95) 五十嵐はるみ / バルコニーで抱きしめて
EMD(0.170047) BPM(89) 池田綾子 / 人と人
EMD(0.170195) BPM(86) 岩男潤子 / 風の記憶
EMD(0.170315) BPM(89) クラムボン / That's The Spirit
EMD(0.170569) BPM(89) Singers Guild / Like A Best Friend feat. Da-little
EMD(0.171069) BPM(89) セシル・コルベル / Three Ravens
EMD(0.171075) BPM(89) 奥華子 / 春風
求め方としては以下で
まず、楽曲のクロマグラムを毎回求めてたんじゃ時間がかかるので、手元にあった5,000曲ほどの楽曲データの曲のテンポとクロマグラムを求め、一旦、MySQLに登録。
なお、楽曲データは「center_mp3」というディレクトリーに曲の中央30秒だけ切り出して前もって「center_曲番号.mp3」というファイル名でmp3形式で格納してあります。
import MySQLdb import librosa import numpy as np import glob import re import base64 from io import StringIO music_db = 'DB名' music_db_host = 'ホスト名' music_db_port = 'ポート番号' music_db_user = 'ユーザー名' music_db_passwd = 'パスワード' # 楽曲のテンポとクロマグラムを求めMySQLに登録 def loadChroma(mp3Name): # mp3番号取り出し m = re.search(r"center_mp3/center_([0-9]+).mp3", mp3Name) seq = int(m.group(1)) # mp3読み込み y, sr = librosa.load(mp3Name) # ビートの抽出 tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr) # クロマベクトルを推定 chromagram = librosa.feature.chroma_cqt(y=y, sr=sr) # ビート間の平均値を用いてクロマベクトルをビートに同期 beat_chroma = librosa.util.sync(chromagram, beat_frames, aggregate=np.mean) # クロマベクトルをテキスト形式に変換 f = StringIO() np.savetxt(f, beat_chroma) beat_chroma = f.getvalue() f.close() # データ出力 conn = MySQLdb.connect(db=music_db, host=music_db_host, port=music_db_port ,user=music_db_user, passwd=music_db_passwd) cursor = conn.cursor() sql = 'delete from chroma where seq=%d' % seq cursor.execute( sql ) sql = 'insert into chroma values (%d, %f, "%s")' % (seq, tempo, base64.b64encode(beat_chroma)) cursor.execute( sql ) cursor.close() conn.close() return tempo if __name__ == "__main__": mp3s = glob.glob('center_mp3/center_*.mp3') for mp3 in mp3s: tempo = loadChroma(mp3)
で、MySQLからそれぞれの楽曲のクロマグラムを呼び出し、比較の元となる曲から全ての曲へのEMDの値を求め、EMDの値の小さい(距離の近い)順に楽曲情報を出力すると
from music_def import * from musicCom import * import numpy as np import cv2 import MySQLdb from StringIO import StringIO import base64 music_db = 'DB名' music_db_host = 'ホスト名' music_db_port = 'ポート番号' music_db_user = 'ユーザー名' music_db_passwd = 'パスワード' # cv2.EMD用に2次元配列をシグネチャに変換する def img_to_sig(arr): # cv2.EMDに渡す値は単精度浮動小数点数 sig = np.empty((arr.size, 3), dtype=np.float32) count = 0 for i in range(arr.shape[0]): for j in range(arr.shape[1]): sig[count] = np.array([arr[i,j], i, j]) count += 1 return sig # MySQLより曲のテンポとクロマグラムを読み出す def getChroma(cursor, seq): cursor.execute( "select * from chroma where seq = '%s'" % seq ) chroma = cursor.fetchone() sigData = base64.b64decode(chroma[2]) f = StringIO(sigData) beat_chroma = np.loadtxt(f) f.close() return chroma[1], beat_chroma # クロマグラムの類似度を求める def calcEMD(cursor, seq): org_tempo, org_chroma = getChroma(cursor, seq) sig1 = img_to_sig(org_chroma) cursor.execute( "select seq from chroma" ) seqs = cursor.fetchall() EMD = {} for rec in seqs: seq = rec[0] target_tempo, target_chroma = getChroma(cursor, seq) sig2 = img_to_sig(target_chroma) dist, _, flow = cv2.EMD(sig1, sig2, cv2.DIST_L2) EMD[seq] = dist #EMD[seq] = dist + abs(target_tempo - org_tempo) / org_tempo return EMD if __name__ == "__main__": org_seq = 123 # 安室奈美恵 / TSUKI conn = MySQLdb.connect(db=music_db, host=music_db_host, port=music_db_port ,user=music_db_user, passwd=music_db_passwd) cursor = conn.cursor() EMD = calcEMD(cursor, org_seq) cursor.close() conn.close() cnt = 0 for k, v in sorted(EMD.items(), key=lambda x: x[1]): cnt += 1 if cnt > 20: break print str(k) + ": " + str(v)
元とした曲からの曲同士のクロマグラムの近さ順のリストが出来ますので、それを、リスト順に対応するYouTube動画をプレイリストにしてみたのが以下になります。
(実際には楽曲に対応するYouTube動画を探すのにYouTubeのAPIなども使っていたりするのですが、それは、枝葉の部分なので割愛。)
で、EMDの近さだけで求めたのが、以下ですが、
EMDの値順のみで作成したプレイリスト
曲目リスト
EMD(0.000000) BPM(89) 安室奈美恵 / TSUKI
EMD(0.016275) BPM(151) UVERworld / ALL ALONE
EMD(0.048147) BPM(198) THE ポッシボー / さぁ来い!ハピネス!
EMD(0.048859) BPM(198) LiSA / ID
EMD(0.051561) BPM(95) 和楽器バンド / 起死回生
EMD(0.052369) BPM(57) KOKIA / 穏やかな静けさ~浄歌
EMD(0.055229) BPM(112) 味噌汁's / ジェニファー山田さん
EMD(0.055724) BPM(51) 浜田 省吾 / もうひとつの土曜日
EMD(0.056826) BPM(129) MAN WITH A MISSION / evils fall
EMD(0.057345) BPM(64) 柴田 淳 / 青春の影
EMD(0.058504) BPM(117) 在日ファンク / 京都
EMD(0.059432) BPM(129) 電気グルーヴ / モノノケダンス (Album Mix)
EMD(0.059465) BPM(161) ゆるめるモ! / たびのしたく
EMD(0.060141) BPM(184) ASIAN KUNG-FU GENERATION / ブラッドサーキュレーター
EMD(0.060895) BPM(99) 黒崎真音 / VANISHING POINT
EMD(0.065175) BPM(66) JYONGRI / Lullaby For You
EMD(0.065314) BPM(51) SPICY CHOCOLATE / ミスキャスト feat. 大橋卓弥 (スキマスイッチ) & 奇妙礼太郎
EMD(0.065908) BPM(143) T.M.Revolution / 魔弾~Der Freischutz~
EMD(0.065942) BPM(129) L'Arc~en~Ciel / 賽は投げられた
EMD(0.066481) BPM(117) FACT / the shadow of envy
どうも、結果のプレイリストがイマイチだなーって感じなので上のソースの
EMD[seq] = dist
の部分を
EMD[seq] = dist + abs(target_tempo - org_tempo) / org_tempo
に変えて、なんとなく、EMDの値に、元となる曲からテンポの差分だけ、適当にテンポの差分に応じた下駄を履かせて、あまりテンポの違う曲は、結果として含まれないようにしたのが最初の安室ちゃんの『TSUKI』のプレイリストなのですが
最初のプレイリストには、結構好きな感じの曲が多いのですが、たまたまなのか、
同様の方法でいくつかの曲で同じようにプレイリストを作ってみたものが以下ですが。。。。
どうなんですかね〜〜???
今回、ビート間の平均値を用いてクロマベクトルを集約しましたが、ビート間の平均値ではなくビート間の中央値でクロマベクトルを集約したり、クロマベクトルを求まる際にどの周波数以降から解析を始めるかなどlibrosaの関数には色々パラメーターがありますので、色々試してみろと面白いかもしれませんね。
◆ サカナクション / 『バッハの旋律を夜に聴いたせいです。』
曲目リスト
EMD(0.000000) BPM(129) サカナクション / 『バッハの旋律を夜に聴いたせいです。』
EMD(0.153091) BPM(129) AKB48 / ラッキーセブン
EMD(0.165080) BPM(123) 平井 大 / Summer Queen
EMD(0.214691) BPM(143) SKE48 / 片想いFinally
EMD(0.214947) BPM(129) the GazettE / INSIDE BEAST
EMD(0.252699) BPM(123) nao / 相対性VISION
EMD(0.253024) BPM(129) LiSA / Rally Go Round
EMD(0.255534) BPM(123) ケツメイシ / カラーバリエーション
EMD(0.257377) BPM(123) スマイレージ / エイティーン エモーション
EMD(0.267599) BPM(129) t.Komine(うたたP) / こちら、幸福安心委員会です。 (feat. 初音ミク)
EMD(0.268358) BPM(123) 岡村靖幸 / ア・チ・チ・チ
EMD(0.272274) BPM(129) 福原香織とRAB / トロ子のランナンバン FULL Size
EMD(0.277282) BPM(123) Blue Bird Beach / Poppa' Rand Beat
EMD(0.284105) BPM(129) RADWIMPS / おしゃかしゃま
EMD(0.286933) BPM(123) UVERworld / CHANCE!(Re-Sing ver.)
EMD(0.289706) BPM(151) UVERworld / ALL ALONE
EMD(0.302139) BPM(129) 広瀬 香美 / 幸せをつかみたい
EMD(0.302544) BPM(129) 平成維新 / リプレイ
EMD(0.313201) BPM(129) MAX / Tacata'
EMD(0.316087) BPM(129) AVTechNO! / tear (feat. 初音ミク)
◆ Silent Siren / KAKUMEI
曲目リスト
EMD(0.000000) BPM(80) Silent Siren / KAKUMEI
EMD(0.098072) BPM(80) シェネル / let go feat. Matt Cab
EMD(0.105850) BPM(80) AKB48 / ポニーテールとシュシュ
EMD(0.113203) BPM(83) PartyRockets / セツナソラ
EMD(0.118565) BPM(80) LinQ / チャイムが終われば
EMD(0.119156) BPM(83) BACK-ON / ニブンノイチ
EMD(0.120463) BPM(83) LINDBERG / BELIEVE IN LOVE
EMD(0.121551) BPM(80) スキマスイッチ / 小さな手
EMD(0.123131) BPM(80) 華原朋美 / やさしさで溢れるように
EMD(0.126966) BPM(80) MOSAIC.WAV / NAOKO-THUNDER VERNIER II ~幼女期の終わり~
EMD(0.127512) BPM(78) JUJU / やさしさで溢れるように
EMD(0.132170) BPM(80) GO!GO!7188 / ジェットにんぢん 2010
EMD(0.132186) BPM(83) PUFFY / TEEN TITANS THEME
EMD(0.138393) BPM(86) 私立恵比寿中学 / 未確認中学生X
EMD(0.140335) BPM(83) 吉川友 / アカネディスコ
EMD(0.146087) BPM(78) Tre-Dot / Rock On
EMD(0.147748) BPM(83) 伴 都美子 / manacles
EMD(0.149089) BPM(75) SWEETSOP greetings CHOP STICK & MAI from 努 / YEAH! YEAH! YEAH! -オンナゴコロと秋のSOCA-
EMD(0.152594) BPM(83) 乃木坂46 / ガールズルール
EMD(0.153496) BPM(83) SOULHEAD / 大きな世界の小さな僕ら
ということで、今回は叩き台ということで。
今回はクロマベクトルの類似度でしたが、次回は、音声認識でよく使われるという、音響スペクトルの特徴量であるMFCC(メル周波数ケプストラム係数)やそのΔ特徴量(どちらかというと音色みたいなものを表すのでしょうか)で同じようなことをやってみようと思います。