Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

c++推理问题 #148

Open
interstellar-space opened this issue Jun 4, 2024 · 0 comments
Open

c++推理问题 #148

interstellar-space opened this issue Jun 4, 2024 · 0 comments

Comments

@interstellar-space
Copy link

interstellar-space commented Jun 4, 2024

C++推理后处理不能画出车道线。可以帮忙看一下问题吗

#include <iostream>
#include <fstream>
#include <NvInfer.h>
#include <memory>
#include <NvOnnxParser.h>
#include <vector>
#include <cuda_runtime_api.h>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/cudawarping.hpp>
#include <opencv2/core.hpp>
#include <opencv2/cudaarithm.hpp>
#include <algorithm>
#include <numeric>

struct Detection
{
    float background;
    float foreground;
    float start_y;
    float start_x;
    float theta;
    float length;
    float lane_x_coordinates[72];
};

// utilities ----------------------------------------------------------------------------------------------------------
// class to log errors, warnings, and other information during the build and inference phases
class Logger : public nvinfer1::ILogger
{
public:
    void log(Severity severity, const char *msg) noexcept override
    {
        // remove this 'if' if you need more logged info
        if ((severity == Severity::kERROR) || (severity == Severity::kINTERNAL_ERROR))
        {
            std::cout << msg << "\n";
        }
    }
} gLogger;

// destroy TensorRT objects if something goes wrong
struct TRTDestroy
{
    template <class T>
    void operator()(T *obj) const
    {
        if (obj)
        {
            obj->destroy();
        }
    }
};

template <class T>
using TRTUniquePtr = std::unique_ptr<T, TRTDestroy>;

// calculate size of tensor
size_t getSizeByDim(const nvinfer1::Dims &dims)
{
    size_t size = 1;
    for (size_t i = 0; i < dims.nbDims; ++i)
    {
        size *= dims.d[i];
    }
    return size;
}

// preprocessing stage ------------------------------------------------------------------------------------------------
void preprocessImage(cv::cuda::GpuMat &gpu_frame, float *gpu_input, const nvinfer1::Dims &dims)
{
    auto input_width = dims.d[2];
    auto input_height = dims.d[1];
    auto channels = dims.d[0];
    auto input_size = cv::Size(input_width, input_height);
    // resize
    cv::cuda::GpuMat resized;
    cv::cuda::resize(gpu_frame, resized, input_size, 0, 0, cv::INTER_NEAREST);
    // normalize
    cv::cuda::GpuMat flt_image;
    resized.convertTo(flt_image, CV_32FC3, 1.f / 255.f);
    cv::cuda::subtract(flt_image, cv::Scalar(0.485f, 0.456f, 0.406f), flt_image, cv::noArray(), -1);
    cv::cuda::divide(flt_image, cv::Scalar(0.229f, 0.224f, 0.225f), flt_image, 1, -1);
    // to tensor
    std::vector<cv::cuda::GpuMat> chw;
    for (size_t i = 0; i < channels; ++i)
    {
        chw.emplace_back(cv::cuda::GpuMat(input_size, CV_32FC1, gpu_input + i * input_width * input_height));
    }
    cv::cuda::split(flt_image, chw);
}

std::vector<std::vector<float>> softmax(const std::vector<std::vector<float>> &x)
{
    std::vector<std::vector<float>> y(x.size(), std::vector<float>(x[0].size()));

    // Assume that the softmax is performed along the last axis (columns).
    for (size_t i = 0; i < x.size(); ++i)
    {
        float maxVal = *std::max_element(x[i].begin(), x[i].end());

        std::vector<float> expVec(x[i].size());
        float sum = 0.0f;
        for (size_t j = 0; j < x[i].size(); ++j)
        {
            expVec[j] = exp(x[i][j] - maxVal);
            sum += expVec[j];
        }

        for (size_t j = 0; j < y[i].size(); ++j)
        {
            y[i][j] = expVec[j] / sum;
        }
    }

    return y;
}

