Advertisement

目标检测(R-CNN,SPP,Fast R-CNN,Faster R-CNN)

阅读量:

在这里插入图片描述
简而言之目标检测就是识别分类目标+框好对象

虽然图像分类的表现已经突破天际,甚至准确率高于人类,但目标检测领域其实仍然处于发展阶段。早期的传统目标检测基本就是–匹配 ,即把目标对象裁剪后用一些类似角点检测的方法得到特征,再在图像上使用滑动窗口并依次对比。

等到卷积神经网络在图像处理领域开始大显神通后,便可以通过手工标注训练集,构建卷积网络,并更改最后的输出层,使其输出类别 和目标定位框 (输出层分别是两个神经网络,一个负责在经过卷积抽象完成得到图像特征上做分类,一个负责做回归,即输出定位框的四个角坐标值),然后训练网络使均方误差最小就ok。想要得到类别使用VGG,ALexNet等架构就可以轻松完成任务,但如何得到更为精确的定位框呢?

既然是框,那何不使用滑动窗口呢?即使用一个小窗口,在小窗口区域内判断是否存在目标,然后再更改窗口大小,重复重复重复重复重复重复。所以明显的缺点就是计算消耗太大,不但小窗口的大小应该设置的范围,而且每个小窗口都要一次卷积来判断是否存在目标以及具体是哪一种类,虽然在深度学习兴起前,往往是在滑动窗口中使用普通的线性函数来判别会减少一些时间,但与今天的速度相比,还是慢了太多太多。不过所幸存在方法改进这一计算消耗。
在这里插入图片描述
比如滑动窗口的卷积实现,因为每次移动的“裁剪”其实会有很多计算重复部分,而这些部分是可以共享的(类似于卷积的参数共享,部分是适用于全局的,以便可以一次得到结果,加快速度),另外一些在空空如也的地方耗费太多的时间也是不可行的。所以将不做裁剪,直接对于整张图,在整张图上做卷积的“移动”,如上图所示,并且可以将原来最后的全连接层也换成大数量的卷积以完成相当的效果,采用卷积的方式实现全连接处理并不会减少参数的数量,但是使得输入的图像的尺寸可以更加灵活。不过问题依旧是无法准确定出边界。

R-CNN
即带区域的CNN。意在先选出一些区域使分类器更有意义的候选提议区域proposal region,避免网络在无效的空白地方耗太久,然后直接在这些被选出的少数窗口上卷积,最后再训练一个SVM用来得到目标分类的分数,一个网络用于得到边框的回归预测。
在这里插入图片描述
选出这些有意义的候选提议区域是利用了 选择性搜索 方法(Selective Search),具体的实现是根据图像的颜色、纹理等等,计算相似区域的层次分组来完成图像分割。如在论文:Selective Search for Object Recognition 中,下图的左边最下是使用了Huttenlocher方法,再逐步的从小的区域得到更大部分的提议区域,就可以得到一些类似于右边的候选区域了。
在这里插入图片描述
即R-CNN的训练过程为:
1.从输入图像上生成1000-2000个候选提议区域
2.预训练一个标准网络如vgg或AlexNet将充当图像的特征提取器,为每个小区域提取一个固定长度的特征向量如4092维的向量。
3.SVM对每个候选区域进行分类,得到具体的分数。
4.然后对这些分数采用非极大值抑制算法,即留下最好的,舍弃其他的框。

问题:
1.2000个就不多了吗??在GPU上跑一张图仍然需要数十秒。
2.特征提取器的神经网络单独训练。
3.SVM单独训练。
4.2000个小区域大小是不等的!这将导致提取的特征长度不固定,将无法训练。所以在传入VGG前,会对其进行裁剪,扭曲放大等操作。

SPP
SPP的贡献就是解决了候选区域不等的问题——空间金字塔池化层 。即对于大小不同的区域特征,直接分块,再最大池化取特征,拼起来(如下面论文中的图片,对特征切分为1,4,16块,池化之后就可以得到固定大小的特征)。不仅解决了不等问题,相当于还做了这么一个特征组合,使结果更鲁棒了。不过现在一般就直接全局平均池化层所替代了。

