了解Tensorflow对象检测API的改进版本

了解Tensorflow对象检测API的改进版本,tensorflow,object-detection,Tensorflow,Object Detection,我在项目中使用Tensorflow对象检测API,遇到以下链接: 代码附加在此链接上的zip文件中。我不明白的具体部分是: input_graph = tf.Graph() with tf.Session(graph=input_graph): score = tf.placeholder(tf.float32, shape=(None, 1917, 90), name="Postprocessor/convert_scores") expand = tf.placeholder

我在项目中使用Tensorflow对象检测API,遇到以下链接: 代码附加在此链接上的zip文件中。我不明白的具体部分是:

input_graph = tf.Graph()
with tf.Session(graph=input_graph):
    score = tf.placeholder(tf.float32, shape=(None, 1917, 90), name="Postprocessor/convert_scores")
    expand = tf.placeholder(tf.float32, shape=(None, 1917, 1, 4), name="Postprocessor/ExpandDims_1")
    for node in input_graph.as_graph_def().node:
        if node.name == "Postprocessor/convert_scores":
            score_def = node
        if node.name == "Postprocessor/ExpandDims_1":
            expand_def = node

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)
        dest_nodes = ['Postprocessor/convert_scores','Postprocessor/ExpandDims_1']

        edges = {}
        name_to_node_map = {}
        node_seq = {}
        seq = 0
        for node in od_graph_def.node:
            n = _node_name(node.name)
            name_to_node_map[n] = node
            edges[n] = [_node_name(x) for x in node.input]
            node_seq[n] = seq
            seq += 1

        for d in dest_nodes:
            assert d in name_to_node_map, "%s is not in graph" % d

        nodes_to_keep = set()
        next_to_visit = dest_nodes[:]
        while next_to_visit:
            n = next_to_visit[0]
            del next_to_visit[0]
            if n in nodes_to_keep:
                continue
            nodes_to_keep.add(n)
            next_to_visit += edges[n]

        nodes_to_keep_list = sorted(list(nodes_to_keep), key=lambda n: node_seq[n])

        nodes_to_remove = set()
        for n in node_seq:
            if n in nodes_to_keep_list: 
                continue
            nodes_to_remove.add(n)
        nodes_to_remove_list = sorted(list(nodes_to_remove), key=lambda n: node_seq[n])

        keep = graph_pb2.GraphDef()
        for n in nodes_to_keep_list:
            keep.node.extend([copy.deepcopy(name_to_node_map[n])])

        remove = graph_pb2.GraphDef()
        remove.node.extend([score_def])
        remove.node.extend([expand_def])
        for n in nodes_to_remove_list:
            remove.node.extend([copy.deepcopy(name_to_node_map[n])])

        with tf.device('/gpu:0'):
            tf.import_graph_def(keep, name='')
        with tf.device('/cpu:0'):
            tf.import_graph_def(remove, name='')

通过正确地将操作分配给GPU和CPU,它减少了处理每个图像所需的时间。我得到的基本想法是,它试图在CPU和GPU上分配操作,但对这两个图形的解释,它们的结构和工作将非常有帮助。谢谢

我对该代码的理解是:

  • 它创建了一个带有两个占位符的图形
    'Postprocessor/convert_scores'
    'Postprocessor/ExpandDims_1'
  • 将其转换为
    图形_def
    ,并保留与占位符对应的节点

    • 这两个节点对应于模型输出的1917个框,第一个是类概率,第二个是框坐标
  • 它创建第二个图形并加载经过训练的模型

  • 它列出了图形中的所有节点以及它们之间的连接方式
  • 列出连接到
    'Postprocessor/convert_scores'
    'Postprocessor/ExpandDims_1'
    的所有节点,并将它们存储在保留列表中
  • 列出所有不在
    节点\u保留\u列表中的节点
    ,并将它们存储在
    节点\u删除\u列表中

  • 然后,它创建一个graph def,并用所有
    节点的副本填充它以保存列表
    节点

  • 然后是第二个图形定义,其中包含所有
    节点的副本\u至\u删除\u列表
    节点

  • 最后,它加载两个graph Def,第一个带有设备
    '/gpu:0'
    ,第二个
    '/cpu:0'

正如作者所说,这样做的目的是在GPU上运行CNN,并在CPU上进行后处理,因为这样做速度更快。 如果你看一下mobilenet+SSD,你会发现模型输出了一堆框(1917),然后在这些框上进行了相当复杂的后处理(至少从图形的角度来看),以提供最终输出(
检测框
检测得分
检测类
数量检测

它在这段代码中不可见,但占位符稍后用于在
删除
图形中插入
保留
图形的输出。执行分为2个步骤(2次调用
sess.run()

编辑

1917
值来自原始图形,使用不同的模型,它将是不同的值,但即使是不同的节点等。。。这就是为什么这个解决方案与其说是一个真正的解决方案,不如说是一个黑客,因为它需要根据您想要应用它的每个新模型进行定制

我刚才看了这张图,我认为模型输出了一堆具有一定大小或纵横比的盒子,以及另一堆具有不同纵横比的盒子等等,所有这些都合并在一起,最终得到了这张
1917
box图


ExpandDims
只是操作的名称,因为它没有在图中命名。之所以存在
\u 1
,是因为此范围内的图形中可能已经存在一个。至于这些节点的具体原因,这只是作者在调查了这些性能问题后做出的任意选择。基本上,慢的部分是在这些节点之后。但他可以选择稍有不同的节点,例如在
ExpandDims
操作之前,它会执行相同的操作。这些特定节点的实际用途与他在这里所做的无关。对于
ExpandDims
,这是一个非常普通的操作,只需添加一个维度1即可。

谢谢您的帮助。我仍然不明白为什么占位符被硬编码为1917。这个数字有变化吗?另外,如果我们只是创建占位符,那么需要创建一个新的输入图吗?我们不能在检测图中创建它们吗?此外,这些“转换分数”和“扩展分数”节点在做什么?你能澄清一下吗?@user8652313如果你使用ssd_mobilenet_v1_coco_2017_11_17模型,就像链接一样,你可以研究神经网络模型。我的猜测是1917是检测到的最大边界框。90是类的数量(参考:)。
(score, expand) = sess.run([score_out, expand_out], feed_dict={image_tensor: image_np_expanded})
(boxes, scores, classes, num) = sess.run(
      [detection_boxes, detection_scores, detection_classes, num_detections],
      feed_dict={score_in:score, expand_in: expand})
print 'Iteration %d: %.3f sec'%(i, time.time()-start_time)