目录

1 yolov5s_postprocess_simple.h

2 yolov5s_postprocess_simple.hip

3 demo

4 说明

4.1 封装成类

4.2 修改nms错误

 4.3 修改sizeof错误


1 yolov5s_postprocess_simple.h

// yolov5s_postprocess_simple.h
#pragma once

#include <hip/hip_runtime.h>
#include <vector>
#include <iostream>
#include <algorithm>


// 常量定义
constexpr int MAX_CLASSES = 80;
constexpr int OBJECT_TOPK = 1000;        // 单张图最大输出框数量
constexpr int NUM_BOX_ELEMENT = 7;       // left, top, right, bottom, confidence, class_id, keep_flag
constexpr int OUT_ELEM_SIZE = 6;         // 输出最终保留的6个值 (left, top, right, bottom, confidence, class_id)

 inline void checkHipError(hipError_t err) {
    if (err != hipSuccess) {
        std::cerr << "HIP Error: " << hipGetErrorString(err) << std::endl;
        exit(-1);
    }
}



// Bounding box结构体
struct Box {
    float left;       // 左上角 x
    float top;        // 左上角 y
    float right;      // 右下角 x
    float bottom;     // 右下角 y
    float confidence; // 置信度
    int class_id;     // 类别

    Box() : left(0), top(0), right(0), bottom(0), confidence(0), class_id(-1) {}
    Box(float l, float t, float r, float b, float conf, int cls)
        : left(l), top(t), right(r), bottom(b), confidence(conf), class_id(cls) {}
};

// 后处理类
class Yolov5sPostProcessSimple {
public:
    Yolov5sPostProcessSimple();
    ~Yolov5sPostProcessSimple();

    // 初始化:分配GPU/CPU内存和流
    bool init(int max_batch = 1);

    // 销毁资源
    void destroy();

    // // 执行GPU解码 + Fast NMS
    // std::vector<Box> process(
    //     float* predict,           // host pointer to [rows, cols] tensor
    //     int rows,                 // number of boxes (e.g., 25200)
    //     int cols,                 // 5 + num_classes
    //     float confidence_threshold = 0.25f,
    //     float nms_threshold = 0.45f
    // );
    
    // 执行GPU解码 + Fast NMS
    std::vector<Box> process(
        float* predict           // host pointer to [rows, cols] tensor
    );

    // 是否已初始化
    bool is_initialized() const { return initialized_; }

private:
    // GPU 流
    hipStream_t stream_;

    // 显存指针
    float* predict_device_;      // 输入预测结果显存
    float* output_device_;       // 输出结果显存 [count + boxes]
    float* output_host_;         // 主机端映射内存用于同步

    // 缓存参数
    int max_objects_ = 1000;            // 最大检测框数(默认1000)
    size_t max_input_bytes_;            // predict最大占用字节
    bool initialized_;  
    int rows_ = 25200;
    int cols_ = 85;            
    float confidence_threshold_ = 0.25;
    float nms_threshold_ = 0.45;    

};


2 yolov5s_postprocess_simple.hip

#include "hip/hip_runtime.h"
#include "yolov5s_postprocess_simple.h"
#include <hip/hip_runtime.h>
#include <iostream>

using namespace std;


static __device__ float box_iou(
    float aleft, float atop, float aright, float abottom,
    float bleft, float btop, float bright, float bbottom)
{

    float cleft = max(aleft, bleft);
    float ctop = max(atop, btop);
    float cright = min(aright, bright);
    float cbottom = min(abottom, bbottom);

    float c_area = max(cright - cleft, 0.0f) * max(cbottom - ctop, 0.0f);
    if (c_area == 0.0f)
        return 0.0f;

    float a_area = max(0.0f, aright - aleft) * max(0.0f, abottom - atop);
    float b_area = max(0.0f, bright - bleft) * max(0.0f, bbottom - btop);
    return c_area / (a_area + b_area - c_area);
}


