2
3

More than 3 years have passed since last update.

YOLOv3の特徴マップを見てみよう

Last updated at Posted at 2020-08-28

YOLOv3の特徴マップを見てみたい

最近の流行として判断根拠の可視化がよく見受けられると思います。
自分は大学でpythonを始めることになって、yolov3を少し扱っているので特徴マップが可視化できればいいなと思っていました。
また、初心者ですのでプログラムに詳しかったり、基本的な書き方ができていたりしないので、何卒お手柔らかにお願いします。
ここで、今自分の扱っているyolov3のコードは
Github : "qqwweee/keras-yolo3"
のコードです。
このコードを少しいじって特徴マップの可視化を行いたいと思います。

※あくまで自分が特徴マップっぽいのを取り出せたかもしれない!と思う記事になります。

いじったところ

yolov3は出力が小,中,大と3つ出力されるみたいなので3つ取り出してみました。
yolo.pyでいじったところは最初のinitのところとgenerateの最後の部分とdetect_imageです。
コメントが書いてあるのでみてみてください。

yolo.py
    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults) # set up default values
        self.__dict__.update(kwargs) # and update with user overrides
        self.class_names = self._get_class()
        self.anchors = self._get_anchors()
        self.sess = K.get_session()
        #feature_getを増やしました。
        self.boxes, self.scores, self.classes ,self.feature_get= self.generate()


    def generate(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'

        # Load model, or construct model and load weights.
        num_anchors = len(self.anchors)
        num_classes = len(self.class_names)
        is_tiny_version = num_anchors==6 # default setting
        try:
            self.yolo_model = load_model(model_path, compile=False)
        except:
            self.yolo_model = tiny_yolo_body(Input(shape=(None,None,3)), num_anchors//2, num_classes) \
                if is_tiny_version else yolo_body(Input(shape=(None,None,3)), num_anchors//3, num_classes)
            self.yolo_model.load_weights(self.model_path) # make sure model, anchors and classes match
        else:
            assert self.yolo_model.layers[-1].output_shape[-1] == \
                num_anchors/len(self.yolo_model.output) * (num_classes + 5), \
                'Mismatch between model and given anchor and class sizes'

        print('{} model, anchors, and classes loaded.'.format(model_path))

        # Generate colors for drawing bounding boxes.
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
            map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                self.colors))
        np.random.seed(10101)  # Fixed seed for consistent colors across runs.
        np.random.shuffle(self.colors)  # Shuffle colors to decorrelate adjacent classes.
        np.random.seed(None)  # Reset seed to default.
        # Generate output tensor targets for filtered bounding boxes.
        self.input_image_shape = K.placeholder(shape=(2, ))
        if self.gpu_num>=2:
            self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
        boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
                len(self.class_names), self.input_image_shape,
                score_threshold=self.score, iou_threshold=self.iou)
        #以下を追加。特徴マップを取得したい層を3つ決める
        #summaryをみて適当に出力の一つ前の活性化層を見てみた
        feature_get = [self.yolo_model.get_layer("leaky_re_lu_58").output , self.yolo_model.get_layer("leaky_re_lu_65").output , self.yolo_model.get_layer("leaky_re_lu_72").output]
        return boxes, scores, classes, feature_get



    def detect_image(self, image):
        start = timer()
        if self.model_image_size != (None, None):
            assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required'
            assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required'
            boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))
        else:
            new_image_size = (image.width - (image.width % 32),
                              image.height - (image.height % 32))
            boxed_image = letterbox_image(image, new_image_size)
        image_data = np.array(boxed_image, dtype='float32')
        print(image_data.shape)
        image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        #feature_getを1つ増やしました
        out_boxes, out_scores, out_classes, feature_get= self.sess.run(
            [self.boxes, self.scores, self.classes, self.feature_get],
            feed_dict={
                self.yolo_model.input: image_data,
                self.input_image_shape: [image.size[1], image.size[0]],
                K.learning_phase(): 0
            })

        print('Found {} boxes for {}'.format(len(out_boxes), 'img'))

        font = ImageFont.truetype(font='font/FiraMono-Medium.otf',
                    size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
        thickness = (image.size[0] + image.size[1]) // 300



        for i, c in reversed(list(enumerate(out_classes))):
            predicted_class = self.class_names[c]
            box = out_boxes[i]
            score = out_scores[i]

            label = '{} {:.2f}'.format(predicted_class, score)
            draw = ImageDraw.Draw(image)
            label_size = draw.textsize(label, font)

            top, left, bottom, right = box
            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
            right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
            print(label, (left, top), (right, bottom))


            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            # My kingdom for a good redistributable image drawing library.
            for i in range(thickness):
                draw.rectangle(
                    [left + i, top + i, right - i, bottom - i],
                    outline=self.colors[c])
            draw.rectangle(
                [tuple(text_origin), tuple(text_origin + label_size)],
                fill=self.colors[c])
            draw.text(text_origin, label, fill=(0, 0, 0), font=font)
            del draw

        end = timer()
        return image, feature_get

ここで、特徴マップを取得するためのコードを書きます
そのまま出力するとたくさんでてくるのでとりあえず今回は平均値の一番高いマップを小,中,大それぞれ取り出してみようと思います。

feature_img.py
import numpy as np
import keras.backend as K
from keras.models import load_model
from keras.layers import Input
from PIL import Image, ImageDraw,ImageOps 
import tensorflow as tf
import cv2
import math
import collections

def feature(conv_list): 
    #平均値の一番高いマップを取得しようと思います。
    conv_list1 = np.asarray(conv_list[0])
    conv_list2 = np.asarray(conv_list[1])
    conv_list3 = np.asarray(conv_list[2])
    bg = np.zeros((720,720))
    ave1 = []
    ave2 = []
    ave3 = []

    for i in range(conv_list1.shape[3]):
        conv = conv_list1[0,:,:,i]
        ave1.append(np.mean(conv))
    max1 = np.max(ave1)
    index1 = ave1.index(max1)
    conv1 = conv_list1[0,:,:,index1]
    norm1 = np.max(conv1)
    norm_1 = np.min(conv1)
    conv1 = (conv1 - norm_1) / (norm1 - norm_1)

    for i in range(conv_list2.shape[3]):
        conv = conv_list2[0,:,:,i]
        ave2.append(np.mean(conv))
    max2 = np.max(ave2)
    index2 = ave2.index(max2)
    conv2 = conv_list2[0,:,:,index2]
    norm2 = np.max(conv2)
    norm_2 = np.min(conv2)
    conv2 = (conv2 - norm_2) / (norm2 - norm_2)

    for i in range(conv_list3.shape[3]):
        conv = conv_list3[0,:,:,i]
        ave3.append(np.mean(conv))
    max3 = np.max(ave3)
    index3 = ave3.index(max3)
    conv3 = conv_list3[0,:,:,index3]
    norm3 = np.max(conv3)
    norm_3 = np.min(conv3)
    conv3 = (conv3 - norm_3) / (norm3 - norm_3)

    conv1 = conv1 * 255
    conv2 = conv2 * 255
    conv3 = conv3 * 255
    conv1 = cv2.resize(conv1, (350, 350), interpolation=cv2.INTER_NEAREST)
    conv2 = cv2.resize(conv2, (350, 350), interpolation=cv2.INTER_NEAREST)
    conv3 = cv2.resize(conv3, (350, 350), interpolation=cv2.INTER_NEAREST)

    bg[5:355,5:355] = conv1
    bg[5:355,365:715] = conv2
    bg[365:715,5:355] = conv3

    return bg

最後に、yolo_video.pyをいじりました。

yolo_video.py
def detect_img(yolo):
    while True:
        img = input('Input image filename:')

        try:
            image = Image.open(img)
        except:
            print('Open Error! Try again!')
            continue
        else:
            r_image , feature_get = yolo.detect_image(image)
            #特徴マップの表示以下で行います。
            img_feature = feature_img.feature(feature_get)
            cv2.imwrite("feature_map.png" , img_feature)
            r_image.show()
    yolo.close_session()

これで、yolo_video.py --imageで実行すれば特徴マップが得られると思います。

実行例

今回は猫をフリー素材のぱくたそさんから借りてきましたので検出しようと思います
ちなみに重みはデフォルトのyolo.h5を使用しました
↓検出された猫です

ccccc.png

特徴マップはこうなりました

feature_map.png

なんだかよくわかりません。
猫の目と口が特徴としてでてるみたいに見えます。
ちなみに入力画像の猫ちゃんはネットワークに入れられる際に416×416のグレー画像の上に横幅を合わせて貼り付けられてリサイズされています。特徴マップはその大きさに合わせられています。
yolov3はグリッドセルを中心?にして検出するみたいなので、どこらへんを中心として見ているかがこれではわからないのと、3枚のマップのうちどれを見て猫と判断しているのかがわからないのでそこら辺も見えるようにしなければならないと思いました。
また、今回は最後の活性化層を見て見ましたが、別の層を見てみるといろいろ変わって来るかもしれません。
取得されたマップの値も処理の仕方がわからないのでもっと良いやり方があると思います。

初めてこのような記事を書いて見ましたが、同じ初心者の方の参考になってくれると嬉しいです。

2
3
0

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
2
3