77888
現在リアルタイムにサーマルカメラで撮影し取得した画像に対し，keras-
yolo3というpythonの物体検出ライブラリを用いて物体検出を行うことを考えています．

サーマルカメラで撮影し，画像を取得したらその画像を保存するというプログラムを実行したところ，1秒に約10～11枚の画像が出力されました(FPS平均は約10)．

ここに取得した画像をkeras-
yolo3で読み込み，検出結果を出力するようにプログラムを書き換えて実行したところ，1枚の画像に対して物体検出に約10秒かかってしまいました(FPS平均は約0.1)．

目標としては，10枚の画像に対して1秒で物体検出を行えるようにしたいのですが，処理速度を上げるにはどうすればいいでしょうか．

### 環境

Windows10  
Anaconda 64bit  
Python 3.6.6  
GPU: GeForce RTX 2060 (Computer Capability : 7.5)  
CUDA 10.0  
cuDNN 7.4  
tensorflow-gpu 1.13.1  
keras 2.2.4  
numpy 1.16.4

### 参考

  * [keras-yolo3 GitHub](https://github.com/qqwweee/keras-yolo3)
  * [keras-yolo3を使ってリアルタイム物体検出を簡単に試してみる](https://arakan-pgm-ai.hatenablog.com/entry/2019/03/20/090000)
  * [keras-yolo3で独自データセットの学習方法](https://sleepless-se.net/2019/06/21/how-to-train-keras%E2%88%92yolo3/)

### ソースコード

物体検出のプログラム(といってもyolo.pyの中身を少し修正したもの)

```

    import colorsys
    import os
    import numpy as np
    import cv2
    from timeit import default_timer as timer
    from keras import backend as K
    from keras.models import load_model
    from keras.layers import Input
    from PIL import Image, ImageFont, ImageDraw
    from yolo3.model import yolo_eval, yolo_body, tiny_yolo_body
    from yolo3.utils import letterbox_image
    from keras.utils import multi_gpu_model
    
    from time import time
    
    class YOLO(object):
        _defaults = {
            "model_path": 'model_data/trained_nose.h5',
            "anchors_path": 'model_data/yolo_anchors.txt',
            "classes_path": 'model_data/my_classes_nose.txt',
            "score" : 0.3,
            "iou" : 0.45,
            "model_image_size" : (416, 416),
            "gpu_num" : 1,
        }
    
        @classmethod
    
        def get_defaults(cls, n):
            if n in cls._defaults:
                return cls._defaults[n]
            else:
                return "Unrecognized attribute name '" + n + "'"
    
    
        def __init__(self, **kwargs):
            self.__dict__.update(self._defaults)
            self.__dict__.update(kwargs)
            self.class_names = self._get_class()
            self.anchors = self._get_anchors()
            self.sess = K.get_session()
            self.boxes, self.scores, self.classes = self.generate()
    
    
        def _get_class(self):
            classes_path = os.path.expanduser(self.classes_path)
            with open(classes_path) as f:
                class_names = f.readlines()
            class_names = [c.strip() for c in class_names]
            return class_names
    
    
        def _get_anchors(self):
            anchors_path = os.path.expanduser(self.anchors_path)
            with open(anchors_path) as f:
                anchors = f.readline()
            anchors = [float(x) for x in anchors.split(',')]
            return np.array(anchors).reshape(-1, 2)
    
    
        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.'
            num_anchors = len(self.anchors)
            num_classes = len(self.class_names)
            is_tiny_version = num_anchors==6
            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))
    
            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)
            np.random.shuffle(self.colors)
            np.random.seed(None)
    
            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)
            return boxes, scores, classes
    
    
        def detect_image(self, image, t_start, num):
    
            image = image.convert('RGB')
    
            t = time()
            pass_time = t - t_start
            print("yolo_time_3: ",pass_time)
    
            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)
    
            t = time()
            pass_time = t - t_start
            print("yolo_time_4: ",pass_time)
    
            image_data = np.array(boxed_image, dtype='float32')
            image_data /= 255.
            image_data = np.expand_dims(image_data, 0)
            out_boxes, out_scores, out_classes = self.sess.run(
                [self.boxes, self.scores, self.classes],
                feed_dict={
                    self.yolo_model.input: image_data,
                    self.input_image_shape: [image.size[1], image.size[0]],
                    K.learning_phase(): 0
                })
    
            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
    
            t_l_b_r = []
    
            t = time()
            pass_time = t - t_start
            print("yolo_time_5: ",pass_time)
    
            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'))
                t_l_b_r.append(top)
                t_l_b_r.append(left)
                t_l_b_r.append(bottom)
                t_l_b_r.append(right)
                #print(t_l_b_r)
                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])
    
                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
    
            t = time()
            pass_time = t - t_start
            print("yolo_time_6: ",pass_time)
    
            result = np.asarray(image)
            result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
    
    <検出結果を表示，保存します>
    
            cv2.namedWindow("result", cv2.WINDOW_NORMAL)
            cv2.imshow("result", result)  
            cv2.imwrite("d:/study_data/image_'+format(num)+'.bmp",result)
    
            return t_l_b_r
    
        def close_session(self):
            self.sess.close()
    
    
```

メインのプログラム

```

    import usb.core
    import usb.util
    import numpy as np
    import os
    from PIL import Image
    from yolo_ex import YOLO
    
    import sys
    import argparse
    
    ~~~~~
    
    関係ない箇所については省略させていただきます
    
    ~~~~~
    
    <yolo.video.py から拾ってきました>
    
    FLAGS = None
    parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
    parser.add_argument('--model_path', type=str,help='path to model weight file, default ' + YOLO.get_defaults("model_path"))
    parser.add_argument('--anchors_path', type=str,help='path to anchor definitions, default ' + YOLO.get_defaults("anchors_path"))
    parser.add_argument('--classes_path', type=str,help='path to class definitions, default ' + YOLO.get_defaults("classes_path"))
    parser.add_argument('--gpu_num', type=int,help='Number of GPU to use, default ' + str(YOLO.get_defaults("gpu_num")))
    parser.add_argument('--image', default=False, action="store_true",help='Image detection mode, will ignore all positional arguments')
    parser.add_argument("--input", nargs='?', type=str,required=False,default='./path2your_video',help = "Video input path")
    parser.add_argument("--output", nargs='?', type=str, default="",help = "[Optional] Video output path")
    FLAGS = parser.parse_args()
    
    <main関数です>
    
    if __name__ == '__main__':
      import cv2
      from time import time
    
      num = 0
    
    <カメラからデータを取得します>
    
      t0 = time()
    
      time_np = np.empty(0)
    
      while True:
    
        t = time()
        if num == 0:
            t_start = time()
        pass_time = t - t_start
        print("\n\nyolo_time_1: ",pass_time)
    
        time_np = np.append(time_np, pass_time)
        t0 = time()
    
    <熱カメラからデータを取得し，輝度値に変換します>    
    
        image = cam.get_image()
        image = rescale(image)
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    
    <得られた画像にサイズ変換を施します>
    
        image = add_margin(image,SIZE)
    
        image = Image.fromarray(image)
        tt = time()
        pass_time2 = tt - t_start
        print("yolo_time_2: ",pass_time2)
    
    <検出プログラムに画像を渡します>
    
        image_yolo = YOLO.detect_image(YOLO(**vars(FLAGS)), image, t_start, num)
    
        num += 1
    
        if cv2.waitKey(1) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            break
    
    
```

### どこで時間がかかっているのか

上記のプログラムを動かし，各過程における時間を出力したところ，結果は画像のようになりました．

[![画像の説明をここに入力](https://i.stack.imgur.com/KsR1I.png)](https://i.stack.imgur.com/KsR1I.png)

画像を検出プログラム内の関数"detect_image()"に渡す直前から，渡した直後のところで10秒ほどかかっていたので，この部分をどうにかするしかなさそうに見えますが...
CC BY-SA 4.0
2021-06-21T05:20:41.463
77677
2021-07-02T06:11:46.533
2021-07-02T06:11:46.533
41740
41740
question
python keras
プログラムの実行速度をあげたい
そのライブラリを知らないので一般論ですが、

現在のコードだと一枚画像を処理するたびにモデルを作成してウェイトをロードして... とやっているように見えます。  
このモデルの初期化処理はかなり時間がかかります。

特別に内部状態を持っていないモデルは何度でも推論（=物体検出）を行うことができます。  
なので、起動時に一度だけモデルを作成し、以後の無限ループ部分ではそのモデルを使いまわすことができるはずです。

モデルが状態を持っていた場合でも、状態だけをリセットする方法がないか探してみてはどうでしょうか。

* * *

追記

[detect_video](https://github.com/qqwweee/keras-
yolo3/blob/e6598d13c703029b2686bc2eb8d5c09badf42992/yolo.py#L172)
関数が、やろうとしていることに非常に近いと思います。  
この関数は動画ファイルを入力としていますが、ファイル190行目で動画から1枚画像を取り出して、それを192行目で `yolo.detect_image`
に渡しています。  
この `yolo` インスタンスは無限ループの中で再作成されていません。  
190行目の処理を `cam.get_image()` に入れ替えて、画像形式の変換も適切に行えば期待通りの動きをすると思います。
CC BY-SA 4.0
2021-06-30T14:49:14.693
77888
2021-07-01T11:09:02.123
2021-07-01T11:09:02.123
20885
20885
77677
answer