In-Depth Guide to YOLOv8 TensorRT C++ Deployment: From XMake Build to Inference Pipeline

This project has been uploaded to GitHub

https://github.com/Bayesianovich/yolov8-fire-smoke-detection, feel free to star ⭐ ⭐

Project Overview

This project is a high-performance fire and smoke detection system based on YOLOv8 and TensorRT, implemented in C++, with the following features:

  • Real-time Detection Performance: Achieves high-performance inference using TensorRT GPU acceleration
  • Intelligent Dual Condition Trigger: Alarm is triggered only when both fire and smoke are detected
  • Modern Build System: Uses XMake instead of CMake for simpler configuration
  • Memory Efficient Management: Zero-copy memory management and asynchronous CUDA stream processing

Technical Architecture

Core Components

Project Structure
├── main.cpp                    # Main application
├── include/
│   └── yolov8_trt_demo.h      # Detector header file
├── src/
│   └── yolov8_trt_demo.cpp    # Detector implementation
├── xmake.lua                  # XMake build configuration
├── classes.txt                # Class label file
└── firesmokev1.engine         # TensorRT engine file

Key Dependency Libraries

  • TensorRT 8.6+: GPU inference acceleration engine
  • CUDA 12.1+: GPU parallel computing platform
  • cuDNN 8.9+: Deep learning GPU acceleration library
  • OpenCV 4.8+: Computer vision processing library

In-Depth Analysis of XMake Build System

Why Choose XMake Over CMake?

XMake has the following advantages over CMake:

  1. Simplified Syntax: Lua-based configuration language, lower learning curve
  2. Automatic Dependency Management: Built-in package management system
  3. Cross-Platform Support: Unified configuration files support multiple platforms
  4. Build Speed: Incremental compilation and parallel build optimizations

XMake Configuration File Explained

1. Basic Project Configuration

-- Basic project information
set_project("yolov8_demo")
set_version("1.0.0")
set_languages("c++17")

-- Build mode configuration
add_rules("mode.debug", "mode.release")

2. Path Variable Management

-- Centralized management of dependency library paths
local tensorrt_root = "F:/TensorRT-8.6.1.6"        
local cudnn_root = "F:/cudnn_64-8.9.0.131"         
local cuda_root = "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v12.1"  
local opencv_root = "F:/opencv_cpu_install"        

3. Target Configuration

target("yolov8_demo")
    set_kind("binary")
    
    -- Source file configuration
    add_files("main.cpp", "src/yolov8_trt_demo.cpp")
    
    -- Header file inclusion
    add_includedirs(
        "include",
        tensorrt_root .. "/include",
        cudnn_root .. "/include",
        cuda_root .. "/include",
        opencv_root .. "/include"
    )
    
    -- Library file linking
    add_linkdirs(
        tensorrt_root .. "/lib",
        cudnn_root .. "/lib/x64",
        cuda_root .. "/lib/x64",
        opencv_root .. "/lib"
    )

4. Dependency Library Linking

-- Core TensorRT libraries
add_links(
    "nvinfer",          -- Inference engine
    "nvinfer_plugin",   -- Plugin support
    "nvonnxparser",     -- ONNX parsing
    "nvparsers"         -- Other format parsing
)

-- CUDA runtime libraries
add_links("cudart", "cublas", "curand")

-- cuDNN deep learning library
add_links("cudnn")

-- OpenCV modules (version specific)
add_links(
    "opencv_core480",
    "opencv_imgproc480", 
    "opencv_imgcodecs480",
    "opencv_highgui480",
    "opencv_videoio480",
    "opencv_video480",
    "opencv_dnn480"
)

5. Automated Build Post-Processing