static __global__ void __launch_bounds__(1024) fast_nms_kernel(float *bboxes, int max_objects, float threshold, int NUM_BOX_ELEMENT)
{
/*
参数解析: 
bboxes:存储了所有待处理的检测框信息的一维数组;
max_objects:最大的输出检测框数量; 案例设置的是1000, 预计一张图不会超过1000个bounding box
threshold:用于判断两个检测框是否重叠的 IOU 阈值;
NUM_BOX_ELEMENT:每个检测框存储的元素个数
一般包含: left, top, right, bottom, confidence, class, keepflag
*/
    
    // 计算position, 超过count不用进行下面计算了
    int position = blockDim.x * blockIdx.x + threadIdx.x;
    int count = min((int)*bboxes, max_objects);
    if (position > count){
        return; 
    }

    /*
    重叠度高, 并且类别相同,然后是condience小于另外一个, 就删掉他
    极端情况下会有误删, 如果测试cpu map的时候, 只能采用cpu nms
    日常推理的时候, 则可以使用这个NMS
    left, top, right, bottom, confidence, class, keepflag
    */
    
    // 这里计算出来当前的指针, 在bboxes上
    float *pcurrent = bboxes + 1  + position * NUM_BOX_ELEMENT; 
    // 便利每一个bbox
    for (int i = 0; i < count; ++i){
        float *pitem = bboxes + 1 + i * NUM_BOX_ELEMENT;
        // NMS计算需要保证类别必须相同
        //if (i == position || pcurrent[5] ){//这一行错误,结果会有重叠框。
        if (i == position || pcurrent[5] != pitem[5]){
            continue;
        }
        
        // 判断置信度大小, 如果比pcurrent大,干掉pcurrent
        if (pitem[4] > pcurrent[4]){
            // 如果两个一样大,保留编号小的那个
            if (pitem[4] == pcurrent[4] && i < position){
                continue;
            }
                
            // 拿前面四个信息计算IOU
            float iou = box_iou(
                pcurrent[0], pcurrent[1], pcurrent[2], pcurrent[3],
                pitem[0], pitem[1], pitem[2], pitem[3]);

            if (iou > threshold){
                pcurrent[6] = 0;  // 这里pitem跟pcurrent重合度高而且达到阈值
                return;
            }
        }
    }
}


static __global__ void __launch_bounds__(1024) decode_kernel(
    float *predict, int num_bboxes, int num_classes, float confidence_threshold,
    float *invert_affine_matrix, float *parray, int max_objects, int NUM_BOX_ELEMENT)
{
    // 确保有足够的thread, 每一个thread处理一个bounding box
    // 如果threadId超过了bounding box的数量, 这样就不会进行后续处理, 每个预测框都敲好被处理了一次
    int position = blockDim.x * blockIdx.x + threadIdx.x;
    if (position >= num_bboxes){
        return;
    }

    /*
     predict是n x 85tensor输出的首地址
     pitem 就是每行的指针, pitem[0] - pitem[3] 是位置信息, pitem[4]是objness
    */
    float *pitem = predict + (num_classes + 5) * position;
    float objectness = pitem[4];
    if (objectness < confidence_threshold){
        return;
    }

    // 从这个元素开始都是confidence
    float *class_confidence = pitem + 5;
    // 这里是第一个condience, 取到数值
    float confidence = *class_confidence++;

    // for循环判断是哪个类别
    int label = 0;
    for (int i = 1; i < num_classes; i++, ++class_confidence)
    {
        if (*class_confidence > confidence)
        {   
            // 如果大了, 就更新class_confidence
            confidence = *class_confidence;
            label = i; // 取到label
        }
    }

    /*
    上面的最后算出来的condifence是class_confidence只是条件概率
    当前bounding box的 confidence(置信度) =  objectness(物体概率) x class_confidence(条件概率)
    最后拿来计算置信度的confidence是最大的class_confidence
    */
    confidence *= objectness;
    if (confidence < confidence_threshold){
        return;
    }

    /*
    这里是恢复boudingbox的操作, 需要先取出来中心点(cx, cy), width, height
    */
    float cx = *pitem++;
    float cy = *pitem++;
    float width = *pitem++;
    float height = *pitem++;
    float left = cx - width * 0.5f;
    float top = cy - height * 0.5f;
    float right = cx + width * 0.5f;
    float bottom = cy + height * 0.5f;
    // affine_project(invert_affine_matrix, left,  top,    &left,  &top);
    // affine_project(invert_affine_matrix, right, bottom, &right, &bottom);

    
    /*
    atomicAdd()简介: 
    int atomicAdd(int* address, int val);
    这个函数执行的操作是将指定地址 address 处的值与 val 相加,并将结果写回 address 处。这个操作是原子性的,即不会受到并发写入的干扰,保证了数据的正确性。
    使用 atomicAdd 函数可以保证多个线程在对同一个内存地址进行写操作时,不会发生数据覆盖的问题。
    由于每个线程都会在输出中写入一个bounding box,因此需要使用原子操作确保每个线程写入的位置唯一
    */

    /*
    [count, box1, box2, box3]
    因为GPU解码是多线程的, 所以需要用count记录已经处理了多少个bounding box。
    CPU单线程不需要, GPU需要确保不会将一个检测框重复输出或者漏掉。
    atomicAdd -> count +=1 返回 old_count
    这里是对parray(output_device第一个值+1)
    */
    int index = atomicAdd(parray, 1);
    // 如果超过了1000, 这个线程就没必要处理后面的boxes
    if (index >= max_objects)  
        return;
    
    // left, top, right, bottom, confidence, class, keepflag
    float *pout_item = parray + 1 + index * NUM_BOX_ELEMENT;
    *pout_item++ = left;
    *pout_item++ = top;
    *pout_item++ = right;
    *pout_item++ = bottom;
    *pout_item++ = confidence;
    *pout_item++ = label;
    *pout_item++ = 1; // 1 = keep, 0 = ignore
}