另外SPP也不是对每个小窗口跑了,而是用了一次卷积的方法,和本文开头改进传统滑动窗口的方法一致,一次性的得到所有候选框的特征。最后还是会有一个SVM做分类。
在这里插入图片描述
Fast R-CNN
Fast R-CNN是在SPP基础上改进了——ROI Pooling (regions of interest),它可以把不同大小区域全部映射到一个固定尺度的特征向量。具体操作方法是:将这个区域进行划分,且与输出的维度相同,即如果固定输出为2X2,则将这个区域划分成4份,然后直接最大池化就能得到2X2的固定特性向量输出了。如下图中,黄色和橙色分别是两个不同大小的候选区,同样划分成2X2的4分区域,最大池化后都变成一样的大小。
另外移除了SVM,变成了一个回归分类层,即两个都是全连接层FC网络。
并且还尝试将回归预测网络放入模型的内部,和小区域分类合并,以完成多目标识别。
在这里插入图片描述
ROI不是均分,只是为了固定大小。
在这里插入图片描述

即Fast R-CNN的训练过程为:
1.对整个图像统一卷积一次得到所有特征。
2.ROI池化层对每个潜在候选提取固定长度的特征向量,并传递给全连接层。
3.两个全连接层分支,一个估计softmax概率用于分类,一个预测的边框值。
4.输出层的输出会计算每个已标记的ROI的多任务损失,以联合训练分类和边框回归。
5.预测边框回归中,使用L1正则而中L2(R-CNN与SPP都是L2),使用L1将不会对异常值很敏感,会使整个网络更加的鲁棒。