after_build(function (target)
    local targetdir = target:targetdir()
    
    -- Automatically copy necessary DLL files
    os.trycp(opencv_root .. "/bin/opencv_*.dll", targetdir)
    os.trycp(tensorrt_root .. "/lib/*.dll", targetdir)
    os.trycp(cuda_root .. "/bin/cudart64*.dll", targetdir)
    os.trycp(cudnn_root .. "/bin/*.dll", targetdir)
    
    -- Copy project resource files
    os.trycp("*.engine", targetdir)
    os.trycp("classes.txt", targetdir)
end)

Common Build Commands

# Build release version (recommended)
xmake build

# Build debug version
xmake config --mode=debug
xmake build

# Clean build files
xmake clean

# Clean TensorRT engine files
xmake clean-engines

# Run program
xmake run yolov8_demo

# Display project information
xmake info

YOLOv8 TensorRT Detector Implementation Analysis

Core Class Structure

class YOLOv8TRTDetector {
private:
    // TensorRT components
    nvinfer1::IRuntime* runtime;
    nvinfer1::ICudaEngine* engine;
    nvinfer1::IExecutionContext* context;
    
    // Memory management
    void* buffers[2];              // GPU input/output buffers
    std::vector prob;       // CPU output buffer
    cudaStream_t stream;           // CUDA asynchronous stream
    
    // Model parameters
    float conf_threshold = 0.25f;
    float iou_threshold = 0.25f;
    int inputH = 640, inputW = 640;
    
public:
    void initConfig(const std::string& engine_file, 
                   float conf_threshold, float iou_threshold);
    void detect(cv::Mat& frame, std::vector& results);
    ~YOLOv8TRTDetector();
};

Detection Result Structure

struct DetectResult {
    int class_id;      // Class ID (0=Fire, 1=Smoke)
    float conf;        // Confidence
    cv::Rect box;      // Bounding box
};

Inference Data Flow Explained

1. Image Preprocessing Stage

// Create square canvas (proportional scaling strategy)
int max_side = std::max(original_h, original_w);
cv::Mat canvas = cv::Mat::zeros(max_side, max_side, CV_8UC3);

// Copy original image to top-left corner
cv::Rect roi(0, 0, original_w, original_h);
frame.copyTo(canvas(roi));

// Preprocess using OpenCV DNN
cv::Mat tensor = cv::dnn::blobFromImage(
    canvas,                    // Input image
    1.0/255.0,                // Normalization factor
    cv::Size(inputW, inputH), // Target size 640x640
    cv::Scalar(0,0,0),        // Mean
    true,                     // BGR to RGB
    false                     // No cropping
);

Preprocessing Steps Explained:

  1. Size Normalization: Create a square canvas to avoid image distortion
  2. Pixel Normalization: Normalize pixel values from [0,255] to [0,1]
  3. Channel Rearrangement: Convert from HWC (Height x Width x Channel) to CHW format
  4. Color Space Conversion: Convert BGR to RGB to match model training data

2. Asynchronous Data Transfer

// Asynchronous transfer from CPU to GPU
cudaMemcpyAsync(
    buffers[0],                           // Target GPU buffer
    tensor.ptr(),                  // Source CPU data
    inputH * inputW * 3 * sizeof(float), // Data size
    cudaMemcpyHostToDevice,               // Transfer direction
    stream                                // CUDA stream
);

Advantages:

  • Asynchronous Execution: CPU and GPU work in parallel, improving efficiency
  • Memory Bandwidth Optimization: Reduces synchronization wait time
  • Pipelined Processing: Enables parallel processing of multiple frames

3. TensorRT Inference Execution

// Execute inference
context->enqueueV2(buffers, stream, nullptr);

TensorRT Advantages:

  • Graph Optimization: Automatically optimizes network structure
  • Precision Optimization: Supports FP16/INT8 quantization
  • Kernel Fusion: Reduces memory access frequency
  • Dynamic Shapes: Supports variable input sizes

4. Result Parsing and NMS Processing

// Parse detection results (format: 1×6×8400)
cv::Mat detMat(output_feat, output_detbox, CV_32F, (float*)prob.data());
cv::Mat detMat_t = detMat.t(); // Transpose to 8400×6