bool Lane_IOU(const std::vector<float> &parent_box, const std::vector<float> &compared_box, float threshold)
{
    int n_offsets = 72;
    int n_strips = n_offsets - 1;

    int start_a = static_cast<int>(parent_box[2] * n_strips + 0.5);
    int start_b = static_cast<int>(compared_box[2] * n_strips + 0.5);
    int start = std::max(start_a, start_b);
    int end_a = start_a + static_cast<int>(parent_box[4] - 1 + 0.5 - ((parent_box[4] - 1) < 0));
    int end_b = start_b + static_cast<int>(compared_box[4] - 1 + 0.5 - ((compared_box[4] - 1) < 0));
    int end = std::min({end_a, end_b, 71});
    if ((end - start) < 0)
    {
        return false;
    }
    float dist = 0.0f;
    for (int i = 5 + start; i <= 5 + end; ++i)
    {
        if (parent_box[i] < compared_box[i])
        {
            dist += compared_box[i] - parent_box[i];
        }
        else
        {
            dist += parent_box[i] - compared_box[i];
        }
    }
    return dist < (threshold * (end - start + 1));
}

std::vector<int> Lane_nms(const std::vector<std::vector<float>> &proposals, const std::vector<float> &scores, float overlap = 50, int top_k = 4)
{
    std::vector<int> keep_index;
    std::vector<size_t> indices(scores.size());
    std::iota(indices.begin(), indices.end(), 0);

    // Sort indices based on corresponding scores in descending order
    std::sort(indices.begin(), indices.end(), [&scores](int a, int b)
              { return scores[a] > scores[b]; });

    std::vector<int> r_filters(scores.size(), 0);

    for (size_t i = 0; i < indices.size(); ++i)
    {
        size_t index = indices[i];
        if (r_filters[index] == 1) // Ensure we check r_filters for the right index
        {
            continue;
        }

        keep_index.push_back(index);

        if (static_cast<int>(keep_index.size()) > top_k) // We cast size to int to compare with top_k
        {
            break;
        }

        if (i == indices.size() - 1) // If it's the last index, break out of the loop
        {
            break;
        }

        // Iterate over the rest of the proposals from this point on
        for (size_t j = i + 1; j < indices.size(); ++j)
        {
            size_t sub_index = indices[j];
            if (!r_filters[sub_index]) // Check if not already filtered
            {
                if (Lane_IOU(proposals[index], proposals[sub_index], overlap))
                {
                    r_filters[sub_index] = 1;
                }
            }
        }
    }

    // Resize to remove any excess elements in case fewer than top_k were kept
    keep_index.resize(std::min(top_k, static_cast<int>(keep_index.size())));

    return keep_index;
}
std::vector<std::vector<cv::Point2f>> predictions_to_pred(const std::vector<std::vector<float>> &predictions,
                                                          const std::vector<float> &prior_ys,
                                                          int n_strips, int ori_img_w, int ori_img_h, int img_w, int img_h, int cut_height)
{
    std::vector<std::vector<cv::Point2f>> lanes;

    for (const auto &lane : predictions)
    {
        std::vector<float> lane_xs(lane.begin() + 6, lane.end()); // normalized value
        int start = std::min(std::max(0, static_cast<int>(round(lane[2] * n_strips))), n_strips);
        int length = static_cast<int>(round(lane[5]));
        int end = start + length - 1;
        end = std::min(end, static_cast<int>(prior_ys.size()) - 1);

        // Extend prediction until x is outside the image
        std::vector<bool> mask(start, false);
        for (int i = start - 1; i >= 0; --i)
        {
            if (lane_xs[i] < 0.0f || lane_xs[i] > 1.0f)
            {
                mask[i] = true;
            }
            else if (i < start - 1 && mask[i + 1])
            {
                mask[i] = true;
            }
        }

        std::fill(lane_xs.begin() + end + 1, lane_xs.end(), -2.0f);
        for (int i = 0; i < start; ++i)
        {
            if (mask[i])
            {
                lane_xs[i] = -2.0f;
            }
        }

        std::vector<float> lane_ys;
        for (size_t i = 0; i < lane_xs.size(); ++i)
        {
            if (lane_xs[i] >= 0)
            {
                lane_ys.push_back(prior_ys[i]);
            }
        }

        lane_xs.erase(std::remove_if(lane_xs.begin(), lane_xs.end(),
                                     [](float x)
                                     { return x < 0; }),
                      lane_xs.end());

        if (lane_xs.size() <= 1)
        {
            continue;
        }

        std::reverse(lane_xs.begin(), lane_xs.end());
        std::reverse(lane_ys.begin(), lane_ys.end());

        auto scale_x = static_cast<float>(ori_img_w) / img_w;
        auto scale_y = static_cast<float>(ori_img_h) / (img_h - cut_height);

        for (size_t i = 0; i < lane_xs.size(); ++i)
        {
            lane_xs[i] = lane_xs[i] * scale_x;
            lane_ys[i] = (lane_ys[i] * (img_h - cut_height) + cut_height) * scale_y;
        }

        std::vector<cv::Point2f> points;
        for (size_t i = 0; i < lane_xs.size(); ++i)
        {
            points.emplace_back(lane_xs[i], lane_ys[i]);
        }

        std::cout << "lane_xs: ";
        for (const auto &x : lane_xs)
        {
            std::cout << x << " ";
        }
        std::cout << "\nlane_ys: ";
        for (const auto &y : lane_ys)
        {
            std::cout << y << " ";
        }
        std::cout << "\n";

        lanes.push_back(points);
    }

    std::cout << "lanes: " << lanes.size() << "\n";

    return lanes;
}

