SSD网络建立在VGG-16网络上,在单个前向传播网络中完成目标检测和定位。该网络将边界框的输出离散为一组默认框,其中每个特征框的位置具有不同的横纵比和尺寸。在预测目标时,网络为每个默认框中存在的每个对象类别生成分数,并对框进行调整,以便更好的匹配对象形状。
1.1 run
- 下载预训练ssd_inception_v2_coco_2017_11_17 模型并使用该模型进行推理。
- preprocessor
(1)resize image to [300*300*3]
(2)normalization [-1,1]
- featureextractor
inceptionV2 提取特征,the feature maps generated are used by the anchor generation step to generate default bounding boxes for each feature map.本例中,用于anchor generation的feature maps的尺寸是 [(19x19), (10x10), (5x5), (3x3), (2x2), (1x1)]
- boxpredictor
产生box encodings和class scores,产生的信息传给postprocessor
- gridanchorgenerator
产生默认boundingBox。在tensorrt中实现该功能插件层叫做gridAnchorGenerator插件,注册的插件名称是GridAnchor_TRT
- postprocessor
nms tensorrt中注册插件 NMS_TRT
- flattenconcat
1.2 process
- processing the input graph
tensorflow SSD graph 有一些tensorrt当前不支持的操作,使用GraphSurgeon,可以将graph中的多个操作组合成单个自定义操作,该操作可以使用TensorRT中的插件层实现。目前,GraphSurgeon提供了将命名空间中所有节点缝合到一个自定义节点的能力。
要使用GraphSurgeon,应该使用-p flag 和config file 调用“convert_to_uff”。config script应该包含所有自定义插件的属性,这些插件将嵌入生成的.uff文件中。SSD的当前示例脚本位于/usr/src/tensorrt/samples/sampleUffSSD/config.py
。
使用GraphSurgeon,能从graph中删除预处理器命名空间,stitch 'GridAnchorGenerator’名称空间以创建’GridAnchorGenerator’插件,将后处理器名称空间sticth到’NMS’插件,并将BoxPredictor中的concat操作标记为’FlatConcat’插件。
TensorFlow graph有一些操作,如’Assert’和’Identity’,可以删除这些操作进行推断。删除像“Assert”这样的操作,然后递归删除剩余节点(一旦删除Assert,就没有输出)。
Identity
操作被删除,输入被转发到所有连接的输出。
- uff_ssd plugins
GridAnchorGeneration
plugin
该插件层在TensorFlow SSD网络中实现网格锚生成步骤。对于每个特征贴图,我们计算每个网格单元的边界框。在该网络中,有6个feature map,每个grid cell的框数如下:
This plugin layer implements the grid anchor generation step in the TensorFlow SSD network. For each feature map we calculate the bounding boxes for each grid cell. In this network, there are 6 feature maps and the number of boxes per grid cell are as follows:
- [19x19] feature map: 3 boxes (19x19x3x4(co-ordinates/box))
- [10x10] feature map: 6 boxes (10x10x6x4)
- [5x5] feature map: 6 boxes (5x5x6x4)
- [3x3] feature map: 6 boxes (3x3x6x4)
- [2x2] feature map: 6 boxes (2x2x6x4)
- [1x1] feature map: 6 boxes (1x1x6x4)
NMS
plugin
“NMS”插件根据BoxPredictor生成的位置和置信度预测生成检测输出。该层有三个输入张量,分别对应于位置数据(locData
)、置信度数据(confData
)和priorbox数据(priorData
)。
检测输出插件的输入必须在所有特征图上展平和连接。我们使用示例中实现的“FlattConcat”插件来实现这一点。从box预测器生成的位置数据具有以下维度:
19x19x12 -> Reshape -> 1083x4 -> Flatten -> 4332x1
10x10x24 -> Reshape -> 600x4 -> Flatten -> 2400x1
依此类推,以获取剩余的要素贴图。
串联后,locData
输入的输入维度的顺序为7668x1。
从box预测器生成的置信度数据具有以下维度:
19x19x273 -> Reshape -> 1083x91 -> Flatten -> 98553x1
10x10x546 -> Reshape -> 600x91 -> Flatten -> 54600x1
依此类推,以获取剩余的要素贴图。
串联后,confData
输入的输入维度为174447x1。
从网格定位生成器插件生成的先前数据具有以下尺寸,例如19x19特征图具有2x4332x1(此处有两个通道,因为一个通道用于存储NMS步骤中使用的每个坐标的方差)。连接后,priorData输入的输入维度为2x7668x1。
struct DetectionOutputParameters
{
bool shareLocation, varianceEncodedInTarget;
int backgroundLabelId, numClasses, topK, keepTopK;
float confidenceThreshold, nmsThreshold;
CodeTypeSSD codeType;
int inputOrder[3];
bool confSigmoid;
bool isNormalized;
};
shareLocation
和varianceEncodedInTarget
用于Caffe实现,因此对于TensorFlow网络,它们应分别设置为true
和false
。confSigmoid
和isNormalized
参数是TensorFlow实现所必需的。如果confSigmoid
设置为true
,它将计算所有置信度得分的sigmoid值。isNormalized
标志指定是否对数据进行了规范化,并将TensorFlow图设置为true
。
1.3 code
- 下载VOC dataset
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
tar xvf VOCtest_06-Nov-2007.tar
如果下载链接断开,请尝试其他源http://vision.cs.utexas.edu/voc/VOC2007_test/. 如果不想将VOC保存在示例根目录中,则需要在运行之前将 --voc_dir
参数调整为voc_evaluation.py
脚本。此参数的默认值为<SAMPLE_ROOT>/VOCdevkit/VOC2007
。
- 运行推理过程
python detect_objects.py <IMAGE_PATH>
第一次运行推理时:
该脚本从TensorFlow对象检测API下载预训练的“ssd_inception_v2_coco_2017_11_17”模型。该脚本将此模型转换为TensorRT格式,并根据此模型的特定版本进行转换。
该脚本构建一个TensorRT推理引擎并将其保存到文件中。在此步骤中,所有TensorRT优化都将应用于冻结图。这是一个耗时的操作,可能需要几分钟。
-
运行VOC evaluation 脚本
-
使用tensorrt运行脚本
python voc_evaluation.py
-
使用tensorflow运行脚本
python voc_evaluation.py tensorflow
使用tensorflow运行脚本比用tensorrt慢很多。AP和mAP会在结尾显示
def ssd_unsupported_nodes_to_plugin_nodes(ssd_graph):
channels = ModelData.get_input_channels()
height = ModelData.get_input_height()
width = ModelData.get_input_width()
Input = gs.create_plugin_node(name="Input",
op="Placeholder",
dtype=tf.float32,
shape=[1, channels, height, width]
)
PriorBox = gs.create_plugin_node(
name="GridAnchor",
op="GridAnchor_TRT",
minSize = 0.2,
maxSize = 0.95,
aspectRatios = [1.0, 2.0, 0.5, 3.0, 0.33],
variance=[0.1, 0.1, 0.2,0.2],
featureMapShapes=[19,10,5,3,2,1],
numLayers=6
)
NMS = gs.create_plugin_node(
name="NMS",
op="NMS_TRT",
shareLocation=1,
varianceEncodedInTarget=0,
backgroundLabelId=0,
confidenceThreshold=1e-8,
nmsThreshold=0.6,
topK=100,
keepTopK=100,
numClasses=91,
inputOrder=[0, 2, 1],
confSigmoid=1,
isNormalized=1
)
concat_priorbox = gs.create_node(
"concat_priorbox",
op="ConcatV2",
dtype=tf.float32,
axis=2
)
concat_box_loc = gs.create_plugin_node(
"concat_box_loc",
op="FlattenConcat_TRT",
dtype=tf.float32,
axis=1,
ignoreBatch=0
)
concat_box_conf = gs.create_plugin_node(
"concat_box_conf",
op="FlattenConcat_TRT",
dtype=tf.float32,
axis=1,
ignoreBatch=0
)
namespace_plugin_map = {
"MultipleGridAnchorGenerator": PriorBox,
"Postprocessor": NMS,
"Preprocessor": Input,
"ToFloat": Input,
"image_tensor": Input,
"MultipleGridAnchorGenerator/Concatenate": concat_priorbox,
"MultipleGridAnchorGenerator/Identity": concat_priorbox,
"concat": concat_box_loc,
"concat_1": concat_box_conf
}
ssd_graph.collapse_namespaces(namespace_plugin_map)
ssd_graph.remove(ssd_graph.graph_outputs, remove_exclusive_dependencies=False)
return ssd_graph
def model_to_uff(model_path, output_uff_path, slient=False):
dynamic_graph = gs.DynamicGraph(model_path)
dynamic_graph = ssd_unsupported_nodes_to_plugin_nodes(dynamic_graph)
uff.from_tensorflow(
dynamic_graph.as_graph_def(),
[ModelData.OUTPUT_NAME],
output_filename = output_uff_path,
text=True
)