for (int i = 0; i < detMat_t.rows; ++i) {
    // Extract class probabilities
    cv::Mat scores = detMat_t.row(i).colRange(4, output_feat);
    
    // Get highest probability class
    cv::Point classIdPoint;
    double max_class_score;
    cv::minMaxLoc(scores, 0, &max_class_score, 0, &classIdPoint);
    
    if (max_class_score > conf_threshold) {
        // Extract bounding box coordinates (center point format)
        float cx = detMat_t.at(i, 0);
        float cy = detMat_t.at(i, 1);
        float w = detMat_t.at(i, 2);
        float h = detMat_t.at(i, 3);
        
        // Convert to top-left corner coordinates format
        int left = static_cast(cx - w / 2);
        int top = static_cast(cy - h / 2);
        
        // Map coordinates back to original image size
        left = std::max(0, static_cast(left * x_scale));
        top = std::max(0, static_cast(top * y_scale));
    }
}

Data Format Explanation:

  • Output Dimensions: 1×6×8400
  • 6 Channels: [cx, cy, w, h, conf, class_prob…]
  • 8400 Candidate Boxes: From 3 different scales of feature maps

5. NMS Non-Maximum Suppression

std::vector nms_indices;
cv::dnn::NMSBoxes(
    boxes,          // Candidate boxes
    confidences,    // Confidence scores
    conf_threshold, // Confidence threshold
    iou_threshold,  // IoU threshold
    nms_indices     // Output retained box indices
);

NMS Algorithm Principle:

  1. Confidence Sorting: Sort candidate boxes in descending order of confidence
  2. IoU Calculation: Calculate overlap (Intersection over Union)
  3. Overlap Suppression: Remove redundant boxes with IoU exceeding the threshold
  4. Optimal Retention: Retain only the best detection box for each target

6. Coordinate System Transformation

Due to proportional scaling and padding during preprocessing, detection results need to be mapped back to the original image coordinate system:

// Calculate scaling factors
float x_scale = canvas.cols / static_cast(inputW);
float y_scale = canvas.rows / static_cast(inputH);

// Coordinate transformation and boundary check
left = std::max(0, static_cast(left * x_scale));
top = std::max(0, static_cast(top * y_scale));
width = std::min(static_cast(width * x_scale), original_w - left);
height = std::min(static_cast(height * y_scale), original_h - top);

Main Application Logic

Core Business Process

int main() {
    // 1. Initialize detector
    auto detector = std::make_shared();
    detector->initConfig("firesmokev1.engine", 0.25f, 0.25f);
    
    // 2. Video processing loop
    while (true) {
        cap.read(frame);
        detector->detect(frame, results);
        
        // 3. Dual condition judgment logic
        bool has_fire = false, has_smoke = false;
        for (const auto& result : results) {
            if (result.class_id == 0) has_fire = true;   // Fire
            if (result.class_id == 1) has_smoke = true;  // Smoke
        }
        
        // 4. Alarm is triggered only when both fire and smoke are detected
        if (has_fire && has_smoke) {
            // Draw alert box and save frame
            drawAlertBox(frame, results);
            saveFrame(frame, frame_count);
        }
    }
}

Intelligent Dual Condition Trigger Mechanism

// Dual condition check
if (has_fire && has_smoke) {
    // Draw smoke detection box (orange)
    for (const auto& result : results) {
        if (result.class_id == 1) {  // Only draw smoke
            cv::rectangle(frame, result.box, cv::Scalar(0, 165, 255), 3);
            std::string label = "SMOKE " + cv::format("%.2f", result.conf) 
                              + " [FIRE+SMOKE ALERT!]";
        }
    }
    
    // Save alert frame
    std::string save_name = save_dir + "/frame_" + cv::format("%04d", frame_count) + ".jpg";
    cv::imwrite(save_name, frame);
    
} else {
    // Display normal status
    cv::putText(frame, "Normal - No Fire+Smoke Condition", ...);
}