/*
decode_kernel_invoker(
    predict_device, rows, cols - 5, confidence_threshold,
    nms_threshold, nullptr, output_device, max_objects, NUM_BOX_ELEMENT, stream);
*/
void decode_kernel_invoker(
    float* predict, int num_bboxes, int num_classes, float confidence_threshold, 
    float nms_threshold, float* invert_affine_matrix, float* parray, int max_objects, int NUM_BOX_ELEMENT, hipStream_t stream){
/*
参数解析: 
predict: 预测结果, 这个就是data, 未处理未过滤的predict
num_bboxes: 在预测结果的(n x num_classes+ 5) tensor中, 多少行就是多少个box
num_classes: 类别数量
confidence_threshold: 置信度阈值
nms_threshold: nms阈值
invert_affine_matrix: 逆矩阵的指针
parray: 输出结果数组
max_objects: 最大数量框, 这边设置的是1000, 只是拿来确保有足够的内存
NUM_BOX_ELEMENT: Box的element, left, top, right, bottom, confidence, class, keepflag 一共7个
stream: 流
*/ 
    // 这里是确保有足够的线程去处理每一个box, 也就是每一个预测结果,所以用num_boxxes
    // 确保每个block的线程不超过512
    auto block = num_bboxes > 512 ? 512 : num_bboxes;
    auto grid = (num_bboxes + block - 1) / block;

    /* 如果核函数有波浪线,没关系,他是正常的,你只是看不顺眼罢了 */
    decode_kernel<<<grid, block, 0, stream>>>(
        predict, num_bboxes, num_classes, confidence_threshold, 
        invert_affine_matrix, parray, max_objects, NUM_BOX_ELEMENT
    );

    // 这里是针对每张图的框,确保每个狂都能被线程处理
    // 同样确保每个block的线程不超过512
    block = max_objects > 512 ? 512 : max_objects;
    grid = (max_objects + block - 1) / block;
    fast_nms_kernel<<<grid, block, 0, stream>>>(parray, max_objects, nms_threshold, NUM_BOX_ELEMENT);
}