Faster R-CNN
Faster R-CNN解决的问题就是2000个候选区还是太多了!干脆直接换方法。
方法(paper: https://arxiv.org/abs/1612.03144):
1.去除选择搜索,而是使用RPN网络(如下图)放在卷积后面,用于生成这些候选区。
2.锚框anchor boxes。
在这里插入图片描述
RPN使用CNN对原始图片提取特征,通过滑动得到的所有卷积特征的框(如图的3X3小红框),都负责检测k=9种尺寸的锚框并判断是否有物体,然后将每个锚框的特征结果都转换为一个统一的特征(如256维),一部分用作输出类别,一部分做框回归。

加入PRN神经网络(得到回归和分类分数)是为了提取边缘即得到候选区,不搜索但是引入不同大小高矮胖瘦的多种形态的锚框,候选区的分数将一起写进锚框中,计算其IoU,即边界框与真实标定框的交并商。如果锚框的最大IoU大于0.7,则该锚框为正,小于0.4,则为负,且忽略所有IoU为[0.4,0.7]的锚框和图像之外的锚框,这将使候选区的数量大大减少,然后再输入到ROI中等等。
在这里插入图片描述

TensorFlow Object Detection API
默认提供了5个预训练模型,都是使用 coco 数据集训练完成的,有SSD+MobieNet, SSD+Inception,R-FCN+ResNet101,Faster RCNN+ResNet101, Faster RCNN+Inception_ResNet。

复制代码
可以在 https://github.com/tensorflow/models 下载该API
    2.安装或升级protobuf:https://github.com/google/protobuf/releases
    3.解压、编译protobuf:# From models/research :
    protoc ob] ect:_detection/protos/*.proto --python_out=. 
    4配.环境变量:export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
    5.测试:python object_detection/builders/model_builder_test.py
    
    
    

测试成功就装好了,然后打开jupyter,一块一块就有demo结果了。

复制代码
    # From tensorflow/models/research/object_detection
    jupyter notebook
    
    

它已经训练好的网络进行自己的图片测试,实例代码如下:

复制代码
    import numpy as np
    import os
    import six.moves.urllib as urllib
    import sys
    import tarfile
    import tensorflow as tf
    import zipfile
    
    from distutils.version import StrictVersion
    from collections import defaultdict
    from io import StringIO
    from matplotlib import pyplot as plt
    from PIL import Image
    
    #导入上层目录
    sys.path.append("..")
    from object_detection.utils import ops as utils_ops
    
    if StrictVersion(tf.__version__) < StrictVersion('1.9.0'):
      raise ImportError('Please upgrade your TensorFlow installation to v1.9.* or later!')
    
    
    # 这条命令让 matplotlib 不再使用窗口展示出来,而是直接在 notebook 中显示
    %matplotlib inline
    
    from utils import label_map_util
    from utils import visualization_utils as vis_util
    
    #使用模型的载入
    MODEL_NAME = 'ssd_mobilenet_v1_coco_2017_11_17'#模型名称,选用了ssd+moblienet结构。
    MODEL_FILE = MODEL_NAME + '.tar.gz'
    DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'
    
    #pb文件即是导入网络的结构和数据
    PATH_TO_FROZEN_GRAPH = MODEL_NAME + '/frozen_inference_graph.pb'
    
    #pbtx保存了index到类别的映射,如类别为5的是XXX类
    PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')
    
    #开始下载网络模型
    opener = urllib.request.URLopener()
    opener.retrieve(DOWNLOAD_BASE + MODEL_FILE, MODEL_FILE)
    tar_file = tarfile.open(MODEL_FILE)
    for file in tar_file.getmembers():
      file_name = os.path.basename(file.name)
      if 'frozen_inference_graph.pb' in file_name:
    tar_file.extract(file, os.getcwd())
    
    #新建一个tf图,用来载入模型
    detection_graph = tf.Graph()
    with detection_graph.as_default():
      od_graph_def = tf.GraphDef()
      with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid:#指向了pb文件,即导入网络模型
    serialized_graph = fid.read()
    od_graph_def.ParseFromString(serialized_graph)
    tf.import_graph_def(od_graph_def, name='')
    
    #真正实现将index转为类别名
    category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True)
    
    #载入要测试的图片
    def load_image_into_numpy_array(image):
      (im_width, im_height) = image.size
      return np.array(image.getdata()).reshape(
      (im_height, im_width, 3)).astype(np.uint8)
    
    PATH_TO_TEST_IMAGES_DIR = 'test_images'#可以在此路径中加入新的想要测试的图片
    TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 3) ]#只载入两张
    
    # 定义输出图像的大小
    IMAGE_SIZE = (12, 8)
    
    def run_inference_for_single_image(image, graph):
      with graph.as_default():
    with tf.Session() as sess:
      # Get handles to input and output tensors
      ops = tf.get_default_graph().get_operations()
      all_tensor_names = {output.name for op in ops for output in op.outputs}
      tensor_dict = {}
      for key in [
          'num_detections', 'detection_boxes', 'detection_scores',
          'detection_classes', 'detection_masks'
      ]:
        tensor_name = key + ':0'#得到一些相应的tensor名
        if tensor_name in all_tensor_names:
          tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(
              tensor_name)#建立一个tensor名字典用于存放最后的结果
              
      if 'detection_masks' in tensor_dict:#mask表示如果有分割效果
      #单张图片
        detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0])#删除维度
        detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])
        #锚框mask坐标到图像mask坐标的转换并且调整大小
        real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)#目标数量
        detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])
        detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])
        detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(
            detection_masks, detection_boxes, image.shape[0], image.shape[1])
        detection_masks_reframed = tf.cast(
            tf.greater(detection_masks_reframed, 0.5), tf.uint8)
        #添加维度
        tensor_dict['detection_masks'] = tf.expand_dims(
            detection_masks_reframed, 0)
      image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')
    
      #RUN,真正开始计算
      output_dict = sess.run(tensor_dict,
                             feed_dict={image_tensor: np.expand_dims(image, 0)})
    
     #得到各个结果
      output_dict['num_detections'] = int(output_dict['num_detections'][0])
      output_dict['detection_classes'] = output_dict[ 'detection_classes'][0].astype(np.uint8)
      output_dict['detection_boxes'] = output_dict['detection_boxes'][0]
      output_dict['detection_scores'] = output_dict['detection_scores'][0]
      if 'detection_masks' in output_dict:
        output_dict['detection_masks'] = output_dict['detection_masks'][0]
      return output_dict
    
    #开始测试
    for image_path in TEST_IMAGE_PATHS:
      image = Image.open(image_path)
      image_np = load_image_into_numpy_array(image)
      #载入的图像应该变成 [1, None, None, 3]
      image_np_expanded = np.expand_dims(image_np, axis=0)
      output_dict = run_inference_for_single_image(image_np, detection_graph)
      
    #可视化结果
      vis_util.visualize_boxes_and_labels_on_image_array(
      image_np,
      output_dict['detection_boxes'],
      output_dict['detection_classes'],
      output_dict['detection_scores'],
      category_index,
      instance_masks=output_dict.get('detection_masks'),
      use_normalized_coordinates=True,
      line_thickness=8)
      plt.figure(figsize=IMAGE_SIZE)
      plt.imshow(image_np)
    
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-17/tc7AF3g1rzG5sOWwZUhYDmaRIevQ.png)

就有了测试结果:
demo结果
如何用这个api训练一个目标检测网络在下一篇整理:目标检测(YOLO,FPN,RetinaNet,SSD).

全部评论 (0)

还没有任何评论哟~