Mask RCNN道路分割(三)(四)——数据集介绍和基于UAS数据集的训练和验证

第三章 数据集介绍

3.1 数据集制作

3.1.1 数据集一:全天候道路图像分割数据集UAS-UESTC All-Day Scenery

该数据集是本人使用的第一个数据集,专注于道路分割,使用效果较好。在此对原作者的无私开源深表感谢。
本数据集包含sun_sight、rain_sight、night_sight、dusk_sight四种天气状况下的道路图像,图像存储为.jpg格式,标注mask为.png格式,且mask文件中仅包含道路一种类别。需要说明的是,SunLabel*.png中,数字0代表道路区域,而不是数字1。因此,在数据集类的load_mask()函数中需要注意,这一行应写成:mask[:, :, 0] = 1-skimage.io.imread(mask_path),如不用1-,将出现道路区域和背景区域预测反了的情况。

3.1.2 数据集二:CamVid

该数据集包含701张标注好的图像,图像和标注文件同为.png格式。标注的mask文件为单通道png图像,图像数值从1~32,分别代表不同的类别。实验中将图像按照0.8:0.2分为训练集和测试集,对应560张训练图像和141张测试图像。

  • datasets
    • camvid
      • ImageSets 存放数据集分割文件
        • Segmentation
          • train.txt
          • val.txt
      • JPEGImages 存放图像文件
        • train
          • xxx.png
        • val
          • xxx.png
      • SegmentationClass 存放数据集标注文件
          • xxx_P.png
      • codes.txt 存放类别文件

3.1.3 数据集三:KITTI

3.1.4 数据集四:BDD100K

第四章 基于UAS数据集的训练和验证

3.1 数据集准备

3.2 代码准备

创建脚本my_road_v1_1.py,以下分步讲解该脚本内容。

注意:文件命名为my_road_v1_0.py,在引用时可以使用import my_road_v1_0 as my_road,这样在以后升版为my_road_v1_*.py时,只需简单修改import语句就能继续兼容,避免过多修改。

3.2.1 定义工作目录和相关路径
ROOT_DIR = os.path.abspath("../../")
PRJ_DIR = os.path.join(ROOT_DIR, 'exp200515')
sys.path.append(ROOT_DIR)  # 将根目录加入环境变量
COCO_WEIGHTS_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
DEFAULT_LOGS_DIR = os.path.join(ROOT_DIR, "logs")
3.2.2 继承mrcnn.config.Config并创建RoadConfig类
class RoadConfig(Config):
	'''事实上还可以修改很多参数,来创造不同的实验条件'''
    NAME = "my_road"
    IMAGES_PER_GPU = 1
    NUM_CLASSES = 1 + 1  # Background + road
    STEPS_PER_EPOCH = 100
    DETECTION_MIN_CONFIDENCE = 0.9
3.2.3 继承mrcnn.utils.Dataset并创建RoadDataset类
class RoadDataset(utils.Dataset):

    def load_road(self, dataset_dir="datasets/my_road", subset="train"):
        """Load a subset of the Balloon dataset.
        dataset_dir: Root directory of the dataset.
        subset: Subset to load: train or val
        """
        # Add classes. We have only one class to add.
        self.add_class("road", 1, "road")
        assert subset in ["train", "val"]
        dataset_dir = os.path.join(dataset_dir, subset)
        image_paths = glob.glob(dataset_dir+"/*.jpg")

        for image_path in image_paths:
            image_name = os.path.basename(image_path)
            image = skimage.io.imread(image_path)
            height, width = image.shape[:2]
            self.add_image(
                "road",
                image_id=image_name,  # use file name as a unique image id
                path=image_path,
                width=width, height=height) # totally there are 5 keys in image_info, "source", "id", "path", "height", "width"

    def load_mask(self, image_id):
        """Generate instance masks for an image.
       Params:
        image_id: A int number of image_id, not the info["image_id"] 
       Returns:
        masks: A bool array of shape [height, width, instance count] with
            one mask per instance.
        class_ids: a 1D array of class IDs of the instance masks.
        """
        image_info = self.image_info[image_id]
        if image_info["source"] != "road":
            return super(self.__class__, self).load_mask(image_id)

        info = self.image_info[image_id]
        mask = np.zeros([info["height"], info["width"], 1], dtype=np.uint8)
        image_path = self.image_reference(image_id)
        image_dir = os.path.dirname(image_path)
        image_name = os.path.basename(image_path).split(".")[0]
        image_idx = re.findall(r"\d+", image_name) # get the number in image filename, for example, "SunSight254.jpg" will get idx of 254
        mask_name = "SunLabel"+image_idx[0]+".png"
        mask_path = os.path.join(image_dir, mask_name)
        mask[:, :, 0] = 1-skimage.io.imread(mask_path)
		# 返回mask: [height,width,n_instance]
		# class_ids: [n_instance]
        return mask.astype(np.bool), np.ones([mask.shape[-1]], dtype=np.int32)

    def image_reference(self, image_id):
        """Return the path of the image."""
        info = self.image_info[image_id]
        if info["source"] == "road":
            return info["path"]
        else:
            super(self.__class__, self).image_reference(image_id)