#if 0
vector<Box> gpu_decode(float *predict, int rows, int cols, float confidence_threshold, float nms_threshold)
{

    /*
    参数解析:
    predict 是指向data的指针
    rows 是输出data的行数
    cols是是5(位置信息) + 类别
    confidence_threshold 置信度阈值
    nms_threshold nms阈值
    */
    vector<Box> box_result; // 用来保存解码后的边界框信息
    // 创建一个hipStream_t类型的流, stream
    hipStream_t stream = nullptr;
    checkHipError(hipStreamCreate(&stream));

    // 定义三个float指针变量
    float *predict_device = nullptr;
    float *output_device = nullptr;
    float *output_host = nullptr;

    /*表示输出数量不确定的数组,用[count, box1, box2, box3]的方式,此时需要有最大数量限制
    分配一块足够大的内存, 第一个元素是count, 这里我们设置每个图像不超过1000个bounding box
    */
    int max_objects = 1000;
    // left, top, right, bottom, confidence, class, keepflag 一共7个
    int NUM_BOX_ELEMENT = 7;

    // 分配内存到GPU上
    checkHipError(hipMalloc(&predict_device, rows * cols * sizeof(float)));
    // 分配一块足够大的内存, 第一个元素是count
    checkHipError(hipMalloc(&output_device, sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float)));
    // 分配CPU内存
    checkHipError(hipHostMalloc(&output_host, sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float)));
    // 把predict的内存复制到predict_device
    checkHipError(hipMemcpyAsync(predict_device, predict, rows * cols * sizeof(float), hipMemcpyHostToDevice, stream));
    // 调用kernel函数, 在GPU进行解码
    decode_kernel_invoker(
        predict_device, rows, cols - 5, confidence_threshold,
        nms_threshold, nullptr, output_device, max_objects, NUM_BOX_ELEMENT, stream);

    // 使用hipMemcpyAsync将解码好的output_device拷贝到CPU上面去
    checkHipError(hipMemcpyAsync(output_host, output_device,
                                 sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float),
                                 hipMemcpyDeviceToHost, stream));
    printf("===============sizeof(int) is %d, sizeof(float) is %d,\n", sizeof(int), sizeof(float));

    // 等待异步拷贝完成
    checkHipError(hipStreamSynchronize(stream));

    // 这里解码完了之后, 这个是框框的个数
    int num_boxes = min((int)output_host[0], max_objects);
    // 遍历每一个框框
    for (int i = 0; i < num_boxes; i++)
    {   
        // 找到第一个地址
        float *ptr = output_host + 1 + NUM_BOX_ELEMENT * i;
        int keep_flag = ptr[6]; // 最后一个位置就是flag的值
        if (keep_flag)  // True
        {
            box_result.emplace_back(
                ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], (int)ptr[5]);
        }
    }

    // 销毁先前创建的CUDA流对象, 释放流对象占用的内存空间
    checkHipError(hipStreamDestroy(stream));
    checkHipError(hipFree(predict_device));  // 释放GPU上分配预测结果的内存
    checkHipError(hipFree(output_device));   // 释放GPU上分配解码输出的内存
    checkHipError(hipHostFree(output_host)); // 释放在主机上分配的输出结果缓冲区的内存

    return box_result;
}

#endif

// =========================================
// 类方法实现
// =========================================

Yolov5sPostProcessSimple::Yolov5sPostProcessSimple()
    : stream_(nullptr)
    , predict_device_(nullptr)
    , output_device_(nullptr)
    , output_host_(nullptr)
    , max_objects_(OBJECT_TOPK)
    , max_input_bytes_(0)
    , initialized_(false)
{}

Yolov5sPostProcessSimple::~Yolov5sPostProcessSimple() {
    destroy();
}

bool Yolov5sPostProcessSimple::init(int max_batch) {
    if (initialized_) return true;

    checkHipError(hipStreamCreate(&stream_));

    // 假设最大输入为 25200 x 85 (YOLOv5s)
    // 可根据实际模型调整
    size_t max_predict_size = rows_ * cols_ * sizeof(float);
    checkHipError(hipMalloc(&predict_device_, max_predict_size));
    max_input_bytes_ = max_predict_size;

    size_t output_size = sizeof(float) + max_objects_ * NUM_BOX_ELEMENT * sizeof(float);
    checkHipError(hipMalloc(&output_device_, output_size));
    checkHipError(hipHostMalloc(&output_host_, output_size));

    initialized_ = true;
    return true;
}

