2019年版いろんなアイドルの平均顔を作ってみた(Python)
今更ながら、アイドルの平均顔を作ってみたくなったのでPythonで作ってみた。
先に結果を載せるとこんな感じになりました。(大量に作ったので、いい感じにできたやつを先に紹介します)
※各グループの公式サイトの画像を使用しているので、完成度にはばらつきがあります。
日向坂や他の48グループ、ハロプロ、韓国アイドルなどいろんなグループの平均顔もつくったのでこの記事の一番下にまとめて載せておきました。
坂道シリーズの期生ごとの平均顔や、48のチームごとの平均顔も作っています。
目次
画像を集める
まずは、平均顔を作りたいアイドルグループの画像を探します。
平均顔を作るためにはある程度正面を向いた画像がよろしいので、公式サイトの宣材写真的なやつをとってくることにしました。
画像は自分で一個一個保存していってもいいですが、AKBとかは人数が多いのでスクレイピングしてしまったほうが楽だと思います。
私は、こんな感じで画像を一気に集めました。保存するファイル名とかは適当です。
import requests from bs4 import BeautifulSoup URL = 'https://www.akb48.co.jp/about/members/' #AKB48の公式サイト images = [] soup = BeautifulSoup(requests.get(URL).content,'lxml') for link in soup.find_all("img"): if link.get("src").endswith(".jpg"): images.append(link.get("src")) for i, target in enumerate(images): try: resp = requests.get(target) except: continue with open('./akb_{}.jpg'.format(i), 'wb') as f: f.write(resp.content)
スクレイピングはこちらのサイトが参考になりました。
画像の左右を反転する
取得した画像は、右を向いている人や左を向いている人、正面を向いている人などバラバラです。これでは平均顔がきれいに作れないので、向きを合わせるために画像を反転させます。
今回は全員が向って左向きになるように合わせました。正面の人はそのままにします。
import cv2 img_path = "./image_path/filename.jpg" img = cv2.imread(img_path) #画像読み込み img = cv2.flip(img, 1) #反転 cv2.imwrite(img_path, img) #上書き保存
画像を回転させ、顔がまっすぐになるようにする
すべての画像の顔の部分がしっかり重なった、きれいな平均顔を作るためには集めてきた画像を回転させて顔をまっすぐにしてやる必要があります。
そのために、今回はdlibという機械学習ライブラリを使って顔器官(顔のパーツ)を検出し、左右の目の高さがそろうように画像を回転させようと思います。
dlibで顔を検出
まずは、dlibで顔検出を行います。
dlibのget_frontal_face_detector()により、顔の位置の矩形情報を得ることができます。
今回は、画像に写っている顔は1つだけという想定でやっていきます。
import cv2 import dlib img_path = "./saito_asuka.jpg" img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) detector = dlib.get_frontal_face_detector() faces = detector(img) for i, rect in enumerate(faces): cv2.rectangle(img, (rect.left(), rect.top()), (rect.right(), rect.bottom()), (255, 255, 0), thickness=2) #得られた顔の位置に矩形を描画
矩形を描画した画像を表示してみるとこんな感じになっています。
このようにして、画像の顔の位置を得ることができました。
dlibで顔のパーツを検出
次に、dlibのshape_predictor()で顔器官の検出をします。
shape_predictor()は人の顔の画像を入力すると、目や口や鼻などの顔の重要なランドマークの位置を特定してくれます。
顔器官の検出を行うには、学習済みデータが必要です。
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
こちらを解凍して使いました。
from imutils import face_utils predictor_path = "./shape_predictor_68_face_landmarks.dat" predictor = dlib.shape_predictor(predictor_path) faces = detector(img) for i, rect in enumerate(faces): shape = predictor(img, rect) shape = face_utils.shape_to_np(shape) #顔のランドマークの位置に対応した68個の座標を得る for point in shape: cv2.circle(img, tuple(point), 1, (255, 255, 0), -1) #得られたランドマークの座標を画像に描画
そうすると、こんな感じになります。
点の数は68個あり、それぞれの点の番号と顔のパーツが対応している。
例えば、向かって左目は36~41番目の点と対応しており、右目は42~47番目と対応している。
左右の目の位置を求める
次に、左右の目の位置を求めていきます。
先ほど得た68個を使います。
left_eye_center = np.zeros(2) right_eye_center = np.zeros(2) for left_eye in shape[36:42]: #向かって左目 left_eye_center += left_eye for right_eye in shape[42:48]: #右目 right_eye_center += right_eye left_eye_center = left_eye_center / 6 right_eye_center = right_eye_center / 6 #6点の座標の平均をとり、それぞれの目の中心とする cv2.circle(img, tuple(left_eye_center.astype(np.int)), 2, (255, 255, 0), -1) cv2.circle(img, tuple(right_eye_center.astype(np.int)), 2, (255, 255, 0), -1) #左右それぞれの目の中心に点を打つ
こうして得られた画像はこんな感じです。
左右それぞれの目のほぼ中心を求めることができています。
画像を回転させ、左右の目の高さを合わせる
左右の目の座標がわかったので、顔がどれだけ傾いているかを計算することができます。
上図のように向かって左目の中心座標を原点とするXY座標系を考え、右目の中心点をP(x, y)とすると顔の傾きは
で得られます。
そして、左目を中心にして画像を 回転させてやれば顔の傾きはゼロになります。
import math row, col, ch = img.shape tan = (left_eye_center[1] - right_eye_center[1]) / (right_eye_center[0] - left_eye_center[0]) deg = math.degrees(math.atan(tan)) M = cv2.getRotationMatrix2D(tuple(left_eye_center.astype(np.int)), -deg, 1) #2次元回転を表すアフィン変換を求める img = cv2.warpAffine(img, M, (col, row), borderValue=(255, 255, 255)) #borderValueは領域外の色を指定
参考
画像の幾何学変換 — opencv 2.2 documentation
OpenCV - 画像座標系における回転、拡大縮小について - Pynote
こうしてできた画像がこちら
画像を回転させたことにより、しっかりと顔の傾きをなくすことができています。
画像を重ねるときの基準点を計算する
画像と画像を重ねて平均顔を作るときには、1つ目の画像の顔の部分と2つ目の画像の顔の部分がしっかりと重なるようにしなければなりません。
今回は、画像と画像の目の位置を合わせて重ねていくことにします。
先ほど求めた左目の中心点と右目の中心点の線分の中心を顔の基準点とし、そこが重なるようにすることで目の位置を合わせます。
下の図の赤い点が基準点です。
eye_center = (left_eye_center + right_eye_center) / 2 #eye_centerが基準点 #この基準点の座標は回転させる前の座標
いま、顔の傾きをなくすために画像を回転させているので、回転後の基準点の座標を計算してやる必要があります。
上図のように、点(x, y)を角度 回転させた点(X, Y)は加法定理より
となります。これを用いて、回転後の基準点を計算します。
rad = math.radians(-deg) #これが α (弧度法) x_eye_center_rotated = left_eye_center[0] + (eye_center[0] - left_eye_center[0]) * math.cos(rad) - (left_eye_center[1] - eye_center[1]) * math.sin(rad) #回転後の基準点のx座標 y_eye_center_rotated = left_eye_center[1] - (eye_center[0] - left_eye_center[0]) * math.sin(rad) - (left_eye_center[1] - eye_center[1]) * math.cos(rad) #回転後の基準点のy座標 cv2.circle(img, (int(x_eye_center_rotated), int(y_eye_center_rotated)), 2, (255, 0, 0), -1)
こうしてできた画像がこちら。
きちんと回転後の基準点の計算ができていますね。
複数の画像を重ね合わせ平均顔をつくる
いよいよ、画像を重ねて平均顔を作っていきます。
最終的なコードはこのようになりました。
import numpy as np import cv2 import dlib from imutils import face_utils import math import glob from copy import copy folder_path = "./nogizaka46/" #乃木坂の画像が詰まってるフォルダ img_path_list = glob.glob(folder_path + "*.jpg") detector = dlib.get_frontal_face_detector() predictor_path = "./shape_predictor_68_face_landmarks.dat" predictor = dlib.shape_predictor(predictor_path) for img_num, img_path in enumerate(img_path_list): img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) row, col, ch = img.shape faces = detector(img) for face_num, rect in enumerate(faces): shape = predictor(img, rect) shape = face_utils.shape_to_np(shape) left_eye_center = np.zeros(2) right_eye_center = np.zeros(2) for left_eye in shape[36:42]: left_eye_center += left_eye for right_eye in shape[42:48]: right_eye_center += right_eye left_eye_center = left_eye_center / 6 right_eye_center = right_eye_center / 6 #顔の大きさをそろえるときに使う #left_right_eye_dist = math.sqrt((left_eye_center[0] - right_eye_center[0]) ** 2 + (left_eye_center[1] - right_eye_center[1]) ** 2) tan = (left_eye_center[1] - right_eye_center[1]) / (right_eye_center[0] - left_eye_center[0]) deg = math.degrees(math.atan(tan)) rad = math.radians(-deg) eye_center = (left_eye_center + right_eye_center) / 2 M = cv2.getRotationMatrix2D(tuple(left_eye_center.astype(np.int)), -deg, 1) img = cv2.warpAffine(img, M, (col, row), borderValue=(255, 255, 255)) #回転中心の座標はint型になっているはずなので計算精度を上げる処理 left_eye_center = left_eye_center.astype(np.int) x_eye_center_rotated = left_eye_center[0] + (eye_center[0] - left_eye_center[0]) * math.cos(rad) - (left_eye_center[1] - eye_center[1]) * math.sin(rad) y_eye_center_rotated = left_eye_center[1] - (eye_center[0] - left_eye_center[0]) * math.sin(rad) - (left_eye_center[1] - eye_center[1]) * math.cos(rad) #最初の画像を基準として、その上に次々重ねていくイメージ if img_num == 0: row_original = row col_original = col x_eye_center_rotated_original = x_eye_center_rotated y_eye_center_rotated_original = y_eye_center_rotated img_original = copy(img) #顔の大きさをそろえるときに使う #left_right_eye_dist_original = left_right_eye_dist #2つ目以降の画像の処理 else: #顔の大きさをそろえるときに使う #scale = left_right_eye_dist_original / left_right_eye_dist #originalとの左右の目の距離の比 #img = cv2.resize(img, (int(col * scale), int(row * scale))) #originalに合わせて画像をresize #x_eye_center_rotated = x_eye_center_rotated * scale #y_eye_center_rotated = y_eye_center_rotated * scale #imgのresizeに合わせて目の中心座標も変更 #1枚目の画像の基準点と2つ目以降の画像の基準点との距離 x_eye_center_dist_from_original = x_eye_center_rotated_original - x_eye_center_rotated y_eye_center_dist_from_original = y_eye_center_rotated_original - y_eye_center_rotated #基準点の距離分平行移動させて基準点を合わせる M_shift = np.float32([[1, 0, x_eye_center_dist_from_original], [0, 1, y_eye_center_dist_from_original]]) img = cv2.warpAffine(img, M_shift, (col_original, row_original), borderValue=(255, 255, 255)) #すべての画像のweightが等しくなるように重ねて平均顔完成 img_original = cv2.addWeighted(img_original, 1 - 1 / (img_num + 1), img, 1 / (img_num + 1), 0) cv2.imwrite("./average_face/nogizaka46.jpg", cv2.cvtColor(img_original, cv2.COLOR_RGB2BGR))
所々出てくる”#顔の大きさをそろえるときに使う”という部分は、顔の大きさが違う画像が含まれるときなどに使用します。
1枚目の基準となる画像の目と目の距離と、2枚目以降の目と目の距離の比から画像を拡大縮小して顔の大きさをそろえます。
今回は公式サイトの画像を使用しているので、同グループの顔の大きさや画像の大きさがほぼ一定なので使用しませんでした。
ただし、別グループどうしの平均顔を作る際には使うといいと思います。
そんなこんなで完成した画像がこちらです。
乃木坂46の平均顔はこんな感じらしいです。しっかりと目の位置があっているのでやりたかったことはできました。
最後に
ここまで読んでいただきありがとうございました。
ここから先は、今回作成したいろんなアイドルの平均顔を紹介していきます。
私の趣味にだいぶ偏った内容になっています。
みたいグループがなかったら、上のコードで作ってみてください。
【めちゃ手軽】eBay輸出セラーのためのリサーチサイトを作成しました。【eBayリサーチツール】
この度、eBay輸出をしているセラーのためのリサーチサイトを作成したので紹介していきたいと思います。
早速ですが、以下がそのサイトになります。その名も"dululuttu"(ドゥルルットゥ)です。
dululuttu | ebay research tool for sellers
eBay輸出をする際、日本から出品されたものでよく売れるている商品を探すことは大切なことです。しかし、eBayサイトでAdvanced Searchなどを使って検索するのはとても面倒ですし、必ずしもよく売れる商品を見つけられるとは限りません。
特に初心者の方には、たくさんのカテゴリーの中から売れ筋の良いカテゴリーを探すことも難しいことだと思います。
そこで、dululuttuではAdvanced Searchよりも手軽に検索でき、よく売れているカテゴリーのランキングなども見れるようにしました。
また、他のリサーチサイトではサイトに登録しないと使えないものやAPIキーを取得しなければならないものなどがありますが、dululuttuではそのような作業は一切必要ないので今すぐに使い始めることができます。
dululuttuでできること
下の画像はdululuttuでできることを簡単に紹介したものです。
dululuttuでは、カテゴリーの売れ筋ランキングや、商品タイトルに含まれる単語ランキングを表示することができます。詳しい説明は、dululuttuの使い方に書いていきたいと思います。
dululuttuの使い方
1. dululuttuのサイトに行き、まずは検索オプションを指定しましょう。
一つ目の赤枠はeBayサイトの国を指定します。自分が出品しているeBayサイトを選択して下さい。
だいたいはアメリカのeBayサイトである"eBay United States"のままでいいと思います。
二つ目の赤枠の"Located in"ではどこの国から出品された商品を表示するか指定できます。
デフォルトでは"Japan"になっており、日本から出品されたもののみから検索するようになっています。
そして"Sold out in the past 〇〇 days"は、直近の何日間に売れた商品を表示するかを指定できます。
例えばこれを"7"に設定すると、7日前から今日までの間に売れた商品に検索結果が限定されます。
2. 検索オプションを指定すると、以下のような画面になると思います。
これで、eBayサイトとLocation、経過日数を指定することができました。
この後カテゴリーを指定すると、検索結果が表示されます。しかし、どのカテゴリーがよく売れているのかなんてわかりませんよね。
そんな時に、dululuttuではカテゴリーごとの商品が売れた数を取得しランキング化して表示することができるので、効果的に人気商品にたどり着くことができます!
上の画像で赤枠に囲われている部分は、カテゴリー情報を表示しています。
今、"Home"というカテゴリーにいて"Home"の子カテゴリーにあたるカテゴリー群がその下に箇条書きで表示されています。
この部分はeBayのサイトでも似たような表示になっています。
それでは、カテゴリーランキングを表示して一番商品が売れているカテゴリーを見つけてみましょう。
上の画像で青枠で囲まれている"View ranking"をクリックしてみましょう。しばらく待つと、子カテゴリー群の表示が変わると思います。
画像のように子カテゴリーの右側に数字が表示され、この数字が大きい順に並び変えられました。この数字はそのカテゴリに属する商品が、指定した検索オプション中で売れた数を表しています。
上の画像では、例えば"Collectibles"に属する商品で日本から出品されたものが、"eBay United States"で過去7日間に16443個売れたことを表しています。
この機能を使えば、人気の高いカテゴリーが一目瞭然ですね!
3. では、カテゴリーを選択してみましょう。カテゴリーランキングによると"Collectibles"が一番よく売れているようなのでこれをクリックしてみましょう。そうすると、下の画像のような表示になると思います。
サイトの右側に検索結果が表示されました。表示される商品はすべて実際に売れた商品のみとなっています。
表示された検索結果の画像かタイトルをクリックすると、eBayでのそのアイテムのページを表示することができます。しかし、その商品はもう売れてしまっているため、遷移先のページには類似商品が表示されている場合もあります。
また、検索結果の画像の下にある"Search by image"という部分をクリックするとGoogleで画像検索をすることができます。同じ商品がamazonなどで販売されていないか探して、仕入れ先をみつけましょう。
4. それでは、さらに細かくカテゴリーを選択してみましょう。もう一度"View ranking"をクリックして"Collectibles"の子カテゴリー群のランキングを表示しましょう。
この記事を書いているときは"Animation Art & Characters"というカテゴリーが一番上に来ました。このカテゴリーが一番売れているようなので、次はこのカテゴリーをクリックしてみました。すると下の画像のよう画面になりました。
様々なアニメ関係のアイテムが表示されました。アニメに詳しい人はこの中からいい感じの商品を見つけられるのかもしれませんが、多くの人は何が何だかわからないと思います。そこで、出現単語ランキングを表示して人気のアニメや売れている商品に共通する特徴を見つけましょう!
上の画像の赤枠で囲ってある"View"をクリックしてみましょう。しばらくたつと、下の画像のように単語頻度ランキングが表示されます。
このランキングは検索結果のタイトルに含まれる単語の中で、出現回数が多い順に表示しています。また、右の棒の長さで出現数を表現しています。
上の画像の例では、"japan"が一番多く出現しているということになります。これについては検索オプションでLocated in Japanを指定しているので多く出現するのは当たり前です。他にも"the"やなどのあまり意味のないものは無視してください。
もう少し下のほうまで見てみると、"Pokemon"や"bandai"、"persona"などがでてきますね。これらは人気商品を探すうえで意味のある単語です。
よく売れている商品はこれらの単語に関連のある商品であることが予想されます。
ここで、"persona"って何?と思う方もいらっしゃるのではないでしょうか。
このように知らない単語や気になる単語があった場合は、単語の左側のチェックボックスにチェックを入れて上の画像の赤枠で囲った"Add words"というボタンを押してみてください。すると、現在の検索結果の中でさらに"persona"というキーワードで検索することができます。
キーワード検索をすると、"persona"に関連がある商品が80個ほど表示され、その中にはフィギュア系の商品やキーホルダーがたくさんありました。どうやら、"persona"というのはゲームかアニメ関係の単語のようです。(一応ネットで調べたらペルソナというゲームがあるようです)
このように、ペルソナというゲームのフィギュアやキーホルダーが過去7日間に3、40個ほど売れているということがわかりました。
さらにGoogle画像検索をすると同様の商品が楽天やamazonでも売られており、仕入れ先の確保もできそうです。
同じような商品を自分でも出品してみましょう!
このように、カテゴリーランキングや単語頻度ランキングを使ってよく売れている商品を見つけることができました。
最後に
今後も様々な機能を追加していく予定です。「こんな機能があったら便利」とか「ここをもっとこうしてほしい」という要望がございましたら、この記事に気軽にコメントをお願いします。
また、たくさんの人にdululuttuを使っていただきたいのでよろしければSNSなどへのシェアをお願いいたします!
FFTでインターホンの音を検知する(Python)
インターホンが鳴ったかどうかをFFT(高速フーリエ変換)で調べる。
フーリエ変換とは、ある信号を様々な周波数の正弦波の和として表す考え方。
フーリエ変換やFFTに関してはこちらの記事が参考になった。
⇒離散フーリエ変換 - 人工知能に関する断創録
PyAudioでマイクから録音する
まずは、PyAudioを使ってパソコンにつないだUSBマイクから音を録音した。
以下のコードで、インターホンの「ピンポーン」という音を含んだ3秒間の音声を録音し、sound_data.wavというファイルに保存した。
import pyaudio import numpy as np import wave CHUNK = 1024 RATE = 44100 RECORD_TIMES = 3 #3秒間録音 data = [] file_name = "./sound_data.wav" p = pyaudio.PyAudio() stream = p.open(format = pyaudio.paInt16, channels = 1, #モノラル input_device_index = 0, rate = RATE, frames_per_buffer = CHUNK, input = True, output = False) for i in range(int(RATE / CHUNK * RECORD_TIMES)): d = np.frombuffer(stream.read(CHUNK), dtype='int16') data.append(d) wf = wave.open(file_name, 'w') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(RATE) stream.stop_stream() stream.close() p.terminate()
録音した音声の波の形を見てみる
matplotlibを使ってグラフに表示してみる。
import matplotlib.pyplot as plt wf = wave.open(file, "rb") data = np.frombuffer(wf.readframes(wf.getnframes()), dtype='int16') wf.close() x = np.arange(data.shape[0]) / RATE plt.plot(x, data) plt.show()
するとこんなグラフがえられた。
横軸は時間、縦軸は振幅を表している。
約0.5秒くらいから「ピンポーン」が始まっている。
これを、FFTして各周波数成分ごとに分けて表示していく。
fft_data = np.abs(np.fft.fft(data)) #FFTした信号の強度 freqList = np.fft.fftfreq(data.shape[0], d=1.0/RATE) #周波数(グラフの横軸)の取得 plt.plot(freqList, fft_data) plt.xlim(0, 5000) #0~5000Hzまでとりあえず表示する plt.show()
横軸は周波数(Hz)、縦軸は周波数成分の強度を表している。
グラフの2600Hzと2050Hz付近に大きなピークがみられる。おそらく、これらが「ピーン」と「ポーン」だと思われる。
確認のため、雑音も含めたインターホンの音のグラフも見てみる。
このグラフは、インターホンの音とテレビの雑音が含まれている音の波形である。
これを、FFTすると
こんな感じになった。やはり、2600Hzと2050Hz付近に大きなピークがみられる。インターホンの周波数はこの2つで間違いないと思う。
インターホンが鳴ったかどうかを判断する
今回は、インターホンが鳴ったらその時の音を3秒間保存する機能を作った。
コードは以下の通り
import pyaudio import numpy as np import wave from datetime import datetime CHUNK = 1024 RATE = 44100 l = 10 ** 7 sound_count = 0 data1 = [] data2 = [] freqList = np.fft.fftfreq(int(1.5 * RATE / CHUNK) * CHUNK * 2, d = 1.0 / RATE) p = pyaudio.PyAudio() stream = p.open(format = pyaudio.paInt16, channels = 1, input_device_index = 0, rate = RATE, frames_per_buffer = CHUNK, input = True, output = False) try: while stream.is_active(): for i in range(int(1.5 * RATE / CHUNK)): d = np.frombuffer(stream.read(CHUNK), dtype='int16') if sound_count == 0: data1.append(d) else: data1.append(d) data2.append(d) if sound_count >= 1: if sound_count % 2 == 1: data = np.asarray(data1).flatten() fft_data = np.fft.fft(data) data1 = [] else: data = np.asarray(data2).flatten() fft_data = np.fft.fft(data) data2 = [] fft_abs = np.abs(fft_data) data2050 = fft_abs[np.where((freqList < 2150) & (freqList > 2000))] #2050Hz付近の周波数成分 data2600 = fft_abs[np.where((freqList < 2700) & (freqList > 2500))] #2600Hz付近の周波数成分 if (data2050.max() > 0.5 * l) & (data2600.max() > 1 * l): #2050Hz付近と2600Hz付近の強度が一定以上あったとき、インターホンが鳴ったと判断 this_time = datetime.now().strftime("%Y-%m-%d %H-%M-%S ") file_name = this_time + ".wav" wf = wave.open(file_name, 'w') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(RATE) wf.writeframes(data) wf.close() print("The bell is ringing! " + this_time) data1 = [] data2 = [] sound_count = 0 sound_count += 1 except KeyboardInterrupt: stream.stop_stream() stream.close() p.terminate()
tryとかexceptはよくわからないので適当。
data1とdata2に分けている理由は3秒間の録音の切れ目で「ピーン」と「ポーン」が分かれてしまい、判定できないことを防ぐため。また、datetimeでインターホンが鳴った時の時間を取得している。
Pythonでモザイクアートを作った
Pythonでモザイク写真を作った。
モザイク写真とは、たくさんの写真を組み合わせて全体で一つの画像のようにしたもの。結婚式なんかでよく目にするやつ。モザイクアートとも呼ばれる。
今回はこの画像をモザイク写真にしていく。
こんなのができた
この画像を拡大すると小さい猫の画像の集合になっていることがわかる。
画像を大量に集める
まずは、モザイク写真を作るうえでその材料となる画像を大量に集める。今回はFlicker APIを使って画像を集めた。
FlickerのAPIキーを取得し、それを使って猫の画像を集めていく。
コードは以下の通り
from flickrapi import FlickrAPI import requests import time key = "" secret = "" wait_time = 1 text = 'cat' savedir = './cats/' flickr = FlickrAPI(key, secret, format='parsed-json') result = flickr.photos.search( text = text, per_page = 500, media = 'photos', sort = 'relevance', safe_search = 1, extras = 'url_q, licence' ) photos = result['photos'] for i, photo in enumerate(photos['photo']): try: url_q = photo['url_q'] except: continue resp = requests.get(url_q) with open(savedir + url_q.split('/')[-1], 'wb') as f: f.write(resp.content) time.sleep(wait_time)
参考 ↓↓
qiita.com
これで猫の画像が500枚得られた。サイズはすべて150×150になってる。また、画像を取得する際サーバーへの負荷を考えて画像の取得毎に1秒待つことにしている。
モザイク写真の作り方
モザイク写真を作るときには、モザイク化したい画像を150×150の領域に分けていく。そして、分けられた領域の1つ1つと最も似ている画像を大量にある画像群から選んでいく。
似ている画像の選び方は、ピクセルごとに差をとり二乗したものを、150×150のすべてで計算し和をとる。その数値が一番小さかった画像が、分けられた領域と一番似ている画像ということにした。
コードは以下の通り
※最初は150×150の画像を使ってモザイク写真を作っていく予定だったが、計算量が多くて時間がかかってしまったため50×50にresizeしている。
import numpy as np import cv2 import glob files = glob.glob('./cats/*.jpg') size = 50 #150×150の画像を50×50にresizeして行列で表す small_img = [] for file in files: image = cv2.imread(file) image = cv2.resize(image, (size, size)) image = image /255 small_img.append(image) small_img = np.asarray(small_img) pic_filename = './cat_image.jpg' #モザイク写真にしたい画像のパス big_img = cv2.imread(pic_filename) #big_imgのサイズが50で割り切れるように指定する。このサイズを大きくするほど鮮明なモザイク写真ができる height = (big_img.shape[0] - big_img.shape[0] % size) * 2 width = (big_img.shape[1] - big_img.shape[1] % size) * 2 big_img = big_img / 255 big_img = np.asarray(big_img) big_img = cv2.resize(big_img, (width, height)) for i in range(int(height / size)): print('%d / %d' %(i + 1, int(height / size))) for j in range(int(width / size)): cut = big_img[size * i: size * (i + 1), size * j: size * (j + 1)] losses = [] #small_imgの中で小さく分けた領域に一番似ているものを探す for s_img in small_img: loss = cut - s_img loss = loss * loss loss = np.sum(loss) losses.append(loss) losses = np.asarray(losses) big_img[size * i: size * (i + 1), size * j: size * (j + 1)] = small_img[np.argmin(losses)] big_img = (big_img * 255).astype(np.int64) cv2.imwrite('cat_mosaic.jpg', big_img)
この記事の上のほうに貼ってあるモザイク写真は2200×2900にwidthとheightを指定している。
このサイズを大きくするほど、鮮明な画像になる。次の画像は4400×5800に指定した画像である。
完成したモザイク写真を見ると、配色が似ている部分には同じ画像が連続している。
これはちょっと嫌なので、もっともっと超大量に画像を準備して分ける領域を大きめにすれば鮮明さを保ったまま、同じ画像が連続することを防げるんじゃないかと思う。
SRGANでDVDの映像を高画質化したかった(tensorflow)
SRGANを使ってDVDの高画質化を目指す。
といっても、自分でやったのは学習用の画像を用意することとtensorflowが吐き出すエラーを眺めて変顔をしていたことくらいである。(僕はよくわからんエラーと直面すると顔が変になってしまう)
SRGANとは
SRGAN(Super-Resolution Usinga Generative Adversarial Network)とは、GAN:敵対的生成ネットワーク(Generative Adversarial Network)の構造を用いて低解像度画像から高解像度画像に生成する機械学習モデルである。
低解像度の画像に処理を行い高解像度にする技術を超解像というらしい。
以下がその論文
[1609.04802v5] Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network
以下の記事ではGANについて詳しく書かれてる。SRGANについての記述もある。日本語。
elix-tech.github.io
今回は、SRGANにDVDのキャプチャを入力し超解像していく。超解像後の画像サイズは縦横がそれぞれ4倍になる。
モデルはGitHubの公開リポジトリのものを利用した。
github.com
こちらのファイルをすべてダウンロードした。
学習用画像の準備
今回、高画質化したいDVDはライブ映像が収録されたものであるため、学習用画像の作成にもライブ映像を使うことにした。
そこで、Blu-rayのライブ映像をキャプチャしたものを学習用として用意した。
コードはこんなかんじ
import cv2 input_video = 'video.m2ts' cap = cv2.VideoCapture(input_video) frame_count = int(cap.get(7)) for num, i in enumerate(range(0, frame_count, 1000)): cap.set(1, i) _, frame = cap.read() cv2.imwrite('%d.png' %(num), frame)
参考
動画を扱う — OpenCV-Python Tutorials 1 documentation
あらかじめBlu-ray映像をvideo.m2tsとして保存してある。
ちなみに今回学習用に用意したBlu-rayディスクはこれ
NMB48 渡辺美優紀卒業コンサート in ワールド記念ホール ~最後までわるきーでゴメンなさい~ [Blu-ray]
- 出版社/メーカー: laugh out loud records
- 発売日: 2017/10/11
- メディア: Blu-ray
- この商品を含むブログを見る
これで1920×1080のライブ画像が1400枚くらい取得できた。
ここから適当にtrain用に1000枚、valid用に400枚選びそれぞれのフォルダに保存。
どうやら、これらの画像を1/4倍した画像も必要らしいので適当にresizeして保存しておいた。(たぶんなくても学習はできるっぽい)
というわけで以下のフォルダに学習用画像の準備ができた。
- 1080_1920_train ... 1080×1920の画像1000枚
- 1080_1920_valid ... 1080×1920の画像400枚
- 270_480_train ... 270×480の画像1000枚
- 270_480_valid ... 270×480の画像400枚
学習
モデルなどはGitHubのものをほぼそのまま使った。config.pyを少しいじって準備した画像を利用できるようにした。
config.TRAIN.hr_img_path = "1080_1920_train" config.TRAIN.lr_img_path = "270_480_train" config.VALID.hr_img_path = "1080_1920_valid" config.VALID.lr_img_path = "270_480_valid"
これだけ。
python main.py
で学習開始したが、このままではOOMエラーがでてしまった。
batch_sizeはデフォルトで16だが、4まで減らしたらOOMがでなくなった。
僕のPCはgtx1060 6GBを使っているので、もっと高性能なGPUを積んでいるPCなら16のままいけるかも。
学習は全部終わるのに5日くらいかかった。
DVDの高画質化
ようやく今回の目的であるDVDの高画質化をする。
高画質化するDVDはこれ
NMB48 GRADUATION CONCERT ~KEI JONISHI/SHU YABUSHITA/REINA FUJIE~ [DVD]
- 出版社/メーカー: laugh out loud records
- 発売日: 2017/09/20
- メディア: DVD
- この商品を含むブログを見る
dvd.VOBとしてあらかじめ保存しておく。
DVDのサイズは480×720なので、SRGANに通すと1920×2880まで大きくなるというわけである。これで、超高画質で楽しめるとわくわくしながら以下のコードを書いた
import time import cv2 import numpy as np import tensorflow as tf import tensorlayer as tl from model import SRGAN_g checkpoint_dir = "checkpoint" t_image = tf.placeholder('float32', [1, None, None, 3], name='input_image') net_g = SRGAN_g(t_image, is_train=False, reuse=False) sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)) tl.layers.initialize_global_variables(sess) tl.files.load_and_assign_npz(sess=sess, name=checkpoint_dir + '/g_srgan.npz', network=net_g) input_video = 'dvd.VOB' output_video = 'dvd_hr.avi' cap = cv2.VideoCapture(input_video) frame_count = int(cap.get(7)) fps = 30 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) * 4 height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) * 4 fourcc = cv2.VideoWriter_fourcc(*'XVID') vw = cv2.VideoWriter(output_video, fourcc, fps, (width, height)) while(True): ret, img = cap.read() if ret == True: img = (img / 127.5) - 1 img = sess.run(net_g.outputs, {t_image: [img]}) img = (img + 1) * 127.5 img = img.astype(np.uint8) vw.write(img[0]) else : break cap.release() cv2.destroyAllWindows()
結果
たしかにサイズは大きくなっているはずだが、動画を再生しても高画質化されてるという実感は得られなかった。
原因として考えられること(他に原因がわかる方がいらしたらおしえてください!)
- DVDを4倍にしたら1920×2880になるが、学習用の画像サイズが1080×1920なのでそれ以上大きいサイズに超解像する場合にはうまくいかないのではないか
- 今回もちいたDVDの映像自体がすでに綺麗なので、高画質化した効果が実感しづらい
- ディスプレイが小さいため効果が実感しづらい
ていうか、そもそも学習がうまくいっていない可能性があるので別の画像で試してみた。
左がもともと150×150の画像、右がSRGANにかけて600×600に超解像したもの。
SRGANめちゃめちゃすごいじゃん。車の光沢感がしっかりとでてる。
このように150×150などの小さい画像を超解像することはうまくいったので、学習用の画像のサイズを大きくしたらDVD映像も超解像できるかもしれないと思った。