しばらく前に、ゴルフゲームのプレイ動画から、1ホール毎に切り出しするPythonコードを紹介しました。今回は、分割したファイルへタグ付けするコードを紹介したいと思います。

出来栄え

こんな感じにタグ付けされます。検索で、コースとかホールとか入れると、サクっと抽出。同じホールをパパパっと見ていって比較すると、高低差どのぐらいみたほうがいいのか?、とか、カーブどのくらいがいいか、とか判断しやすくっていいです。あとは、腕が伴えばねぇ。。。

またも、C#からPythonへの移植でハマる

以前に書いてたコードはC#。今回もPythonへ書き換えていて、困ってしまいました。タグ付け方法がわ・か・ら・な・いー! C#の時は、WindowsAPICodePackを使ってました。Pythonでは使えないけど、なんてったってPythonだから、ググればすぐにわかるでしょ、って思ったけどなかなかヒットしない。読み込みはあるけど、書き方がわからん。

ググってわからない時は、じじぃに聞け!

じじぃ(もとい、経験豊富な年長者(;^_^A)は、大抵、聞けば何かしら知ってる。ちょっとヘルプ投げて、おだてておいたら、早速教えてくれました。ブログに書いてくれてましたので、リンク貼っときます。ちなみに、このブログもテンプレとか真似させてもらってます。

たのじぃの書き捨てノート
PythonでWindows ファイルの拡張プロパティを読み書きする
Pythonで、Windows拡張タグを読み書きするサンプルコードの紹介。GetDetailsOfで読むことはできる。書き込みは、propsys を使う。
dummy

コードはこちら

画像判定用の比較方法は、昔、C#で使ってた時のものに変えました。RGB各要素の差分が100以上の要素数をカウントして判断する方法。こちらのほうがコードもシンプルだし、早くて安定して判定できる。後は、タグ付け用の比較画像のリストを追加していけば、いろいろ判定してタグ付けしていってくれます。実際は沢山の参照画像を登録してますが、下のコードでは適度に省いておきました。


import os
import glob
import cv2
import ffmpeg
import numpy as np
import pythoncom
from win32com.propsys import propsys
from win32com.shell import shellcon

# タグの読み込み
def readTags(filename):
    # get PROPERTYKEY for "System.Keywords"
    pk = propsys.PSGetPropertyKeyFromName("System.Keywords")
    # get property store for a given shell item (here a file)
    ps = propsys.SHGetPropertyStoreFromParsingName(filename, None, shellcon.GPS_READWRITE, propsys.IID_IPropertyStore)
    # read & print existing (or not) property value, System.Keywords type is an array of string
    keywords = ps.GetValue(pk).GetValue()
    print(f"Read Tags:{filename}:{keywords}")
    return keywords

# タグの書き込み
def writeTags(filename,keywords):
    # get PROPERTYKEY for "System.Keywords"
    pk = propsys.PSGetPropertyKeyFromName("System.Keywords")
    # get property store for a given shell item (here a file)
    ps = propsys.SHGetPropertyStoreFromParsingName(filename, None, shellcon.GPS_READWRITE, propsys.IID_IPropertyStore)

    # build an array of string type PROPVARIANT
    newValue = propsys.PROPVARIANTType(keywords, pythoncom.VT_VECTOR | pythoncom.VT_BSTR)
    # write property
    ps.SetValue(pk, newValue)
    ps.Commit()
    print(f"Write Tags:{filename}:{keywords}")

# 比較画像クラス
class comp_image:
    def __init__(self,t,img,x,y,w,h):
        self.tag = t
        self.x1 = x
        self.y1 = y
        self.x2 = x+w
        self.y2 = y+h
        self.sq = w*h
        self.fr = 0.0
        self.cmpimage = img[y:y+h,x:x+w]
        self.res = False

    # 参照画像と比較してみる
    def comp(self,frame,threshold):
        # 差分を取る
        im_diff = np.abs(frame[self.y1:self.y2,self.x1:self.x2].astype(int) - self.cmpimage.astype(int))
        # 100以上の差を数える
        judge=im_diff>100
        self.fr = judge.sum()
        if self.fr < threshold:
            self.res = True
            return True
        else:
            self.res = False
            return False

    # テスト表示
    def testview(self):
        cv2.imshow("test",self.cmpimage)
        while True:
            if cv2.waitKey(25) & 0xFF == ord('q'): 
                break
        cv2.destroyAllWindows()


def AddTag(mvfile,mabiki,comp_images,overwrite):
    # 既についてるKeywordsを読み込む
    keywords = readTags(mvfile)
    # 5個なかったら、基本タグをセットしておく
    if keywords == None or len(keywords)<5:
        keywords=['Tour**','Course**','Hole**','Par**','***']
    # 上書きモード
    FixedTag = [False,False,False,False,False]
    if not overwrite:
        if keywords[0]!='Tour**':
            FixedTag[0] = True
        if keywords[1]!='Course**':
            FixedTag[1] = True
        if keywords[2]!='Hole**':
            FixedTag[2] = True
        if keywords[3]!='Par**':
            FixedTag[3] = True
        if keywords[4]!='***':
            FixedTag[4] = True
        
    # VideoCapture
    cap = cv2.VideoCapture(mvfile)
    if (cap.isOpened()== False):  
        print("ビデオファイルを開くとエラーが発生しました") 
  
    while(cap.isOpened()):
        
        # 読み飛ばし
        for i in range(mabiki):
            ret, frame = cap.read()

        # False なら終了
        if ret == True:
            # View (見ながらするならコメント外す)
            # miniframe = cv2.resize(frame,dsize=None, fx=0.5 , fy=0.5)
            # cv2.imshow("Video", miniframe)
            # if cv2.waitKey(25) & 0xFF == ord('q'): 
            #    break
            if not FixedTag[0]:
                # Tour Check
                for ci in comp_images['Tour']:                    
                    if ci.comp(frame,20):
                        # みつけたのでタグ上書き
                        print(f"Find:{ci.tag}, {ci.fr:.1f}")
                        keywords[0] = ci.tag
                        FixedTag[0] = True
                        break
            if not FixedTag[1]:
                # Course Check
                for ci in comp_images['Course']:
                    if ci.comp(frame,20):
                        # みつけたのでタグ上書き
                        print(f"Find:{ci.tag}, {ci.fr:.1f}")
                        keywords[1] = ci.tag
                        FixedTag[1] = True
                        break
            if not FixedTag[2] or not FixedTag[3]:
                # Hole&Par
                for ci in comp_images['HolePar']:
                    if ci.comp(frame,20):
                        # みつけたのでタグ上書き
                        print(f"Find:{ci.tag}, {ci.fr:.1f}")
                        keywords[2] = ci.tag.split(',')[0]
                        keywords[3] = ci.tag.split(',')[1]
                        FixedTag[2] = FixedTag[3] = True
                        break
            if not FixedTag[4]:
                # OtherInfo Check
                for ci in comp_images['Others']:
                    if ci.comp(frame,20):
                        # みつけたのでタグ上書き
                        keywords[4] = ci.tag
                        FixedTag[4] = True
                        break            
        else:
            break
        if FixedTag[0] and FixedTag[1] and FixedTag[2] and FixedTag[3] and FixedTag[4]:
            break
        

    # 終了処理
    cap.release()
    cv2.destroyAllWindows()
    writeTags(mvfile,keywords)

# タグ付け用、比較画像のリスト
Tour2      = comp_image('Tour2'  ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour2_JuniperPoint_Hole2_Par4.jpg"), 190,180,370,90)
Tour3      = comp_image('Tour3'  ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour3_NamhaeCliffs_Hole8_Par3.jpg"), 190,180,370,90)
Tour4      = comp_image('Tour4'  ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour4_TheMilano_Hole9_Par5.jpg"), 130,190,485,50)

PorthelloCove  = comp_image('PorthelloCove' ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_PorthelloCove_Hole9_Par5.jpg"), 220,310,310,40)
MapleBay       = comp_image('MapleBay'      ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_MapleBay_Hole6_Par3.jpg"), 220,310,310,40)
TheOasis       = comp_image('TheOasis'      ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_TheOasis_Hole9_Par5.jpg"), 220,310,310,40)
GokashoBay     = comp_image('GokashoBay'    ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_GokashoBay_Hole9_Par5.jpg"), 220,310,310,40)
CityPark       = comp_image('CityPark'      ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour6_CityPark_Hole7_Par4.jpg"), 220,310,310,40)

Hole9Par5     = comp_image('Hole9,Par5'   ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_PorthelloCove_Hole9_Par5.jpg"), 220,358,310,30)
Hole8Par4     = comp_image('Hole8,Par4'   ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour11_ChateauLavande_Hole8_Par4.jpg"), 220,358,310,30)
Hole8Par3     = comp_image('Hole8,Par3'   ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_MapleBay_Hole8_Par3.jpg"), 220,358,310,30)
Hole7Par4     = comp_image('Hole7,Par4'   ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour10_MapleBay_Hole7_Par4.jpg"), 220,358,310,30)
Hole7Par3     = comp_image('Hole7,Par3'   ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\Tour11_GrunbergSlopes_Hole7_Par3.jpg"), 220,358,310,30)

Coin400  = comp_image('400' ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\400.jpg"), 290,115,150,45) 
Coin1_6K = comp_image('1.6K',cv2.imread(r"D:\GolfClash\RefImage\StdSize\1_6K.jpg"), 290,115,150,45) 
Coin6K   = comp_image('6K'  ,cv2.imread(r"D:\GolfClash\RefImage\StdSize\6K.jpg"), 290,115,150,45) 
Coin200K = comp_image('200K',cv2.imread(r"D:\GolfClash\RefImage\StdSize\200K.jpg"), 290,115,150,45) 

TourImages = [Tour2,Tour3,Tour4]
CourseImages  = [PorthelloCove,MapleBay,TheOasis]
HoleParImages =  [Hole9Par5,Hole8Par4,Hole8Par3,Hole7Par4,Hole7Par3]
Others = [Coin400,Coin1_6K,Coin6K,Coin200K]
 
comp_images = {'Tour':TourImages,'Course':CourseImages,'HolePar':HoleParImages,'Others':Others}

# 指定フォルダ内のmp4ファイルを順次処理する
top_level_dir_path = r'D:\GolfClash\CutMovie'
#top_level_dir_path = r'E:\CutTemp'

# サブディレクトリ、ファイルの絶対パスのリストを得る
paths = map(os.path.abspath, glob.glob(top_level_dir_path + '/**/*.mp4', recursive=True))
# ファイルのみフィルタリングする
paths = filter(os.path.isfile, paths)
# ソートされていないので昇順にソートする
paths = sorted(paths)
# 1つずつ処理する
for i, p in enumerate(paths):
    print('[%s / %s] Start processing %s' % (i + 1, len(paths), p))
    AddTag(p,5,comp_images,False)