void Yolov5sPostProcessSimple::destroy() {
    if (stream_) {
        checkHipError(hipStreamDestroy(stream_));
        stream_ = nullptr;
    }
    if (predict_device_) {
        checkHipError(hipFree(predict_device_));
        predict_device_ = nullptr;
    }
    if (output_device_) {
        checkHipError(hipFree(output_device_));
        output_device_ = nullptr;
    }
    if (output_host_) {
        checkHipError(hipHostFree(output_host_));
        output_host_ = nullptr;
    }
    max_input_bytes_ = 0;
    initialized_ = false;
}

// std::vector<Box> Yolov5sPostProcessSimple::process(
//     float* predict,
//     int rows,
//     int cols,
//     float confidence_threshold,
//     float nms_threshold)
std::vector<Box> Yolov5sPostProcessSimple::process(float* predict)
{
    std::vector<Box> box_result;
    if (!initialized_) {
        fprintf(stderr, "Error: Yolov5sPostProcessSimple not initialized!\n");
        return box_result;
    }

    int num_classes = cols_ - 5;
    size_t input_bytes = rows_ * cols_ * sizeof(float);

    // 检查输入大小是否超出预分配
    if (input_bytes > max_input_bytes_) {
        fprintf(stderr, "Input size too large: %zu > %zu\n", input_bytes, max_input_bytes_);
        return box_result;
    }

    // 复制输入到GPU
    checkHipError(hipMemcpyAsync(predict_device_, predict, input_bytes,
                             hipMemcpyHostToDevice, stream_));

    // 重置输出计数器
    float init_count = 0.0f;
    checkHipError(hipMemcpyAsync(output_device_, &init_count, sizeof(float),
                             hipMemcpyHostToDevice, stream_));

    // 调用核函数
    decode_kernel_invoker(
        predict_device_, rows_, num_classes, confidence_threshold_,
        nms_threshold_, nullptr, output_device_, max_objects_, NUM_BOX_ELEMENT, stream_
    );

    // 拷贝结果回主机
    size_t output_size = sizeof(float) + max_objects_ * NUM_BOX_ELEMENT * sizeof(float);
    checkHipError(hipMemcpyAsync(output_host_, output_device_, output_size,
                             hipMemcpyDeviceToHost, stream_));

    // 同步流
    checkHipError(hipStreamSynchronize(stream_));

    // 解析结果
    int num_boxes = (int)output_host_[0];
    num_boxes = min(num_boxes, max_objects_);

    for (int i = 0; i < num_boxes; ++i) {
        float* ptr = output_host_ + 1 + i * NUM_BOX_ELEMENT;
        if ((int)ptr[6] == 1) { // keep flag
            box_result.emplace_back(ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], (int)ptr[5]);
        }
    }

    return box_result;
}

3 demo