cv::Mat imshow_lanes(cv::Mat &img, const std::vector<std::vector<cv::Point2f>> &lanes, int width = 4)
{
    std::vector<std::vector<cv::Point>> lanes_xys;

    for (const auto &lane : lanes)
    {
        std::vector<cv::Point> xys;
        for (const auto &point : lane)
        {
            if (point.x <= 0.0f || point.y <= 0.0f)
            {
                continue;
            }
            int x = static_cast<int>(point.x);
            int y = static_cast<int>(point.y);
            xys.emplace_back(x, y);
        }
        if (!xys.empty())
        {
            lanes_xys.push_back(xys);
        }
    }
    std::cout << "lanes_xys: " << lanes_xys.size() << "\n";

    std::sort(lanes_xys.begin(), lanes_xys.end(),
              [](const std::vector<cv::Point> &a, const std::vector<cv::Point> &b)
              { return a[0].x < b[0].x; });

    std::vector<cv::Scalar> COLORS = {cv::Scalar(255, 0, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 0, 255), cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 255)};

    for (size_t idx = 0; idx < lanes_xys.size(); ++idx)
    {
        const auto &xys = lanes_xys[idx];
        for (size_t i = 1; i < xys.size(); ++i)
        {
            cv::line(img, xys[i - 1], xys[i], COLORS[idx % COLORS.size()], width);
        }
    }

    cv::imwrite("test.jpg", img);
    // cv::imshow("Lanes", img);
    // cv::waitKey(0);

    return img;
}

