複数画像をKerasのVGG16で特徴抽出してk-means++でクラスタリング
VGG16, VGG19, ResNet50, InceptionV3など、 ImageNetで学習済みのモデルがKerasで使える。 物体認識だけでなく特徴抽出にも使えるので、 複数画像をVGG16で特徴抽出して、これをk-means++でクラスタリングしてみた。 なお複数画像は、ハワイで撮影したフラダンスの動画をフレーム分割して用意した。 以下に説明するコードは、ここ に置いておく。
ディレクトリー構成
ディレクトリー構成は以下の通り。
src/image_clustering.py
が実行ファイルとなる。
data/video/
にクラスタリングしたい動画を置く。
data/images/targrt/
に動画をフレーム分割した画像が保存される。
data/images/clustered/
に分類済みの画像が保存される。
なお、動画でなくても、クラスタリングしたい画像を data/images/targrt/
に置くこともできる。
なお、ここ
には src/image_clustering.py
しかないので、
data/video/
と data/images/
フォルダーを追加して使ってください。
image_clustering ├─data │ ├─images │ │ ├─clustered │ │ └─target │ └─video └─src └─image_clustering.py
初期設定
グローバル変数は固定。
__init__()
の引数に、動画の場合は video_file
にファイル名を指定する。
画像の場合は、input_video
をFalseに変更する。
DATA_DIR = '../data/' VIDEOS_DIR = '../data/video/' # The place to put the video TARGET_IMAGES_DIR = '../data/images/target/' # The place to put the images which you want to execute clustering CLUSTERED_IMAGES_DIR = '../data/images/clustered/' # The place to put the images which are clustered IMAGE_LABEL_FILE ='image_label.csv' # Image name and its label class Image_Clustering: def __init__(self, n_clusters=50, video_file='IMG_2140.MOV', image_file_temp='img_%s.png', input_video=True): self.n_clusters = n_clusters # The number of cluster self.video_file = video_file # Input video file name self.image_file_temp = image_file_temp # Image file name template self.input_video = input_video # If input data is a video
メイン関数
以下の順で処理を行う。1.は動画の場合のみ。
def main(self): if self.input_video == True: self.video_2_frames() self.label_images() self.classify_images()
動画のフレーム分割
OpenCVでフレーム分割して、data/images/targrt/
に保存する。
def video_2_frames(self): print('Video to frames...') cap = cv2.VideoCapture(VIDEOS_DIR+self.video_file) # Remove and make a directory. if os.path.exists(TARGET_IMAGES_DIR): shutil.rmtree(TARGET_IMAGES_DIR) # Delete an entire directory tree if not os.path.exists(TARGET_IMAGES_DIR): os.makedirs(TARGET_IMAGES_DIR) # Make a directory i = 0 while(cap.isOpened()): flag, frame = cap.read() # Capture frame-by-frame if flag == False: break # A frame is not left cv2.imwrite(TARGET_IMAGES_DIR+self.image_file_temp % str(i).zfill(6), frame) # Save a frame i += 1 print('Save', TARGET_IMAGES_DIR+self.image_file_temp % str(i).zfill(6)) cap.release() # When everything done, release the capture print('')
実行すると、次々と画像が保存されていく。 フラダンスの動画は17秒だが、フレーム数は530枚となっている。
> python .\image_clustering.py
Video to frames...
Save ../data/images/target/img_000001.png
Save ../data/images/target/img_000002.png
Save ../data/images/target/img_000003.png
...
Save ../data/images/target/img_000530.png
画像のラベリング(特徴抽出とクラスタリング)
モデルにはVGG16を使用。 include_top=False
とすることで、特徴抽出が可能になる。
動画を分割したフレームはpngで保存しているが、画像はjpgも対象。
jpegとかも対象にしたい場合は、この辺を書き換えてください。
後述する__feature_extraction()
にVGG16のモデルと画像を渡して特徴を取得する。
全画像の特徴をk-means++でクラスタリングを行い、ラベルを取得して画像とセットにして、pandasでCSVに保存する。
def label_images(self): print('Label images...') # Load a model model = VGG16(weights='imagenet', include_top=False) # Get images images = [f for f in os.listdir(TARGET_IMAGES_DIR) if f[-4:] in ['.png', '.jpg']] assert(len(images)>0) X = [] pb = ProgressBar(max_value=len(images)) for i in range(len(images)): # Extract image features feat = self.__feature_extraction(model, TARGET_IMAGES_DIR+images[i]) X.append(feat) pb.update(i) # Update progressbar # Clutering images by k-means++ X = np.array(X) kmeans = KMeans(n_clusters=self.n_clusters, random_state=0).fit(X) print('') print('labels:') print(kmeans.labels_) print('') # Merge images and labels df = pd.DataFrame({'image': images, 'label': kmeans.labels_}) df.to_csv(DATA_DIR+IMAGE_LABEL_FILE, index=False)
__feature_extraction()
の中身は、
Applications - Keras Documentation
に書かれてある通り。
リサイズして、4次元データにして、zero-centeringしてから特徴を取得。
ただし、特徴は (1, 7, 7, 512)
の4次元で返ってくるので、
flatten()
で (25088,)
の1次元に変換する。
def __feature_extraction(self, model, img_path): img = image.load_img(img_path, target_size=(224, 224)) # resize x = image.img_to_array(img) x = np.expand_dims(x, axis=0) # add a dimention of samples x = preprocess_input(x) # RGB 2 BGR and zero-centering by mean pixel based on the position of channels feat = model.predict(x) # Get image features feat = feat.flatten() # Convert 3-dimentional matrix to (1, n) array return feat
実行した結果、画像530枚でも、VGG16による特徴抽出は24秒で終わった。なお、GPUはGeForce 1080 x1。 k-means++で50クラスタに分類しているが、だいたい連続するフレームが同じラベルになっていることが分かる。 しかし、ラベル7などフレームが飛んでいても同じラベルになっているものもある。
Label images... 99% (527 of 530) |################################## | Elapsed Time: 0:00:24 ETA: 0:00:00 labels: [28 28 28 28 28 28 28 28 28 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 11 11 11 11 11 11 11 11 11 32 32 32 32 32 32 32 32 32 32 32 27 27 27 27 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 37 37 37 37 37 37 37 37 37 37 37 37 37 37 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 29 29 29 29 29 29 29 29 29 29 29 29 29 1 1 1 1 1 42 42 42 42 42 42 45 45 45 45 45 45 45 45 45 45 45 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 35 35 35 35 35 35 35 35 35 35 35 35 19 19 19 19 19 19 41 41 41 41 41 41 41 49 49 49 49 49 49 49 13 13 13 13 13 13 44 44 44 44 44 44 44 44 44 44 44 5 5 17 17 17 17 17 17 17 17 17 17 13 13 5 5 34 34 34 34 34 34 34 34 34 34 5 5 5 5 5 5 25 25 25 25 25 25 25 25 25 15 15 15 15 15 15 31 31 31 31 31 48 48 48 48 48 48 40 40 40 40 40 40 40 20 20 20 20 20 20 20 39 39 39 39 39 39 39 39 2 2 2 2 2 2 2 2 2 2 2 2 2 2 18 18 18 18 18 18 18 18 18 18 18 18 18 46 46 46 46 46 46 46 46 46 47 47 47 47 47 47 47 47 47 22 22 22 22 22 22 6 6 6 6 6 6 6 6 6 6 43 43 43 43 43 43 43 43 43 43 30 30 30 30 30 30 30 30 30 30 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 7 7 7 10 10 10 10 10 10 10 10 10 24 24 24 24 24 24 24 24 24 14 14 0 0 0 0 0 0 0 0 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 36 36 36 36 36 36 36 36 36 36 36 36 12 12 12 12 12 12 12 12 8 8 8 8 8 8 8 8 23 23 23 23 23 23 23 23 23]
CSVに保存したデータは以下の通り。
image,label img_000000.png,28 img_000001.png,28 img_000002.png,28 ... img_000009.png,3 img_000010.png,3 img_000011.png,3 ... img_000024.png,16 img_000025.png,16 img_000026.png,16 ...
画像の分類(ラベルごとにディレクトリーにまとめる)
CSVファイルを読み込んで、ラベルの数だけ data/images/clustered/
にラベル名のディレクトリーを作成し、
data/images/target/
から画像をラベルごとにコピペする。
def classify_images(self): print('Classify images...') # Get labels and images df = pd.read_csv(DATA_DIR+IMAGE_LABEL_FILE) labels = list(set(df['label'].values)) # Delete images which were clustered before if os.path.exists(CLUSTERED_IMAGES_DIR): shutil.rmtree(CLUSTERED_IMAGES_DIR) for label in labels: print('Copy and paste label %s images.' % label) # Make directories named each label new_dir = CLUSTERED_IMAGES_DIR + str(label) + '/' if not os.path.exists(new_dir): os.makedirs(new_dir) # Copy images to the directories clustered_images = df[df['label']==label]['image'].values for ci in clustered_images: src = TARGET_IMAGES_DIR + ci dst = CLUSTERED_IMAGES_DIR + str(label) + '/' + ci shutil.copyfile(src, dst)
以下が実行ログ。
Classify images... Copy and paste label 0 images. Copy and paste label 1 images. Copy and paste label 2 images. ... Copy and paste label 49 images.
分類結果
先ほどのラベル7の画像を見てみると、左手を頭より上に、右手を胸の高さに上げている画像がまとめられている。 画像のindexは427から443に富んでいるが、どちらも同じポーズをしている。
またラベル45の画像を見てみると、両手を胸の高さに上げて、海の方を向いている画像がまとめられている。
感想
今回、動画からフレーム分割した画像ということもあって、特徴がほとんど変わらない画像が対象となってはいるが、 上手くクラスタリングできている。今後はなるべく似ていない画像で試してみようと思う。 あとできれば、k-means++を使わずにCNNに閉じた方法があれば試してみたい。