后处理
? ? ? ? 接上篇【模型加速】CUDA-Pointpillars项目解读(2),就PointPillars而言神经网络部分的耗时相对较少,时间消耗主要在后处理部分。PointPillars检测头输出3路信息,分别是类别置信度(cls_prds),带编码的3D框回归值(box_preds)和方向(dir_cls_preds)。这3路输出信息结合设定的anchors生成真实的预测框(box_preds)和类别置信度(cls_preds),再送入NMS。解码的过程包虽不复杂,但包含一系列细小的操作。在onnx上展开来看,部分操作如下:
?操作虽多,但基本上还是常规操作,可以支持直接转TensorRT。不过,在cuda-pointpillars项目中,作者将这部分从onnx中移除了,取而代之使用cuda算子进行实现。所以,你看到的cuda-pointpillars中的onnx应该是以cls_preds,box_preds,dir_cls_preds这3路分支为最终的输出。
?其解码部分的cuda核心实现如下:
__global__ void postprocess_kernal(const float *cls_input,
float *box_input,
const float *dir_cls_input,
float *anchors,
float *anchor_bottom_heights,
float *bndbox_output,
int *object_counter,
const float min_x_range,
const float max_x_range,
const float min_y_range,
const float max_y_range,
const int feature_x_size,
const int feature_y_size,
const int num_anchors,
const int num_classes,
const int num_box_values,
const float score_thresh,
const float dir_offset)
{
int loc_index = blockIdx.x;
int ith_anchor = threadIdx.x;
if (ith_anchor >= num_anchors) {
return;
}
int col = loc_index % feature_x_size;
int row = loc_index / feature_x_size;
float x_offset = min_x_range + col * (max_x_range - min_x_range) / (feature_x_size - 1);
float y_offset = min_y_range + row * (max_y_range - min_y_range) / (feature_y_size - 1);
int cls_offset = loc_index * num_anchors * num_classes + ith_anchor * num_classes;
float dev_cls[2] = {-1, 0};
const float *scores = cls_input + cls_offset;
float max_score = sigmoid(scores[0]);
int cls_id = 0;
for (int i = 1; i < num_classes; i++) {
float cls_score = sigmoid(scores[i]);
if (cls_score > max_score) {
max_score = cls_score;
cls_id = i;
}
}
dev_cls[0] = static_cast<float>(cls_id);
dev_cls[1] = max_score;
if (dev_cls[1] >= score_thresh)
{
int box_offset = loc_index * num_anchors * num_box_values + ith_anchor * num_box_values;
int dir_cls_offset = loc_index * num_anchors * 2 + ith_anchor * 2;
float *anchor_ptr = anchors + ith_anchor * 4;
float z_offset = anchor_ptr[2] / 2 + anchor_bottom_heights[ith_anchor / 2];
float anchor[7] = {x_offset, y_offset, z_offset, anchor_ptr[0], anchor_ptr[1], anchor_ptr[2], anchor_ptr[3]};
float *box_encodings = box_input + box_offset;
float xa = anchor[0];
float ya = anchor[1];
float za = anchor[2];
float dxa = anchor[3];
float dya = anchor[4];
float dza = anchor[5];
float ra = anchor[6];
float diagonal = sqrtf(dxa * dxa + dya * dya);
box_encodings[0] = box_encodings[0] * diagonal + xa;
box_encodings[1] = box_encodings[1] * diagonal + ya;
box_encodings[2] = box_encodings[2] * dza + za;
box_encodings[3] = expf(box_encodings[3]) * dxa;
box_encodings[4] = expf(box_encodings[4]) * dya;
box_encodings[5] = expf(box_encodings[5]) * dza;
box_encodings[6] = box_encodings[6] + ra;
float yaw;
int dir_label = dir_cls_input[dir_cls_offset] > dir_cls_input[dir_cls_offset + 1] ? 0 : 1;
float period = 2 * M_PI / 2;
float val = box_input[box_offset + 6] - dir_offset;
float dir_rot = val - floor(val / (period + 1e-8) + 0.f) * period;
yaw = dir_rot + dir_offset + period * dir_label;
int resCount = (int)atomicAdd(object_counter, 1);
bndbox_output[0] = resCount+1;
float *data = bndbox_output + 1 + resCount * 9;
data[0] = box_input[box_offset];
data[1] = box_input[box_offset + 1];
data[2] = box_input[box_offset + 2];
data[3] = box_input[box_offset + 3];
data[4] = box_input[box_offset + 4];
data[5] = box_input[box_offset + 5];
data[6] = yaw;
data[7] = dev_cls[0];
data[8] = dev_cls[1];
}
}
cudaError_t postprocess_launch(const float *cls_input,
float *box_input,
const float *dir_cls_input,
float *anchors,
float *anchor_bottom_heights,
float *bndbox_output,
int *object_counter,
const float min_x_range,
const float max_x_range,
const float min_y_range,
const float max_y_range,
const int feature_x_size,
const int feature_y_size,
const int num_anchors,
const int num_classes,
const int num_box_values,
const float score_thresh,
const float dir_offset,
cudaStream_t stream)
{
int bev_size = feature_x_size * feature_y_size;
dim3 threads (num_anchors);
dim3 blocks (bev_size);
postprocess_kernal<<<blocks, threads, 0, stream>>>
(cls_input,box_input,dir_cls_input,
anchors,anchor_bottom_heights,
bndbox_output,
object_counter,
min_x_range,max_x_range,min_y_range,max_y_range,
feature_x_size,feature_y_size,
num_anchors,num_classes,num_box_values,
score_thresh,
dir_offset);
return cudaGetLastError();
}
通过大量减少TensorRT中的小型操作,改用CUDA直接实现,这里有一定的提速。唯一我觉得不美观的地方就是这样一来在推理代码中就得自带一堆的解码参数,主要是anchor相关的参数。模型一旦调整,需要更新相应的参数文件。最后是NMS,通过nms_cpu函数来完成,暂时还没有cuda实现。我自测了以下,整个后处理部分的时间消耗波动较大。这个应该跟进入NMS的框的数量有很大的关系。nms之前是通过置信度(score_thresh)对框的数量做了一个限制。最终输出的3d预测框(bndbox)信息如下图所示。
|