精华内容
下载资源
问答
  • COCO数据集是微软发布的一个大型图像数据集, 专为对象检测、分割、人体关键点检测、语义...下面记录一下我是如何利用COCO的数据集来制作目标检测数据,以及对数据进行增广处理的。首先我们去COCO的官网http://msc...

    fd64aabbe0977bd90d7a5dea8b5cabda.png

    COCO数据集是微软发布的一个大型图像数据集, 专为对象检测、分割、人体关键点检测、语义分割和字幕生成而设计。这个数据集还提供了Matlab, Python 和 Lua 的 API 接口. 该 API 接口可以提供完整的图像标签数据的加载, parsing 和可视化。

    下面记录一下我是如何利用COCO的数据集来制作目标检测的数据,以及对数据进行增广处理的。

    首先我们去COCO的官网http://mscoco.org下载图像数据。之后我们还要下载安装COCO API, https://github.com/Xinering/cocoapi

    COCOAPI安装好后,在PythonAPI的目录下,有一个pycocoDemo.ipynb的文件,用Jupyter Notebook运行这个文件,可以看到cocoapi的用法,我根据这个文件的示例来进行改造,把COCO的图像数据和目标检测用到的标注框的数据打包制作为Tensorflow的tfrecord格式的数据,方便之后用Tensorflow来进行训练。代码如下:

    #-*- encoding: utf-8 -*-
    import tensorflow as tf
    import cv2
    import numpy as np
    import os
    from multiprocessing import Process, Queue
    import sys
    import time
    import random
    import math
    from pycocotools.coco import COCO
     
    #读取COCO的标注文件,里面包含了检测框的数据
    annFile='annotations/instances_train2014.json'
    COCO训练集的图像数据保存的目录
    train_path = '/data/AI/cocoapi/images/'
    coco=COCO(annFile)
    cores=8   #定义用到的CPU处理的核心数
    max_num=1000   #每个TFRECORD文件包含的最多的图像数
     
    # display COCO categories and supercategories
    cats = coco.loadCats(coco.getCatIds())
    cats_dict = {}
    for cat in cats:
        cats_dict[cat['id']] = cat['name']
     
    #获取COCO数据集中所有图像的ID    
    imgIds = coco.getImgIds()
    print(len(imgIds))
    #构建训练集文件列表,里面的每个元素是路径名+图片文件名
    train_images_filenames = os.listdir(train_path)
    #查找训练集的图片是否都有对应的ID,并保存到一个列表中
    train_images = []
    i = 1
    total = len(train_images_filenames)
    for image_file in train_images_filenames:
        if int(image_file[15:-4]) in imgIds:
            train_images.append(train_path+','+image_file)
        if i%100==0 or i==total:
            print('processing image list %i of %ir' %(i, total), end='')
        i+=1
    random.shuffle(train_images)
     
    all_cat = set()   #保存目标检测所有的类别, COCO共定义了90个类别,其中只有80个类别有目标检测数据
    imagefile_box = {}
    #获取每个图像的目标检测框的数据并保存
    for item in train_images:
        boxes = [[],[],[],[],[]]
        filename = item.split(',')[1]
        imgid = int(filename[15:-4])
        annIds = coco.getAnnIds(imgIds=imgid, iscrowd=None)
        anns = coco.loadAnns(annIds)
        for ann in anns:
            bbox = ann['bbox']
            xmin = int(bbox[0])
            xmax = int(bbox[0] + bbox[2])
            ymin = int(bbox[1])
            ymax = int(bbox[1] + bbox[3])
            catid = ann['category_id']
            all_cat.add(cats_dict[catid])
            boxes[0].append(catid)
            boxes[1].append(xmin)
            boxes[2].append(ymin)
            boxes[3].append(xmax)
            boxes[4].append(ymax)
        imagefile_box[filename] = boxes
     
    #获取有目标检测数据的80个类别的名称    
    all_cat_list = list(all_cat)
    all_cat_dict = {}
    for i in range(len(all_cat_list)):
        all_cat_dict[all_cat_list[i]]=i
    print(all_cat_dict)
     
    #把图像以及对应的检测框,类别等数据保存到TFRECORD    
    def make_example(image, height, width, label, bbox, filename):
        colorspace = b'RGB'
        channels = 3
        img_format = b'JPEG'
        return tf.train.Example(features=tf.train.Features(feature={
            'image' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
            'height' : tf.train.Feature(int64_list=tf.train.Int64List(value=[height])),
            'width' : tf.train.Feature(int64_list=tf.train.Int64List(value=[width])),
            'channels' : tf.train.Feature(int64_list=tf.train.Int64List(value=[channels])),
            'colorspace' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[colorspace])),
            'img_format' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_format])),
            'label' : tf.train.Feature(int64_list=tf.train.Int64List(value=label)),
            'bbox_xmin' : tf.train.Feature(int64_list=tf.train.Int64List(value=bbox[0])),
            'bbox_xmax' : tf.train.Feature(int64_list=tf.train.Int64List(value=bbox[2])),
            'bbox_ymin' : tf.train.Feature(int64_list=tf.train.Int64List(value=bbox[1])),
            'bbox_ymax' : tf.train.Feature(int64_list=tf.train.Int64List(value=bbox[3])),
            'filename': tf.train.Feature(bytes_list=tf.train.BytesList(value=[filename]))
        }))
     
    #定义多进程函数用于生成TFRECORD文件
    def gen_tfrecord(trainrecords, targetfolder, startnum, queue):
        tfrecords_file_num = startnum
        file_num = 0
        total_num = len(trainrecords)
        pid = os.getpid()
        queue.put((pid, file_num))
        writer = tf.python_io.TFRecordWriter(targetfolder+"train_"+str(tfrecords_file_num)+".tfrecord")
        for record in trainrecords:
            file_num += 1
            fields = record.split(',')
            img = cv2.imread(fields[0]+fields[1])
            height, width, _ = img.shape
            img_jpg = cv2.imencode('.jpg', img)[1].tobytes()
            bbox = imagefile_box[fields[1]]
            bbox[1] = [item for item in bbox[1]]   #xmin
            bbox[3] = [item for item in bbox[3]]   #xmax
            bbox[2] = [item for item in bbox[2]]  #ymin
            bbox[4] = [item for item in bbox[4]]  #ymax
            catnames = [cats_dict[item] for item in bbox[0]]
            label = [all_cat_dict[item] for item in catnames]
            ex = make_example(img_jpg, height, width, label, bbox[1:], fields[1].encode())
            writer.write(ex.SerializeToString())
            #每写入100条记录,向父进程发送消息,报告进度
            if file_num%100==0:
                queue.put((pid, file_num))
            if file_num%max_num==0:
                writer.close()
                tfrecords_file_num += 1
                writer = tf.python_io.TFRecordWriter(targetfolder+"train_"+str(tfrecords_file_num)+".tfrecord")
        writer.close()        
        queue.put((pid, file_num))
     
    #定义多进程处理    
    def process_in_queues(fileslist, cores, targetfolder):
        total_files_num = len(fileslist)
        each_process_files_num = int(total_files_num/cores)
        files_for_process_list = []
        for i in range(cores-1):
            files_for_process_list.append(fileslist[i*each_process_files_num:(i+1)*each_process_files_num])
        files_for_process_list.append(fileslist[(cores-1)*each_process_files_num:])
        files_number_list = [len(l) for l in files_for_process_list]
        
        each_process_tffiles_num = math.ceil(each_process_files_num/max_num)
        
        queues_list = []
        processes_list = []
        for i in range(cores):
            queues_list.append(Queue())
            #queue = Queue()
            processes_list.append(Process(target=gen_tfrecord, 
                                          args=(files_for_process_list[i],targetfolder,
                                          each_process_tffiles_num*i+1,queues_list[i],)))
     
        for p in processes_list:
            Process.start(p)
     
        #父进程循环查询队列的消息,并且每0.5秒更新一次
        while(True):
            try:
                total = 0
                progress_str=''
                for i in range(cores):
                    msg=queues_list[i].get()
                    total += msg[1]
                    progress_str+='PID'+str(msg[0])+':'+str(msg[1])+'/'+ str(files_number_list[i])+'|'
                progress_str+='r'
                print(progress_str, end='')
                if total == total_files_num:
                    for p in processes_list:
                        p.terminate()
                        p.join()
                    break
                time.sleep(0.5)
            except:
                break
        return total
     
    if __name__ == '__main__':
        print('Start processing train data using %i CPU cores:'%cores)
        starttime=time.time()        
        total_processed = process_in_queues(train_images, cores, targetfolder='/data/data/coco/train_tf/')
        endtime=time.time()
        print('nProcess finish, total process %i images in %i seconds'%(total_processed, int(endtime-starttime)), end='')

    TFRECORD数据生成后,我们可以进行很多的数据增广(Data augmentation)的处理了。具体的数据增广包括了以下几个方面:

    1. 对图像大小调整到固定的大小(例如416*416),并相应调整Bounding Box的坐标
    2. 把图像大小调整到固定的大小的120%(例如500*500),然后再随机裁剪图片到固定的大小(416*416),并相应调整Bounding Box的坐标
    3. 对图像进行Expand操作,例如随机生成一个是图像1-3倍大小的一个图像(像素值都为0),然后随机放置这个图像进去。然后再把扩展后的图像调整大小到固定的大小(416*416),并相应调整Bounding Box的坐标。相当于缩小Bounding Box
    4. 随机对图像进行Patch的操作,Patch的大小是图像的0.1-1.0,Patch和Bounding Box的IOU值需要在[0.1,0.3,0.5,0.7,0.9]这几个随机值中。相当于对Bounding Box进行扩大
    5. 随机调整图像的颜色,对比度,明亮度,并对图像的数值进行标准化的操作。

    具体的代码如下:

    import tensorflow as tf
    import numpy as np
    import os
    import cv2
    import matplotlib.pyplot as plt
     
    resize_width = 458
    resize_height = 458
    image_width = 416
    image_height = 416
    image_width_delta = resize_width - image_width
    image_height_delta = resize_height - image_height 
    batch_size = 32
    labels = ['umbrella',
     'sandwich',
     'handbag',
     'person',
     'snowboard',
     'cell phone',
     'traffic light',
     'potted plant',
     'toaster',
     'baseball glove',
     'cow',
     'surfboard',
     'remote',
     'toilet',
     'baseball bat',
     'giraffe',
     'book',
     'bottle',
     'stop sign',
     'frisbee',
     'boat',
     'sheep',
     'mouse',
     'motorcycle',
     'car',
     'bird',
     'pizza',
     'bed',
     'kite',
     'zebra',
     'broccoli',
     'cat',
     'chair',
     'bench',
     'teddy bear',
     'tennis racket',
     'laptop',
     'sink',
     'sports ball',
     'skateboard',
     'parking meter',
     'carrot',
     'hair drier',
     'banana',
     'wine glass',
     'scissors',
     'spoon',
     'cake',
     'fire hydrant',
     'dog',
     'backpack',
     'airplane',
     'clock',
     'keyboard',
     'truck',
     'bicycle',
     'skis',
     'bus',
     'hot dog',
     'dining table',
     'cup',
     'toothbrush',
     'horse',
     'elephant',
     'refrigerator',
     'knife',
     'suitcase',
     'apple',
     'donut',
     'couch',
     'train',
     'microwave',
     'bear',
     'oven',
     'bowl',
     'orange',
     'tv',
     'tie',
     'vase',
     'fork']
     
    #解析TFRECORD文件,对图像进行缩放,随机裁减,翻转和图像标准化的操作,并相应的调整检测框的位置
    def _parse_function(example_proto):
        features = {"image": tf.FixedLenFeature([], tf.string, default_value=""),
                    "height": tf.FixedLenFeature([1], tf.int64, default_value=[0]),
                    "width": tf.FixedLenFeature([1], tf.int64, default_value=[0]),
                    "channels": tf.FixedLenFeature([1], tf.int64, default_value=[3]),
                    "colorspace": tf.FixedLenFeature([], tf.string, default_value=""),
                    "img_format": tf.FixedLenFeature([], tf.string, default_value=""),
                    "label": tf.VarLenFeature(tf.int64),
                    "bbox_xmin": tf.VarLenFeature(tf.int64),
                    "bbox_xmax": tf.VarLenFeature(tf.int64),
                    "bbox_ymin": tf.VarLenFeature(tf.int64),
                    "bbox_ymax": tf.VarLenFeature(tf.int64),
                    "filename": tf.FixedLenFeature([], tf.string, default_value="")
                   }
        parsed_features = tf.parse_single_example(example_proto, features)
        
        label = tf.expand_dims(parsed_features["label"].values, 0)
        label = tf.cast(label, tf.int64)
        height = parsed_features["height"]
        width = parsed_features["width"]
        channels = parsed_features["channels"]
        
        #Resize the image
        image_raw = tf.image.decode_jpeg(parsed_features["image"], channels=3)
        image_decoded = tf.image.convert_image_dtype(image_raw, tf.float32)
        shape = tf.shape(image_decoded)
        height, width = shape[0], shape[1]
        image_resize = tf.image.resize(image_decoded, [resize_height, resize_width])
        height_ratio = tf.cast(resize_height/height, tf.float16)
        width_ratio = tf.cast(resize_width/width, tf.float16)
        #Adjust the bbox
        xmin = tf.cast(tf.expand_dims(parsed_features["bbox_xmin"].values, 0), tf.float16)
        xmax = tf.cast(tf.expand_dims(parsed_features["bbox_xmax"].values, 0), tf.float16)
        ymin = tf.cast(tf.expand_dims(parsed_features["bbox_ymin"].values, 0), tf.float16)
        ymax = tf.cast(tf.expand_dims(parsed_features["bbox_ymax"].values, 0), tf.float16)
        xmin = tf.cast(xmin*width_ratio, tf.int64)
        xmax = tf.cast(xmax*width_ratio, tf.int64)
        ymin = tf.cast(ymin*height_ratio, tf.int64)
        ymax = tf.cast(ymax*height_ratio, tf.int64)
     
        #Generate the random crop offset
        random_width_start = tf.random.uniform([1], minval=0, maxval=image_width_delta, dtype=tf.dtypes.int64)
        random_height_start = tf.random.uniform([1], minval=0, maxval=image_height_delta, dtype=tf.dtypes.int64)
        random_start = tf.concat([random_height_start, random_width_start, tf.constant([0], dtype=tf.dtypes.int64)], axis=0)
        
        #Adjust the bbox coordinates with random crop offset
        def f1():
            xmin_temp = xmin - random_width_start
            xmin_temp = tf.clip_by_value(xmin_temp, 0, image_width)
            xmax_temp = xmax - random_width_start
            xmax_temp = tf.clip_by_value(xmax_temp, 0, image_width)
            ymin_temp = ymin - random_height_start
            ymin_temp = tf.clip_by_value(ymin_temp, 0, image_height)
            ymax_temp = ymax - random_height_start
            ymax_temp = tf.clip_by_value(ymax_temp, 0, image_height)
            return xmin_temp, xmax_temp, ymin_temp, ymax_temp
        #Adjust the bbox coordinates with image flipped and random crop offset
        def f2():
            xmin_temp1 = xmin - random_width_start
            xmax_temp1 = xmax - random_width_start
            xmin_temp = image_width - tf.clip_by_value(xmax_temp1, 0, image_width)
            xmax_temp = image_width - tf.clip_by_value(xmin_temp1, 0, image_width)
            ymin_temp = ymin - random_height_start
            ymin_temp = tf.clip_by_value(ymin_temp, 0, image_height)
            ymax_temp = ymax - random_height_start
            ymax_temp = tf.clip_by_value(ymax_temp, 0, image_height)
            return xmin_temp, xmax_temp, ymin_temp, ymax_temp
        
        #Generate the random flip flag
        random_flip = tf.random.uniform([1], minval=0, maxval=1, dtype=tf.dtypes.float32)
        #Get the bbox coordinates after random flip and crop
        xmin, xmax, ymin, ymax = tf.cond(tf.less(random_flip[0], 0.5), f1, f2)
        
        image_sliced = tf.slice(image_resize, random_start, [image_height, image_width, -1])
        image_flipped = tf.cond(tf.less(random_flip[0], 0.5), lambda:image_sliced, lambda:tf.image.flip_left_right(image_sliced))
        image_standard = tf.image.per_image_standardization(image_flipped)
        image_train = tf.transpose(image_standard, perm=[2, 0, 1])
     
        #evaluate which anchor most fit the box
        box_width = (xmax-xmin)
        box_height = (ymax-ymin)
        box_area = box_width*box_height
        intersect_area_list = []
        for i in range(9):
            intersect_area_list.append(tf.minimum(box_width, anchors[2*i])*tf.minimum(box_height, anchors[2*i+1]))
        intersect_area = tf.concat(intersect_area_list, axis=0)
        iou = intersect_area/(box_area+anchors_area-intersect_area)
        anchor_id = tf.reshape(tf.argmax(iou, axis=0), [1, -1])
        
        bbox = tf.concat(axis=0, values=[xmin, xmax, ymin, ymax, anchor_id, label])
        bbox = tf.transpose(bbox, [1, 0])
        
        return bbox, image_train, image_flipped, image_decoded
     
    #构建处理TFRECORD的pipeline
    with tf.device('/cpu:0'):
        train_files = tf.data.Dataset.list_files("/data/data/coco/train_tf/*.tfrecord")
        dataset_train = train_files.interleave(tf.data.TFRecordDataset, cycle_length=4, num_parallel_calls=4)
        dataset_train = dataset_train.shuffle(buffer_size=epoch_size)
        dataset_train = dataset_train.repeat(100)
        dataset_train = dataset_train.map(_parse_function, num_parallel_calls=12)
        dataset_train = dataset_train.padded_batch(batch_size, 
                                                   padded_shapes=([None,None], 
                                                   [None, None, None], 
                                                   [None, None, None], 
                                                   [None, None, None]))
        dataset_train = dataset_train.prefetch(batch_size)
        iterator = tf.data.Iterator.from_structure(dataset_train.output_types, dataset_train.output_shapes)
        bbox, image_train, image_flipped, image_decoded = iterator.get_next()
        train_init_op = iterator.make_initializer(dataset_train)
     
    #验证数据
    with tf.Session() as sess:
        sess.run(train_init_op)
        images_run, bbox_run, images_d = sess.run([image_flipped, bbox, image_decoded])
     
    image_index = 0     #select one image in the batch
    image = images_run[image_index]
    image_bbox = bbox_run[image_index]
    for i in range(image_bbox.shape[0]):
        cv2.rectangle(image, (image_bbox[i][0],image_bbox[i][2]), (image_bbox[i][1],image_bbox[i][3]), (0,255,0), 2)
    plt.imshow(image)

    运行结果如下:

    cc4dd934dfca8a9ed8354bcdda6d2218.png

    文章转载自:https://blog.csdn.net/gzroy/article/details/95027532

    展开全文
  • 目标检测数据预处理

    2020-07-13 17:04:47
    目标检测中,卷积网络训练时的图片通常是要求固定尺寸大小的图片(例如416*416),但是直接将图片进行resize处理会引起图片一定程度上的失真,如下图。 一种有效的方法是将图片长宽等比例缩放至所需尺寸,剩余部分...

    前言

    目标检测中,卷积网络训练时的图片通常是要求固定尺寸大小的图片(例如416*416),但是直接将图片进行resize处理会引起图片一定程度上的失真,如下图。
    在这里插入图片描述
    一种有效的方法是将图片长宽等比例缩放至所需尺寸,剩余部分利用padding进行填充,以解决图像失真问题。
    在这里插入图片描述

    代码

    from PIL import Image
    def letterbox_image(image, size):
        # 对图片进行resize,使图片不失真。在空缺的地方进行padding
        iw, ih = image.size
        w, h = size
        scale = min(w/iw, h/ih)
        nw = int(iw*scale)
        nh = int(ih*scale)
    
        image = image.resize((nw,nh), Image.BICUBIC)
        new_image = Image.new('RGB', size, (128,128,128))
        new_image.paste(image, ((w-nw)//2, (h-nh)//2))
        return new_image
    
    img = Image.open("123.jpg")
    new_image = letterbox_image(img,[416,416])
    new_image.show()
    

    其中:image为数据集中的图片,size为卷积神经网络输入需要的尺寸
    该方法的思想为:

    1. 计算等比缩小的系数
    2. 将原图像乘以系数等比缩小
    3. 将剩余部分进行填充
    展开全文
  • 作者:蒋天园Date:2020-05-26来源:3D目标检测深度学习方法数据预处理综述前言这一篇的内容主要要讲一点在深度学习的3D目标检测网络中,我们都采用了哪些数据预处理的方法,主要讲两个方面的知识,第一个是...
    作者:蒋天园
    Date:2020-05-26
    来源:3D目标检测深度学习方法数据预处理综述

    前言

    这一篇的内容主要要讲一点在深度学习的3D目标检测网络中,我们都采用了哪些数据预处理的方法,主要讲两个方面的知识,第一个是representation,第二个数据预处理内容是数据增广。

    作为本篇博文的引言,我们先给一种博主制作的比较重要的3D检测方法图鉴,如下,就笔者的个人理解,今年的CVPR出现了很多的one-stage的方法,同时出现了很多融合的方法,这里的融合有信息融合,有representation融合,同时根据近两年的发展来看,voxel-based的方法占据了主导地位,这是得益于卷积结构优越性(point-based方法采用pointnet++结构,是MLP搭建的),但是今年的oral文章3D-SSD是一篇在point-based方法上很有建树的文章,所以在3D检测中了解主要的representation代表方法也是很重要的。

    v2-031a01f59a475aaa7502a551b511690b_b.jpg

    1.representation

    做3D视觉的,尤其是基于点云做3D视觉任务的同学们都比较清楚的知道,点云因为其具有稀疏性和无规则性使得在二维上非常成熟的CNN结构不能直接的运用在点云中。我们先了解一下点云的这两个特性,如下图所示,下图中的(i)表示二维图像的排列方式,(ii),(ii),(iv)表示的是点云的数据,可以看出点云的排列稀疏性,对比(ii)和(iii)可以得知虽然点云数据的排列顺序假使是一样的,但是其对应的几何结构不同,而(iii)和(iv)则可以看出尽管几何结构表示同一个,但是排列顺序却是可以不一样的。

    v2-fc94f020d50e99bbd86788a96d55687c_b.jpg

    知道了上述的两个点云数据的两种特性,所以设计一种合适的点云表达形式是比寻找到一个高效的深度学习框架更加重要的内容,就3D检测方面而言,到CVPR20,至少有三种比较重要和值得探究的representation方式,分表示pointrepresentation,voxel representation和graph representation。

    1.1 point representation

    如题,就是采用最原始的点作为深度学习网络的输入,不采用任何的预处理工作,这类工作都是在pointnet++的基础上进行的。

    这里首选插入一点采用point-based方法的基础backbone,如下所示,左图表示的是pointnet的特征提取结构,也就是point-based的基础模块,右图就是point-based方法的基础架构,由point-encoder层和point-decoder层组成,encoder层主要逐渐下采样采点特取语义信息,decoder过程是将encoder过程得到的特征信息传递给没有被采样到的点。使得全局的点都具有encoder的特征信息。最后再通过每个点作为anchor提出候选框。

    v2-6365c91e918bde09fc8ee75abffdead8_b.jpg

    最早的工作是CVPR18上的F-pointnet,该文章的作者也是pointnet/pointnet++的作者,具体做法如下可知,首先通过二维的检测框架得到二维目标检测结果,然后将二维检测结果通过视锥投影到三维,再采用三维破pointnet++延伸结构检测为三维目标框。算是三阶段的基于point输入的目标检测方法

    v2-7f7e86263058e011dfb9d0cb3dd20867_b.jpg

    随后的CVPR19的比较经典的Point_based方法是point-rcnn,该工作不仅仅采用point作为representation,同时和上诉的F-pointnet比较没有采用二维信息,仅仅采用点云作为网络输入。如下图所示,该工作是一个两阶段的检测方法,第一阶段根据语义分割信息对每一个点都提出一个候选框,随后再采用多特征融合进一步优化proposals。

    v2-7b039b9478ece6ec42aff73d092ce44c_b.jpg

    除了CVPR会议,在IROS和ICRA机器人相关的会议上也有很多这方面的研究,其中有一篇在F-pointnet上做出更细致的优化的文章F-ConvNet也是很优秀的工作,有兴趣的同学可以去了解下;这里直接介绍今年CVPR20上的基于Pointrepresentation的文章 3D-SSD,如下图所示,平平无奇的一阶段encoder过程,也就是把pointnet++的decoder部分给去除掉(这样做的目的是减少网络前馈时间,在KITTI上达到35FPS),本文的最主要的贡献点在于将Pointnet++的采样方法由欧式空间度量改为特征空间度量和欧式空间度量相结合的方法,同样的也采用了anchor-free的设计方法,使得显存占用更少。

    v2-dfba48405f0eb1ca2f4a9bce40a20d51_b.jpg

    point representation方法小总结

    就笔者个人对这方面的理解,该类方法的优点是作为最原始的点云数据,保留了最细致的几何结构信息,网络的输入信息损失几乎是所有representation方法中最小的。但是缺点也很明显,第一点,MLP的感知能力不如CNN,因此主流的effective的方法都是voxel-based的,第二点,pointnet++结构的采样是很耗时的,所以在实时性上也不及voxel-based的方法。所以今年的CVPR oral文章3D-SSD为了实时性,丢掉了FP层,同时设计了新的SA模块。

    1. 2 voxel representation

    1.2.1 Voxelization 步骤

    在3D目标检测中,对整个场景的point2voxel的过程可以简单描述为如下:1.设置Voxelization参数(每个voxel可以存放点的个数(max_points_number),voxel长宽高的大小(whl))2.对依次每一个点,根据其对应的坐标(x,y,z)得到该点在voxel的索引。3.根据索引判断该voxel种是否已经存在max_points_number个点,如果存在,则将该点直接丢弃,如果不满足,则将该点加入到该voxel中。4. 计算voxel特征采用voxelnet的图表示为如下:

    v2-52480a3e3b7c3f3f2247e6c50a387005_b.jpg

    1.2.2 Voxelization 参数

    上文中的体素化过程涉及到两个重要的内容,一个是体素参数,另外一个是voxel特征根据该voxel中的特征如何求取。

    Voxelization对参数的要求比较高,就发展历史来说,VoxelNet(CVPR18)是第一篇采用voxel-representation作为点云输入的网络结构,该文章中max_points_number设置的为35,voxel大小设置为0.5;采用的voxel特征提取方式为增加一个pointnet对每个voxel中的35个点特征提取得到相应的voxel特征(如下图中的featurelearning network所示的内容),但是该参数很明显是会丢失比较多的几何结构信息,但是以这样的参数划分在当时还受到VoxelNet网络后续的3D卷积的显存占用的影响(3D卷积很占显存,因此网络预处理的参数受到影响)。

    v2-831bae4a4c03b8bcb8b5a2db4ebfa552_b.jpg

    18年的SECOND提出的3D稀疏卷积大大减少了3D卷积的内存占用,(我们知道,3D卷积本身会对空间中每一个voxel都进行卷积,但是3D稀疏卷积只保留了空间中非空的voxel,采用map映射的方式得到卷积后的voxel空间索引);因此参数的设置自由了很多,就目前在KITTI和Nuscence上的sota的内容而言,我们一般采用的参数和特征提取方为:

    max−points−number=5,(w,l)=(0.05,0.05),h=0.1

    采用的voxel特征直接为mean特征,即对每一个voxel中的所有点的坐标求均值即可。

    1.2.3 related paper

    这里只介绍一些在voxelrepresentation上做文章的研究内容,而不是采用voxel作为网络输入的研究工作,因此voxel-based的研究方法比较多,后续笔者会出一篇在这方面的研究综述,所以这里推荐的几篇文章都是在voxel-representation上的做出研究的工作。

    voxel-based的先驱的两篇文章voxelnet和second的主要贡献分别是第一个提出采用voxel作为网络输入的方法和引入稀疏卷积替代3D卷积。这里补充一点voxel-based方法的backbone,目前几乎所有的voxel-based方法都采用如下的encoder的backone作为特征提取器。下图左图表示的是点的voxel表示,经过Voxelization化后,经过逐步下采样的encoder过程降为二维 feature map,最后再根据二维的feature的每一个像素点作为anchor point提出候选框。

    v2-4dca4141d5ebe1f2f1ca88988ec62fa5_b.jpg

    在voxel-representation上做文章的研究工作,如下图,这是一篇发表在sensors2020上的

    文章Voxel-FPN:multi-scale voxel feature aggregation in 3D object detection frompoint clouds,该研究工作的主要内容是通过不同scale的体素划分,最后将其整合成到RPN网络结构中的FPN网络中,需要注意的是,这里的scale的大小要和最初划分的scale对应起来。

    v2-0cf04fd4176cb0dc62c5516ac5e9f0dd_b.jpg

    同样采用该思想的还有今年的CVPR20的文章HVNet,如下图所示,也是对场景中点云采用multi-scale的体素划分,最后也会形成一个FPN的结构,但是不同的细节和实现之处还是有很多的,HVNet采用了多线程同时并行处理每一个scale的体素划分,同时对于voxel的特征提取和Voxel-FPN也是不一样的。

    v2-2bac855764048595a8f937f68e0dcf87_b.jpg

    在voxel划分上做研究的工作还有如下的waymo组的MVF工作,可以看到,之前的研究工作如果对于一个voxel中点数没有占据max_points_number实际上也会采用全0占据空间,而这一篇文章的一点创新则是修改成可自适应的储存点,不必强行每个voxel的空间占用一样大,可以节省不少内存占用。

    v2-8fe8d0a70f64dfe903b9cbfdb5ebf20b_b.jpg

    1.2.4 voxelrepresentation方法小结

    voxel-representation的方法的优点即是性能好又高效,不仅仅在精度上有着point-based的方法目前无法比拟的精度,在速度上也是很可观的,尤其是在稀疏卷积和3D流型卷积引入到3D目标检测后,发展更为迅速。但是缺点则是该类方法对参数比较敏感,预处理划分voxel的时候需要设置合适的参数,当然从信息论的方面理解,体素划分必然带来信息的丢失,尤其是局部细节信息的丢失,因此今年CVPR20上至少有三篇文章(SA-SSD,pointpainting,HVnet)在细节几何结构上做了一定的研究工作。

    1.3 graph represention

    这是一个比较新的representation,在3D检测中,今年CVPR20第一次出现了以graph作为representation的网络结构,如下图所示,graphrepresentation的核心问题也在于构建一个graph网络,即下图中的图左所示的内容。这也是很多在语义分割中遇到的问题,在目前的建图中大多是采用的knn的方法构建图结构,后续再送入到图卷积进行特种提取,最后根据pointrpn-head提出proposals。

    v2-bd996458237a42d1ce8645767b64fdfd_b.jpg

    就该类方法而言,目前的研究还不是很多,因为GCN非常耗时,可以理解为时pointnet++特征提取网络的升级版,不仅仅提取点之间的信息,同时根据‘点-边’信息提取到更加局部细节的信息,但是优点也可以理解到,3D shape实际上在增加边信息后,会更加容易感知(对比mesh结构可知),但是采用何种方式构建合适的graph都还是很需要研究的内容。

    1.4 point-voxel fusion

    既然我们知道point-based的方法具有保持了几何structure的能力,同时voxel-based的方法具有高效的感知能力,那么最新的研究就在考虑如何做这方面的fusion工作,PV-RCNN(CVPR20)采用的将voxel特征经过multi-scale的形式赋予到point上,最后再refine阶段将点的局部信息融合到pointnet中。SA-SSD采用voxel2point添加附加任务使得voxelbackbone具有structure aware能力。实际上根据representation fusion的经验,应该还是大有可做的。

    2 Augmentation

    实际上3D目标检测的数据增广方式和二维目标检测的方式大多相同,我们总结为如下一些比较常见的数据增广方式,根据动态图很容易的看到的出来数据增广的方式,这里笔者着重介绍一下groundtruth augmentor的方法,这应该是根据3D点云的稀疏空间特性所特有的数据增广方式。

    v2-1d3b1e726cf16f920c09ba3dea2e5c4b_b.jpg

    ground truth augmentor

    就KITTIobject 3D的数据而言,每一帧的object数量从无到二十多个不等,ground truth augmentor的想法则是先从所有训练集中根据类别把groundtruth建立成一个data base,然后在训练的时候将data base中的gt按照类别丢一定数量的gt到当前训练的帧中,这里笔者给出一般在KITTI上数据增广的数量如下。即表示一般会选择在场景中丢进去15个car,丢进10个Pedestrians和Cyclists。SAMPLE_GROUPS: ['Car:15','Pedestrian:10', 'Cyclist:10']因为该数据增广的工作在3D目标检测中比较重要,后续还延伸到一些涉及到该方面的研究工作,笔者做一点简单介绍.

    这一篇文章(Class-balanced Grouping and Sampling for Point Cloud 3D Object Detection)的后续研究工作做成了一个detectionzoo,在github上叫det3D,但是该文章的初始问题是想解决在nuscene数据中的数据不平衡问题,根据作者的采样得到如下图表,这里也就是目标检测的longtail问题,数据不平衡问题

    v2-9c9918786df62a89f1760c5f8b956db1_b.jpg

    这里作者采用的gt数据增广方式采样如下个数的gt来平衡数据集本身存在的long tail问题,并最终在nuscence上取得了榜一的成绩。

    v2-7ae28df78f315c64f805255ea5a7f4ef_b.png

    笔者再介绍一篇今年CVPR20上涉及到gt augmentation的工作(oral)(What You See is What You Get:Exploiting Visibility for 3D Object Detection),,如下图,在本文gt数据增广后,俯视图下由(a)变成了(b),但是作者指出这里出现的问题在于有的增广的物体出现在墙后,这在Lidar扫描过程中是不符合规律的,因此作者采取的增广策略是把对应的墙体去掉,如(d)图所示的内容。这就比较符合lidar扫描的特性,即遇到object就会反弹。

    v2-b5e691b7c07cb345fbe41c8afd60d989_b.jpg

    3 笔者的思考

    实际上数据预处理在深度学习中也是比较重要的内容,就representation来说,voxel的方法高效但存在信息丢失,point-basde的方法感知能力不及cnn但输入为最原始的结构,Graph构建了更容易感知的结构,但也要承担GCN网络过长的前馈时间;就augmentation来说,gtaugmentation尽管在涨点上成了众人皆知的trick,但是要能很好的用起来该方法还是有一些值得研究的trick在里面,就比如上述提到的两篇文章。最后写一个flag,后续尽快写一个voxel-based方法研究的发展概述,主要也是笔者的理解。

    推荐文献

    [1]VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection

    [2]Frustum PointNets for 3D Object Detection from RGB-D Data

    [3]PointRCNN: 3D Object Proposal Generation and Detection from Point Cloud’

    [4]3DSSD: Point-based 3D Single Stage Object Detector

    [5]Point-GNN: Graph Neural Network for 3D Object Detection in a Point Cloud

    往期干货资源:

    汇总 | 国内最全的3D视觉学习资源,涉及计算机视觉、SLAM、三维重建、点云处理、姿态估计、深度估计、3D检测、自动驾驶、深度学习(3D+2D)、图像处理、立体视觉、结构光等方向!

    汇总 | 3D目标检测(基于点云、双目、单目)

    汇总 | 6D姿态估计算法(基于点云、单目、投票方式)

    汇总 | 三维重建算法实战(单目重建、立体视觉、多视图几何)

    汇总 | 3D点云后处理算法(匹配、检索、滤波、识别)

    汇总 | SLAM算法(视觉里程计、后端优化、回环检测)

    汇总 | 深度学习&自动驾驶前沿算法研究(检测、分割、多传感器融合)

    汇总 | 相机标定算法

    汇总 | 事件相机原理

    汇总 | 结构光经典算法

    汇总 | 缺陷检测常用算法与实战技巧

    展开全文
  • 目标检测训练数据预处理

    目录

    1. 比例缩放

    2. 使用letterbox


    目标检测训练中,我们的数据集尺寸大部分时侯都是不符合网络输入的,需要对尺寸进行修改,下面我介绍两种常用尺寸变换方法:

    1. 比例缩放

    这种方法就是简单的对图片尺寸进行比例缩放,一般使用cv2.resize()对图片进行缩放,然后计算长宽缩放比例,再通过比例来缩放标注的目标框尺寸。具体代码如下:

    def read_and_resize_picture(img_path, img_boxes):
        """
        :function:读取图片,并缩放图片尺寸到指定尺寸,同时修改目标框尺寸
        :param img_path: 单张图片路径
        :param img_boxes: 对应图片目标框[[xmin,ymin,xmax,ymax],...,[xmin,ymin,xmax,ymax]]
        :return:图片(已经读取啦),修改的目标框
        """
        img = cv2.imread(img_path,flags=1)
        height, width = img.shape[0:2]
        if height == 416 and width == 416:
            return img, img_boxes
        else:
            h_s = height / 416.
            w_s = width / 416.
            # box = img_boxes
            # 将原图resize成300,300
            img_resize = cv2.resize(img, dsize=(416, 416), interpolation=cv2.INTER_LINEAR)
            # imgs.append(img_resize)
            img_boxes[:, 0:3:2] = img_boxes[:, 0:3:2] / w_s
            img_boxes[:, 1:4:2] = img_boxes[:, 1:4:2] / h_s
            # boxes.append(box)
            return img_resize, img_boxes

    这种方法修改图片尺寸方便,但是会改变图片特征

    2. 使用letterbox

    先对图片填充黑边,使图片尺寸为正方形,然后再缩放,这样不会改变图片尺寸,代码如下:

    import numpy as np
    import cv2
    import math
    def cv2_letterbox_image(image, expected_size, box):
        """
        function:使用letterbox填充并修改图片尺寸
        :param image: 原图
        :param expected_size: 想要修改后的尺寸
        :param box: 原图目标框
        :return: 修改后的图片,目标框
        """
        ih, iw = image.shape[0:2]
        ew, eh = expected_size
        scale = min(eh / ih, ew / iw)
        nh = int(ih * scale)
        nw = int(iw * scale)
        for i in range(4):
            box[i] = box[i] * scale
        image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_CUBIC)
        top = (eh - nh) // 2
        box[1] = box[1] + top
        box[3] = box[3] + top
        bottom = eh - nh - top
        left = (ew - nw) // 2
        box[0] = box[0] + left
        box[2] = box[2] + left
        right = ew - nw - left
        new_img = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT)
        return new_img, box
    def cv2_deletterbox_image(image, current_size, box):
        """
        function:去除图像灰边,并还原目标框尺寸
        :param image: 原图
        :param current_size: 当前图片尺寸(网络输入尺寸)
        :param box: 网络输入尺寸下的目标框
        :return: 匹配原图的目标框
        """
        ih, iw = image.shape[0:2]
        ew, eh = current_size
        scale = min(eh / ih, ew / iw)
        nh = int(ih * scale)
        nw = int(iw * scale)
    
        top = (eh - nh) // 2
        box[1] = box[1] - top
        box[3] = box[3] - top
        left = (ew - nw) // 2
        box[0] = box[0] - left
        box[2] = box[2] - left
        for i in range(4):
            box[i] = box[i] / scale
    
        return box
    def view_1():
        box = np.array([147, 336, 402, 437])
    
        ptr1 = (box[0], box[1])
        ptr2 = (box[2], box[3])
        color = (0, 0, 255)
        path = "YOLOV3_high/data/val/JPEG/image_1.jpg"
        img = cv2.imread(path)
        dst_img, box_new = cv2_letterbox_image(img, (415, 415), box)
        cv2.rectangle(img, ptr1, ptr2, color, 2)
        cv2.imshow('src_img', img)
        cv2.rectangle(dst_img, (box_new[0], box_new[1]), (box_new[2], box_new[3]), color, 2)
        cv2.imshow("dst_img", dst_img)
        # cv2.imwrite("pred_img.jpg",dst_img)
        cv2.waitKey(0)
    def view_2():
        box = np.array([147, 336, 402, 437])
    
        ptr1 = (box[0], box[1])
        ptr2 = (box[2], box[3])
        color = (0, 0, 255)
        path = "YOLOV3_high/data/val/JPEG/image_1.jpg"
        img = cv2.imread(path)
        img_src = img.copy()
        dst_img, box_new = cv2_letterbox_image(img, (416, 416), box)
        cv2.rectangle(img, ptr1, ptr2, color, 2)
        cv2.imshow('src_img', img)
        cv2.rectangle(dst_img, (box_new[0], box_new[1]), (box_new[2], box_new[3]), color, 2)
        cv2.imshow("dst_img", dst_img)
    
        box_src = cv2_deletterbox_image(img_src, (416,416), box_new)
        cv2.rectangle(img_src, (box_src[0], box_src[1]), (box_src[2], box_src[3]), color, 2)
        cv2.imshow("img_src", img_src)
        cv2.waitKey(0)
    if __name__ == "__main__":
        # view_1()
        view_2()
    
    

     

    展开全文
  • 这一篇的内容主要要讲一点在深度学习的3D目标检测网络中,我们都采用了哪些数据预处理的方法,主要讲两个方面的知识,第一个是representation,第二个数据预处理内容是数据增广。 作为本篇博文的引言,我们先给一种...
  • 这一篇的内容主要要讲一点在深度学习的3D目标检测网络中,我们都采用了哪些数据预处理的方法,主要讲两个方面的知识,第一个是representation,第二个数据预处理内容是数据增广。 作为本篇博文的引言,我们先给一种...
  • 数据集: ... 说明:本文使用2012 工程目录 datasets |_____commodity______Annotations ...数据预处理 from xml.etree import ElementTree as ET from collections import OrderedDict as Dict import num
  • 数据预处理 def letterbox_image(img, inp_dim): '''resize image with unchanged aspect ratio using padding Parameters ---------- img : numpy.ndarray Image inp_dim: tuple(int) shape of the...
  • COCO数据集是微软发布的一个大型图像数据集, 专为对象检测、分割、人体关键点检测、语义...下面记录一下我是如何利用COCO的数据集来制作目标检测数据,以及对数据进行增广处理的。首先我们去COCO的官网http://msc...
  • 它的 game.zip 里是 类似于 两个文件夹,一个lable 相当于Annotations ,一个...#根据PaddlexX格式的要求,生成三个数据集 import os import zipfile import xml.etree.ElementTree as ET import re import num...
  • CornerNet 是最经典的Anchor-free方法,本博客主要介绍数据预处理中最核心步骤: ground truth heatmaps。 方法 如上图所示, 有三种颜色的图形, 红色框代表ground truth, 绿色框的角点在橙色圆圈里面, gound ...
  • 作者 | 蒋天元来源 | 3D视觉工坊(ID: QYong_2014)这一篇的内容主要要讲一点在深度学习的3D目标检测网络中,我们都采用了哪些数据预处理的方法,主要讲两个方面的知识,第一个是representation,第二个数据预处理内容...
  • 这里记一下keras的预处理数据增强方法,想看pytorch的移步博主另一篇博客 https://blog.csdn.net/qq_36852276/article/details/94588656 多分类问题 使用keras自带的类ImageDataGenerator定义一个对象,这个...
  • 目标检测任务中常常会遇到图像中目标被遮挡严重的问题,由于遮挡数据复杂多样,遮挡信息丢失严重,模型在训练过程中往往陷入过拟合问题,对训练集中外的数据检测效果下降,在模型层面很难做到很好的改善,这时候就...
  • 在2D目标检测中,一般不需要对图像进行预处理,输入原始图像即可得到最终的检测结果,但是在点云3D目标检测中,往往需要对点云进行一定的预处理,本文将介绍在PointPillars模型中如何对点云进行预处理。 1. 模型...
  • Date:2020-5-25作者:蒋天园首发:公众号【3D视觉工坊】原文链接:3D目标检测深度学习方法数据预处理综述欢迎加入国内最大的3D视觉交流社区,1700+的领域从业者正在共同进步~往期干货资源:汇总 | 国内最全的3D视觉...
  • 读取图片信息,将图片数据与标签信息加载到tensorflow队列中,方便训练时读取
  • 以下链接是个人关于detectron2(目标检测框架),所有见解,如有错误欢迎大家指出,我会第一时间纠正。...该篇博客,主要讲解的是数据预处理,其还包含了数据增强,如中心剪切,多尺度训练等等 batch_size统一(...
  • 4.2 目标检测YOLO-V3算法--数据预处理&数据增广(百度架构师手把手带你零基础实践深度学习原版笔记系列) 目录 4.2 目标检测YOLO-V3算法--数据预处理&数据增广(百度架构师手把手带你零基础实践深度学习原版...
  • 想了解更多好玩的人工智能应用,请关注公众号“机器AI学习 数据AI挖掘”,”智能应用"菜单中包括:颜值检测、植物花卉识别、文字识别、人脸美妆等有趣的智能应用。。本文介绍如下三个方面的知识:1、如何将pascal ...
  • COCO数据集是微软发布的一个大型图像数据集, 专为对象检测、分割、人体关键点检测、语义...下面记录一下我是如何利用COCO的数据集来制作目标检测数据,以及对数据进行增广处理的。 首先我们去COCO的官网http://...
  • 目标检测DOTA数据预处理相关函数

    千次阅读 热门讨论 2019-03-15 13:23:30
    1.从DOTA数据集中选出自己需要的类别 2.DOTA数据gt可视化 3.对DOTA数据进行分割 4.分割后处理 5.转换成VOC形式的xml文件 6.对xml形式的数据进行数据扩增 1.从DOTA数据集中选出自己需要的类别 import os ...
  • 【第二部分】读取目标检测tfrecord数据并使用opencv在图片上画出目标边框 整个过程分为如下两步: 1.编写tfrecord解析函数,即反序列化函数。 2.获取图片标注数据,并使用OpenCV绘制边框。 具体代码如下...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 381
精华内容 152
关键字:

数据预处理目标检测