Design Philosophy:

  • Reduce False Alarms: Individual fire or smoke does not trigger an alarm
  • Improve Accuracy: Dual confirmation mechanism ensures real fire detection
  • Clear Visualization: Different states have clear visual feedback

Performance Optimization Strategies

1. Memory Management Optimization

// Pre-allocate GPU memory to avoid runtime allocation
cudaMalloc(&buffers[0], inputH * inputW * 3 * sizeof(float));
cudaMalloc(&buffers[1], output_feat * output_detbox * sizeof(float));

// Pre-allocate CPU buffer
prob.resize(output_feat * output_detbox);

2. CUDA Stream Asynchronous Processing

// Create CUDA stream
cudaStreamCreate(&stream);

// Asynchronous memory copy and inference
cudaMemcpyAsync(..., stream);
context->enqueueV2(buffers, stream, nullptr);
cudaMemcpyAsync(..., stream);

// Synchronize wait for completion
cudaStreamSynchronize(stream);

3. Memory Bandwidth Optimization

  • Zero-Copy Technology: Process data directly on the GPU
  • Memory Pool Reuse: Avoid frequent allocation and deallocation
  • Data Prefetching: Load next frame data in advance

4. FPS Performance Monitoring

int64 start = cv::getTickCount();
// ... Inference process ...
int64 end = cv::getTickCount();
float fps = cv::getTickFrequency() / (end - start);
cv::putText(frame, cv::format("FPS: %.2f", fps), ...);

Deployment Recommendations and Best Practices

1. Environment Configuration

# Ensure CUDA version compatibility
nvidia-smi  # Check CUDA version

# Verify TensorRT installation
ls $TRT_ROOT/lib/  # Check library files

# Check cuDNN version
cat $CUDNN_ROOT/include/cudnn.h | grep CUDNN_MAJOR

2. Model Optimization

// Recommended TensorRT configuration
builder->setMaxBatchSize(1);
config->setMaxWorkspaceSize(1 << 30);  // 1GB
config->setFlag(BuilderFlag::kFP16);   // Enable FP16

3. Error Handling

// Resource initialization validation
if (!runtime || !engine || !context) {
    std::cerr << "TensorRT initialization failed" << std::endl;
    exit(-1);
}

// CUDA error checking
#define CUDA_CHECK(call) \
    do { \
        cudaError_t err = call; \
        if (err != cudaSuccess) { \
            std::cerr << "CUDA error: " << cudaGetErrorString(err) << std::endl; \
            exit(-1); \
        } \
    } while(0)

4. Production Environment Recommendations

  1. Batch Processing Optimization: Support multi-frame parallel inference
  2. Model Quantization: Use INT8 quantization for further acceleration
  3. Dynamic Resolution: Adjust input size based on hardware performance
  4. Multi-threaded Processing: Separate inference and post-processing threads
  5. Memory Monitoring: Regularly check GPU memory usage

Conclusion

This project demonstrates how to build a high-performance deep learning inference application using a modern C++ technology stack:

  1. XMake Build System provides a more concise configuration method than CMake
  2. TensorRT Optimization Engine achieves GPU-accelerated inference
  3. Intelligent Dual Condition Trigger reduces false alarm rates in fire detection
  4. Asynchronous Memory Management ensures real-time performance
  5. Comprehensive Error Handling ensures system stability

This project can serve as a reference template for other deep learning C++ deployment projects, especially in industrial monitoring, security systems, and other applications with high real-time requirements.

Through reasonable architectural design and performance optimization, we successfully implemented an efficient and stable fire and smoke detection system, providing a reliable technical foundation for practical applications.

If you want to see the complete code, I have uploaded it to GitHub

Click on yolov8-fire-smoke-detection to jump, feel free to Star ⭐ ⭐ ⭐

Leave a Comment