<filename>yolov3.py
# -*- coding: utf-8 -*-

import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D
from paddle.fluid.param_attr import ParamAttr
from paddle.fluid.regularizer import L2Decay

from darknet import ConvBNLayer, DarkNet53_conv_body


# 定义生成YOLO-V3预测输出的模块
# 也就是教程中图x所示的由ci生成ri和ti的过程
class YoloDetectionBlock(fluid.dygraph.Layer):
    def __init__(self, name_scope, channel, is_test=True):
        super(YoloDetectionBlock, self).__init__(name_scope)

        assert channel % 2 == 0, \
            "channel {} cannot be divided by 2".format(channel)

        self.conv0 = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.conv1 = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )
        self.conv2 = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.conv3 = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )
        self.route = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.tip = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )

    def forward(self, inputs):
        out = self.conv0(inputs)
        out = self.conv1(out)
        out = self.conv2(out)
        out = self.conv3(out)
        route = self.route(out)
        tip = self.tip(route)
        return route, tip


# 定义上采样模块
class Upsample(fluid.dygraph.Layer):
    def __init__(self, name_scope, scale=2):
        super(Upsample, self).__init__(name_scope)
        self.scale = scale

    def forward(self, inputs):
        # get dynamic upsample output shape
        shape_nchw = fluid.layers.shape(inputs)
        shape_hw = fluid.layers.slice(shape_nchw, axes=[0], starts=[2], ends=[4])
        shape_hw.stop_gradient = True
        in_shape = fluid.layers.cast(shape_hw, dtype='int32')
        out_shape = in_shape * self.scale
        out_shape.stop_gradient = True

        # reisze by actual_shape
        out = fluid.layers.resize_nearest(
            input=inputs, scale=self.scale, actual_shape=out_shape)
        return out