// post-processing stage ----------------------------------------------------------------------------------------------
void postprocessResults(cv::Mat &frame, float *gpu_output, const nvinfer1::Dims &dims, int batch_size)
{
    float conf_threshold = 0.4f;

    // copy results from GPU to CPU
    std::vector<float> cpu_output(getSizeByDim(dims) * batch_size);
    cudaMemcpy(cpu_output.data(), gpu_output, cpu_output.size() * sizeof(float), cudaMemcpyDeviceToHost);
    for (int i = 0; i < cpu_output.size(); ++i)
    {
        std::cout << cpu_output[i] << " ";
        if (!((i + 1) % 78))
        {
            std::cout << "\n\n";
        }
    }
    std::cout << "------------------------\n";
    std::vector<std::vector<float>> detections(cpu_output.size() / 78, std::vector<float>(2));
    for (size_t i = 0; i < cpu_output.size() / 78; ++i)
    {
        detections[i][0] = cpu_output[i * 78];
        detections[i][1] = cpu_output[(i + 1) * 78];
    }
    const auto xyscores = softmax(detections);
    for (const auto &score : xyscores)
    {
        for (const auto &val : score)
        {
            std::cout << val << " ";
        }
        std::cout << "\n";
    }
    std::cout << "------------------------\n";
    std::vector<float> scores;
    std::vector<std::vector<float>> predictions(cpu_output.size() / 78, std::vector<float>(78));
    for (int i = 0; i < xyscores.size(); ++i)
    {
        scores.emplace_back(xyscores[i][1]);
        if (xyscores[i][1] >= conf_threshold)
        {
            std::copy(cpu_output.begin() + i * 78, cpu_output.begin() + (i + 1) * 78, predictions[i].begin());
        }
    }
    std::cout << "predictions: " << predictions.size() << "\n";

    int n_offsets = 72;
    int n_strips = n_offsets - 1;
    int img_w = 800;
    int img_h = 320;
    int ori_img_w = 1280;
    int ori_img_h = 720;
    int cut_height = 160;
    std::vector<std::vector<float>> nms_predictions;
    for (auto &prediction : predictions)
    {
        prediction[5] = std::round(prediction[5] * n_strips);
        std::vector<float> predict(sizeof(Detection) - sizeof(float));
        for (size_t j = 0; j < 4; ++j)
        {
            predict[j] = prediction[j];
        }
        for (size_t j = 5; j < prediction.size(); ++j)
        {
            predict[j - 1] = prediction[j];
        }
        nms_predictions.emplace_back(predict);
    }
    std::cout << "nms_predictions: " << nms_predictions.size() << "\n";

    for (auto &nms_prediction : nms_predictions)
    {
        nms_prediction[4] *= n_strips;
        for (size_t i = 5; i < nms_prediction.size(); ++i)
        {
            nms_prediction[i] *= ori_img_w - 1;
        }
    }

    auto keep = Lane_nms(nms_predictions, scores, 50, 5);
    std::cout << "keep: " << keep.size() << "\n";

    std::vector<float> prior_ys(n_offsets);
    for (int i = 0; i < n_offsets; ++i)
    {
        prior_ys[i] = 1.0f - static_cast<float>(i) / n_strips;
    }

    auto lanes = predictions_to_pred(predictions, prior_ys, n_strips, ori_img_w, ori_img_h, img_w, img_h, cut_height);
    imshow_lanes(frame, lanes);
}

// initialize TensorRT engine and parse ONNX model --------------------------------------------------------------------
void parseOnnxModel(const std::string &model_path, TRTUniquePtr<nvinfer1::ICudaEngine> &engine,
                    TRTUniquePtr<nvinfer1::IExecutionContext> &context)
{
    TRTUniquePtr<nvinfer1::IBuilder> builder{nvinfer1::createInferBuilder(gLogger)};
    TRTUniquePtr<nvinfer1::INetworkDefinition> network{builder->createNetworkV2(1)};
    TRTUniquePtr<nvonnxparser::IParser> parser{nvonnxparser::createParser(*network, gLogger)};
    TRTUniquePtr<nvinfer1::IBuilderConfig> config{builder->createBuilderConfig()};
    // parse ONNX
    if (!parser->parseFromFile(model_path.c_str(), static_cast<int>(nvinfer1::ILogger::Severity::kINFO)))
    {
        std::cerr << "ERROR: could not parse the model.\n";
        return;
    }
    // allow TensorRT to use up to 1GB of GPU memory for tactic selection.
    config->setMaxWorkspaceSize(1ULL << 30);
    // use FP16 mode if possible
    if (builder->platformHasFastFp16())
    {
        std::cout << "fp16\n";
        config->setFlag(nvinfer1::BuilderFlag::kFP16);
    }
    // we have only one image in batch
    builder->setMaxBatchSize(1);
    // generate TensorRT engine optimized for the target platform
    engine.reset(builder->buildEngineWithConfig(*network, *config));
    context.reset(engine->createExecutionContext());
}