3.2.4 改写train(model)函数
def train(model):
    dataset_train = RoadDataset()
    dataset_train.load_road(args.dataset, "train")
    dataset_train.prepare()

    dataset_val = RoadDataset()
    dataset_val.load_road(args.dataset, "val")
    dataset_val.prepare()
    
    print("Training network heads")
    model.train(dataset_train, dataset_val,
                learning_rate=config.LEARNING_RATE,
                epochs=30,
                layers='heads')
3.2.5 改写color_splash(image, mask)函数
3.2.6 改写detect_and_color_splash(model, image_path=None, video_path=None)函数
3.2.7 改写__name__ == ‘main’:代码段
if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description='Train Mask R-CNN to detect balloons.')
    parser.add_argument(...)
    args = parser.parse_args()
    if args.command == "train":
    elif args.command == "splash":

3.3 实验记录

实验记录:

  • samples/my_road_v1_0
  • 使用UAS_v1_0数据集,仅包含sun_sight文件夹内图像。
  • 从balloon.py改写samples/my_road_v1_0/my_road_v1_0.py,其中load_mask(self, image_id):函数中应修改为mask[:, :, 0] = 1-skimage.io.imread(mask_path)
  • 使用与balloon.py中相同的参数配置。
  • samples/my_road_v1_1
  • 使用UAS_v1_1数据集,包含sun_sight和rain_sight文件夹内的图像
  • 和上一版本一样,未针对数据集配置训练的参数,而是使用默认的’head’,这样只训练了最后一层。未设置anchor大小。
  • 未解决问题:训练中使用–last参数,会出现bug。
  • samples/my_road_v1_2
  • 使用UAS_v1_1数据集。
  • 修改anchor大小。设置训练的层数。
  • 比较resnet50和resnet101的分别
  • 对实验结果进行验证,计算mIOU。
  • 对最后生成的mask进行CRF,计算精度差异。

3.4 验证测试

3.5 查看模型训练状况

3.6 问题解决

3.6.1 参数–weights=last无效

  • 原因在model.py文件中MaskRCNN类的find_last()函数

第2075行,原为:dir_names = filter(lambda f: f.startswith(key), dir_names),修改为:dir_names = list(filter(lambda f: f.startswith(key), dir_names))
原因是filter函数在python2中返回的是列表,而在Python3中返回的是一个filter迭代器对象,必须加上list(filter())转换成列表才能使用。

第五章 基于CamVid数据集的训练和验证

附录一 相关函数和知识点

(一)Numpy

numpy.tile()

np.tile(a,(2,1)),第一个参数为Y轴复制倍数,第二个为X轴复制倍数。不改变rank,改变shape.

numpp.clip()

numpy.clip(a, a_min, a_max, out=None)
将数组中的数值限定到最大最小值之间,小于最小值的直接改成最小值,大于最大值的直接改成最大值。

np.apply_along_axis()

1.函数原型
numpy.apply_along_axis(func, axis, arr, *args, **kwargs)
2.作用:
将arr数组的每一个元素经过func函数变换形成的一个新数组
3.参数介绍:
其中func,axis,arr是必选的
func是我们写的一个函数
axis表示函数func对arr是作用于行还是列
arr便是我们要进行操作的数组了
注意,当axis=0时,表示每次送入func的参数是arr[0, i], a[1,i], a[2,i] …
即将a[:, i]作为第i次调用func的参数,需要调用colum次。

