post Image
けものフレンズのワンシーンから話数と、どのあたりかを推測する

モチベーション

かばんちゃん萌え

は?可愛すぎやろ(キレ気味)。何が可愛いかって全部だぞお前。
Twitterで二次絵を見て悶絶する日々を送っております。

ここから本題

ごちうさーちマジですごくて、高速だし、正確だしでこのソフトをリスペクトで、けものフレンズ版を自作してみた。アルゴリズムとかは参考にしたけど、できるだけごちうさーちのソースプログラムは勉強を兼ねて見ないで作成した。本家はC#だけど、今回はPython3で作る。使用したライブラリ等のインストールについてはここでは解説しない。OpenCVとimagehash、mysql connectorを使う。

データベースの作成

OpenCVで、動画から1フレームずつ取り出して、画像を64bitまでdHashを使って圧縮する。それをDBに登録する。ここではPHPMyAdmin。1レコードは「dHash値、フレーム数、話数」です。

(2018年3月7日追記)
最初の投稿では、numpy.ndarrayとPIL.JpegImagePlugin.JpegImageFile間の変換方法がわからず、一度cv2.imwriteでbuffer.jpgに書き出してからImage.openで読み込むというアホすぎるコードでしたが、変換方法がわかりましたのでそちらの方法に書き換えています。

KemonoFriendsDBgenerator.py
#.mp4から1フレームずつ取り出しdHash計算後、指定されたDBに登録
#2017/12/28
#2018/03/07

import cv2
import numpy as np
from PIL import Image
import imagehash
import mysql.connector

conn = mysql.connector.connect(user='root', password='', host='localhost', database='kemonofriends')
cur = conn.cursor()

cap=cv2.VideoCapture("KemonoFriends1.mp4")
i=1
while(cap.isOpened()):
    ret,frame=cap.read()
    pilImg = Image.fromarray(np.uint8(frame))
    hash=imagehash.dhash(pilImg)

    print("dHash="+str(hash)+",Frame="+str(i))
    cur.execute("insert into main values ('"+str(hash)+"',"+str(i)+",1);")
    conn.commit()
    i+=1
    if cv2.waitKey(1) & 0xFF==ord('q'):
        break

cur.close
conn.close
cap.release()
cv2.destroyAllWindows()

dHash、フレーム数、話数の順で昇順に並べて、完成したDBをcsvでエクスポートした。50万行ぐらいになった。メモリに展開して15MBぐらい。

KemonoFriendsDB.csv(一部抜粋)
5979a89c9eeeae2a,34080,12
5979a89c9eeeae6a,34078,12
5979a89c9eeeae6a,34079,12
5979bb9b9999b973,27207,7
5979bb9b9999b973,27208,7
5979bb9b9999b973,27209,7
5979bb9b9999b9b3,27210,7
5979bb9b9999b9b3,27211,7
5979f0b2c666636e,31367,9
597c789898b6766c,26162,7
597c789898b6766c,26163,7
597c789898b6766c,26164,7
597c789898b6766c,26165,7
597c789898b6766c,26166,7
597c7c9898b6766c,26161,7
597c7c989c96766c,26139,7
597c7c989c96766c,26140,7
597c7c989c96766c,26141,7
5982c6ddc2aacae8,2660,1
5988c162a2e1e5e3,21076,2
5988c162a2e1e5e3,21077,2
598d8e8d8d189868,40668,1
598d8e8d8d189868,40669,1
5991858785ebc9cb,13397,10

メインプログラムの作成

探したい画像を入力すると、dHashを計算して、csvから一番近いdHash値を探して、フレーム数から、何話の何分何秒当たりのシーンかを当てる。線形探索で探す。他の探索アルゴリズムより遅いので、できるだけ速くなるように申し訳程度にハミング距離の計算部分はビット演算にしてる。

KemonoFriendsSearch.py
#画像を読みこみdHash計算後、それに最も近い値をcsvから探索
#2017/12/30
#2018/01/15

from PIL import Image
import imagehash
import csv

#ハミング距離
def getHammingDistance(n,m):
    data=n ^ m
    data=(data & 0x5555555555555555)+((data & 0xAAAAAAAAAAAAAAAA)>> 1)
    data=(data & 0x3333333333333333)+((data & 0xCCCCCCCCCCCCCCCC)>> 2)
    data=(data & 0x0F0F0F0F0F0F0F0F)+((data & 0xF0F0F0F0F0F0F0F0)>> 4)
    data=(data & 0x00FF00FF00FF00FF)+((data & 0xFF00FF00FF00FF00)>> 8)
    data=(data & 0x0000FFFF0000FFFF)+((data & 0xFFFF0000FFFF0000)>>16)
    data=(data & 0x00000000FFFFFFFF)+((data & 0xFFFFFFFF00000000)>>32)
    return data

#探索する画像のdHash計算
hash=imagehash.dhash(Image.open("buffer.png"))
hash_str=str(hash)
hash_int=int(hash_str,16)

#csv読み込み
f = open("KemonoFriendsDB.csv", "r")
csv_data = csv.reader(f)
db = [ e for e in csv_data]
f.close()

result="近い画像が見つかりました"

ans=-1          #答えとなるレコード
min=65          #答えとなるハッシュ値とのハミング距離
max=len(db)-1   #DBのレコード数
Hamming_limit=5 #同じ画像を発見できなかった時、許容できるハミング距離の上限

#線形探索
for i in range(len(db)-1):
    diff=getHammingDistance(int(db[i][0],16),hash_int)

    #print("lines="+str(i)+","+hash_str+","+db[i][0]+",diff="+str(diff)+",min="+str(min))

    #同じ画像があった
    if(diff==0):
        min=diff
        ans=i
        result="この画像と同じ画像がdbに存在する"
        break

    #近い画像があった
    if(diff<min):
        min=diff
        ans=i

print("\n結果:")
print("探してた画像のdHash="+hash_str)
print("dbから検索結果dHash="+db[ans][0])
print("ハミング距離="+str(min))

if(min>Hamming_limit):
    result="検索失敗 この画像はdbに存在しない"
    print(result)
else:
    seconds=    int(int(db[ans][1])/30)%60
    minutes=int(int(int(db[ans][1])/30)/60)
    print(result)
    print("フレーム="+db[ans][1])
    print(db[ans][2]+"話"+str(minutes)+"分"+str(seconds)+"秒あたり")

実行結果

入力画像1

あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛
2017-12-31 (7).png

結果1

「5話9分35秒あたり」とな…
2018-01-15 (3).png
確認してみる。
2018-01-13 (1).png
OHHHHHHHHHHHHHHHHHHHHH!!!!!!YEEEEEEEEEEEEEEEES!!!!!!!!!!!!!!

入力画像2

一応もう一枚やってみますか…
2018-01-15.png

結果2

2018-01-15 (2).png

2018-01-15 (5).png
フレーム数をキャストして「分、秒」を算出してるので若干の誤差はでますね。
これで尊い映像を素早く見つけられるようになったぞ!

入力画像3

友人と遊んだときの記念撮影を入力してみる。
01.png

結果3

精度良し。
2018-01-15 (4).png

参考文献

配列を1次元に変換するNumPyのflatten関数の使い方


『 Python 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。