还是找不到mwww.zgdlztb.com目前,正常的wwWmwww.zgdlztb.comcom介入地止啊

&figure&&img src=&https://pic1.zhimg.com/v2-9fc12fd06ea300e4549ba_b.jpg& data-rawwidth=&2200& data-rawheight=&1212& class=&origin_image zh-lightbox-thumb& width=&2200& data-original=&https://pic1.zhimg.com/v2-9fc12fd06ea300e4549ba_r.jpg&&&/figure&&blockquote&转载请注明出处:&a href=&https://zhuanlan.zhihu.com/MCPRL& class=&internal&&CV喵的进阶&/a& &br&&a href=&https://link.zhihu.com/?target=https%3A//arxiv.org/abs/v1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&原文链接&/a&&/blockquote&&h2&&b&一、问题介绍&/b&&/h2&&p&物体检测的主要方法是单阶段(eg:YOLO,SSD)和两阶段(eg:Faster RCNN)。通常情况下,单阶段方法速度更快,两阶段方法mAP更高。本文作者想将二种方法结合,使其能够速度和效果都有很好的表现。&/p&&h2&&b&二、相关工作&/b&&/h2&&ul&&li&&b&两阶段方法&/b&&/li&&/ul&&p&第一阶段:生成候选框。主要方法:Selective Search, EdgeBoxes, DeepMask, RPN&/p&&p&第二阶段:用卷积网络来分类--类别标签,回归--候选区域。&/p&&ul&&li&&b&单阶段方法&/b&&/li&&/ul&&p&单阶段检测方法依据一定规律和密度采样出不用的位置,尺寸,比例,从而省去了生成候选框的这一阶段的网络。主要是将不同尺寸的不同特征层的集中在一起,进行一次回归,得到预测框的位置信息和大小。类别不平衡则会严重影响该方法的精确度。&/p&&h2&&b&三、网络结构&/b&&/h2&&figure&&img src=&https://pic1.zhimg.com/v2-9fc12fd06ea300e4549ba_b.jpg& data-size=&normal& data-rawwidth=&2200& data-rawheight=&1212& class=&origin_image zh-lightbox-thumb& width=&2200& data-original=&https://pic1.zhimg.com/v2-9fc12fd06ea300e4549ba_r.jpg&&&figcaption&RefineDet的网络结构 灰绿色四边形是不同feature map之间相连接的refined anchors,里面小星星代表refined anchor boxes的中心&/figcaption&&/figure&&p&本文将结合两阶段和单阶段的优点。基于单阶段的网络框架,加入了互相连接的模块:
anchor refinement module(ARM), object detection module(ODM),和两者之间的连接块,
transfer connection block(TCB). &/p&&ul&&li&ARM:识别并去除不好的anchor,从而减少分类器的搜索区间,粗略调整anchor的位置和大小,为后面的回归提供更好的初始化。&/li&&li&ODM:输入时refined anchors,为了提高回归和类别预测。&/li&&/ul&&p&&b&流程:&/b&&/p&&ol&&li&在每个feature map上按照一定规律的分割出cell,每个cell会有n个anchor。与对应cell相关的anchor的初始位置会被修正。&/li&&li&对每个feature map cell的原始anchor,预测其坐标的四个offests,和前景存在的物体的两个scores( negative confidence score和 positive confidence score),这样每个feature map cell就得到了n个refined anchors(这一步即是与SSD最大不同的之处)。&/li&&li&refined anchor会进行一波筛选,negative confidence score超过预设的阈值的anchor会被丢掉。最后留下的是 negative hard refined anchor和positive refined anchor。以此减少样本失衡。&/li&&li&筛选后的refined anchor会被送到ODM,进一步精确位置坐标和大小,以及类别分数。&/li&&/ol&&p&&b&训练:&/b&&/p&&p&训练技巧使用了与SSD相同的数据扩充技巧,骨干网络是VGG16(用于Pascal VOC数据集)和ResNet-101(用于COCO数据集),骨干网络基础上做了小小的改动。&/p&&ul&&li&VGG16为例介绍改动:类似于 DeepLab-LargeFOV,通过二次抽样将VGG16的fc6和fc7转变成了fc_conv。使用了L2范数将con4_3和conv5_3放缩到10和8,反向传播的时候再学习scales。为了获得高层的信息产生多尺度的检测,在阶段的VGG16后面增添了两个层: conv6_1 和conv6_2&/li&&li&默认参数:4个默认scale: [8,16,32,64] ,3个默认ratio: [0.5,1,2],IoU的阈值为0.5&/li&&li&困难样本挖掘时,基于top loss选择,保证正负样本比例是1:3,而不是使用全部负样本或是随机选择负样本。&/li&&li&Loss函数详见论文,将ARM和ODM两部分的loss合并。&/li&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-b9b275a18a73ba56a580_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&904& data-rawheight=&166& class=&origin_image zh-lightbox-thumb& width=&904& data-original=&https://pic4.zhimg.com/v2-b9b275a18a73ba56a580_r.jpg&&&/figure&&ul&&li&优化:nms,soft_nms&/li&&/ul&&h2&&b&四、实验结果&/b&&/h2&&p&实验丰富,一部分实验用于验证整个框架的效果,一部分实验用于验证框架中三个每个模块的效果。取得了比较不错的效果&/p&&ul&&li&pacal_voc VGG16&/li&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-1a95dcbf0b307d08d2049e_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1972& data-rawheight=&1260& class=&origin_image zh-lightbox-thumb& width=&1972& data-original=&https://pic1.zhimg.com/v2-1a95dcbf0b307d08d2049e_r.jpg&&&/figure&&ul&&li&MSCOCO ResNet-101&/li&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-b2b17cdd7a1817fcecec5_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1972& data-rawheight=&474& class=&origin_image zh-lightbox-thumb& width=&1972& data-original=&https://pic1.zhimg.com/v2-b2b17cdd7a1817fcecec5_r.jpg&&&/figure&&h2&&b&五、总结&/b&&/h2&&p&框架结构简单又有效,代码公开。在同时期的SSD的衍生网络中速度与精度表现上脱颖而出。&/p&&hr&&p&第一次写文章,很多不足,欢迎指教~&/p&&p&总结的时候更是暴露出的自己还没有足够的积累。&/p&&p&总之,先迈出第一步。相信以后会越来越好。&/p&
转载请注明出处: 一、问题介绍物体检测的主要方法是单阶段(eg:YOLO,SSD)和两阶段(eg:Faster RCNN)。通常情况下,单阶段方法速度更快,两阶段方法mAP更高。本文作者想将二种方法结合,使其能够速度和效果都有很好的表现。二、相关工作…
&figure&&img src=&https://pic2.zhimg.com/v2-f69b20adf49edbbcba375d_b.jpg& data-rawwidth=&1252& data-rawheight=&782& class=&origin_image zh-lightbox-thumb& width=&1252& data-original=&https://pic2.zhimg.com/v2-f69b20adf49edbbcba375d_r.jpg&&&/figure&&blockquote&原文作者:Chad Hart&br&原文地址:&a href=&http://link.zhihu.com/?target=https%3A//webrtchacks.com/webrtc-cv-tensorflow/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Computer Vision on the Web with WebRTC and TensorFlow - webrtcHacks&/a&&br&摘要:本文作者介绍了结合WebRTC与TensorFlow实现图像检测的具体过程,不论对于TensorFlow的使用者,还是WebRTC的开发者来讲都有参考意义。由于文章较长,我们将分为上下篇进行连载。&/blockquote&&p&TensorFlow是目前最流行的机器学习框架之一。TensorFlow的一大优势是,它的很多库都有人积极进行维护和更新。而我最喜欢的其中一个库就是TensorFlow对象检测API。Tensorflow对象检测API可以对一张图形上的多个对象进行分类,并提供它们的具体位置。该API在将近1000个对象类上进行了预先训练,可提供各种经过预先训练的模型,让你可以在速度与准确性之间权衡取舍。&/p&&p&有这些模型的指引固然很好,但所有这些模型都要使用图像才能发挥作用,而这些图像则需要你自行添加到一个文件夹中。我其实很想将其与实时的WebRTC流配合到一起,通过网络实现实时的计算机视觉。由于未能找到这方面的任何例子或指南,我决定写这篇博文来介绍具体的实现方法。对于使用RTC的人,可以将本文作为一篇快速指南加以参考,了解如何使用TensorFlow来处理WebRTC流。对于使用TensorFlow的人士,则可以将本文作为一份快速简介,了解如何向自己的项目中添加WebRTC。使用WebRTC的人需要对Python比较熟悉。而使用TensorFlow的人则需要熟悉网络交互和一些JavaScript。&/p&&p&本文不适合作为WebRTC或TensorFlow的入门指南使用。如需这样的指南,应参考TensorFlow入门指南、WebRTC入门指南等,网上的相关介绍与指南数不胜数。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-fecae593a7acdad50293_b.jpg& data-size=&normal& data-rawwidth=&1080& data-rawheight=&675& class=&origin_image zh-lightbox-thumb& width=&1080& data-original=&https://pic4.zhimg.com/v2-fecae593a7acdad50293_r.jpg&&&figcaption&利用Tensor Flow和WebRTC检测猫咪&/figcaption&&/figure&&p&&b&直接告诉我如何实现吧&/b&&/p&&p&如果你来这里只是为了快速找到一些参考信息,或者懒得读详细的文字介绍,按照下面的方法即可快速着手。首先安装Docker。加载一个命令提示窗口,接着键入下面的命令:&/p&&div class=&highlight&&&pre&&code class=&language-python3&&&span&&/span&&span class=&n&&docker&/span& &span class=&n&&run&/span& &span class=&o&&-&/span&&span class=&n&&it&/span& &span class=&o&&-&/span&&span class=&n&&p&/span& &span class=&mi&&5000&/span&&span class=&p&&:&/span&&span class=&mi&&5000&/span& &span class=&n&&chadhart&/span&&span class=&o&&/&/span&&span class=&n&&tensorflow&/span&&span class=&o&&-&/span&&span class=&nb&&object&/span&&span class=&o&&-&/span&&span class=&n&&detection&/span&&span class=&p&&:&/span&&span class=&n&&runserver&/span&
&/code&&/pre&&/div&&p&然后在浏览器地址栏中键入并转到http://localhost:5000/local,接受摄像头权限请求,你应该会看到类似下面的界面: &/p&&figure&&img src=&https://pic4.zhimg.com/v2-68ae269382eae54c62672edb0cb90b63_b.jpg& data-size=&normal& data-rawwidth=&865& data-rawheight=&657& class=&origin_image zh-lightbox-thumb& width=&865& data-original=&https://pic4.zhimg.com/v2-68ae269382eae54c62672edb0cb90b63_r.jpg&&&figcaption&请移步原文观看油管视频&/figcaption&&/figure&&p&&b&基本架构&/b&&/p&&p&我们首先建立一个基本架构,用以在本地将一个本地网络摄像头流从WebRTC的getUserMedia发送到一个Python服务器,这要用到Flask网络服务器和TensorFlow 对象检测API(Object Detection API)。具体的设置大致如下图所示。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-92c774f771cf_b.jpg& data-size=&normal& data-rawwidth=&2000& data-rawheight=&822& class=&origin_image zh-lightbox-thumb& width=&2000& data-original=&https://pic3.zhimg.com/v2-92c774f771cf_r.jpg&&&figcaption&为搭配使用WebRTC与TensorFlow对象检测API而建立的基本架构&/figcaption&&/figure&&p&Flask将提供html和JavaScript文件供浏览器呈现。getUserMedia.js负责抓取本地视频流。接下来,objDetect.js会使用HTTP POST方法向TensorFlow对象检测API发送图像,该API则返回它所看到的对象(它称之为“类”)及对象在图像中的位置。我们会将这些详细信息封装到一个JSON对象中,然后将该对象发回给objDetect.js,这样我们就能将我们所看到的对象的方框和标签显示出来。&/p&&p&&b&配置&/b&&/p&&p&&b&设置和前提条件&/b&&/p&&p&在开始之前,我们需要先对Tensorflow和对象检测API进行一些设置。&/p&&p&&b&使用Docker轻松完成设置&/b&&/p&&p&我在OSX、Windows 10和Raspbian已经设置过好几次(过程可不简单)。各种版本依赖关系错综复杂,把这些关系理顺并非易事,特别是当你只是想看看一些前期工作是否行得通时,你可能会感到气馁。我推荐使用Docker来避免这些棘手问题。你将需要学习Docker,这也是非学不可的东西,与其试着构建合适的Protobuf版本,倒不如花些时间学习它来得更为高效。TensorFlow项目维护了一些官方的Docker映像,比如tensorflow/tensorflow。&/p&&p&如果你使用Docker,我们就可以使用我为这篇博文创建的映像。在命令行中,请运行以下命令:&/p&&div class=&highlight&&&pre&&code class=&language-python3&&&span&&/span&&span class=&n&&git&/span& &span class=&n&&clone&/span& &span class=&n&&https&/span&&span class=&p&&:&/span&&span class=&o&&//&/span&&span class=&n&&github&/span&&span class=&o&&.&/span&&span class=&n&&com&/span&&span class=&o&&/&/span&&span class=&n&&webrtcHacks&/span&&span class=&o&&/&/span&&span class=&n&&tfObjWebrtc&/span&&span class=&o&&.&/span&&span class=&n&&git&/span&
&span class=&n&&cd&/span& &span class=&n&&tfObjWebrtc&/span&
&span class=&n&&docker&/span& &span class=&n&&run&/span& &span class=&o&&-&/span&&span class=&n&&it&/span& &span class=&o&&-&/span&&span class=&n&&p&/span& &span class=&mi&&5000&/span&&span class=&p&&:&/span&&span class=&mi&&5000&/span& &span class=&o&&--&/span&&span class=&n&&name&/span& &span class=&n&&tf&/span&&span class=&o&&-&/span&&span class=&n&&webrtchacks&/span& &span class=&o&&-&/span&&span class=&n&&v&/span& &span class=&err&&$&/span&&span class=&p&&(&/span&&span class=&n&&pwd&/span&&span class=&p&&):&/span&&span class=&o&&/&/span&&span class=&n&&code&/span& &span class=&n&&chadhart&/span&&span class=&o&&/&/span&&span class=&n&&tensorflow&/span&&span class=&o&&-&/span&&span class=&nb&&object&/span&&span class=&o&&-&/span&&span class=&n&&detection&/span&&span class=&p&&:&/span&&span class=&n&&webrtchacks&/span&
&/code&&/pre&&/div&&p&请注意,docker run中的$(pwd)仅适用于Linux和Windows Powershell。在Windows 10命令行中,请使用%cd%。&/p&&p&看到这里,你应该已经进入了Docker容器。现在,请运行:&/p&&div class=&highlight&&&pre&&code class=&language-python3&&&span&&/span&&span class=&n&&python&/span& &span class=&n&&setup&/span&&span class=&o&&.&/span&&span class=&n&&py&/span& &span class=&n&&install&/span&
&/code&&/pre&&/div&&p&这样,就会使用最新的TensorFlow Docker映像,并将Docker主机上的端口5000连接到端口5000,将容器命名为tf-webrtchacks,将一个本地目录映射到容器中的一个新/code目录,将该目录设为默认目录(我们接下来将在该目录中操作),然后运行bash以便进行命令行交互。完成这些准备工作后,我们才能开始。&/p&&p&如果你才刚开始接触TensorFlow,可能需要先按照tensorflow/tensorflow中的说明运行初始Jupyter notebook,然后再回来执行上述命令。&/p&&p&&b&另一种麻烦的实现方法&/b&&/p&&p&如果你打算从头开始,则需要安装TensorFlow,它自身有很多依赖项,比如Python。TensorFlow项目针对各种平台都提供了指南,具体请访问&u&&a href=&http://link.zhihu.com/?target=https%3A//www.tensorflow.org/install& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&tensorflow.org/install&/span&&span class=&invisible&&&/span&&/a&&/u&。对象检测API也有自己的&u&安装说明&/u&,以及一些额外的依赖项。完成这些准备工作后,请运行下面的命令:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&git clone https://github.com/webrtcHacks/tfObjWebrtc.git
cd tfObjWebrtc
python setup.py install
&/code&&/pre&&/div&&p&这样,就应该安装好了所有的Python依赖项,将相应的Tensorflow对象检测API文件都复制了过来,并安装了Protobufs。如果这一步行不通,我建议检查setup.py,然后手动在其中运行命令,以解决存在的任何问题。&/p&&p&&b&第1部分——确保Tensorflow正常工作&/b&&/p&&p&为确保TensorFlow对象检测API正常工作,我们首先从用于演示对象检测的官方版JupyterNotebook经调整后的版本着手。我将此文件保存为object_detection_tutorial.py。&/p&&p&如果你剪切并粘贴该notebook的每个部分,得到的结果应如下所示:(由于此段代码较长,截图会影响阅读,我们更换为文字排版,左右拖动可查看长代码)&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&# IMPORTS
import numpy as np
import six.moves.urllib as urllib
import sys
import tarfile
import tensorflow as tf
import zipfile
from collections import defaultdict
from io import StringIO
# from matplotlib import pyplot as plt ### CWH
from PIL import Image
if tf.__version__ != '1.4.0':
raise ImportError('Please upgrade your tensorflow installation to v1.4.0!')
# ENV SETUP
### CWH: remove matplot display and manually add paths to references
# This is needed to display the images.
%matplotlib inline
# This is needed since the notebook is stored in the object_detection folder.
sys.path.append(&..&)
# Object detection imports
from object_detection.utils import label_map_util
### CWH: Add object_detection path
#from object_detection.utils import visualization_utils as vis_util ### CWH: used for visualization
# Model Preparation
# What model to download.
MODEL_NAME = 'ssd_mobilenet_v1_coco_'
MODEL_FILE = MODEL_NAME + '.tar.gz'
DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'
# Path to frozen detection graph. This is the actual model that is used for the object detection.
PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'
# List of the strings that is used to add correct label for each box.
PATH_TO_LABELS = os.path.join('object_detection/data', 'mscoco_label_map.pbtxt') ### CWH: Add object_detection path
NUM_CLASSES = 90
# Download Model
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())
# Load a (frozen) Tensorflow model into memory.
detection_graph = tf.Graph()
with detection_graph.as_default():
od_graph_def = tf.GraphDef()
with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:
serialized_graph = fid.read()
od_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(od_graph_def, name='')
# Loading label map
label_map = label_map_util.load_labelmap(PATH_TO_LABELS)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True)
category_index = label_map_util.create_category_index(categories)
# Helper code
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)
# Detection
# For the sake of simplicity we will use only 2 images:
# image1.jpg
# image2.jpg
# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.
PATH_TO_TEST_IMAGES_DIR = 'object_detection/test_images' #cwh
TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 3) ]
# Size, in inches, of the output images.
IMAGE_SIZE = (12, 8)
with detection_graph.as_default():
with tf.Session(graph=detection_graph) as sess:
# Definite input and output Tensors for detection_graph
image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
# Each box represents a part of the image where a particular object was detected.
detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')
# Each score represent how level of confidence for each of the objects.
# Score is shown on the result image, together with the class label.
detection_scores = detection_graph.get_tensor_by_name('detection_scores:0')
detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')
num_detections = detection_graph.get_tensor_by_name('num_detections:0')
for image_path in TEST_IMAGE_PATHS:
image = Image.open(image_path)
# the array based representation of the image will be used later in order to prepare the
# result image with boxes and labels on it.
image_np = load_image_into_numpy_array(image)
# Expand dimensions since the model expects images to have shape: [1, None, None, 3]
image_np_expanded = np.expand_dims(image_np, axis=0)
# Actual detection.
(boxes, scores, classes, num) = sess.run(
[detection_boxes, detection_scores, detection_classes, num_detections],
feed_dict={image_tensor: image_np_expanded})
### CWH: below is used for visualizing with Matplot
# Visualization of the results of a detection.
vis_util.visualize_boxes_and_labels_on_image_array(
np.squeeze(boxes),
np.squeeze(classes).astype(np.int32),
np.squeeze(scores),
category_index,
use_normalized_coordinates=True,
line_thickness=8)
plt.figure(figsize=IMAGE_SIZE)
plt.imshow(image_np)
&/code&&/pre&&/div&&p&在这里我就不再赘述实际TensorFlow代码的作用了,这方面的信息可在Jupyter演示及其他教程中找到。我将重点介绍我们对代码所做的修改。 &/p&&p&我注释了几个小节:&/p&&ol&&li&更改了一些位置引用&/li&&li&删除了对Python matplot的所有引用。Python matplot用于在GUI环境中以可视化方式呈现输出结果。在我的Docker环境中没有设置它——根据你采用的具体运行方式,可以酌情决定是否保留这些引用。&/li&&/ol&&p&&b&对象检测API的输出结果&/b&&/p&&p&正如第111行所示,对象检测API输出4种对象:&/p&&ol&&li&类——一个由对象名组成的数组&/li&&li&分值——一个由置信度分值组成的数组&/li&&li&方框——检测到的每个对象所在的位置&/li&&li&数量——检测到的对象总数&/li&&/ol&&p&类、分值和方框都是相互并列、大小相等的数组,因此classes[n]与scores[n]和boxes[n]都是一一对应的。&/p&&p&由于我删去了可视化功能,我们需要通过某种方式来查看结果,所以我们要把下面的命令添加到文件末尾:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&### CWH: Print the object details to the console instead of visualizing them with the code above
classes = np.squeeze(classes).astype(np.int32)
scores = np.squeeze(scores)
boxes = np.squeeze(boxes)
threshold = 0.50
#CWH: set a minimum score threshold of 50%
obj_above_thresh = sum(n & threshold for n in scores)
print(&detected %s objects in %s above a %s score& % ( obj_above_thresh, image_path, threshold))
for c in range(0, len(classes)):
if scores[c] & threshold:
class_name = category_index[classes[c]]['name']
print(& object %s is a %s - score: %s, location: %s& % (c, class_name, scores[c], boxes[c]))
&/code&&/pre&&/div&&p&第一个np.squeeze部分只是将多维数组输出缩减成一维,这与原来的可视化代码一样。我认为这是TensorFlow的一个副产品,因为它通常会输出多维数组。&/p&&p&接着我们要为它输出的分值设置一个阈值。好像TensorFlow默认会返回100个对象。其中很多对象嵌套在置信度更高的对象内或与这些对象重叠。在选择阈值方面我还没有发现任何最佳做法,不过对于这些示例图像来说,50%似乎是合适的。&/p&&p&最后,我们需要循环遍历这些数组,直接输出那些超过阈值的分值。&/p&&p&如果运行下面的命令:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&python object_detection_tutorial.py
&/code&&/pre&&/div&&p&应该会获得下面的输出:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&detected 2 objects in object_detection/test_images/image1.jpg above a 0.5 score
object 0 is a dog - score: 0.940691, location: [ 0....]
object 1 is a dog - score: 0.934503, location: [ 0....]
detected 10 objects in object_detection/test_images/image2.jpg above a 0.5 score
object 0 is a person - score: 0.916878, location: [ 0....]
object 1 is a kite - score: 0.829445, location: [ 0....]
object 2 is a person - score: 0.778505, location: [ 0..057667
object 3 is a kite - score: 0.769985, location: [ 0..4374091
object 4 is a kite - score: 0.755539, location: [ 0....]
object 5 is a person - score: 0.634234, location: [ 0....]
object 6 is a kite - score: 0.607407, location: [ 0....]
object 7 is a person - score: 0.589102, location: [ 0....]
object 8 is a person - score: 0.512377, location: [ 0....]
object 9 is a person - score: 0.501464, location: [ 0....]
&/code&&/pre&&/div&&p&&b&第2部分——打造一项对象API网络服务&/b&&/p&&p&在这一部分,我们将对教程代码作一些改动,以将其作为一项网络服务加以运行。我在Python方面的经验颇为有限(主要在Raspberry Pi项目中使用过),所以如有不对的地方,请添加备注或提交拉取请求,以便我可以修正。&/p&&p&&b&2.1将演示代码转变成一项服务&/b&&/p&&p&至此我们已经让TensorFlow Object API能够正常工作了,接下来我们就将它封装成一个可以调用的函数。我将演示代码复制到了一个名为object_detection_api.py的新python文件中。你可以看到,我删除了很多没有用到或注释掉的行,以及用于将详细信息输出到控制台的部分(暂时删除)。&/p&&p&由于我们要将这些信息输出到网络上,因此最好将我们的输出结果封装成一个JSON对象。为此,请务必向你导入的内容中添加一个importjso语句,然后再添加下面的命令:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&# added to put object in JSON
class Object(object):
def __init__(self):
self.name=&Tensor Flow Object API Service 0.0.1&
def toJSON(self):
return json.dumps(self.__dict__)
&/code&&/pre&&/div&&p&接下来,我们要重复利用之前的代码创建一个get_objects函数:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def get_objects(image, threshold=0.5):
image_np = load_image_into_numpy_array(image)
# Expand dimensions since the model expects images to have shape: [1, None, None, 3]
image_np_expanded = np.expand_dims(image_np, axis=0)
# Actual detection.
(boxes, scores, classes, num) = sess.run(
[detection_boxes, detection_scores, detection_classes, num_detections],
feed_dict={image_tensor: image_np_expanded})
classes = np.squeeze(classes).astype(np.int32)
scores = np.squeeze(scores)
boxes = np.squeeze(boxes)obj_above_thresh = sum(n & threshold for n in scores)
obj_above_thresh = sum(n & threshold for n in scores)
print(&detected %s objects in image above a %s score& % (obj_above_thresh, threshold))
&/code&&/pre&&/div&&p&在此函数中我们添加了一个图像输入参数和一个默认为0.5的threshold值。其余内容都是在演示代码的基础上重构的。&/p&&p&现在我们再向此函数添加一些代码,以查询具体的值并将它们输出到一个JSON对象中:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&output = []
#Add some metadata to the output
item = Object()
item.numObjects = obj_above_thresh
item.threshold = threshold
output.append(item)
for c in range(0, len(classes)):
class_name = category_index[classes[c]]['name']
if scores[c] &= threshold:
# only return confidences equal or greater than the threshold
print(& object %s - score: %s, coordinates: %s& % (class_name, scores[c], boxes[c]))
item = Object()
item.name = 'Object'
item.class_name = class_name
item.score = float(scores[c])
item.y = float(boxes[c][0])
item.x = float(boxes[c][1])
item.height = float(boxes[c][2])
item.width = float(boxes[c][3])
output.append(item)
outputJson = json.dumps([ob.__dict__ for ob in output])
return outputJson
&/code&&/pre&&/div&&p&这一次我们是使用Object类来创建一些初始元数据并将这些元数据添加到output列表中。然后我们使用循环向此列表中添加Object数据。最后,将此列表转换成JSON并予以返回。&/p&&p&之后,我们来创建一个测试文件(这里要提醒自己:先做测试),以检查它是否调用了object_detection_test.py:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import scan_image
from PIL import Image
# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.
PATH_TO_TEST_IMAGES_DIR = 'object_detection/test_images' #cwh
TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 3) ]
for image_path in TEST_IMAGE_PATHS:
image = Image.open(image_path)
response = object_detection_api.get_objects(image)
print(&returned JSON: \n%s& % response)
&/code&&/pre&&/div&&p&至此万事俱备,接下来就是运行了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&python object_detection_test.py
&/code&&/pre&&/div&&p&除了前面的控制台输出之外,你应该还会看到一个JSON字符串:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&returned JSON:
[{&threshold&: 0.5, &name&: &webrtcHacks Sample Tensor Flow Object API Service 0.0.1&, &numObjects&: 10}, {&name&: &Object&, &class_name&: &person&, &height&: 0.6614, &width&: 0.43335, &score&: 0.479, &y&: 0.4099, &x&: 0.24854}, {&name&: &Object&, &class_name&: &kite&, &height&: 0.11816, &width&: 0.7505, &score&: 0.7749, &y&: 0.8103, &x&: 0.89404}, {&name&: &Object&, &class_name&: &person&, &height&: 0.6709, &width&: 0.8263, &score&: 0.6724, &y&: 0.4663, &x&: 0.580734}, {&name&: &Object&, &class_name&: &kite&, &height&: 0.73273, &width&: 0.2583, &score&: 0.8064, &y&: 0.12439, &x&: 0.71753}, {&name&: &Object&, &class_name&: &kite&, &height&: 0.1443, &width&: 0.94092, &score&: 0.4636, &y&: 0.78784, &x&: 0.6394}, {&name&: &Object&, &class_name&: &person&, &height&: 0.5999, &width&: 0.57727, &score&: 0.1169, &y&: 0.2175, &x&: 0.6709}, {&name&: &Object&, &class_name&: &kite&, &height&: 0.78687, &width&: 0.24805, &score&: 0.2927, &y&: 0.76904, &x&: 0.05273}, {&name&: &Object&, &class_name&: &person&, &height&: 0.3652, &width&: 0.61536, &score&: 0.1912, &y&: 0.3418, &x&: 0.89941}, {&name&: &Object&, &class_name&: &person&, &height&: 0.5452, &width&: 0.90356, &score&: 0.3203, &y&: 0.0437, &x&: 0.26636}, {&name&: &Object&, &class_name&: &person&, &height&: 0.8259, &width&: 0.59009, &score&: 0.0388, &y&: 0.3054, &x&: 0.915108}]
&/code&&/pre&&/div&&p&&b&2.2添加一个网络服务器&/b&&/p&&p&我们已经有了函数——接下来我们就用它来打造一项网络服务。&/p&&p&&b&先使用测试用的路由(Route)运行&/b&&/p&&p&我们有了一个可以轻松添加到网络服务的良好API。我发现使用Flask是最简单的测试方法。我们来创建一个server.py,然后执行一次快速测试:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import object_detection_api
from PIL import Image
from flask import Flask, request, Response
app = Flask(__name__)
@app.route('/')
def index():
return Response('Tensor Flow object detection')
@app.route('/test')
def test():
PATH_TO_TEST_IMAGES_DIR = 'object_detection/test_images'
TEST_IMAGE_PATHS = [os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 3)]
image = Image.open(TEST_IMAGE_PATHS[0])
objects = object_detection_api.get_objects(image)
return objects
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
&/code&&/pre&&/div&&p&现在,运行该服务器:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&python server.py
&/code&&/pre&&/div&&p&&b&确保该服务正常工作&/b&&/p&&p&然后调用该网络服务。就我自己的情况而言,我只是从主机运行了下面的命令(因为我的Docker实例现在正在前台运行该服务器):&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&curl http://localhost:5000/test | python -m json.tool
&/code&&/pre&&/div&&p&json.tool将帮助你为输出结果设置格式。你应该会看到下面的结果:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
% Received % Xferd
Average Speed
0:00:01 --:--:--
&name&: &webrtcHacks Sample Tensor Flow Object API Service 0.0.1&,
&numObjects&: 2,
&threshold&: 0.5
&class_name&: &dog&,
&height&: 0.4951,
&name&: &Object&,
&score&: 0.1162,
&width&: 0.06445,
&x&: 0.30536,
&y&: 0.086304
&class_name&: &dog&,
&height&: 0.2527,
&name&: &Object&,
&score&: 0.1799,
&width&: 0.1743,
&x&: 0.6865,
&y&: 0.57739
&/code&&/pre&&/div&&p&好了,接下来我们就要接受一个包含一个图片文件及其他一些参数的POST,使用真实路由运行了。为此,需要在/test路由函数下添加一个新的/image路由:&/p&&div class=&highlight&&&pre&&code class=&language-python3&&&span&&/span&&span class=&nd&&@app&/span&&span class=&o&&.&/span&&span class=&n&&route&/span&&span class=&p&&(&/span&&span class=&s1&&'/image'&/span&&span class=&p&&,&/span& &span class=&n&&methods&/span&&span class=&o&&=&/span&&span class=&p&&[&/span&&span class=&s1&&'POST'&/span&&span class=&p&&])&/span&
&span class=&k&&def&/span& &span class=&nf&&image&/span&&span class=&p&&():&/span&
&span class=&k&&try&/span&&span class=&p&&:&/span&
&span class=&n&&image_file&/span& &span class=&o&&=&/span& &span class=&n&&request&/span&&span class=&o&&.&/span&&span class=&n&&files&/span&&span class=&p&&[&/span&&span class=&s1&&'image'&/span&&span class=&p&&]&/span&
&span class=&c1&&# get the image&/span&
&span class=&c1&&# Set an image confidence threshold value to limit returned data&/span&
&span class=&n&&threshold&/span& &span class=&o&&=&/span& &span class=&n&&request&/span&&span class=&o&&.&/span&&span class=&n&&form&/span&&span class=&o&&.&/span&&span class=&n&&get&/span&&span class=&p&&(&/span&&span class=&s1&&'threshold'&/span&&span class=&p&&)&/span&
&span class=&k&&if&/span& &span class=&n&&threshold&/span& &span class=&ow&&is&/span& &span class=&kc&&None&/span&&span class=&p&&:&/span&
&span class=&n&&threshold&/span& &span class=&o&&=&/span& &span class=&mf&&0.5&/span&
&span class=&k&&else&/span&&span class=&p&&:&/span&
&span class=&n&&threshold&/span& &span class=&o&&=&/span& &span class=&nb&&float&/span&&span class=&p&&(&/span&&span class=&n&&threshold&/span&&span class=&p&&)&/span&
&span class=&c1&&# finally run the image through tensor flow object detection`&/span&
&span class=&n&&image_object&/span& &span class=&o&&=&/span& &span class=&n&&Image&/span&&span class=&o&&.&/span&&span class=&n&&open&/span&&span class=&p&&(&/span&&span class=&n&&image_file&/span&&span class=&p&&)&/span&
&span class=&n&&objects&/span& &span class=&o&&=&/span& &span class=&n&&object_detection_api&/span&&span class=&o&&.&/span&&span class=&n&&get_objects&/span&&span class=&p&&(&/span&&span class=&n&&image_object&/span&&span class=&p&&,&/span& &span class=&n&&threshold&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&objects&/span&
&span class=&k&&except&/span& &span class=&ne&&Exception&/span& &span class=&k&&as&/span& &span class=&n&&e&/span&&span class=&p&&:&/span&
&span class=&nb&&print&/span&&span class=&p&&(&/span&&span class=&s1&&'POST /image error: &/span&&span class=&si&&%e&/span&&span class=&s1&&'&/span& &span class=&o&&%&/span& &span class=&n&&e&/span&&span class=&p&&)&/span&
&span class=&k&&return&/span& &span class=&n&&e&/span&
&/code&&/pre&&/div&&p&这样就会从一个采用表单编码方式的POST中获取图片,并且可以选择指定一个阈值,然后将该图片传递给我们的object_detection_api。&/p&&p&我们来测试一下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&curl -F &image=@./object_detection/test_images/image1.jpg& http://localhost:5000/image | python -m json.tool
&/code&&/pre&&/div&&p&这时看到的结果应该与上面使用/test路径时相同。继续测试,可以指定你任选的其他本地图像的路径。&/p&&p&&b&让该服务在localhost以外的位置也能正常工作&/b&&/p&&p&如果你打算在localhost上运行浏览器,可能就不需要再做些什么。但如果是真实的服务,甚至是在需要运行很多测试的情况下,这就不太现实了。如果要跨网络运行网络服务,或者使用其他资源运行网络服务,都需要用到CORS。幸好,在路由前添加以下代码就可以轻松解决这一问题:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&# for CORS
@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,POST') # Put any other methods you need here
return response
&/code&&/pre&&/div&&p&&b&让该服务支持安全源&/b&&/p&&p&最佳做法是搭配HTTPS使用WebRTC,因为Chrome和Safari等浏览器若不作专门配置,则仅支持安全源(不过Chrome可以很好地支持localhost,你也可以将Safari设为允许在非安全网站上捕获信息——跳转到此处的调试工具部分了解详情)。为此,你需要获取一些SSL证书或生成一些自托管证书。我将我自己的证书放在了ssl/目录中,然后将最后一行app.run更改为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
app.run(debug=True, host='0.0.0.0', ssl_context=('ssl/server.crt', 'ssl/server.key'))
&/code&&/pre&&/div&&p&如果你使用的是自签名证书,你在使用CURL进行测试时可能需要添加--insecure选项:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&curl -F &image=@./object_detection/test_images/image2.jpg& --insecure https://localhost:5000/image | python -m json.tool
&/code&&/pre&&/div&&p&严格来讲并非一定要生成你自己的证书,而且这会增加一定的工作量,所以在server.py最底部,我依然让SSL版本保持被注释掉的状态。&/p&&p&如果是要投入生产环境中使用的应用程序,你可能需要使用nginx之类的代理向外发送HTTPS,同时在内部依然使用HTTP(此外还要做很多其他方面的改进)。&/p&&p&&b&添加一些路由以便提供我们的网页&/b&&/p&&p&在开始介绍浏览器端的工作之前,我们先为后面需要用到的一些路由生成存根。为此,请将下面的代码放在index()路由后面:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@app.route('/local')
def local():
return Response(open('./static/local.html').read(), mimetype=&text/html&)
@app.route('/video')
def remote():
return Response(open('./static/video.html').read(), mimetype=&
&/code&&/pre&&/div&&p&Python方面的工作到此就结束了。接下来我们将用到JavaScript,并且需要编写一些HTML。&/p&&hr&&p&&i&我们将在下篇分享浏览器端的开发,以及优化方面经验。&/i&&/p&
原文作者:Chad Hart 原文地址: 摘要:本文作者介绍了结合WebRTC与TensorFlow实现图像检测的具体过程,不论对于TensorFlow的使用者,还是WebRTC的开发者来讲都有参考意义。由于文章较…
&figure&&img src=&https://pic3.zhimg.com/v2-68dbe8ed2d039e925fffa_b.jpg& data-rawwidth=&1471& data-rawheight=&1206& class=&origin_image zh-lightbox-thumb& width=&1471& data-original=&https://pic3.zhimg.com/v2-68dbe8ed2d039e925fffa_r.jpg&&&/figure&&p&&u&Faster R-CNN的极简实现&/u&: &a href=&http://link.zhihu.com/?target=https%3A//github.com/chenyuntc/simple-faster-rcnn-pytorch& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&github: simple-faster-rcnn-pytorch&/a&&/p&&p&&u&本文插图地址(含五幅高清矢量图):&/u&&a href=&http://link.zhihu.com/?target=https%3A//www.draw.io/%3Flightbox%3D1%26highlight%3D0000ff%26edit%3D_blank%26layers%3D1%26nav%3D1%26title%3Dfaster-rcnn%25E7%259A%%2589%25AF%25E6%259C%25AC%25E7%259A%%2589%25AF%25E6%259C%25AC.xml%23Uhttps%253A%252F%252Fraw.githubusercontent.com%252Fchenyuntc%252Fcloud%252Fmaster%252Ffaster-rcnn%59A%5E5%5AF%59C%2525AC%59A%5E5%5AF%59C%2525AC.xml& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&draw.io &/a&&/p&&h2&&b&1 概述&/b&&/h2&&p&在目标检测领域, Faster R-CNN表现出了极强的生命力,
虽然是2015年的&u&&a href=&http://link.zhihu.com/?target=https%3A//arxiv.org/abs/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&论文&/a&&/u&, 但它至今仍是许多目标检测算法的基础,这在日新月异的深度学习领域十分难得。Faster R-CNN还被应用到更多的领域中, 比如人体关键点检测、目标追踪、 实例分割还有图像描述等。&/p&&p&现在很多优秀的Faster R-CNN博客大都是针对论文讲解,本文将尝试从编程角度讲解Faster R-CNN的实现。由于Faster R-CNN流程复杂,符号较多,容易混淆,本文以VGG16为例,所有插图、数值皆是基于VGG16+VOC2007 。&/p&&h2&&b&1.1 目标&/b&&/h2&&p&从编程实现角度角度来讲, 以Faster R-CNN为代表的Object Detection任务,可以描述成:&/p&&p&给定一张图片, 找出图中的有哪些&b&对象&/b&,以及这些对象的&b&位置&/b&和置信&b&概率&/b&。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-79ee2d46ba80bb55b991_b.jpg& data-size=&normal& data-rawwidth=&801& data-rawheight=&317& class=&origin_image zh-lightbox-thumb& width=&801& data-original=&https://pic2.zhimg.com/v2-79ee2d46ba80bb55b991_r.jpg&&&figcaption&目标检测任务&/figcaption&&/figure&&h2&&b&1.2 整体架构&/b&&/h2&&p&Faster R-CNN的整体流程如下图所示。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-4e372ed28ebde2_b.jpg& data-size=&normal& data-rawwidth=&951& data-rawheight=&504& class=&origin_image zh-lightbox-thumb& width=&951& data-original=&https://pic3.zhimg.com/v2-4e372ed28ebde2_r.jpg&&&figcaption&Faster R-CNN整体架构&/figcaption&&/figure&&p&从编程角度来说, Faster R-CNN主要分为四部分(图中四个绿色框):&/p&&ul&&li&Dataset:数据,提供符合要求的数据格式(目前常用数据集是VOC和COCO)&/li&&li&Extractor: 利用CNN提取图片特征&code&features&/code&(原始论文用的是ZF和VGG16,后来人们又用ResNet101)&/li&&li&RPN(&i&Region Proposal Network): &/i& 负责提供候选区域&code&rois&/code&(每张图给出大概2000个候选框)&/li&&li&RoIHead: 负责对&code&rois&/code&分类和微调。对RPN找出的&code&rois&/code&,判断它是否包含目标,并修正框的位置和座标&/li&&/ul&&p&Faster R-CNN整体的流程可以分为三步:&/p&&ul&&li&提特征: 图片(&code&img&/code&)经过预训练的网络(&code&Extractor&/code&),提取到了图片的特征(&code&feature&/code&)&/li&&li&Region Proposal: 利用提取的特征(&code&feature&/code&),经过RPN网络,找出一定数量的&code&rois&/code&(region of interests)。&/li&&li&分类与回归:将&code&rois&/code&和图像特征&code&features&/code&,输入到&code&RoIHead&/code&,对这些&code&rois&/code&进行分类,判断都属于什么类别,同时对这些&code&rois&/code&的位置进行微调。&/li&&/ul&&h2&&b&2 详细实现&/b&&/h2&&h2&&b&2.1 数据&/b&&/h2&&p&对与每张图片,需要进行如下数据处理:&/p&&ul&&li&图片进行缩放,使得长边小于等于1000,短边小于等于600(至少有一个等于)。&/li&&li&对相应的bounding boxes 也也进行同等尺度的缩放。&/li&&li&对于Caffe 的VGG16 预训练模型,需要图片位于0-255,BGR格式,并减去一个均值,使得图片像素的均值为0。&/li&&/ul&&p&最后返回四个值供模型训练:&/p&&ul&&li&images : 3×H×W ,BGR三通道,宽W,高H&/li&&li&bboxes: 4×K ,
K个bounding boxes,每个bounding box的左上角和右下角的座标,形如(Y_min,X_min, Y_max,X_max),第Y行,第X列。&/li&&li&labels:K, 对应K个bounding boxes的label(对于VOC取值范围为[0-19])&/li&&li&scale: 缩放的倍数, 原图H' ×W'被resize到了HxW(scale=H/H' )&/li&&/ul&&p&需要注意的是,目前大多数Faster R-CNN实现都只支持batch-size=1的训练(&u&&a href=&https://zhuanlan.zhihu.com/github.com/jwyang/faster-rcnn.pytorch& class=&internal&&这个&/a&&/u& 和&u&&a href=&http://link.zhihu.com/?target=https%3A//github.com/precedenceguo/mx-rcnn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这个&/a&&/u&实现支持batch_size&1)。&/p&&h2&&b&2.2 Extractor&/b&&/h2&&p&Extractor使用的是预训练好的模型提取图片的特征。论文中主要使用的是Caffe的预训练模型VGG16。修改如下图所示:为了节省显存,前四层卷积层的学习率设为0。Conv5_3的输出作为图片特征(feature)。conv5_3相比于输入,下采样了16倍,也就是说输入的图片尺寸为3×H×W,那么&code&feature&/code&的尺寸就是C×(H/16)×(W/16)。VGG最后的三层全连接层的前两层,一般用来初始化RoIHead的部分参数,这个我们稍后再讲。总之,一张图片,经过extractor之后,会得到一个C×(H/16)×(W/16)的feature map。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-239eca20b6f_b.jpg& data-size=&normal& data-rawwidth=&296& data-rawheight=&544& class=&content_image& width=&296&&&figcaption&Extractor: VGG16&/figcaption&&/figure&&p&&br&&/p&&h2&&b&2.3 RPN&/b&&/h2&&p&Faster R-CNN最突出的贡献就在于提出了Region Proposal Network(RPN)代替了Selective Search,从而将候选区域提取的时间开销几乎降为0(2s -& 0.01s)。&/p&&p&&b&2.3.1 Anchor&/b&&/p&&p&在RPN中,作者提出了&code&anchor&/code&。Anchor是大小和尺寸固定的候选框。论文中用到的anchor有三种尺寸和三种比例,如下图所示,三种尺寸分别是小(蓝128)中(红256)大(绿512),三个比例分别是1:1,1:2,2:1。3×3的组合总共有9种anchor。&/p&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-7abead97efcc46a3ee5b030a2151643f_b.jpg& data-size=&normal& data-rawwidth=&253& data-rawheight=&245& class=&content_image& width=&253&&&figcaption&Anchor&/figcaption&&/figure&&p&&br&&/p&&p&然后用这9种anchor在特征图(&code&feature&/code&)左右上下移动,每一个特征图上的点都有9个anchor,最终生成了 (H/16)× (W/16)×9个&code&anchor&/code&. 对于一个512×62×37的feature map,有 62×37×9~ 20000个anchor。 也就是对一张图片,有20000个左右的anchor。这种做法很像是暴力穷举,20000多个anchor,哪怕是蒙也能够把绝大多数的ground truth bounding boxes蒙中。&/p&&p&&b&2.3.2 训练RPN&/b&&/p&&p&RPN的总体架构如下图所示:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-e7eeb94a86ece2dadfa9db_b.jpg& data-size=&small& data-rawwidth=&650& data-rawheight=&478& class=&origin_image zh-lightbox-thumb& width=&650& data-original=&https://pic3.zhimg.com/v2-e7eeb94a86ece2dadfa9db_r.jpg&&&figcaption&RPN架构&/figcaption&&/figure&&p&anchor的数量和feature map相关,不同的feature map对应的anchor数量也不一样。RPN在&code&Extractor&/code&输出的feature maps的基础之上,先增加了一个卷积(用来语义空间转换?),然后利用两个1x1的卷积分别进行二分类(是否为正样本)和位置回归。进行分类的卷积核通道数为9×2(9个anchor,每个anchor二分类,使用交叉熵损失),进行回归的卷积核通道数为9×4(9个anchor,每个anchor有4个位置参数)。RPN是一个全卷积网络(fully convolutional network),这样对输入图片的尺寸就没有要求了。&/p&&p&接下来RPN做的事情就是利用(&code&AnchorTargetCreator&/code&)将20000多个候选的anchor选出256个anchor进行分类和回归位置。选择过程如下:&/p&&ul&&li&对于每一个ground truth bounding box (&code&gt_bbox&/code&),选择和它重叠度(IoU)最高的一个anchor作为正样本&/li&&li&对于剩下的anchor,从中选择和任意一个&code&gt_bbox&/code&重叠度超过0.7的anchor,作为正样本,正样本的数目不超过128个。&/li&&li&随机选择和&code&gt_bbox&/code&重叠度小于0.3的anchor作为负样本。负样本和正样本的总数为256。&/li&&/ul&&p&对于每个anchor, gt_label 要么为1(前景),要么为0(背景),而gt_loc则是由4个位置参数(tx,ty,tw,th)组成,这样比直接回归座标更好。&/p&&p&&img src=&http://www.zhihu.com/equation?tex=t_x+%3D+%28x+%E2%88%92+x_a%29%2Fw_a%3B+t_y+%3D+%28y+%E2%88%92+y_a%29%2Fh_a%3B%5C%5C+t_w+%3D+log%28w%2Fw_a%29%3B+t_h+%3D+log%28h%2Fh_a%29%3B%5C%5C+t_x%5E%2A+%3D+%28x%5E%2A+%E2%88%92+x_a%29%2Fw_a%3B+t_y%5E%2A+%3D+%28y%5E%2A+%E2%88%92+y_a%29%2Fh_a%3B%5C%5C+t_w%5E%2A+%3D+log%28w%5E%2A%2Fw_a%29%3B+t_h%5E%2A+%3D+log%28h%5E%2A%2Fh_a%29%3B%5C%5C& alt=&t_x = (x - x_a)/w_a; t_y = (y - y_a)/h_a;\\ t_w = log(w/w_a); t_h = log(h/h_a);\\ t_x^* = (x^* - x_a)/w_a; t_y^* = (y^* - y_a)/h_a;\\ t_w^* = log(w^*/w_a); t_h^* = log(h^*/h_a);\\& eeimg=&1&&&/p&&p&计算分类损失用的是交叉熵损失,而计算回归损失用的是Smooth_l1_loss. 在计算回归损失的时候,只计算正样本(前景)的损失,不计算负样本的位置损失。&/p&&p&&b&2.3.3 RPN生成RoIs&/b&&/p&&p&RPN在自身训练的同时,还会提供RoIs(region of interests)给Fast RCNN(RoIHead)作为训练样本。RPN生成RoIs的过程(&code&ProposalCreator&/code&)如下:&/p&&ul&&li&对于每张图片,利用它的feature map, 计算 (H/16)× (W/16)×9(大概20000)个anchor属于前景的概率,以及对应的位置参数。&/li&&li&选取概率较大的12000个anchor&/li&&li&利用回归的位置参数,修正这12000个anchor的位置,得到RoIs&/li&&li&利用非极大值((Non-maximum suppression, NMS)抑制,选出概率最大的2000个RoIs&/li&&/ul&&p&注意:在inference的时候,为了提高处理速度,1分别变为.&/p&&p&注意:这部分的操作不需要进行反向传播,因此可以利用numpy/tensor实现。&/p&&p&RPN的输出:RoIs(形如2000×4或者300×4的tensor)&/p&&h2&&b&2.4 RoIHead/Fast R-CNN&/b&&/h2&&p&RPN只是给出了2000个候选框,RoI Head在给出的2000候选框之上继续进行分类和位置参数的回归。&/p&&p&&b&2.4.1 网络结构&/b&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-5b0d1ca6e990fcdecd22_b.jpg& data-size=&normal& data-rawwidth=&508& data-rawheight=&764& class=&origin_image zh-lightbox-thumb& width=&508& data-original=&https://pic3.zhimg.com/v2-5b0d1ca6e990fcdecd22_r.jpg&&&figcaption&RoIHead网络结构&/figcaption&&/figure&&p&&br&&/p&&p&由于RoIs给出的2000个候选框,分别对应feature map不同大小的区域。首先利用&code&ProposalTargetCreator&/code& 挑选出128个sample_rois, 然后使用了RoIPooling 将这些不同尺寸的区域全部pooling到同一个尺度(7×7)上。下图就是一个例子,对于feature map上两个不同尺度的RoI,经过RoIPooling之后,最后得到了3×3的feature map.&/p&&figure&&img src=&https://pic4.zhimg.com/v2-d9eb14da175f7ae2ed6b6d77f8993207_b.jpg& data-size=&small& data-rawwidth=&862& data-rawheight=&260& class=&origin_image zh-lightbox-thumb& width=&862& data-original=&https://pic4.zhimg.com/v2-d9eb14da175f7ae2ed6b6d77f8993207_r.jpg&&&figcaption&RoIPooling&/figcaption&&/figure&&p&RoI Pooling 是一种特殊的Pooling操作,给定一张图片的Feature map (512×H/16×W/16) ,和128个候选区域的座标(128×4),RoI Pooling将这些区域统一下采样到 (512×7×7),就得到了128×512×7×7的向量。可以看成是一个batch-size=128,通道数为512,7×7的feature map。&/p&&p&为什么要pooling成7×7的尺度?是为了能够共享权重。在之前讲过,除了用到VGG前几层的卷积之外,最后的全连接层也可以继续利用。当所有的RoIs都被pooling成(512×7×7)的feature map后,将它reshape 成一个一维的向量,就可以利用VGG16预训练的权重,初始化前两层全连接。最后再接两个全连接层,分别是:&/p&&ul&&li&FC 21 用来分类,预测RoIs属于哪个类别(20个类+背景)&/li&&li&FC 84 用来回归位置(21个类,每个类都有4个位置参数) &/li&&/ul&&p&&b&2.4.2 训练&/b&&/p&&p&前面讲过,RPN会产生大约2000个RoIs,这2000个RoIs不是都拿去训练,而是利用&code&ProposalTargetCreator&/code& 选择128个RoIs用以训练。选择的规则如下:&/p&&ul&&li&RoIs和gt_bboxes 的IoU大于0.5的,选择一些(比如32个)&/li&&li&选择 RoIs和gt_bboxes的IoU小于等于0(或者0.1)的选择一些(比如 128-32=96个)作为负样本&/li&&/ul&&p&为了便于训练,对选择出的128个RoIs,还对他们的&code&gt_roi_loc&/code& 进行标准化处理(减去均值除以标准差)&/p&&p&对于分类问题,直接利用交叉熵损失. 而对于位置的回归损失,一样采用Smooth_L1Loss, 只不过只对正样本计算损失.而且是只对正样本中的这个类别4个参数计算损失。举例来说:&/p&&ul&&li&一个RoI在经过FC 84后会输出一个84维的loc 向量.
如果这个RoI是负样本,则这84维向量不参与计算 L1_Loss&/li&&li&如果这个RoI是正样本,属于label K,那么它的第 K×4, K×4+1 ,K×4+2, K×4+3 这4个数参与计算损失,其余的不参与计算损失。&/li&&/ul&&p&&b&2.4.3 生成预测结果&/b&&/p&&p&测试的时候对所有的RoIs(大概300个左右) 计算概率,并利用位置参数调整预测候选框的位置。然后再用一遍极大值抑制(之前在RPN的&code&ProposalCreator&/code&用过)。&/p&&p&注意:&/p&&ul&&li&在RPN的时候,已经对anchor做了一遍NMS,在RCNN测试的时候,还要再做一遍&/li&&li&在RPN的时候,已经对anchor的位置做了回归调整,在RCNN阶段还要对RoI再做一遍&/li&&li&在RPN阶段分类是二分类,而Fast RCNN阶段是21分类&/li&&/ul&&h2&&b&2.5 模型架构图&/b&&/h2&&p&最后整体的模型架构图如下:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-7c388efe2f93b79df0f6_b.jpg& data-size=&small& data-rawwidth=&1000& data-rawheight=&2483& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&https://pic3.zhimg.com/v2-7c388efe2f93b79df0f6_r.jpg&&&figcaption&整体网络结构&/figcaption&&/figure&&p&需要注意的是: 蓝色箭头的线代表着计算图,梯度反向传播会经过。而红色部分的线不需要进行反向传播(论文了中提到了&code&ProposalCreator&/code&生成RoIs的过程也能进行反向传播,但需要专门的&u&&a href=&http://link.zhihu.com/?target=https%3A//arxiv.org/abs/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&算法&/a&&/u&)。&/p&&h2&&b&3 概念对比&/b&&/h2&&p&在Faster RCNN中有几个概念,容易混淆,或者具有较强的相似性。在此我列出来并做对比,希望对你理解有帮助。&/p&&h2&&b&3.1 bbox anchor RoI
loc&/b&&/h2&&p&&b&BBox&/b&:全称是bounding box,边界框。其中Ground Truth Bounding Box是每一张图中人工标注的框的位置。一张图中有几个目标,就有几个框(一般小于10个框)。Faster R-CNN的预测结果也可以叫bounding box,不过一般叫 Predict Bounding Box.&/p&&p&&b&Anchor&/b&:锚?是人为选定的具有一定尺度、比例的框。一个feature map的锚的数目有上万个(比如 20000)。&/p&&p&&b&RoI&/b&:region of interest,候选框。Faster R-CNN之前传统的做法是利用selective search从一张图上大概2000个候选框框。现在利用RPN可以从上万的anchor中找出一定数目更有可能的候选框。在训练RCNN的时候,这个数目是2000,在测试推理阶段,这个数目是300(为了速度)&i&我个人实验发现RPN生成更多的RoI能得到更高的mAP。&/i&&/p&&p&RoI不是单纯的从anchor中选取一些出来作为候选框,它还会利用回归位置参数,微调anchor的形状和位置。&/p&&p&可以这么理解:在RPN阶段,先穷举生成千上万个anchor,然后利用Ground Truth Bounding Boxes,训练这些anchor,而后从anchor中找出一定数目的候选区域(RoIs)。RoIs在下一阶段用来训练RoIHead,最后生成Predict Bounding Boxes。&/p&&p&loc: bbox,anchor和RoI,本质上都是一个框,可以用四个数(y_min, x_min, y_max, x_max)表示框的位置,即左上角的座标和右下角的座标。这里之所以先写y,再写x是为了数组索引方便,但也需要千万注意不要弄混了。 我在实现的时候,没注意,导致输入到RoIPooling的座标不对,浪费了好长时间。除了用这四个数表示一个座标之外,还可以用(y,x,h,w)表示,即框的中心座标和长宽。在训练中进行位置回归的时候,用的是后一种的表示。&/p&&h2&&b&3.2 四类损失&/b&&/h2&&p&虽然原始论文中用的&code&4-Step Alternating Training&/code& 即四步交替迭代训练。然而现在github上开源的实现大多是采用&i&近似&/i&联合训练(&code&Approximate joint training&/code&),端到端,一步到位,速度更快。&/p&&p&在训练Faster RCNN的时候有四个损失:&/p&&ul&&li&RPN 分类损失:anchor是否为前景(二分类)&/li&&li&RPN位置回归损失:anchor位置微调&/li&&li&RoI 分类损失:RoI所属类别(21分类,多了一个类作为背景)&/li&&li&RoI位置回归损失:继续对RoI位置微调&/li&&/ul&&p&四个损失相加作为最后的损失,反向传播,更新参数。&/p&&h2&&b&3.3 三个creator&/b&&/h2&&p&在一开始阅读源码的时候,我常常把Faster RCNN中用到的三个&code&Creator&/code&弄混。&/p&&ul&&li&&code&AnchorTargetCreator&/code& : 负责在训练RPN的时候,从上万个anchor中选择一些(比如256)进行训练,以使得正负样本比例大概是1:1. 同时给出训练的位置参数目标。 即返回&code&gt_rpn_loc&/code&和&code&gt_rpn_label&/code&。 &/li&&li&&code&ProposalTargetCreator&/code&: 负责在训练RoIHead/Fast R-CNN的时候,从RoIs选择一部分(比如128个)用以训练。同时给定训练目标, 返回(&code&sample_RoI&/code&, &code&gt_RoI_loc&/code&, &code&gt_RoI_label&/code&)&/li&&li&&code&ProposalCreator&/code&: 在RPN中,从上万个anchor中,选择一定数目(2000或者300),调整大小和位置,生成RoIs,用以Fast R-CNN训练或者测试。&/li&&/ul&&p&其中&code&AnchorTargetCreator&/code&和&code&ProposalTargetCreator&/code&是为了生成训练的目标,只在训练阶段用到,&code&ProposalCreator&/code&是RPN为Fast R-CNN生成RoIs,在训练和测试阶段都会用到。三个共同点在于他们都不需要考虑反向传播(因此不同框架间可以共享numpy实现)&/p&&h2&&b&3.4 感受野与scale&/b&&/h2&&p&从直观上讲,&b&感受野&/b&(&i&receptive field&/i&)就是视觉感受区域的大小。在卷积神经网络中,感受野的定义是卷积神经网络每一层输出的特征图(feature map)上的像素点在原始图像上映射的区域大小。我的理解是,feature map上的某一点&code&f&/code&对应输入图片中的一个区域,这个区域中的点发生变化,&code&f&/code&可能随之变化。而这个区域外的其它点不论如何改变,&code&f&/code&的值都不会受之影响。VGG16的conv5_3的感受野为228,即feature map上每一个点,都包含了原图一个228×228区域的信息。&/p&&p&&b&Scale&/b&:输入图片的尺寸比上feature map的尺寸。比如输入图片是3×224×224,feature map 是 512×14×14,那么scale就是 14/224=1/16。可以认为feature map中一个点对应输入图片的16个像素。由于相邻的同尺寸、同比例的anchor是在feature map上的距离是一个点,对应到输入图片中就是16个像素。在一定程度上可以认为&b&anchor的精度为16个像素&/b&。不过还需要考虑原图相比于输入图片又做过缩放(这也是dataset返回的&code&scale&/code&参数的作用,这个的&code&scale&/code&指的是原图和输入图片的缩放尺度,和上面的scale不一样)。&/p&&h2&&b&4 实现方案&/b&&/h2&&p&其实上半年好几次都要用到Faster R-CNN,但是每回看到各种上万行,几万行代码,简直无从下手。而且直到 &a class=&member_mention& href=&http://www.zhihu.com/people/bbc1cc9eda99b0214ede09c620e76109& data-hash=&bbc1cc9eda99b0214ede09c620e76109& data-hovercard=&p$b$bbc1cc9eda99b0214ede09c620e76109&&@罗若天&/a&大神的&a href=&http://link.zhihu.com/?target=https%3A//github.com/ruotianluo/pytorch-faster-rcnn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ruotianluo/pytorch-faster-rcnn&/a& 之前,PyTorch的Faster R-CNN并未有合格的实现(速度和精度)。最早PyTorch实现的Faster R-CNN有&a href=&http://link.zhihu.com/?target=https%3A//github.com/longcw/faster_rcnn_pytorch& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&longcw/faster_rcnn_pytorch&/a& 和 &a href=&http://link.zhihu.com/?target=https%3A//github.com/pytorch/examples/tree/d8d378c31d0ac03f41dd837a56c2a/fast_rcnn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&fmassa/fast_rcnn&/a& 后者是当之无愧的最简实现(1,245行代码,包括空行注释,纯Python实现),然而速度太慢,效果较差,fmassa最后也放弃了这个项目。前者又太过复杂,mAP也比论文中差一点(0.661VS 0.699)。当前github上的大多数实现都是基于&code&py-faster-rcnn&/code&,RBG大神的代码很健壮,考虑的很全面,支持很丰富,基本上git clone下来,准备一下数据模型就能直接跑起来。然而对我来说太过复杂,我的脑细胞比较少,上百个文件,动不动就好几层的嵌套封装,很容易令人头大。&/p&&p&趁着最近时间充裕了一些,我决定从头撸一个,刚开始写没多久,就发现&a href=&http://link.zhihu.com/?target=https%3A//github.com/chainer/chainercv& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&chainercv&/a&内置了Faster R-CNN的实现,而且Faster R-CNN中用到的许多函数(比如对bbox的各种操作计算),chainercv都提供了内置支持(其实py-faster-rcnn也有封装好的函数,但是chainercv的文档写的太详细了!)。所以大多数函数都是直接copy&paste,把chainer的代码改成pytorch/numpy,增加了一些可视化代码等。不过cupy的内容并没有改成THTensor。因为cupy现在已经是一个独立的包,感觉比cffi好用(虽然我并不会C....)。&/p&&p&&br&&/p&&p&最终写了一个简单版本的Faster R-CNN,代码地址在 &a href=&http://link.zhihu.com/?target=https%3A//github.com/chenyuntc/simple-faster-rcnn-pytorch& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&github:simple-faster-rcnn-pytorch&/a&&/p&&p&这个实现主要有以下几个特点:&/p&&ul&&li&代码简单:除去空行,注释,说明等,大概有2000行左右代码,如果想学习如何实现Faster R-CNN,这是个不错的参考。&/li&&li&效果够好:超过论文中的指标(论文mAP是69.9, 本程序利用caffe版本VGG16最低能达到0.70,最高能达到0.712,预训练的模型在github中提供链接可以下载)&/li&&li&速度足够快:TITAN Xp上最快只要3小时左右(关闭验证与可视化)就能完成训练&/li&&li&显存占用较小:3G左右的显存占用&/li&&/ul&&h2&^_^&/h2&&p&这个项目其实写代码没花太多时间,大多数时间花在调试上。有报错的bug都很容易解决,最怕的是逻辑bug,只能一句句检查,或者在ipdb中一步一步的执行,看输出是否和预期一样,还不一定找得出来。不过通过一步步执行,感觉对Faster R-CNN的细节理解也更深了。&/p&&p&写完这个代码,也算是基本掌握了Faster R-CNN。在写代码中踩了许多坑,也学到了很多,其中几个收获/教训是:&/p&&ul&&li&在复现别人的代码的时候,不要自作聪明做什么“改进”,先严格的按照论文或者官方代码实现(比如把SGD优化器换成Adam,基本训不动,后来调了一下发现要把学习率降10倍,但是效果依旧远不如SGD)。&/li&&li&不要偷懒,尽可能的“Match Everything”。由于torchvision中有预训练好的VGG16,而caffe预训练VGG要求输入图片像素在0-255之间(torchvision是0-1),BGR格式的,标准化只减均值,不除以标准差,看起来有点别扭(总之就是要多写几十行代码+专门下载模型)。然后我就用torchvision的预训练模型初始化,最后用了一大堆的trick,各种手动调参,才把mAP调到0.7(正常跑,不调参的话大概在0.692附近)。某天晚上抱着试试的心态,睡前把VGG的模型改成caffe的,第二天早上起来一看轻轻松松0.705 ...&/li&&li&有个小trick:把别人用其它框架训练好的模型权重转换成自己框架的,然后计算在验证集的分数,如果分数相差无几,那么说明,相关的代码没有bug,就不用花太多时间检查这部分代码了。&/li&&li&认真。那几天常常一连几个小时盯着屏幕,眼睛疼,很多单词敲错了没发现,有些报错了很容易发现,但是有些就。。。 比如计算分数的代码就写错了一个单词。然后我自己看模型的泛化效果不错,但就是分数特别低,我还把模型训练部分的代码又过了好几遍。。。&/li&&li&纸上得来终觉浅, 绝知此事要coding。&/li&&li&当初要是再仔细读一读 &a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&最近一点微小的工作&/a&和&a href=&http://link.zhihu.com/?target=https%3A//github.com/ruotianluo/pytorch-faster-rcnn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ruotianluo/pytorch-faster-rcnn&/a& 的readme,能少踩不少坑。&/li&&/ul&&p&P.S. 在github上搜索faster rcnn,感觉有一半以上都是华人写的。&/p&&p&最后,求Star &a href=&http://link.zhihu.com/?target=https%3A//github.com/chenyuntc/simple-faster-rcnn-pytorch& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&github: simple-faster-rcnn-pytorch&/a&&/p&
Faster R-CNN的极简实现: 本文插图地址(含五幅高清矢量图):1 概述在目标检测领域, Faster R-CNN表现出了极强的生命力, 虽然是2015年的, 但它至今仍是许多目标检测算法的基础,这在日新月异的深度学习领…
&figure&&img src=&https://pic1.zhimg.com/v2-490db97a0f3fa98eb2f44e_b.jpg& data-rawwidth=&464& data-rawheight=&326& class=&origin_image zh-lightbox-thumb& width=&464& data-original=&https://pic1.zhimg.com/v2-490db97a0f3fa98eb2f44e_r.jpg&&&/figure&&p&&b&欢迎交流与转载,文章会同步发布在公众号:机器学习算法全栈工程师(Jeemy110)&/b&&/p&&p&&b&本文翻译自&/b&&a href=&https://link.zhihu.com/?target=http%3A//cv-tricks.com/cnn/understand-resnet-alexnet-vgg-inception/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ResNet, AlexNet, VGG, Inception: Understanding various architectures of Convolutional Networks&/a&,&b&原作者保留版权&/b&&/p&&p&卷积神经网络在视觉识别任务上的表现令人称奇。好的CNN网络是带有上百万参数和许多隐含层的“庞然怪物”。事实上,一个不好的经验规则是:网络越深,效果越好。AlexNet,VGG,Inception和ResNet是最近一些流行的CNN网络。为什么这些网络表现如此之好?它们是如何设计出来的?为什么它们设计成那样的结构?回答这些问题并不简单,但是这里我们试着去探讨上面的一些问题。网络结构设计是一个复杂的过程,需要花点时间去学习,甚至更长时间去自己动手实验。首先,我们先来讨论一个基本问题:&/p&&h2&&b&为什么CNN模型战胜了传统的计算机视觉方法?&/b&&/h2&&figure&&img src=&https://pic2.zhimg.com/v2-a25ec197f6d23c29a72db3cb_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&384& data-rawheight=&288& class=&content_image& width=&384&&&/figure&&p&图像分类指的是给定一个图片将其分类成预先定义好的几个类别之一。图像分类的传统流程涉及两个模块:&b&特征提取&/b&和&b&分类&/b&。&/p&&p&&b&特征提取&/b&指的是从原始像素点中提取更高级的特征,这些特征能捕捉到各个类别间的区别。这种特征提取是使用无监督方式,从像素点中提取信息时没有用到图像的类别标签。常用的传统特征包括GIST, HOG, SIFT, LBP等。特征提取之后,使用图像的这些特征与其对应的类别标签训练一个分类模型。常用的分类模型有SVM,LR,随机森林及决策树等。&/p&&p&&b&上面流程的一大问题&/b&是特征提取不能根据图像和其标签进行调整。如果选择的特征缺乏一定的代表性来区分各个类别,模型的准确性就大打折扣,无论你采用什么样的分类策略。采用传统的流程,目前的一个比较好的方法是使用多种特征提取器,然后组合它们得到一种更好的特征。但是这需要很多启发式规则和人力来根据领域不同来调整参数使得达到一个很好的准确度,这里说的是要接近人类水平。这也就是为什么采用传统的计算机视觉技术需要花费多年时间才能打造一个好的计算机视觉系统(如OCR,人脸验证,图像识别,物体检测等),这些系统在实际应用中可以处理各种各样的数据。有一次,我们用了6周时间为一家公司打造了一个CNN模型,其效果更好,采用传统的计算机视觉技术要达到这样的效果要花费一年时间。&/p&&p&&b&传统流程的另外一个问题&/b&是它与人类学习识别物体的过程是完全不一样的。自从出生之初,一个孩子就可以感知周围环境,随着他的成长,他接触更多的数据,从而学会了识别物体。这是深度学习背后的哲学,其中并没有建立硬编码的特征提取器。它将特征提取和分类两个模块集成一个系统,通过识别图像的特征来进行提取并基于有标签数据进行分类。&/p&&p&这样的集成系统就是多层感知机,即有多层神经元密集连接而成的神经网络。一个经典的深度网络包含很多参数,由于缺乏足够的训练样本,基本不可能训练出一个不过拟合的模型。但是对于CNN模型,从头开始训练一个网络时你可以使用一个很大的数据集如ImageNet。这背后的原因是CNN模型的两个特点:神经元间的权重共享和卷积层之间的稀疏连接。这可以从下图中看到。在卷积层,某一个层的神经元只是和输入层中的神经元局部连接,而且卷积核的参数是在整个2-D特征图上是共享的。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-60ca0505bed29b6b7a28eb66d51715c1_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&671& data-rawheight=&251& class=&origin_image zh-lightbox-thumb& width=&671& data-original=&https://pic1.zhimg.com/v2-60ca0505bed29b6b7a28eb66d51715c1_r.jpg&&&/figure&&p&&br&&/p&&p&为了理解CNN背后的设计哲学,你可能会问:其目标是什么?&/p&&p&&b&(1)准确度&/b&&/p&&p&如果你在搭建一个智能系统,最重要的当然是要尽可能地准确。公平地来说,准确度不仅取决于网路,也取决于训练样本数量。因此,CNN模型一般在一个标准数据集ImageNet上做对比。&/p&&p&ImageNet项目仍然在继续改进,目前已经有包含21841类的14,197,122个图片。自从2010年,每年都会举行ImageNet图像识别竞赛,比赛会提供从ImageNet数据集中抽取的属于1000类的120万张图片。每个网络架构都是在这120万张图片上测试其在1000类上的准确度。&/p&&p&&b&(2)计算量&/b&&/p&&p&大部分的CNN模型都需要很大的内存和计算量,特别是在训练过程。因此,计算量会成为一个重要的关注点。同样地,如果你想部署在移动端,训练得到的最终模型大小也需要特别考虑。你可以想象到,为了得到更好的准确度你需要一个计算更密集的网络。因此,准确度和计算量需要折中考虑。&/p&&p&除了上面两个因素,还有其他需要考虑的因素,如训练的容易度,模型的泛化能力等。下面按照提出时间介绍一些最流行的CNN架构,可以看到它们准确度越来越高。&/p&&h2&AlexNet&/h2&&p&&a href=&https://link.zhihu.com/?target=http%3A//papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AlexNet&/a&是一个较早应用在ImageNet上的深度网络,其准确度相比传统方法有一个很大的提升。它首先是5个卷积层,然后紧跟着是3个全连接层,如下图所示:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ecea42d07f15fb3ae87100_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&686& data-rawheight=&211& class=&origin_image zh-lightbox-thumb& width=&686& data-original=&https://pic1.zhimg.com/v2-ecea42d07f15fb3ae87100_r.jpg&&&/figure&&p&Alex Krizhevs提出的AlexNet采用了ReLU激活函数,而不像传统神经网络早期所采用的Tanh或Sigmoid激活函数,ReLU数学表达为:&/p&&p&&img src=&https://www.zhihu.com/equation?tex=f%28x%29%3Dmax%280%2Cx%29& alt=&f(x)=max(0,x)& eeimg=&1&&&/p&&p&ReLU相比Sigmoid的优势是其训练速度更快,因为Sigmoid的导数在稳定区会非常小,从而权重基本上不再更新。这就是梯度消失问题。因此AlexNet在卷积层和全连接层后面都使用了ReLU。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-345af658efe0ec7cbdc536a_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&320& data-rawheight=&213& class=&content_image& width=&320&&&/figure&&p&AlexNet的另外一个特点是其通过在每个全连接层后面加上Dropout层减少了模型的过拟合问题。Dropout层以一定的概率随机地关闭当前层中神经元激活值,如下图所示:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-3097a52dcebcb93fb90b_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&614& data-rawheight=&328& class=&origin_image zh-lightbox-thumb& width=&614& data-original=&https://pic2.zhimg.com/v2-3097a52dcebcb93fb90b_r.jpg&&&/figure&&p&&b&为什么Dropout有效?&/b&&/p&&p&Dropout背后理念和集成模型很相似。在Drpout层,不同的神经元组合被关闭,这代表了一种不同的结构,所有这些不同的结构使用一个的子数据集并行地带权重训练,而权重总和为1。如果Dropout层有 &img src=&https://www.zhihu.com/equation?tex=n& alt=&n& eeimg=&1&& 个神经元,那么会形成 &img src=&https://www.zhihu.com/equation?tex=2%5E%7Bn%7D& alt=&2^{n}& eeimg=&1&& 个不同的子结构。在预测时,相当于集成这些模型并取均值。这种结构化的模型正则化技术有利于避免过拟合。Dropout有效的另外一个视点是:由于神经元是随机选择的,所以可以减少神经元之间的相互依赖,从而确保提取出相互独立的重要特征。&/p&&h2&VGG16&/h2&&p&&a href=&https://link.zhihu.com/?target=https%3A//arxiv.org/abs/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&VGG16&/a&是牛津大学VGG组提出的。VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,5x5)。对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。&/p&&p&比如,3个步长为1的3x3卷积核连续作用在一个大小为7的感受野,其参数总量为 &img src=&https://www.zhihu.com/equation?tex=3%2A%289C%5E2%29& alt=&3*(9C^2)& eeimg=&1&& ,如果直接使用7x7卷积核,其参数总量为 &img src=&https://www.zhihu.com/equation?tex=49C%5E2& alt=&49C^2& eeimg=&1&& ,这里 &img src=&https://www.zhihu.com/equation?tex=C& alt=&C& eeimg=&1&& 指的是输入和输出的通道数。而且3x3卷积核有利于更好地保持图像性质。VGG网络的架构如下表所示:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-e47b44f1c0cfd111c270f1a73d9d4d4d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&520& data-rawheight=&525& class=&origin_image zh-lightbox-thumb& width=&520& data-original=&https://pic4.zhimg.com/v2-e47b44f1c0cfd111c270f1a73d9d4d4d_r.jpg&&&/figure&&p&可以看到VGG-D,其使用了一种块结构:多次重复使用同一大小的卷积核来提取更复杂和更具有表达性的特征。这种块结构( blocks/modules)在VGG之后被广泛采用。&/p&&p&VGG卷积层之后是3个全连接层。网络的通道数从较小的64开始,然后每经过一个下采样或者池化层成倍地增加,当然特征图大小成倍地减小。最终其在ImageNet上的Top-5准确度为92.3%。&/p&&h2&GoogLeNet/Inception&/h2&&p&尽管VGG可以在ImageNet上表现很好,但是将其部署在一个适度大小的GPU上是困难的,因为需要VGG在内存和时间上的计算要求很高。由于卷积层的通道数过大,VGG并不高效。比如,一个3x3的卷积核,如果其输入和输出的通道数均为512,那么需要的计算量为9x512x512。&/p&&p&在卷积操作中,输出特征图上某一个位置,其是与所有的输入特征图是相连的,这是一种密集连接结构。&a href=&https://link.zhihu.com/?target=https%3A//arxiv.org/pdf/.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GoogLeNet&/a&基于这样的理念:在深度网路中大部分的激活值是不必要的(为0),或者由于相关性是冗余。因此,最高效的深度网路架构应该是激活值之间是稀疏连接的,这意味着512个输出特征图是没有必要与所有的512输入特征图相连。存在一些技术可以对网络进行剪枝来得到稀疏权重或者连接。但是稀疏卷积核的乘法在BLAS和CuBlas中并没有优化,这反而造成稀疏连接结构比密集结构更慢。&/p&&p&据此,GoogLeNet设计了一种称为inception的模块,这个模块使用密集结构来近似一个稀疏的CNN,如下图所示。前面说过,只有很少一部分神经元是真正有效的,所以一种特定大小的卷积核数量设置得非常小。同时,GoogLeNet使用了不同大小的卷积核来抓取不同大小的感受野。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-bd290ae5_b.jpg&}

我要回帖

更多关于 hbzjk.86ztb.com 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信