numpy中的ravel()、flatten()、squeeze()的用法与区别

numpy中的ravel()、flatten()、squeeze()都有将多维数组转换为一维数组的功能,区别:
ravel():如果没有必要,不会产生源数据的副本
flatten():返回源数据的副本
squeeze():只能对维数为1的维度降维
另外,reshape(-1)也可以“拉平”多维数组

np.max

np.max:(a, axis=None, out=None, keepdims=False)
求序列的最值
最少接收一个参数
axis:默认为列向(也即 axis=0),axis = 1 时为行方向的最值;

tf.gather(params,indices,axis=0 )

从params的axis维根据indices的参数值获取切片。比如从10个数里面取出5个数,其形状和原来是不一样的。

tf.image.non_max_suppression()

tf.image.non_max_suppression(
boxes,
scores,
max_output_size,
iou_threshold=0.5,
score_threshold=float(’-inf’),
name=None
)
按照参数scores的降序贪婪的选择边界框的子集。
删除掉那些与之前的选择的边框具有很高的IOU的边框。边框是以[y1,x1,y2,x2],(y1,x1)和(y2,x2)是边框的对角坐标,当然也可以提供被归一化的坐标。返回的是被选中的那些留下来的边框在参数boxes里面的下标位置。那么你可以使用tf.gather的操作或者利用keras.backend的gather函数来从参数boxes来获取选中的边框。
例如:
selected_indices=tf.image.non_max_suppression(boxes,scores,max_output_size,iou_thresholde)
selected_boxes=tf.gather(boxes,selected_indices)
参数:boxes:2-D的float类型的,大小为[num_boxes,4]的张量;
scores:1-D的float类型的大小为[num_boxes]代表上面boxes的每一行,对应的每一个box的一个score;
max_output_size:一个整数张量,代表我最多可以利用NMS选中多少个边框;
iou_threshold:一个浮点数,IOU阙值展示的是否与选中的那个边框具有较大的重叠度;
score_threshold:一个浮点数,来决定上面时候删除这个边框
name:可选
返回的是selected_indices:表示的是一个1-D的整数张量,大小为[M],代表的是选出来的留下来的边框下标,M小于等于max_outpuy_size.

tf.split()

tf.split(
value,
num_or_size_splits,
axis=0,
num=None,
name=‘split’
)
根据官方文档的说法这个函数的用途简单说就是把一个张量划分成几个子张量。
value:准备切分的张量
num_or_size_splits:准备切成几份
axis : 准备在第几个维度上进行切割
其中分割方式分为两种

  1. 如果num_or_size_splits 传入的 是一个整数,那直接在axis=D这个维度上把张量平均切分成几个小张量
  2. 如果num_or_size_splits 传入的是一个向量(这里向量各个元素的和要跟原本这个维度的数值相等)就根据这个向量有几个元素分为几项)

tf.image.crop_and_resize()

tf.image.crop_and_resize(image,boxes,box_ind,crop_size,methpd=‘bilinear’,extrapolation_value=0,name=None)
image: 表示特征图
boxes:指需要划分的区域,输入格式为[ymin,xmin,ymax,xmax]
设crop的区域坐标是[y1,x1,y2,x2],那么想要得到相应正确的crop图形就一定要归一化,即图片的长度是[w,h],则实际输入的boxes为[y1/h,x1/w,y2/h,x2/w]。
不归一化的话,会自动进行补齐,超出1的就全变成成黑色框了。
box_ind: 是boxes和image之间的索引
crop_size: 表示RoiAlign之后的大小。

tf.concat()

tf.boolean_mask

tf.split(
tf.shape(
tf.identity(
tf.where(
tf.cond(
tf.argmax(
tf.stack(
tf.gather_nd(
tf.sets.set_intersection
tf.sparse_tensor_to_dense
tf.unique(
tf.pad(
tf.map_fn(
tf.reshape
detections = tf.concat([
tf.gather(refined_rois, keep),
tf.to_float(tf.gather(class_ids, keep))[…, tf.newaxis],
tf.gather(class_scores, keep)[…, tf.newaxis]
], axis=1)
tf.squeeze
tf.nn.sparse_softmax_cross_entropy_with_logits(
tf.reduce_sum

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值