void Postprocess(
    const std::unique_ptr<tc::InferResult>& result,
    const std::vector<std::string>& filenames,
    const size_t batch_size,
    const std::string& output_name, 
    const int maxBatchSize)
{
    if (!result->RequestStatus().IsOk()) {
        std::cerr << "inference failed with error: " << result->RequestStatus() << std::endl;
        exit(1);
    }

    if (filenames.size() != batch_size) {
        std::cerr << "expected " << batch_size << " filenames, got " << filenames.size() << std::endl;
        exit(1);
    }

    // 获取输出 shape
    std::vector<int64_t> shape;
    tc::Error err = result->Shape(output_name, &shape);
    if (!err.IsOk() || shape.size() != 3) {
        std::cerr << "unable to get proper shape for " << output_name << std::endl;
        exit(1);
    }

    int batch = static_cast<int>(shape[0]);
    int numProposal = static_cast<int>(shape[1]);
    int numOut = static_cast<int>(shape[2]);

    // 获取输出数据
    const uint8_t* result_data;
    size_t output_count;
    err = result->RawData(output_name, &result_data, &output_count);
    if (!err.IsOk()) {
        std::cerr << "unable to get data for " << output_name << std::endl;
        exit(1);
    }
    const float* pdata = reinterpret_cast<const float*>(result_data);
    if (batch * numProposal * numOut != output_count / sizeof(float)) {
        std::cerr << "data size mismatch" << std::endl;
        exit(1);
    }

    // 申请 pinned host buffer 并拷贝
    void* pinned_input = nullptr;
    hipHostAlloc(&pinned_input, output_count, hipHostMallocDefault);
    memcpy(pinned_input, pdata, output_count);
    const void* input_ptrs[] = {pinned_input};

    // // 创建后处理类
    // auto yolov5sPostProcess = std::make_shared<Yolov5sPostProcess>();

    // // 初始化
    // auto t1 = std::chrono::high_resolution_clock::now();
    // yolov5sPostProcess->initialize(batch, numProposal, numOut);
    // auto t2 = std::chrono::high_resolution_clock::now();
    // std::cout << "<<<<<< initialize time = "
    //           << std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1).count() * 1000
    //           << " ms" << std::endl;

    // // 申请输出 buffer
    // void* outputs = nullptr;
    // hipHostAlloc(&outputs, batch * OBJECT_TOPK * OUT_ELEM_SIZE * sizeof(float), hipHostMallocDefault);
    // auto t3 = std::chrono::high_resolution_clock::now();
    // std::cout << "<<<<<< hipHostAlloc time = "
    //           << std::chrono::duration_cast<std::chrono::duration<double>>(t3 - t2).count() * 1000
    //           << " ms" << std::endl;

    // // 调用 GPU 核函数进行 decode + NMS
    // printf("numProposal is %d,numOut is %d\n", numProposal, numOut);
    // auto box_result = yolov5sPostProcess->gpu_decode(
    //     reinterpret_cast<float*>(pinned_input),
    //     numProposal,
    //     numOut - 5 // num_classes
    // );

    // auto box_result = gpu_decode(
    // reinterpret_cast<float*>(pinned_input),
    // numProposal,
    // numOut); // num_classes
     ;
    auto yolov5sPostProcessSimple_p = std::make_shared<Yolov5sPostProcessSimple>();
    yolov5sPostProcessSimple_p->init();
    auto box_result = yolov5sPostProcessSimple_p->process(
        reinterpret_cast<float*>(pinned_input)
    );
    

    // auto t4 = std::chrono::high_resolution_clock::now();
    // std::cout << "<<<<<< decode + NMS time = "
    //           << std::chrono::duration_cast<std::chrono::duration<double>>(t4 - t3).count() * 1000
    //           << " ms" << std::endl;
    
    std::cout << "===== Decoded Boxes =====" << std::endl;
    for (size_t i = 0; i < 50; ++i) {
        const Box& box = box_result[i];
        std::cout << "Box " << i
                << ": [" << box.left << ", " << box.top
                << ", " << box.right << ", " << box.bottom << "]"
                << ", confidence = " << box.confidence
                << ", class_id = " << box.class_id
                << std::endl;
    }
    std::cout << "Total boxes: " << box_result.size() << std::endl;

    // // 绘制结果并保存
    // 绘制结果并保存
    for (int b = 0; b < batch; ++b) {
        const std::string& filename = filenames[b];
        cv::Mat srcImage = cv::imread(filename, 1);
        if (srcImage.empty()) {
            std::cerr << "Failed to load image: " << filename << std::endl;
            continue;
        }

        // 遍历检测框
        for (size_t i = 0; i < box_result.size(); i++) {
            const Box& box = box_result[i];
            //if (box.confidence < 0.5f) continue; // 过滤低置信度

            cv::Point pt1((int)box.left,  (int)box.top);
            cv::Point pt2((int)box.right, (int)box.bottom);

            // 画矩形框
            cv::rectangle(srcImage, pt1, pt2, cv::Scalar(0, 255, 255), 2);

            // 显示类别与置信度
            std::string label = "ID:" + std::to_string(box.class_id) +
                                " conf:" + cv::format("%.2f", box.confidence);
            int baseline = 0;
            cv::Size label_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline);
            cv::Point label_origin(pt1.x, pt1.y - 5);

            // 背景方块
            // cv::rectangle(srcImage,
            //             cv::Rect(label_origin.x,
            //                     label_origin.y - label_size.height,
            //                     label_size.width,
            //                     label_size.height + baseline),
            //             cv::Scalar(0, 255, 255));

            // 文本
            cv::putText(srcImage, label, label_origin,
                        cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1);
        }

        std::string out_filename = "result_" + std::to_string(b) + ".jpg";
        cv::imwrite(out_filename, srcImage);
        std::cout << "Saved result image: " << out_filename << std::endl;
    }
    // 释放资源
    //yolov5sPostProcess->terminate();
    //hipHostFree(outputs);
    hipHostFree(pinned_input);
    // auto t5 = std::chrono::high_resolution_clock::now();
    // std::cout << "<<<<<< total terminate/free time = "
    //           << std::chrono::duration_cast<std::chrono::duration<double>>(t5 - t4).count() * 1000
    //           << " ms" << std::endl;
}
}

