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:
- Simplified Syntax: Lua-based configuration language, lower learning curve
- Automatic Dependency Management: Built-in package management system
- Cross-Platform Support: Unified configuration files support multiple platforms
- 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:
- Size Normalization: Create a square canvas to avoid image distortion
- Pixel Normalization: Normalize pixel values from [0,255] to [0,1]
- Channel Rearrangement: Convert from HWC (Height x Width x Channel) to CHW format
- 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:
- Confidence Sorting: Sort candidate boxes in descending order of confidence
- IoU Calculation: Calculate overlap (Intersection over Union)
- Overlap Suppression: Remove redundant boxes with IoU exceeding the threshold
- 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
- Batch Processing Optimization: Support multi-frame parallel inference
- Model Quantization: Use INT8 quantization for further acceleration
- Dynamic Resolution: Adjust input size based on hardware performance
- Multi-threaded Processing: Separate inference and post-processing threads
- 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:
- XMake Build System provides a more concise configuration method than CMake
- TensorRT Optimization Engine achieves GPU-accelerated inference
- Intelligent Dual Condition Trigger reduces false alarm rates in fire detection
- Asynchronous Memory Management ensures real-time performance
- 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 ⭐ ⭐ ⭐