114
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【デレステ】OpenCVで腋を見せてもらう

Last updated at Posted at 2019-05-15

まずはこの動画を見てくれ。

15分50秒、1秒たりとも目を離すことなく堪能してくれたであろうと思う。
訓練されたPなら全員の特定も余裕だろう。1

今回はこの動画作成に使った技術を紹介しよう。

使用した技術

目grep

技術もへったくれもなかった!

録画したPV動画をコマ送りしながら、同じくらいのシーンをスクリーンショット撮影する。

これを190回繰り返しました。
image_shudou.png

自動化しよう

まあなんだ、キャプチャしながら考えてはいたわけですが、こういうのはやっぱり自動化したほうがいいですよね。

やりたいことは動画から特定のシーンを抜き出すことです。
動画は手動録画したものなので録画開始時刻が微妙にずれているため、時間指定で抜き出すことはできません。
何らかのアルゴリズムで抽出タイミングを算出する必要があるでしょう。

実装方針

ここでは単純に、サンプル画像を1枚撮っておいて、『動画からサンプル画像と最も近いシーンを抜き出す』というアプローチでいきましょう。

まずはサンプル画像を用意。
最初の一枚くらいは仕方ないね。
eve.png

環境準備

Anaconda

Anaconda Promptを起動して、とりあえず全部アプデ。

conda update -n base conda
conda update --all
conda update python

OpenCV

Anaconda navigatorを起動する。

Environment → 『OpenCV』環境を作成
OpenCVおよびscikit-imageをインストールする。
OpenCVで調べたらlibopencvopencvpy-opencvの三つが出てきたんだけど、区別がつかないのでとりあえず全部インストールした。

install.png

あと、そのままだとmatplotlib._qhullがねーぞとかいうエラーが出てきたけど、uninstall matplotlib && install matplotlibとしたらなおった。
よくわからない。

実装

ソース

import cv2
import os
import glob

def save_frame(video_path, frame_num, result_path):
    """
    対象のフレームを画像に保存する。

    Parameters
    ----------
    video_path : string
        動画フルパス
    frame_num : int
        フレーム数
    result_path : string
        保存先の画像フルパス

    Returns
    -------
    ret : boolean
        キャプチャできたらTrue。
    """
    cap = cv2.VideoCapture(video_path)
    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
    ret, frame = cap.read()

    # cv2.imwriteは日本語使えないので、一度テンポラリに保存してから移動する
    temporary_path = 'C:/path/to/dummy.png'

    if ret:
        cv2.imwrite(temporary_path, frame)
        shutil.move(temporary_path, result_path)
        return True

    return False


def get_ssim_list(imageFileName, movieFileName, frameStart=0, frameFinish=9999, frameStep=1):
    """
    動画のSSIMを求める

    Parameters
    ----------
    imageFileName : string
        画像ファイルのフルパス
    movieFileName : string
        動画ファイルのフルパス
    frameStart : int
        調べる開始フレーム
    frameFinish : int
        調べる終了フレーム
    frameStep : int
        フレームを飛ばす数。1だととても遅い。

    Returns
    -------
    ssimArray : array
        {フレーム:SSIM}
    """

    # 初期値
    imageData = cv2.imread(imageFileName)
    movieData = cv2.VideoCapture(movieFileName)
    ssimArray = {}
    frame = frameStart

    while frame < frameFinish:
        # 画像を取得
        movieData.set(cv2.CAP_PROP_POS_FRAMES, frame)
        ret, movieImageData = movieData.read()
        # 取れなくなったら終わり
        if not ret:
            break
        # 返り値に積む
        ssimArray[frame] = compare_ssim(imageData, movieImageData, multichannel=True)
        # 次フレーム
        frame += frameStep

    return ssimArray


# 比較元画像
defaultImage = 'C:/path/to/eve.png';

# 調査元動画ディレクトリ
movieDir = 'C:/path/to/movie/'
# 画像の出力先ディレクトリ
imageDir = 'C:/path/to/image/'

# 動画リストを取得
movieArray = glob.glob(movieDir + '*.mp4', recursive=True)

# 動画リストでループ
for movieFile in movieArray:
    # 保存ファイル名を作成
    a = os.path.basename(movieFile)
    saveImageFile = imageDir + os.path.splitext(a)[0] + ".png"

    # まず大まかに調べる
    ssimList1 = get_ssim_list(defaultImage, movieFile, frameStart=0, frameFinish=600, frameStep=30)
    nearFrame = max(ssimList1, key=ssimList1.get)

    # 一番近いところの周囲をフレーム単位で調査
    ssimList2 = get_ssim_list(defaultImage, movieFile, frameStart=max(0, nearFrame-28), frameFinish=nearFrame+28, frameStep=1)
    nearestFrame = max(ssimList2, key=ssimList2.get)

    # 保存する
    save_frame(movieFile, nearestFrame, saveImageFile)

Pythonは素人なのでソースは適当です。

最初は大まかに1秒ごと調べて、最も近かったところの周囲を再度1フレームずつ調べるという二段階の調査になっています。
さすがに最初から最後まで全フレームを調べるのは重すぎますからね。

実行

全員の処理に3時間弱かかりました。
image_opencv.png

キャプチャ画像のサムネイルを見るかぎりでは、非常に良さそうな結果に見えますね。
髪の毛とかあるからもっと残念なことになるかもしれないと危惧していたのですが、予想外に綺麗にいいかんじになってくれました。

完成したPV

OpenCVが出力した画像を使って作成したPVがこちらになります

残念ながら、カメラが下の方にずれているアイドルが散見されます。
腋ではなく、なにやら別のところを注視しているようにも見えてしまいますね。
しかし、全く関係ないシーンを検出するようなことはありませんでした。
最良とはとても言えないものの、まあそこそこな結果になったのではないでしょうか。

今後の展望

今回は『画像同士の近似度を測る』だけという単純なアプローチでした。
キャプチャの高さがずれているアイドルはおそらく背景に引っ張られているせいだと思うので、次は人物だけをクリッピングして比較するとか、あるいは機械学習やらなにやらを使って骨格の位置で比較する、みたいなことをやりたいですね。

あと、どちらかというとスクリーンショットのキャプチャより、その前のPV録画のほうが辛かったので、むしろそっちを自動化したいところです。

・アイドルを選択する
・シンデレラドリームに着替えてもらう
・PVを再生する
・PCのキャプチャソフトでPVを録画する
・録画されたファイルにアイドル名を付ける
・これを190回繰り返す
movie.png

ちなみにPV録画機材はRagno Grabber2です。
安いわりにけっこうな画質でなんでも録画できるのと、あとパススルーがあるので無遅延でTV出力できるのがとっても便利。

まとめ

サンタクロースちゃんと結婚したい。


  1. 私は20人くらいしかわからない。

114
90
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
114
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?