4 说明

这其实是参考https://blog.csdn.net/bobchen1017/article/details/129664903 进行修改的,做了如下修改。

4.1 封装成类

4.2 修改nms错误

if (i == position || pcurrent[5] )这一行是错误的,要改成if (i == position || pcurrent[5] != pitem[5]),也就是类别不同的时候不做nms,只有同类别的才做nms,这一行如果不修改,那么最终结果有很多重复的框。

static __global__ void fast_nms_kernel(float *bboxes, int max_objects, float threshold, int NUM_BOX_ELEMENT)
{
/*
参数解析: 
bboxes:存储了所有待处理的检测框信息的一维数组;
max_objects:最大的输出检测框数量; 案例设置的是1000, 预计一张图不会超过1000个bounding box
threshold:用于判断两个检测框是否重叠的 IOU 阈值;
NUM_BOX_ELEMENT:每个检测框存储的元素个数
一般包含: left, top, right, bottom, confidence, class, keepflag
*/
    
    // 计算position, 超过count不用进行下面计算了
    int position = blockDim.x * blockIdx.x + threadIdx.x;
    int count = min((int)*bboxes, max_objects);
    if (position > count){
        return; 
    }

    /*
    重叠度高, 并且类别相同,然后是condience小于另外一个, 就删掉他
    极端情况下会有误删, 如果测试cpu map的时候, 只能采用cpu nms
    日常推理的时候, 则可以使用这个NMS
    left, top, right, bottom, confidence, class, keepflag
    */
    
    // 这里计算出来当前的指针, 在bboxes上
    float *pcurrent = bboxes + 1  + position * NUM_BOX_ELEMENT; 
    // 便利每一个bbox
    for (int i = 0; i < count; ++i){
        float *pitem = bboxes + 1 + i * NUM_BOX_ELEMENT;
        // NMS计算需要保证类别必须相同
        //if (i == position || pcurrent[5] ){
        if (i == position || pcurrent[5] != pitem[5]){
            continue;
        }
        
        // 判断置信度大小, 如果比pcurrent大,干掉pcurrent
        if (pitem[4] > pcurrent[4]){
            // 如果两个一样大,保留编号小的那个
            if (pitem[4] == pcurrent[4] && i < position){
                continue;
            }
                
            // 拿前面四个信息计算IOU
            float iou = box_iou(
                pcurrent[0], pcurrent[1], pcurrent[2], pcurrent[3],
                pitem[0], pitem[1], pitem[2], pitem[3]);

            if (iou > threshold){
                pcurrent[6] = 0;  // 这里pitem跟pcurrent重合度高而且达到阈值
                return;
            }
        }
    }
}

 4.3 修改sizeof错误

  // 使用hipMemcpyAsync将解码好的output_device拷贝到CPU上面去
    checkHipError(hipMemcpyAsync(output_host, output_device,
                                 sizeof(float) + max_objects * NUM_BOX_ELEMENT * sizeof(float),
                                 hipMemcpyDeviceToHost, stream));

这个地方最开始是sizeof(int), 但其实这里应该改成sizeof(float),只不过这里用sizeof(int)最终结果也是对的,因为他们两个是一样的,都是4.

5 结果图

更多推荐