// initialize TensorRT engine from serialized model --------------------------------------------------------------------
void loadTrtEngine(const std::string &engine_path, TRTUniquePtr<nvinfer1::IRuntime> &runtime,
                   TRTUniquePtr<nvinfer1::ICudaEngine> &engine,
                   TRTUniquePtr<nvinfer1::IExecutionContext> &context)
{
    std::ifstream engine_file(engine_path, std::ios::binary);
    if (!engine_file)
    {
        std::cerr << "ERROR: could not open the engine file.\n";
        return;
    }

    // 计算文件大小
    engine_file.seekg(0, engine_file.end);
    size_t file_size = engine_file.tellg();
    engine_file.seekg(0, engine_file.beg);

    // 加载文件内容到内存中
    std::vector<char> trt_model_stream(file_size);
    engine_file.read(trt_model_stream.data(), file_size);
    engine_file.close();

    // 创建runtime
    runtime.reset(nvinfer1::createInferRuntime(gLogger));

    // 反序列化计划文件并创建引擎
    engine.reset(runtime->deserializeCudaEngine(trt_model_stream.data(), file_size, nullptr));

    // 创建上下文
    context.reset(engine->createExecutionContext());
}

// main pipeline ------------------------------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        std::cerr << "usage: " << argv[0] << " model.onnx image.jpg\n";
        return -1;
    }
    std::string model_path(argv[1]);
    std::string image_path(argv[2]);
    int batch_size = 1;

    // initialize TensorRT engine and parse ONNX model
    TRTUniquePtr<nvinfer1::IRuntime> runtime{nullptr};
    TRTUniquePtr<nvinfer1::ICudaEngine> engine{nullptr};
    TRTUniquePtr<nvinfer1::IExecutionContext> context{nullptr};
    // parseOnnxModel(model_path, engine, context);
    loadTrtEngine(model_path, runtime, engine, context);

    // get sizes of input and output and allocate memory required for input data and for output data
    std::vector<nvinfer1::Dims> input_dims;               // we expect only one input
    std::vector<nvinfer1::Dims> output_dims;              // and one output
    std::vector<void *> buffers(engine->getNbBindings()); // buffers for input and output data
    for (size_t i = 0; i < engine->getNbBindings(); ++i)
    {
        auto binding_size = getSizeByDim(engine->getBindingDimensions(i)) * batch_size * sizeof(float);
        cudaMalloc(&buffers[i], binding_size);
        if (engine->bindingIsInput(i))
        {
            input_dims.emplace_back(engine->getBindingDimensions(i));
        }
        else
        {
            output_dims.emplace_back(engine->getBindingDimensions(i));
        }
    }
    if (input_dims.empty() || output_dims.empty())
    {
        std::cerr << "Expect at least one input and one output for network\n";
        return -1;
    }

    // read input image
    cv::Mat frame = cv::imread(image_path);
    if (frame.empty())
    {
        std::cerr << "Input image " << image_path << " load failed\n";
        return -1;
    }
    cv::cuda::GpuMat gpu_frame;
    // upload image to GPU
    gpu_frame.upload(frame);

    // preprocess input data
    preprocessImage(gpu_frame, (float *)buffers[0], input_dims[0]);
    // inference
    context->enqueue(batch_size, buffers.data(), 0, nullptr);
    // postprocess results
    postprocessResults(frame, (float *)buffers[1], output_dims[0], batch_size);

    for (void *buf : buffers)
    {
        cudaFree(buf);
    }
    return 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant