You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C++部署Nvidia PeopleNet ONNX模型推理结果异常的问题排查

C++部署Nvidia PeopleNet ONNX模型推理结果异常的问题排查

看起来你已经参考jetson-inference的detectNet实现了PeopleNet的C++推理流程,但一直得不到正确结果对吧?我帮你梳理下预处理、后处理环节里的常见坑,这些都是部署ONNX检测模型时容易踩的点:

一、预处理环节的潜在问题

1. 通道顺序与颜色空间不匹配

我注意到你在blobFromImages里把swapRB设为了false,这里要重点确认:PeopleNet模型要求的输入是RGB还是BGR?
OpenCV默认读取的图像是BGR格式,如果你没有在GPU上先把GpuMat从BGR转成RGB(比如用cv::cuda::cvtColor),那直接用BGR输入到要求RGB的模型里,颜色空间的错误会导致模型完全无法正确识别目标。
解决方式:要么在GPU resize前添加cv::cuda::cvtColor(resized, resized, cv::COLOR_BGR2RGB, stream);,要么把swapRB设为trueblobFromImages自动完成BGR到RGB的转换。

2. 预处理效率与数据一致性优化

你当前的流程是GPU转float32后下载到CPU做归一化,然后再用blobFromImages转NCHW。这里有个可以优化且避免潜在问题的点:
归一化操作其实可以在GPU上完成,不用下载到CPU,既提升效率也避免了stream同步可能带来的潜在数据错误(虽然你加了stream.waitForCompletion(),但批量处理时会拖慢速度)。可以把GPU上的转换一步到位:

// 直接在GPU上完成转float32+归一化到[-1,1]
resized.convertTo(float_img, CV_32F, 1.0/127.5, -1.0, stream);

这等价于(x/255 - 0.5)/0.5,跳过CPU上的二次计算。

二、后处理环节的核心错误(重点!)

这部分是最容易出问题的,尤其是你参考的detectNet和PeopleNet的输出格式可能存在差异:

1. 完全忽略了Batch维度的偏移

我看到你的后处理代码里,读取confbbox的tensor时,直接用了conf.ptr<float>()[c * numCells + y * cells_x + x],完全没有考虑当前batch中第b个样本的偏移量
比如如果conf的shape是[N, num_classes, H, W](N是batch size),那么第b个样本的置信度应该从b * num_classes * numCells这个偏移量开始读取,同理bbox的tensor也是如此。当前代码在batch size>1时,所有样本都会复用第一个样本的输出结果,这肯定会导致错误。

正确的置信度读取方式应该是:

// 计算当前batch样本在tensor中的起始偏移
int conf_offset = b * num_classes * numCells;
const float confidence = conf.ptr<float>()[conf_offset + c * numCells + y * cells_x + x];

bbox的读取也要加上对应的batch偏移:

int bbox_offset = b * num_classes * 4 * numCells;
const float bbox_val = bbox.ptr<float>()[bbox_offset + (c *4 + 0)*numCells + y*cells_x +x];

2. 原始尺寸缩放错误

你当前的scale_xscale_y用的是original_sizes[0],也就是第一个样本的原始尺寸,但batch中每个样本的原始尺寸可能不同!应该用当前batch的第b个样本的尺寸:

// 替换成当前样本的原始尺寸
const float scale_x = static_cast<float>(original_sizes[b].width) / INPUT_WIDTH;
const float scale_y = static_cast<float>(original_sizes[b].height) / INPUT_HEIGHT;

3. 输出tensor的顺序可能搞反了

你把outputs[0]当成bbox,outputs[1]当成confidence,但这个顺序完全取决于你的ONNX模型的输出定义!
建议你用Netron工具打开你的PeopleNet ONNX模型,查看输出节点的名称和顺序:比如有些PeopleNet版本的输出第一个是scores(置信度),第二个是bboxes,如果顺序搞反,整个后处理就全错了。

4. BBox解码公式不匹配PeopleNet的输出格式

PeopleNet的BBox回归输出格式可能和detectNet不同,你参考的detectNet公式不一定适用。比如PeopleNet的每个网格预测的是:

dx = (x_center - grid_x) / grid_width
dy = (y_center - grid_y) / grid_height
dw = log(bbox_width / input_width)
dh = log(bbox_height / input_height)

你需要对照模型的训练文档或ONNX节点定义确认解码公式,如果公式错了,预测的框会完全偏离目标。

5. NMS处理逻辑错误

当前代码把所有类别的检测框混在一起做NMS,这会导致不同类别的重叠框被错误过滤!正确的做法是按类别分组,对每个类别单独做NMS

// 替换原来的NMS逻辑:按类别分组
std::map<int, std::vector<Detection>> class_detections;
for (const auto& d : detections) {
    class_detections[d.class_id].push_back(d);
}

std::vector<Detection> nms_detections;
for (auto& [class_id, dets] : class_detections) {
    std::vector<cv::Rect> boxes;
    std::vector<float> scores;
    std::vector<int> indices;
    for (const auto& d : dets) {
        boxes.push_back(d.box);
        scores.push_back(d.Confidence);
    }
    cv::dnn::NMSBoxes(boxes, scores, CONFIDENCE_THRESHOLD, NMS_THRESHOLD, indices);
    for (int idx : indices) {
        nms_detections.push_back(dets[idx]);
    }
}
batch_detections[b] = nms_detections;

三、其他调试建议

  1. 先测试单样本推理:把batch size设为1,简化问题排查,确认单张图的结果是否正确后再扩展到批量。
  2. 验证预处理结果:把预处理后的blob转成HWC格式的图像(比如用cv::dnn::imagesFromBlob),查看颜色、尺寸是否正常,归一化后的范围是否在[-1,1]之间。
  3. 打印输出tensor的数值:打印几个置信度较高的结果(比如conf>0.5的),看看原始的bbox回归值和置信度是否合理,如果置信度全接近0,说明预处理或模型输入完全错误。
  4. 排除OpenCV DNN的问题:如果条件允许,试试用TensorRT推理同一个ONNX模型,看看结果是否正确,排除OpenCV DNN对某些ONNX算子支持不佳的可能。

内容来源于stack exchange

火山引擎 最新活动