コード例 #1
0
ファイル: detection.py プロジェクト: wajahatali93/keras-yolo3
def parse_params():
    # class YOLO defines the default value, so suppress any default HERE
    parser = arg_params_yolo()
    parser.add_argument('-i',
                        '--path_image',
                        nargs='*',
                        type=str,
                        required=False,
                        help='Images to be processed (sequence of paths)')
    parser.add_argument('-v',
                        '--path_video',
                        nargs='*',
                        type=str,
                        required=False,
                        help='Video to be processed (sequence of paths)')
    arg_params = vars(parser.parse_args())
    for k_name in ('path_image', 'path_video'):
        # if there is only single path still make it as a list
        if k_name in arg_params and not isinstance(arg_params[k_name],
                                                   (list, tuple)):
            arg_params[k_name] = [arg_params[k_name]]
    # Update paths
    for k in (k for k in arg_params if 'path' in k):
        if k in ('path_image', 'path_video'):
            arg_params[k] = [update_path(path_) for path_ in arg_params[k]]
        elif arg_params[k]:
            arg_params[k] = update_path(arg_params[k])
            assert os.path.exists(
                arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.debug('PARAMETERS: \n %s', repr(arg_params))
    return arg_params
コード例 #2
0
def parse_arguments():
    parser = argparse.ArgumentParser(description='Darknet To Keras Converter.')
    parser.add_argument('--config_path',
                        type=str,
                        required=True,
                        help='Path to Darknet cfg file.')
    parser.add_argument('--weights_path',
                        type=str,
                        required=True,
                        help='Path to Darknet weights file.')
    parser.add_argument('--output_path',
                        type=str,
                        required=True,
                        help='Path to output Keras model file.')
    parser.add_argument('-p',
                        '--plot_model',
                        action='store_true',
                        help='Plot generated Keras model and save as image.')
    parser.add_argument(
        '-w',
        '--weights_only',
        action='store_true',
        help='Save as Keras weights file instead of model file.')
    arg_params = vars(parser.parse_args())
    for k in (k for k in arg_params if 'path' in k and 'output' not in k):
        arg_params[k] = update_path(arg_params[k])
        assert os.path.exists(
            arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.debug('PARAMETERS: \n %s', repr(arg_params))
    return arg_params
コード例 #3
0
    def __init__(self,
                 weights_path,
                 anchors_path,
                 classes_path,
                 model_image_size=(None, None),
                 score=0.3,
                 iou=0.45,
                 nb_gpu=1,
                 gpu_frac=None,
                 **kwargs):
        """

        :param str weights_path: path to loaded model weights, e.g. 'model_data/tiny-yolo.h5'
        :param str anchors_path: path to loaded model anchors, e.g. 'model_data/tiny-yolo_anchors.csv'
        :param str classes_path: path to loaded trained classes, e.g. 'model_data/coco_classes.txt'
        :param float score: confidence score
        :param float iou:
        :param tuple(int,int) model_image_size: e.g. for tiny (416, 416)
        :param int nb_gpu:
        :param float gpu_frac: fraction of GPU memory to reserve, None for automatic
        :param kwargs:
        """
        self.__dict__.update(kwargs)  # and update with user overrides
        self.weights_path = update_path(weights_path)
        self.anchors_path = update_path(anchors_path)
        self.classes_path = update_path(classes_path)
        self.score = score
        self.iou = iou
        self.model_image_size = model_image_size
        self.nb_gpu = nb_gpu
        if not self.nb_gpu:
            # disable all GPUs
            os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
        self.class_names = get_class_names(self.classes_path)
        self.anchors = get_anchors(self.anchors_path)
        self._open_session(gpu_frac)
        self.boxes, self.scores, self.classes = self._create_model()
        self._generate_class_colors()
コード例 #4
0
def parse_arguments():
    parser = argparse.ArgumentParser(description='Annotation Converter (COCO).')
    parser.add_argument('--path_annot', type=str, required=True,
                        help='Path to annotation for COCO dataset.')
    parser.add_argument('--path_images', type=str, required=True,
                        help='Path to images of COCO dataset.')
    parser.add_argument('--path_output', type=str, required=False, default='.',
                        help='Path to output folder.')
    arg_params = vars(parser.parse_args())
    for k in (k for k in arg_params if 'path' in k):
        arg_params[k] = update_path(arg_params[k])
        assert os.path.exists(arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.info('PARAMETERS: \n%s', '\n'.join(['"%s": \t\t %r' % (k, arg_params[k])
                                                for k in arg_params]))
    return arg_params
コード例 #5
0
def parse_arguments():
    parser = argparse.ArgumentParser(description='Annotation Converter (VOC).')
    parser.add_argument('--path_dataset', type=str, required=True,
                        help='Path to VOC dataset.')
    parser.add_argument('--sets', type=str, required=False, nargs='+',
                        help='List of sets in dataset.',
                        default=('2007,train', '2007,val', '2007,test'))
    parser.add_argument('--path_output', type=str, required=False, default='.',
                        help='Path to output folder.')
    parser.add_argument('--classes', type=str, required=False, default=None, nargs='*',
                        help='Use only following classes.')
    arg_params = vars(parser.parse_args())
    for k in (k for k in arg_params if 'path' in k):
        arg_params[k] = update_path(arg_params[k])
        assert os.path.exists(arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.info('PARAMETERS: \n%s', '\n'.join(['"%s": \t\t %r' % (k, arg_params[k])
                                                for k in arg_params]))
    return arg_params
コード例 #6
0
ファイル: detection.py プロジェクト: wajahatali93/keras-yolo3
def predict_image(yolo, path_image, path_output=None):
    path_image = update_path(path_image)
    if not path_image:
        logging.debug('no image given')
    elif not os.path.isfile(path_image):
        logging.warning('missing image: %s', path_image)

    image = Image.open(path_image)
    image_pred, pred_items = yolo.detect_image(image)
    if path_output is None or not os.path.isdir(path_output):
        image_pred.show()
    else:
        name = os.path.splitext(os.path.basename(path_image))[0]
        path_out_img = os.path.join(path_output, name + VISUAL_EXT + '.jpg')
        path_out_csv = os.path.join(path_output, name + '.csv')
        logging.info('exporting image: "%s" and detection: "%s"', path_out_img,
                     path_out_csv)
        image_pred.save(path_out_img)
        pd.DataFrame(pred_items).to_csv(path_out_csv)
コード例 #7
0
def parse_params():
    # class YOLO defines the default value, so suppress any default HERE
    parser = arg_params_yolo()
    parser.add_argument('--images',
                        default=False,
                        action='store_true',
                        help='Image detection mode.')
    parser.add_argument('--videos',
                        default=False,
                        action='store_true',
                        help='Video detection mode.')
    parser.add_argument('--stream',
                        default=False,
                        action='store_true',
                        help='Detection from stream.')

    arg_params = vars(parser.parse_args())
    for k in (k for k in arg_params if 'path' in k):
        arg_params[k] = update_path(arg_params[k])
        assert os.path.exists(
            arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.debug('PARAMETERS: \n %s', repr(arg_params))
    return arg_params
コード例 #8
0
ファイル: training.py プロジェクト: wajahatali93/keras-yolo3
def parse_params():
    # class YOLO defines the default value, so suppress any default HERE
    parser = arg_params_yolo()
    parser.add_argument('-d',
                        '--path_dataset',
                        type=str,
                        required=True,
                        help='path to the train source - dataset,'
                        ' with single taining instance per line')
    parser.add_argument(
        '--path_config',
        type=str,
        required=False,
        help='path to the train configuration, using YAML format')
    arg_params = vars(parser.parse_args())
    for k in (k for k in arg_params if 'path' in k):
        if not arg_params[k]:
            continue
        arg_params[k] = update_path(arg_params[k])
        assert os.path.exists(
            arg_params[k]), 'missing (%s): %s' % (k, arg_params[k])
    logging.debug('PARAMETERS: \n %s', repr(arg_params))
    return arg_params
コード例 #9
0
ファイル: model.py プロジェクト: wajahatali93/keras-yolo3
def create_model_bottleneck(input_shape,
                            anchors,
                            num_classes,
                            freeze_body=2,
                            weights_path=None,
                            nb_gpu=1):
    """create the training model"""
    # K.clear_session()  # get a new session
    image_input = Input(shape=(None, None, 3))
    h, w = input_shape
    num_anchors = len(anchors)

    y_true = [
        Input(shape=(h // {
            0: 32,
            1: 16,
            2: 8
        }[l], w // {
            0: 32,
            1: 16,
            2: 8
        }[l], num_anchors // 3, num_classes + 5)) for l in range(3)
    ]

    _LOSS_ARGUMENTS = {
        'anchors': anchors,
        'num_classes': num_classes,
        'ignore_thresh': 0.5
    }
    if not nb_gpu:  # disable all GPUs
        os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

    model_body = yolo_body_full(image_input, num_anchors // 3, num_classes)
    logging.info('Create YOLOv3 model with %i anchors and %i classes.',
                 num_anchors, num_classes)

    if weights_path is not None:
        weights_path = update_path(weights_path)
        if os.path.isfile(weights_path):
            logging.warning('missing weights: %s', weights_path)
        else:
            logging.info('Load weights %s.', weights_path)
            model_body.load_weights(weights_path,
                                    by_name=True,
                                    skip_mismatch=True)
            if freeze_body in [1, 2]:
                # Freeze darknet53 body or freeze all but 3 output layers.
                num = (185, len(model_body.layers) - 3)[freeze_body - 1]
                for i in range(num):
                    model_body.layers[i].trainable = False
                logging.info('Freeze the first %i layers of total %i layers.',
                             num, len(model_body.layers))

    # get output of second last layers and create bottleneck model of it
    out1 = model_body.layers[246].output
    out2 = model_body.layers[247].output
    out3 = model_body.layers[248].output
    model_bottleneck = Model([model_body.input, *y_true], [out1, out2, out3])

    # create last layer model of last layers from yolo model
    in0 = Input(shape=model_bottleneck.output[0].shape[1:].as_list())
    in1 = Input(shape=model_bottleneck.output[1].shape[1:].as_list())
    in2 = Input(shape=model_bottleneck.output[2].shape[1:].as_list())
    last_out0 = model_body.layers[249](in0)
    last_out1 = model_body.layers[250](in1)
    last_out2 = model_body.layers[251](in2)
    model_last = Model(inputs=[in0, in1, in2],
                       outputs=[last_out0, last_out1, last_out2])
    fn_loss = Lambda(yolo_loss,
                     output_shape=(1, ),
                     name='yolo_loss',
                     arguments=_LOSS_ARGUMENTS)
    model_loss_last = fn_loss([*model_last.output, *y_true])
    last_layer_model = Model([in0, in1, in2, *y_true], model_loss_last)

    fn_loss = Lambda(yolo_loss,
                     output_shape=(1, ),
                     name='yolo_loss',
                     arguments=_LOSS_ARGUMENTS)
    model_loss = fn_loss([*model_body.output, *y_true])
    model = Model([model_body.input, *y_true], model_loss)

    if nb_gpu >= 2:
        model = multi_gpu_model(model, gpus=nb_gpu)
        model_bottleneck = multi_gpu_model(model_bottleneck, gpus=nb_gpu)

    return model, model_bottleneck, last_layer_model
コード例 #10
0
class YOLO(object):
    """YOLO detector with tiny alternative

    Example
    -------
    >>> # prepare EMPTY model since download and convert existing is a bit complicated
    >>> anchors = get_anchors(YOLO.get_defaults('anchors_path'))
    >>> classes = get_class_names(YOLO.get_defaults('classes_path'))
    >>> yolo_empty = yolo_body_tiny(Input(shape=(None, None, 3)), len(anchors) // 2, len(classes))
    >>> path_model = os.path.join(update_path('model_data'), 'yolo_empty.h5')
    >>> yolo_empty.save(path_model)
    >>> # use the empty one, so no reasonable detections are expected
    >>> from yolo3.utils import image_open
    >>> yolo = YOLO(weights_path=path_model,
    ...             anchors_path=YOLO.get_defaults('anchors_path'),
    ...             classes_path=YOLO.get_defaults('classes_path'),
    ...             model_image_size=YOLO.get_defaults('model_image_size'))
    >>> img = image_open(os.path.join(update_path('model_data'), 'bike-car-dog.jpg'))
    >>> yolo.detect_image(img)  # doctest: +ELLIPSIS
    (<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=520x518 at ...>, [...])
    """

    _DEFAULT_PARAMS = {
        "weights_path":
        os.path.join(update_path('model_data'), 'tiny-yolo.h5'),
        "anchors_path":
        os.path.join(update_path('model_data'), 'tiny-yolo_anchors.csv'),
        "classes_path":
        os.path.join(update_path('model_data'), 'coco_classes.txt'),
        "score":
        0.3,
        "iou":
        0.45,
        "model_image_size": (416, 416),
        "nb_gpu":
        1,
    }

    @classmethod
    def get_defaults(cls, name):
        if name not in cls._DEFAULT_PARAMS:
            logging.warning('Unrecognized attribute name "%s"', name)
        return cls._DEFAULT_PARAMS.get(name)

    def __init__(self,
                 weights_path,
                 anchors_path,
                 classes_path,
                 model_image_size=(None, None),
                 score=0.3,
                 iou=0.45,
                 nb_gpu=1,
                 gpu_frac=None,
                 **kwargs):
        """

        :param str weights_path: path to loaded model weights, e.g. 'model_data/tiny-yolo.h5'
        :param str anchors_path: path to loaded model anchors, e.g. 'model_data/tiny-yolo_anchors.csv'
        :param str classes_path: path to loaded trained classes, e.g. 'model_data/coco_classes.txt'
        :param float score: confidence score
        :param float iou:
        :param tuple(int,int) model_image_size: e.g. for tiny (416, 416)
        :param int nb_gpu:
        :param float gpu_frac: fraction of GPU memory to reserve, None for automatic
        :param kwargs:
        """
        self.__dict__.update(kwargs)  # and update with user overrides
        self.weights_path = update_path(weights_path)
        self.anchors_path = update_path(anchors_path)
        self.classes_path = update_path(classes_path)
        self.score = score
        self.iou = iou
        self.model_image_size = model_image_size
        self.nb_gpu = nb_gpu
        if not self.nb_gpu:
            # disable all GPUs
            os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
        self.class_names = get_class_names(self.classes_path)
        self.anchors = get_anchors(self.anchors_path)
        self._open_session(gpu_frac)
        self.boxes, self.scores, self.classes = self._create_model()
        self._generate_class_colors()

    def _open_session(self, gpu_frac):
        if K.backend() == 'tensorflow':
            import tensorflow.compat.v1 as tf
            tf.disable_v2_behavior()
            from keras.backend.tensorflow_backend import set_session
            if tf.__version__[0] == '2':
                logging.debug(
                    "Tensorflow version 2.X detected, enabling compatibility with version 1.X."
                )
                import tensorflow.compat.v1 as tf
                tf.disable_v2_behavior()
                from tensorflow.python.keras.backend import set_session

            config = tf.ConfigProto(allow_soft_placement=True,
                                    log_device_placement=False)

            if self.nb_gpu > 0:
                logging.debug("Creating GPU model")
                config.gpu_options.force_gpu_compatible = True
                if gpu_frac is not None:
                    config.gpu_options.per_process_gpu_memory_fraction = gpu_frac
                # Don't pre-allocate memory; allocate as-needed
                config.gpu_options.allow_growth = True
            else:
                logging.debug("Creating CPU model")
                config = tf.ConfigProto(device_count={'GPU': 0})
            sess = tf.Session(config=config)
            set_session(sess)

        if tf.__version__[0] == '2':
            self.sess = tf.keras.backend.get_session()
        else:
            self.sess = K.get_session()

    def _create_model(self):
        # weights_path = update_path(self.weights_path)
        logging.debug('loading model from "%s"', self.weights_path)
        assert self.weights_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)
        try:
            self.yolo_model = load_model(self.weights_path, compile=False)
        except Exception:
            is_tiny_version = (num_anchors == 6)  # default setting
            logging.exception('Loading weights from "%s"', self.weights_path)
            if is_tiny_version:
                self.yolo_model = yolo_body_tiny(Input(shape=(None, None, 3)),
                                                 num_anchors // 2, num_classes)
            else:
                self.yolo_model = yolo_body_full(Input(shape=(None, None, 3)),
                                                 num_anchors // 3, num_classes)
            # make sure model, anchors and classes match
            self.yolo_model.load_weights(self.weights_path,
                                         by_name=True,
                                         skip_mismatch=True)
        else:
            out_shape = self.yolo_model.layers[-1].output_shape[-1]
            ration_anchors = num_anchors / len(
                self.yolo_model.output) * (num_classes + 5)
            assert out_shape == ration_anchors, \
                'Mismatch between model and given anchor %r and class %r sizes' \
                % (ration_anchors, out_shape)

        logging.info('loaded model, anchors (%i), and classes (%i) from %s',
                     num_anchors, num_classes, self.weights_path)

        # Generate output tensor targets for filtered bounding boxes.
        self.input_image_shape = K.placeholder(shape=(2, ))
        if self.nb_gpu >= 2:
            self.yolo_model = multi_gpu_model(self.yolo_model,
                                              gpus=self.nb_gpu)

        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 _generate_class_colors(self):
        """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))

        def _fn_colorr(x):
            return (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255))

        self.colors = list(map(_fn_colorr, self.colors))
        np.random.seed(10101)  # Fixed seed for consistent colors across runs.
        # Shuffle colors to decorrelate adjacent classes.
        np.random.shuffle(self.colors)
        np.random.seed(None)  # Reset seed to default.

    def detect_image(self, image):
        start = time.time()

        if isinstance(self.model_image_size,
                      (list, tuple, set)) and all(self.model_image_size):
            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')

        logging.debug('image shape: %s', repr(image_data.shape))
        if image_data.max() > 1.5:
            image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        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
            })

        end = time.time()
        logging.debug('Found %i boxes in %f sec.', len(out_boxes),
                      (end - start))

        thickness = (image.size[0] + image.size[1]) // 300

        predicts = []
        for i, c in reversed(list(enumerate(out_classes))):
            draw_bounding_box(image, self.class_names[c], out_boxes[i],
                              out_scores[i], self.colors[c], thickness)
            pred = dict(
                zip(PREDICT_FIELDS,
                    (int(c), self.class_names[c], float(
                        out_scores[i]), *[int(x) for x in out_boxes[i]])))
            predicts.append(pred)
        return image, predicts

    def detect_only(self, frame2):
        image = Image.fromarray(frame2)
        start = time.time()

        if isinstance(self.model_image_size,
                      (list, tuple, set)) and all(self.model_image_size):
            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')

        logging.debug('image shape: %s', repr(image_data.shape))
        if image_data.max() > 1.5:
            image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        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
            })

        end = time.time()
        logging.debug('Found %i boxes in %f sec.', len(out_boxes),
                      (end - start))

        return out_boxes, out_scores, out_classes

    def _close_session(self):
        if self.sess is not None:
            self.sess.close()

    def __del__(self):
        self._close_session()
コード例 #11
0
ファイル: detection.py プロジェクト: wajahatali93/keras-yolo3
def predict_video(yolo, path_video, path_output=None, show_stream=False):
    try:
        path_video = int(path_video)
    except Exception:  # not using web cam
        path_video = update_path(path_video)
    else:  # using the (infinite) stream add option to terminate
        show_stream = True

    # Create a video capture object to read videos
    try:
        video = cv2.VideoCapture(path_video)
    except Exception:
        logging.warning('missing: %s', path_video)
        return

    if path_output is not None and os.path.isdir(path_output):
        video_fps = video.get(cv2.CAP_PROP_FPS)
        video_size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)),
                      int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)))
        name = os.path.splitext(os.path.basename(path_video))[0] \
            if isinstance(path_video, str) else str(path_video)
        path_out = os.path.join(path_output, name + VISUAL_EXT + '.avi')
        logging.info('export video: %s', path_out)
        out_vid = cv2.VideoWriter(path_out, VIDEO_FORMAT, video_fps,
                                  video_size)
        frame_preds = []
        path_json = os.path.join(path_output, name + '.json')
    else:
        out_vid, frame_preds, path_json = None, None, None

    while video.isOpened():
        success, frame = video.read()
        if not success:
            logging.warning('video read status: %r', success)
            break
        image = Image.fromarray(frame)
        t_start = time.time()
        image_pred, pred_items = yolo.detect_image(image)
        frame = np.asarray(image_pred)
        fps = 'FPS: %f' % (1. / (time.time() - t_start))
        cv2.putText(frame,
                    text=fps,
                    org=(3, 15),
                    fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=0.50,
                    color=(255, 0, 0),
                    thickness=2)

        if out_vid:
            out_vid.write(frame)
            frame_preds.append(pred_items)
            with open(path_json, 'w') as fp:
                json.dump(frame_preds, fp)
        if show_stream:
            cv2.imshow('YOLOv3', frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                cv2.destroyWindow('YOLOv3')
                break

    if out_vid:
        out_vid.release()
        logging.info('exported predictions: %s', path_json)
コード例 #12
0
def _main(config_path, weights_path, output_path, weights_only, plot_model):
    assert os.path.isfile(config_path), 'missing "%s"' % config_path
    assert os.path.isfile(weights_path), 'missing "%s"' % weights_path
    assert config_path.endswith('.cfg'), \
        '"%s" is not a .cfg file' % os.path.basename(config_path)
    assert weights_path.endswith('.weights'), \
        '"%s" is not a .weights file' % os.path.basename(config_path)

    output_dir = update_path(os.path.dirname(output_path))
    assert os.path.isdir(output_dir), 'missing "%s"' % output_dir
    output_path = os.path.join(output_dir, os.path.basename(output_path))
    assert output_path.endswith('.h5'), \
        'output path "%s" is not a .h5 file' % os.path.basename(output_path)

    # Load weights and config.
    logging.info('Loading weights: %s', weights_path)
    weights_file = open(weights_path, 'rb')
    major, minor, revision = np.ndarray(shape=(3, ),
                                        dtype='int32',
                                        buffer=weights_file.read(12))
    if (major * 10 + minor) >= 2 and major < 1000 and minor < 1000:
        seen = np.ndarray(shape=(1, ),
                          dtype='int64',
                          buffer=weights_file.read(8))
    else:
        seen = np.ndarray(shape=(1, ),
                          dtype='int32',
                          buffer=weights_file.read(4))
    logging.info('Weights Header: %i.%i.%i %s', major, minor, revision,
                 repr(seen.tolist()))

    logging.info('Parsing Darknet config: %s', config_path)
    unique_config_file = unique_config_sections(config_path)
    cfg_parser = configparser.ConfigParser()
    cfg_parser.read_file(unique_config_file)

    logging.info('Creating Keras model.')
    input_layer = Input(shape=(None, None, 3))
    prev_layer = input_layer
    all_layers = []

    weight_decay = float(cfg_parser['net_0']['decay']
                         ) if 'net_0' in cfg_parser.sections() else 5e-4
    count = 0
    out_index = []
    for section in tqdm.tqdm(cfg_parser.sections()):
        logging.info('Parsing section "%s"', section)
        (all_layers, cfg_parser, section, prev_layer, weights_file, count,
         weight_decay, out_index) = parse_section(all_layers, cfg_parser,
                                                  section, prev_layer,
                                                  weights_file, count,
                                                  weight_decay, out_index)

    # Create and save model.
    if len(out_index) == 0:
        out_index.append(len(all_layers) - 1)
    model = Model(inputs=input_layer,
                  outputs=[all_layers[i] for i in out_index])
    logging.info(model.summary(line_length=120))
    if weights_only:
        model.save_weights(output_path)
        logging.info('Saved Keras weights to "%s"', output_path)
    else:
        model.save(output_path)
        logging.info('Saved Keras model to "%s"', output_path)

    # Check to see if all weights have been read.
    remaining_weights = len(weights_file.read()) / 4
    weights_file.close()
    logging.info('Read %i of %i from Darknet weight.', count,
                 count + remaining_weights)
    if remaining_weights > 0:
        logging.warning('there are %i unused weights', remaining_weights)

    if plot_model:
        path_img = '%s.png' % os.path.splitext(output_path)[0]
        plot(model, to_file=path_img, show_shapes=True)
        logging.info('Saved model plot to %s', path_img)