# 定义YOLO-V3模型
class YOLOv3(fluid.dygraph.Layer):
    def __init__(self, name_scope, num_classes=7, is_train=True):
        super(YOLOv3, self).__init__(name_scope)

        self.is_train = is_train
        self.num_classes = num_classes
        # 提取图像特征的骨干代码
        self.block = DarkNet53_conv_body(self.full_name(),
                                         is_test=not self.is_train)
        self.block_outputs = []
        self.yolo_blocks = []
        self.route_blocks_2 = []
        for i in range(3):
            # 添加从ci生成ri和ti的模块
            yolo_block = self.add_sublayer(
                "yolo_detecton_block_%d" % (i),
                YoloDetectionBlock(self.full_name(),
                                   channel=512 // (2 ** i),
                                   is_test=not self.is_train))
            self.yolo_blocks.append(yolo_block)

            num_filters = 3 * (self.num_classes + 5)

            # 添加从ti生成pi的模块，这是一个Conv2D操作，输出通道数为3 * (num_classes + 5)
            block_out = self.add_sublayer(
                "block_out_%d" % (i),
                Conv2D(self.full_name(),
                       num_filters=num_filters,
                       filter_size=1,
                       stride=1,
                       padding=0,
                       act=None,
                       param_attr=ParamAttr(
                           initializer=fluid.initializer.Normal(0., 0.02)),
                       bias_attr=ParamAttr(
                           initializer=fluid.initializer.Constant(0.0),
                           regularizer=L2Decay(0.))))
            self.block_outputs.append(block_out)
            if i < 2:
                # 对ri进行卷积
                route = self.add_sublayer("route2_%d" % i,
                                          ConvBNLayer(self.full_name(),
                                                      ch_out=256 // (2 ** i),
                                                      filter_size=1,
                                                      stride=1,
                                                      padding=0,
                                                      is_test=(not self.is_train)))
                self.route_blocks_2.append(route)
            # 将ri放大以便跟c_{i+1}保持同样的尺寸
            self.upsample = Upsample(self.full_name())

    def forward(self, inputs):
        outputs = []
        blocks = self.block(inputs)
        for i, block in enumerate(blocks):
            if i > 0:
                # 将r_{i-1}经过卷积和上采样之后得到特征图，与这一级的ci进行拼接
                block = fluid.layers.concat(input=[route, block], axis=1)
            # 从ci生成ti和ri
            route, tip = self.yolo_blocks[i](block)
            # 从ti生成pi
            block_out = self.block_outputs[i](tip)
            # 将pi放入列表
            outputs.append(block_out)

            if i < 2:
                # 对ri进行卷积调整通道数
                route = self.route_blocks_2[i](route)
                # 对ri进行放大，使其尺寸和c_{i+1}保持一致
                route = self.upsample(route)

        return outputs

    def get_loss(self, outputs, gtbox, gtlabel, gtscore=None,
                 anchors=[10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks=[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                 ignore_thresh=0.7,
                 use_label_smooth=False):

        self.losses = []
        downsample = 32
        for i, out in enumerate(outputs):
            anchor_mask_i = anchor_masks[i]
            loss = fluid.layers.yolov3_loss(
                x=out,
                gt_box=gtbox,
                gt_label=gtlabel,
                gt_score=gtscore,
                anchors=anchors,
                anchor_mask=anchor_mask_i,
                class_num=self.num_classes,
                ignore_thresh=ignore_thresh,
                downsample_ratio=downsample,
                use_label_smooth=False)
            self.losses.append(fluid.layers.reduce_mean(loss))
            downsample = downsample // 2
        return sum(self.losses)

    def get_pred(self,
                 outputs,
                 im_shape=None,
                 anchors=[10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks=[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                 valid_thresh=0.01):
        downsample = 32
        total_boxes = []
        total_scores = []
        for i, out in enumerate(outputs):
            anchor_mask = anchor_masks[i]
            anchors_this_level = []
            for m in anchor_mask:
                anchors_this_level.append(anchors[2 * m])
                anchors_this_level.append(anchors[2 * m + 1])

            boxes, scores = fluid.layers.yolo_box(
                x=out,
                img_size=im_shape,
                anchors=anchors_this_level,
                class_num=self.num_classes,
                conf_thresh=valid_thresh,
                downsample_ratio=downsample,
                name="yolo_box" + str(i))
            total_boxes.append(boxes)
            total_scores.append(
                fluid.layers.transpose(
                    scores, perm=[0, 2, 1]))
            downsample = downsample // 2

        yolo_boxes = fluid.layers.concat(total_boxes, axis=1)
        yolo_scores = fluid.layers.concat(total_scores, axis=2)
        return yolo_boxes, yolo_scores

<filename>yolov3.py
# -*- coding: utf-8 -*-

import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D
from paddle.fluid.param_attr import ParamAttr
from paddle.fluid.regularizer import L2Decay

from darknet import ConvBNLayer, DarkNet53_conv_body


# 定义生成YOLO-V3预测输出的模块
# 也就是教程中图x所示的由ci生成ri和ti的过程
class YoloDetectionBlock(fluid.dygraph.Layer):
    def __init__(self, name_scope, channel, is_test=True):
        super(YoloDetectionBlock, self).__init__(name_scope)

        assert channel % 2 == 0, \
            "channel {} cannot be divided by 2".format(channel)

        self.conv0 = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.conv1 = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )
        self.conv2 = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.conv3 = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )
        self.route = ConvBNLayer(
            self.full_name(),
            ch_out=channel,
            filter_size=1,
            stride=1,
            padding=0,
            is_test=is_test
        )
        self.tip = ConvBNLayer(
            self.full_name(),
            ch_out=channel * 2,
            filter_size=3,
            stride=1,
            padding=1,
            is_test=is_test
        )

    def forward(self, inputs):
        out = self.conv0(inputs)
        out = self.conv1(out)
        out = self.conv2(out)
        out = self.conv3(out)
        route = self.route(out)
        tip = self.tip(route)
        return route, tip


# 定义上采样模块
class Upsample(fluid.dygraph.Layer):
    def __init__(self, name_scope, scale=2):
        super(Upsample, self).__init__(name_scope)
        self.scale = scale

    def forward(self, inputs):
        # get dynamic upsample output shape
        shape_nchw = fluid.layers.shape(inputs)
        shape_hw = fluid.layers.slice(shape_nchw, axes=[0], starts=[2], ends=[4])
        shape_hw.stop_gradient = True
        in_shape = fluid.layers.cast(shape_hw, dtype='int32')
        out_shape = in_shape * self.scale
        out_shape.stop_gradient = True

        # reisze by actual_shape
        out = fluid.layers.resize_nearest(
            input=inputs, scale=self.scale, actual_shape=out_shape)
        return out


# 定义YOLO-V3模型
class YOLOv3(fluid.dygraph.Layer):
    def __init__(self, name_scope, num_classes=7, is_train=True):
        super(YOLOv3, self).__init__(name_scope)

        self.is_train = is_train
        self.num_classes = num_classes
        # 提取图像特征的骨干代码
        self.block = DarkNet53_conv_body(self.full_name(),
                                         is_test=not self.is_train)
        self.block_outputs = []
        self.yolo_blocks = []
        self.route_blocks_2 = []
        for i in range(3):
            # 添加从ci生成ri和ti的模块
            yolo_block = self.add_sublayer(
                "yolo_detecton_block_%d" % (i),
                YoloDetectionBlock(self.full_name(),
                                   channel=512 // (2 ** i),
                                   is_test=not self.is_train))
            self.yolo_blocks.append(yolo_block)

            num_filters = 3 * (self.num_classes + 5)

            # 添加从ti生成pi的模块，这是一个Conv2D操作，输出通道数为3 * (num_classes + 5)
            block_out = self.add_sublayer(
                "block_out_%d" % (i),
                Conv2D(self.full_name(),
                       num_filters=num_filters,
                       filter_size=1,
                       stride=1,
                       padding=0,
                       act=None,
                       param_attr=ParamAttr(
                           initializer=fluid.initializer.Normal(0., 0.02)),
                       bias_attr=ParamAttr(
                           initializer=fluid.initializer.Constant(0.0),
                           regularizer=L2Decay(0.))))
            self.block_outputs.append(block_out)
            if i < 2:
                # 对ri进行卷积
                route = self.add_sublayer("route2_%d" % i,
                                          ConvBNLayer(self.full_name(),
                                                      ch_out=256 // (2 ** i),
                                                      filter_size=1,
                                                      stride=1,
                                                      padding=0,
                                                      is_test=(not self.is_train)))
                self.route_blocks_2.append(route)
            # 将ri放大以便跟c_{i+1}保持同样的尺寸
            self.upsample = Upsample(self.full_name())

    def forward(self, inputs):
        outputs = []
        blocks = self.block(inputs)
        for i, block in enumerate(blocks):
            if i > 0:
                # 将r_{i-1}经过卷积和上采样之后得到特征图，与这一级的ci进行拼接
                block = fluid.layers.concat(input=[route, block], axis=1)
            # 从ci生成ti和ri
            route, tip = self.yolo_blocks[i](block)
            # 从ti生成pi
            block_out = self.block_outputs[i](tip)
            # 将pi放入列表
            outputs.append(block_out)

            if i < 2:
                # 对ri进行卷积调整通道数
                route = self.route_blocks_2[i](route)
                # 对ri进行放大，使其尺寸和c_{i+1}保持一致
                route = self.upsample(route)

        return outputs

    def get_loss(self, outputs, gtbox, gtlabel, gtscore=None,
                 anchors=[10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks=[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                 ignore_thresh=0.7,
                 use_label_smooth=False):

        self.losses = []
        downsample = 32
        for i, out in enumerate(outputs):
            anchor_mask_i = anchor_masks[i]
            loss = fluid.layers.yolov3_loss(
                x=out,
                gt_box=gtbox,
                gt_label=gtlabel,
                gt_score=gtscore,
                anchors=anchors,
                anchor_mask=anchor_mask_i,
                class_num=self.num_classes,
                ignore_thresh=ignore_thresh,
                downsample_ratio=downsample,
                use_label_smooth=False)
            self.losses.append(fluid.layers.reduce_mean(loss))
            downsample = downsample // 2
        return sum(self.losses)

    def get_pred(self,
                 outputs,
                 im_shape=None,
                 anchors=[10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks=[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                 valid_thresh=0.01):
        downsample = 32
        total_boxes = []
        total_scores = []
        for i, out in enumerate(outputs):
            anchor_mask = anchor_masks[i]
            anchors_this_level = []
            for m in anchor_mask:
                anchors_this_level.append(anchors[2 * m])
                anchors_this_level.append(anchors[2 * m + 1])

            boxes, scores = fluid.layers.yolo_box(
                x=out,
                img_size=im_shape,
                anchors=anchors_this_level,
                class_num=self.num_classes,
                conf_thresh=valid_thresh,
                downsample_ratio=downsample,
                name="yolo_box" + str(i))
            total_boxes.append(boxes)
            total_scores.append(
                fluid.layers.transpose(
                    scores, perm=[0, 2, 1]))
            downsample = downsample // 2

        yolo_boxes = fluid.layers.concat(total_boxes, axis=1)
        yolo_scores = fluid.layers.concat(total_scores, axis=2)
        return yolo_boxes, yolo_scores

# -*- coding: utf-8 -*- # 定义生成YOLO-V3预测输出的模块 # 也就是教程中图x所示的由ci生成ri和ti的过程 # 定义上采样模块 # get dynamic upsample output shape # reisze by actual_shape # 定义YOLO-V3模型 # 提取图像特征的骨干代码 # 添加从ci生成ri和ti的模块 # 添加从ti生成pi的模块，这是一个Conv2D操作，输出通道数为3 * (num_classes + 5) # 对ri进行卷积 # 将ri放大以便跟c_{i+1}保持同样的尺寸 # 将r_{i-1}经过卷积和上采样之后得到特征图，与这一级的ci进行拼接 # 从ci生成ti和ri # 从ti生成pi # 将pi放入列表 # 对ri进行卷积调整通道数 # 对ri进行放大，使其尺寸和c_{i+1}保持一致