#include "ANSCustomFireNSmoke.h" bool ANSCustomFS::IsDetectedAreaValid(const cv::Rect& area) const { return area.width > MIN_ROI_SIZE && area.height > MIN_ROI_SIZE; } bool ANSCustomFS::IsFireOrSmoke(int classId, float confidence) const { return (classId == 0) || // Fire (classId == 2 && confidence >= SMOKE_CONFIDENCE_THRESHOLD); // Smoke } bool ANSCustomFS::ValidateMotionCorrelation(const std::vector& fireNSmokeRects) const { return !fireNSmokeRects.empty() && fireNSmokeRects.size() < MAX_MOTION_TRACKING; } void ANSCustomFS::UpdatePositiveDetection() { _isFireNSmokeDetected = true; _retainDetectedArea = 0; _isRealFireFrame = true; } void ANSCustomFS::ResetDetectedArea() { _detectedArea = cv::Rect(0, 0, 0, 0); } void ANSCustomFS::UpdatePriorityRegion(const cv::Mat& frame) { std::vector sections = divideImage(frame); int lowestPriority = getLowestPriorityRegion(); if (_currentPriority > lowestPriority || _currentPriority == 0) { _currentPriority = getHighestPriorityRegion(); } else { _currentPriority++; } } int ANSCustomFS::CalculateOptimalCropSize(const cv::Mat& frame) const { int maxDim = std::max(frame.cols, frame.rows); if (maxDim > 1920) return 640; if (maxDim > 1280) return 480; if (maxDim > 640) return 320; return 224; } void ANSCustomFS::RefineDetectedArea(const cv::Mat& frame, const ANSCENTER::Object& detection) { int cropSize = CalculateOptimalCropSize(frame); int xc = detection.box.x + detection.box.width / 2; int yc = detection.box.y + detection.box.height / 2; int x1_new = std::clamp(xc - cropSize / 2, 0, frame.cols - cropSize); int y1_new = std::clamp(yc - cropSize / 2, 0, frame.rows - cropSize); _detectedArea = cv::Rect( x1_new, y1_new, std::min(cropSize, frame.cols - x1_new), std::min(cropSize, frame.rows - y1_new) ); } void ANSCustomFS::ResetDetectionState() { ResetDetectedArea(); _retainDetectedArea = 0; _isFireNSmokeDetected = false; } void ANSCustomFS::UpdateNoDetectionCondition() { _isRealFireFrame = false; _realFireCheck = 0; if (_isFireNSmokeDetected) { _retainDetectedArea++; if (_retainDetectedArea >= RETAINFRAMES) { ResetDetectionState(); } } else { ResetDetectionState(); } } std::vector ANSCustomFS::ConvertToCustomObjects(const std::vector& objects) { std::vector results; results.reserve(objects.size()); for (const auto& obj : objects) { if (obj.confidence >= _detectionScoreThreshold) { CustomObject customObj; customObj.box = obj.box; customObj.classId = obj.classId; customObj.confidence = obj.confidence; customObj.cameraId = obj.cameraId; customObj.className = obj.className; customObj.extraInfo = "Detection Score Threshold:" + std::to_string(_detectionScoreThreshold); results.push_back(customObj); } } return results; } void ANSCustomFS::GetModelParameters() { if (!_readROIs) { cv::Rect exRoi; for (auto& roi : _params.ROI_Values) { if (roi.Name.find("ExclusiveROIs") != std::string::npos) { exRoi.x = roi.ROIPoints[0].x; exRoi.y = roi.ROIPoints[0].y; exRoi.width = roi.ROIPoints[1].x - roi.ROIPoints[0].x; exRoi.height = roi.ROIPoints[2].y - roi.ROIPoints[0].y; _exclusiveROIs.push_back(exRoi); } } // Get parameters for (auto& param : _params.Parameters) { if (param.Name.find("SmokeScore") != std::string::npos) { _smokeDetetectionThreshold = std::stof(param.Value); } else if (param.Name.find("Sensitivity") != std::string::npos) { _motionSpecificity = 1.0f - std::stof(param.Value); _motionSpecificity = std::clamp(_motionSpecificity, 0.0f, 1.0f); // Note: Motion detector threshold is set during LoadModelFromFolder. // Runtime threshold update is not supported through ANSLIB API. } } _readROIs = true; } } std::vector ANSCustomFS::ProcessExistingDetectedArea( const cv::Mat& frame, const std::string& camera_id, const std::vector& fireNSmokeRects, cv::Mat& draw) { #ifdef FNS_DEBUG cv::rectangle(draw, _detectedArea, cv::Scalar(255, 255, 0), 2); // Cyan #endif // Run detection on ROI (no clone - just a view into frame) cv::Mat activeROI = frame(_detectedArea); std::vector detectedObjects; _detector->RunInference(activeROI, camera_id.c_str(), detectedObjects); if (detectedObjects.empty()) { UpdateNoDetectionCondition(); return {}; } std::vector output; output.reserve(detectedObjects.size()); for (auto& detectedObj : detectedObjects) { ProcessDetectedObject(frame, detectedObj, camera_id, fireNSmokeRects, output, draw); } if (output.empty()) { UpdateNoDetectionCondition(); } return output; } bool ANSCustomFS::ProcessDetectedObject( const cv::Mat& frame, ANSCENTER::Object& detectedObj, const std::string& camera_id, const std::vector& fireNSmokeRects, std::vector& output, cv::Mat& draw) { // Adjust coordinates to frame space detectedObj.box.x += _detectedArea.x; detectedObj.box.y += _detectedArea.y; detectedObj.cameraId = camera_id; // Check exclusive ROI overlap if (IsROIOverlapping(detectedObj.box, _exclusiveROIs, INCLUSIVE_IOU_THRESHOLD)) { return false; } // Check confidence threshold if (detectedObj.confidence <= _detectionScoreThreshold) { UpdateNoDetectionCondition(); return false; } // Check if fire or smoke if (!IsFireOrSmoke(detectedObj.classId, detectedObj.confidence)) { UpdateNoDetectionCondition(); return false; } // Check for reflection cv::Mat objectMask = frame(detectedObj.box); if (detectReflection(objectMask)) { UpdateNoDetectionCondition(); return false; } // Check area overlap float areaOverlap = calculateIoU(_detectedArea, detectedObj.box); if (areaOverlap >= MAX_AREA_OVERLAP) { UpdateNoDetectionCondition(); return false; } #ifdef FNS_DEBUG cv::Scalar color = (detectedObj.classId == 0) ? cv::Scalar(0, 255, 255) : cv::Scalar(255, 0, 255); // Yellow/Purple cv::rectangle(draw, detectedObj.box, color, 2); #endif // Check motion correlation if (!ValidateMotionCorrelation(fireNSmokeRects)) { UpdateNoDetectionCondition(); return false; } if (!IsOverlapping(detectedObj, fireNSmokeRects, 0)) { UpdateNoDetectionCondition(); return false; } // Filter validation if (!ValidateWithFilter(frame, detectedObj, camera_id, output, draw)) { return false; } return true; } bool ANSCustomFS::ValidateWithFilter( const cv::Mat& frame, const ANSCENTER::Object& detectedObj, const std::string& camera_id, std::vector& output, cv::Mat& draw) { // Skip filter check after sufficient confirmation frames if (_realFireCheck > FILTER_VERIFICATION_FRAMES) { output.push_back(detectedObj); UpdatePositiveDetection(); return true; } // Run filter inference std::vector filteredObjects; _filter->RunInference(frame, camera_id.c_str(), filteredObjects); std::vector excludedObjects; for (const auto& filteredObj : filteredObjects) { if (EXCLUDED_FILTER_CLASSES.find(filteredObj.classId) == EXCLUDED_FILTER_CLASSES.end()) { excludedObjects.push_back(filteredObj); #ifdef FNS_DEBUG cv::rectangle(draw, filteredObj.box, cv::Scalar(0, 255, 0), 2); cv::putText(draw, filteredObj.className, cv::Point(filteredObj.box.x, filteredObj.box.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2); #endif } } // Check if detection overlaps with excluded objects if (excludedObjects.empty() || !IsOverlapping(detectedObj, excludedObjects, 0)) { output.push_back(detectedObj); UpdatePositiveDetection(); _realFireCheck++; return true; } else { // Decrement but don't go negative _realFireCheck = std::max(0, _realFireCheck - 1); _isRealFireFrame = (_realFireCheck > 0); return false; } } std::vector ANSCustomFS::FindNewDetectedArea( const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw) { std::vector output; // Reset detection state _isRealFireFrame = false; _realFireCheck = 0; // Update priority region UpdatePriorityRegion(frame); _detectedArea = getRegionByPriority(_currentPriority); #ifdef FNS_DEBUG cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 4); // Red #endif if (!IsDetectedAreaValid(_detectedArea)) { ResetDetectedArea(); return output; } // Run detection on new area cv::Mat detectedROI = frame(_detectedArea); std::vector detectedObjects; _detector->RunInference(detectedROI, camera_id.c_str(), detectedObjects); if (detectedObjects.empty()) { ResetDetectedArea(); return output; } // Find best detection and refine area float maxScore = 0.0f; ANSCENTER::Object* bestDetection = nullptr; for (auto& detectedObj : detectedObjects) { detectedObj.box.x += _detectedArea.x; detectedObj.box.y += _detectedArea.y; detectedObj.cameraId = camera_id; if (detectedObj.confidence > DETECTION_CONFIDENCE_THRESHOLD && IsFireOrSmoke(detectedObj.classId, detectedObj.confidence) && detectedObj.confidence > maxScore) { maxScore = detectedObj.confidence; bestDetection = &detectedObj; } } if (bestDetection != nullptr) { RefineDetectedArea(frame, *bestDetection); output.push_back(*bestDetection); UpdatePositiveDetection(); } else { ResetDetectedArea(); } return output; } std::vector ANSCustomFS::FindMovementObjects(const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw) { if (_motionSpecificity < 1) { // 1. Extract the detected area cv::Mat activeROI = frame(_detectedArea); // 2. Detect movement in the detected area std::vector movementObjects; _motiondetector->RunInference(activeROI, camera_id.c_str(), movementObjects); std::vector fireNSmokeRects; float movementarea_threshold = 0; int totalMovementObjects = static_cast(movementObjects.size()); if ((totalMovementObjects > 0) && (totalMovementObjects <= 10)) { for (const auto& rect : movementObjects) { ANSCENTER::Object mBbj; mBbj.box = rect.box; mBbj.box.x += _detectedArea.x; mBbj.box.y += _detectedArea.y; movementarea_threshold = calculateIoU(_detectedArea, mBbj.box); if (movementarea_threshold < 0.5) { fireNSmokeRects.push_back(mBbj); #ifdef FNS_DEBUG cv::rectangle(draw, mBbj.box, cv::Scalar(123, 122, 34), 2); #endif } } } activeROI.release(); return fireNSmokeRects; } else return std::vector{}; } std::vector ANSCustomFS::FindExcludedObjects( const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw) { // Final check for filters // Get activeROI with padding to include surrounding area cv::Rect paddedROI = _detectedArea; paddedROI.x -= 100; paddedROI.y -= 100; paddedROI.width += 200; paddedROI.height += 200; // Ensure paddedROI is within frame bounds paddedROI.x = std::max(0, paddedROI.x); paddedROI.y = std::max(0, paddedROI.y); paddedROI.width = std::min(frame.cols - paddedROI.x, paddedROI.width); paddedROI.height = std::min(frame.rows - paddedROI.y, paddedROI.height); // Run filter inference on padded ROI cv::Mat paddedFrame = frame(paddedROI); std::vector filteredObjects; _filter->RunInference(paddedFrame, camera_id.c_str(), filteredObjects); if (!filteredObjects.empty()) { std::vector excludedObjects; for (const auto& filteredObj : filteredObjects) { if (EXCLUDED_FILTER_CLASSES.find(filteredObj.classId) == EXCLUDED_FILTER_CLASSES.end()) { ANSCENTER::Object exOBbj; exOBbj.box.x = paddedROI.x + filteredObj.box.x; exOBbj.box.y = paddedROI.y + filteredObj.box.y; exOBbj.box.width = filteredObj.box.width; exOBbj.box.height = filteredObj.box.height; excludedObjects.push_back(exOBbj); #ifdef FNS_DEBUG cv::rectangle(draw, exOBbj.box, cv::Scalar(23, 25, 0), 2); cv::putText(draw, filteredObj.className, cv::Point(exOBbj.box.x, exOBbj.box.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2); #endif } } } paddedFrame.release(); return filteredObjects; } ANSCustomFS::ANSCustomFS() : EXCLUDED_FILTER_CLASSES({ 13, 59, 60, 68, 69, 70 }) { _smokeDetetectionThreshold = 0; } bool ANSCustomFS::OptimizeModel(bool fp16) { if (!_detector || !_filter) return false; int detectorResult = _detector->Optimize(fp16); int filterResult = _filter->Optimize(fp16); if ((detectorResult != 1) || (filterResult != 1)) return false; return true; } bool ANSCustomFS::ConfigureParameters(CustomParams& param) { param.ROI_Config.clear(); // We do need to have exclusive ROIs for the model CustomROIConfig exclusiveROI; if (this->_params.ROI_Config.empty()) { exclusiveROI.Name = "ExclusiveROIs"; exclusiveROI.Polygon = false; exclusiveROI.Rectangle = true; exclusiveROI.Line = false; exclusiveROI.MinItems = 0; exclusiveROI.MaxItems = 20; exclusiveROI.ROIMatch = "All Corners"; param.ROI_Config.push_back(exclusiveROI); } else { for (auto& roi : this->_params.ROI_Config) { param.ROI_Config.push_back(roi); } } param.ROI_Options.clear(); param.Parameters.clear(); CustomParameter param1; param1.Name = "SmokeScore"; param1.DataType = "float"; param1.NoOfDecimals = 2; param1.MaxValue = 1; param1.MinValue = 0; param1.StartValue = std::to_string(SMOKE_CONFIDENCE_THRESHOLD); param1.ListItems.clear(); param1.DefaultValue = std::to_string(SMOKE_CONFIDENCE_THRESHOLD); param.Parameters.push_back(param1); CustomParameter param2; param2.Name = "Sensitivity"; param2.DataType = "float"; param2.NoOfDecimals = 2; param2.MaxValue = 1; param2.MinValue = 0; param2.StartValue = std::to_string(MOTION_SENSITIVITY); param2.ListItems.clear(); param2.DefaultValue = std::to_string(MOTION_SENSITIVITY); param.Parameters.push_back(param2); param.ROI_Values.clear(); for (auto& roi : this->_params.ROI_Values) { CustomROIValue roiValue; roiValue.Name = roi.Name; roiValue.ROIPoints = roi.ROIPoints; roiValue.ROIMatch = roi.ROIMatch; roiValue.Option = roi.Option; param.ROI_Values.push_back(roiValue); } return true; } std::vector ANSCustomFS::RunInference(const cv::Mat& input) { return RunInference(input, "CustomCam"); } bool ANSCustomFS::Destroy() { try { _detector.reset(); _filter.reset(); _motiondetector.reset(); return true; } catch (...) { return false; } } bool ANSCustomFS::Initialize(const std::string& modelDirectory, float detectionScoreThreshold, std::string& labelMap) { try { _modelDirectory = modelDirectory; _detectionScoreThreshold = detectionScoreThreshold; // Create model instances using factory pattern (ABI-safe) _detector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); _filter = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); _motiondetector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); _detectorModelType = 4; // TENSORRT _detectorDetectionType = 1; // DETECTION _filterModelType = 4; // TENSORRT _filterDetectionType = 1; // DETECTION _motionModelType = 19; // Motion detection (generic CPU) _motionDetectionType = 1; // DETECTION // Check the hardware type engineType = _detector->GetEngineType(); if (engineType == 1) { // NVIDIA GPU: Use TensorRT _detectorModelType = 4; // TENSORRT _detectorDetectionType = 1; // DETECTION _filterModelType = 4; // TENSORRT _filterDetectionType = 1; // DETECTION std::cout << "NVIDIA GPU detected. Using TensorRT" << std::endl; } else { // CPU/Other: Use OpenVINO _detectorModelType = 17; // OPENVINO _detectorDetectionType = 1; // DETECTION _filterModelType = 17; //Yolo v12 _filterDetectionType = 1; // DETECTION std::cout << "CPU detected. Using OpenVINO and ONNX" << std::endl; } this->_hsvThreshold = 0.25; this->_fire_colour.push_back(std::pair( cv::Scalar(0, 0, 179), cv::Scalar(255, 255, 255)) ); this->_smoke_colour.push_back(std::pair( cv::Scalar(0, 0, 51), cv::Scalar(180, 128, 204)) ); if (this->_detectionScoreThreshold < 0.25f) this->_detectionScoreThreshold = 0.25f; // Initialize parameters _params.ROI_Config.clear(); _params.ROI_Options.clear(); _params.Parameters.clear(); _params.ROI_Values.clear(); _smokeDetetectionThreshold = SMOKE_CONFIDENCE_THRESHOLD; #ifdef FNS_DEBUG this->_loadEngineOnCreate = true; #endif int loadEngineOnCreation = _loadEngineOnCreate ? 1 : 0; int autoEngineDetection = 1; std::string licenseKey = ""; std::string motionDetectorLabels; // Load detector model (fire/smoke detector) float detScoreThreshold = _detectionScoreThreshold; float detConfThreshold = 0.5f; float detNMSThreshold = 0.5f; int detResult = _detector->LoadModelFromFolder( licenseKey.c_str(), "detector", "detector.names", detScoreThreshold, detConfThreshold, detNMSThreshold, autoEngineDetection, _detectorModelType, _detectorDetectionType, loadEngineOnCreation, modelDirectory.c_str(), labelMap); if (detResult != 1) { std::cerr << "ANSCustomFS::Initialize: Failed to load detector model." << std::endl; return false; } // Load filter model (COCO general object detector for false positive filtering) float filterScoreThreshold = 0.25f; float filterConfThreshold = 0.5f; float filterNMSThreshold = 0.5f; int filterResult = _filter->LoadModelFromFolder( licenseKey.c_str(), "filter", "filter.names", filterScoreThreshold, filterConfThreshold, filterNMSThreshold, autoEngineDetection, _filterModelType, _filterDetectionType, loadEngineOnCreation, modelDirectory.c_str(), _filterLabels); if (filterResult != 1) { std::cerr << "ANSCustomFS::Initialize: Failed to load filter model." << std::endl; return false; } // Load motion detector model (background subtraction / frame differencing) float motionScoreThreshold = MOTION_SENSITIVITY; float motionConfThreshold = 0.5f; float motionNMSThreshold = 0.5f; int motionResult = _motiondetector->LoadModelFromFolder( licenseKey.c_str(), "motion", "motion.names", motionScoreThreshold, motionConfThreshold, motionNMSThreshold, autoEngineDetection, _motionModelType, _motionDetectionType, loadEngineOnCreation, "", // Empty model directory - motion detector is algorithmic motionDetectorLabels); if (motionResult != 1) { std::cerr << "ANSCustomFS::Initialize: Failed to load motion detector model." << std::endl; return false; } return true; } catch (const std::exception& e) { std::cerr << "ANSCustomFS::Initialize: Exception: " << e.what() << std::endl; return false; } catch (...) { std::cerr << "ANSCustomFS::Initialize: Unknown exception." << std::endl; return false; } } ANSCustomFS::~ANSCustomFS() { Destroy(); } cv::Rect ANSCustomFS::GenerateMinimumSquareBoundingBox(const std::vector& detectedObjects, int minSize) { try { int adMinSize = minSize - 20; if (adMinSize < 0) adMinSize = 0; if (detectedObjects.empty()) return cv::Rect(0, 0, minSize, minSize); int minX = detectedObjects[0].box.x; int minY = detectedObjects[0].box.y; int maxX = detectedObjects[0].box.x + detectedObjects[0].box.width; int maxY = detectedObjects[0].box.y + detectedObjects[0].box.height; for (const auto& rect : detectedObjects) { minX = std::min(minX, rect.box.x); minY = std::min(minY, rect.box.y); maxX = std::max(maxX, rect.box.x + rect.box.width); maxY = std::max(maxY, rect.box.y + rect.box.height); } int width = maxX - minX; int height = maxY - minY; int squareSize = std::max({ width, height, adMinSize }); int centerX = minX + width / 2; int centerY = minY + height / 2; int squareX = centerX - squareSize / 2; int squareY = centerY - squareSize / 2; return cv::Rect(squareX - 10, squareY - 10, squareSize + 20, squareSize + 20); } catch (std::exception& e) { return cv::Rect(0, 0, minSize, minSize); } } bool ANSCustomFS::detectStaticFire(std::deque& frameQueue) { return false; if (frameQueue.size() < 10) return false; double totalFlow = 0.0; int count = 0; for (size_t i = 2; i < frameQueue.size(); i += 3) { cv::Mat prevGray, currGray; cv::resize(frameQueue[i - 2], prevGray, cv::Size(), 0.5, 0.5); cv::resize(frameQueue[i], currGray, cv::Size(), 0.5, 0.5); cv::cvtColor(prevGray, prevGray, cv::COLOR_BGR2GRAY); cv::cvtColor(currGray, currGray, cv::COLOR_BGR2GRAY); std::vector prevCorners, currCorners; cv::goodFeaturesToTrack(prevGray, prevCorners, 100, 0.01, 10); if (prevCorners.empty()) continue; std::vector status; std::vector err; cv::calcOpticalFlowPyrLK(prevGray, currGray, prevCorners, currCorners, status, err); double avgMotion = 0.0; int validPoints = 0; for (size_t j = 0; j < prevCorners.size(); j++) { if (status[j]) { double dx = currCorners[j].x - prevCorners[j].x; double dy = currCorners[j].y - prevCorners[j].y; avgMotion += std::sqrt(dx * dx + dy * dy); validPoints++; } } if (validPoints > 0) avgMotion /= validPoints; totalFlow += avgMotion; count++; } double avgMotion = (count > 0) ? totalFlow / count : 0; if (avgMotion < 0.5) { return true; } return false; } bool ANSCustomFS::detectScreenFlicker(const std::vector& frames) { if (frames.size() < 10) return false; std::vector intensities; for (const auto& frame : frames) { cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); intensities.push_back(cv::mean(gray)[0]); } cv::Mat fftInput(static_cast(intensities.size()), 1, CV_64F, intensities.data()); cv::Mat fftOutput; cv::dft(fftInput, fftOutput, cv::DFT_REAL_OUTPUT); for (int i = 1; i < fftOutput.rows / 2; i++) { double freq = std::abs(fftOutput.at(i, 0)); if (freq > 50 && freq < 70) { return true; } } return false; } bool ANSCustomFS::detectReflection(const cv::Mat& frame) { cv::Mat gray, edges; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); cv::Canny(gray, edges, 50, 150); int edgeCount = cv::countNonZero(edges); return edgeCount > 5000; } bool ANSCustomFS::MajorityColourInFrame(cv::Mat frame, Range range, float area_threshold) { cv::Mat img_hsv; try { cv::Mat blur, fireMask; cv::GaussianBlur(frame, blur, cv::Size(21, 21), 0); cv::cvtColor(blur, img_hsv, cv::COLOR_BGR2HSV); cv::inRange(img_hsv, range.first, range.second, fireMask); cv::erode(fireMask, fireMask, cv::Mat(), cv::Point(-1, -1), 2); cv::dilate(fireMask, fireMask, cv::Mat(), cv::Point(-1, -1), 2); return (cv::countNonZero(fireMask) / (frame.rows * frame.cols)) > area_threshold; } catch (cv::Exception& e) { return false; } } bool ANSCustomFS::MajorityColourInFrame(cv::Mat frame, std::vector ranges, float area_threshold) { unsigned int current_area = 0; cv::Mat img_hsv; try { cv::cvtColor(frame, img_hsv, cv::COLOR_BGR2HSV); for (const Range& range : ranges) { cv::inRange(img_hsv, range.first, range.second, frame); current_area += cv::countNonZero(frame); } } catch (cv::Exception& e) { return false; } return (float)current_area / ((float)frame.rows * frame.cols) > area_threshold; } bool ANSCustomFS::DetectFireNSmokeColourInFrame(const cv::Mat& frame, const cv::Rect& bBox, const std::vector& ranges, float area_threshold) { if (frame.empty()) { return false; } if (ranges.empty()) { return false; } if (area_threshold < 0.0f || area_threshold > 1.0f) { return false; } try { constexpr int padding = 20; const int x1 = std::max(bBox.x - padding, 0); const int y1 = std::max(bBox.y - padding, 0); const int x2 = std::min(bBox.x + bBox.width + padding, frame.cols); const int y2 = std::min(bBox.y + bBox.height + padding, frame.rows); const int roiWidth = x2 - x1; const int roiHeight = y2 - y1; if (roiWidth <= 0 || roiHeight <= 0) { return false; } cv::Mat roiFrame = frame(cv::Rect(x1, y1, roiWidth, roiHeight)); cv::Mat blur; cv::GaussianBlur(roiFrame, blur, cv::Size(21, 21), 0); cv::Mat img_hsv; cv::cvtColor(blur, img_hsv, cv::COLOR_BGR2HSV); cv::Mat combined_mask = cv::Mat::zeros(img_hsv.size(), CV_8U); cv::Mat temp_mask; for (const Range& range : ranges) { cv::inRange(img_hsv, range.first, range.second, temp_mask); combined_mask |= temp_mask; } static const cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::morphologyEx(combined_mask, combined_mask, cv::MORPH_CLOSE, kernel); cv::morphologyEx(combined_mask, combined_mask, cv::MORPH_OPEN, kernel); const int matched_area = cv::countNonZero(combined_mask); const int total_area = roiWidth * roiHeight; const float matched_ratio = static_cast(matched_area) / static_cast(total_area); return matched_ratio > area_threshold; } catch (const cv::Exception& e) { return false; } } bool ANSCustomFS::IsFireDetected(const cv::Mat image, const cv::Rect bBox) { bool isFireColour = this->DetectFireNSmokeColourInFrame(image, bBox, this->_fire_colour, _hsvThreshold); return isFireColour; } bool ANSCustomFS::IsSmokeDetected(const cv::Mat image, const cv::Rect bBox) { bool isSmokeColour = this->DetectFireNSmokeColourInFrame(image, bBox, this->_smoke_colour, 0.9); return isSmokeColour; } float ANSCustomFS::calculateIoU(const cv::Rect& box1, const cv::Rect& box2) { cv::Rect intersection = box1 & box2; int intersectionArea = intersection.area(); if (intersectionArea <= 0) return 0.0f; int unionArea = box1.area() + box2.area() - intersectionArea; if (unionArea <= 0) return 0.0f; return static_cast(intersectionArea) / unionArea; } bool ANSCustomFS::IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold) { if (objectList.empty()) return false; for (const auto& otherObj : objectList) { float iou = calculateIoU(obj.box, otherObj.box); if (iou > iouThreshold) { return true; } } return false; } bool ANSCustomFS::IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold) { if (objectList.empty()) return false; for (const auto& otherObj : objectList) { float iou = calculateIoU(obj.box, otherObj); if (iou > iouThreshold) { return true; } } return false; } bool ANSCustomFS::IsROIOverlapping(const cv::Rect& obj, const std::vector& objectList, float iouThreshold) { if (objectList.empty()) return false; for (const auto& otherObj : objectList) { float iou = calculateIoU(obj, otherObj); if (iou > iouThreshold) { return true; } } return false; } // Functions for screen size division double ANSCustomFS::calculateDistanceToCenter(const cv::Point& center, const cv::Rect& rect) { cv::Point rectCenter(rect.x + rect.width / 2, rect.y + rect.height / 2); return std::sqrt(std::pow(rectCenter.x - center.x, 2) + std::pow(rectCenter.y - center.y, 2)); } std::vector ANSCustomFS::divideImage(const cv::Mat& image) { if (image.empty()) { std::cerr << "Error: Empty image!" << std::endl; return cachedSections; } cv::Size currentSize(image.cols, image.rows); if (currentSize == previousImageSize) { return cachedSections; } previousImageSize = currentSize; cachedSections.clear(); int width = image.cols; int height = image.rows; int maxDimension = std::max(width, height); int numSections = 10; if (maxDimension <= 2560)numSections = 8; if (maxDimension <= 1920)numSections = 6; if (maxDimension <= 1280)numSections = 4; if (maxDimension <= 960)numSections = 2; if (maxDimension <= 480)numSections = 1; int gridRows = static_cast(std::sqrt(numSections)); int gridCols = (numSections + gridRows - 1) / gridRows; int sectionWidth = width / gridCols; int sectionHeight = height / gridRows; cv::Point imageCenter(width / 2, height / 2); std::vector> distancePriorityList; for (int r = 0; r < gridRows; ++r) { for (int c = 0; c < gridCols; ++c) { int x = c * sectionWidth; int y = r * sectionHeight; int w = (c == gridCols - 1) ? width - x : sectionWidth; int h = (r == gridRows - 1) ? height - y : sectionHeight; ImageSection section(cv::Rect(x, y, w, h)); double distance = calculateDistanceToCenter(imageCenter, section.region); distancePriorityList.emplace_back(distance, section); } } std::sort(distancePriorityList.begin(), distancePriorityList.end(), [](const std::pair& a, const std::pair& b) { if (std::abs(a.first - b.first) > 1e-5) { return a.first < b.first; } return a.second.region.y == b.second.region.y ? a.second.region.x < b.second.region.x : a.second.region.y < b.second.region.y; }); int priority = 1; for (auto& entry : distancePriorityList) { entry.second.priority = priority++; cachedSections.push_back(entry.second); } return cachedSections; } int ANSCustomFS::getHighestPriorityRegion() { if (!cachedSections.empty()) { return cachedSections.front().priority; } return 0; } int ANSCustomFS::getLowestPriorityRegion() { if (!cachedSections.empty()) { return cachedSections.back().priority; } return 0; } cv::Rect ANSCustomFS::getRegionByPriority(int priority) { for (const auto& section : cachedSections) { if (section.priority == priority) { return section.region; } } return cv::Rect(); } std::vector ANSCustomFS::RunInference(const cv::Mat& input, const std::string& camera_id) { std::lock_guard lock(_mutex); if (input.empty() || input.cols < MIN_IMAGE_SIZE || input.rows < MIN_IMAGE_SIZE) { return {}; } if (!_detector) { return {}; } try { GetModelParameters(); #ifdef FNS_DEBUG cv::Mat draw = input.clone(); #else cv::Mat draw; #endif std::vector output; // A. Check if detected area is retained and valid if (_detectedArea.width > MIN_ROI_SIZE && _detectedArea.height > MIN_ROI_SIZE) { #ifdef FNS_DEBUG cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 2); for (const auto& roi : _exclusiveROIs) { cv::rectangle(draw, roi, cv::Scalar(255, 0, 0), 2); } #endif output = ProcessExistingDetectedArea(input, camera_id, draw); } else { // B. Find new detected area output = FindNewDetectedArea(input, camera_id, draw); } #ifdef FNS_DEBUG DisplayDebugFrame(draw); #endif std::vector results = ConvertToCustomObjects(output); if (results.empty()) { UpdateNoDetectionCondition(); } return results; } catch (const std::exception& e) { return {}; } } // New helper function to process detected area std::vector ANSCustomFS::ProcessExistingDetectedArea( const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw) { std::vector output; cv::Mat activeROI = frame(_detectedArea); // Detect movement and objects std::vector movementObjects = FindMovementObjects(frame, camera_id, draw); std::vector detectedObjects; _detector->RunInference(activeROI, camera_id.c_str(), detectedObjects); if (detectedObjects.empty()) { UpdateNoDetectionCondition(); return output; } const bool skipMotionCheck = (_motionSpecificity < 0.0f) || (_motionSpecificity >= 1.0f); const bool validMovement = !movementObjects.empty() && movementObjects.size() < MAX_MOTION_TRACKING; for (auto& detectedObj : detectedObjects) { // Adjust coordinates to full frame detectedObj.box.x += _detectedArea.x; detectedObj.box.y += _detectedArea.y; detectedObj.cameraId = camera_id; // Skip if overlapping with exclusive ROIs if (IsROIOverlapping(detectedObj.box, _exclusiveROIs, INCLUSIVE_IOU_THRESHOLD)) { continue; } // Check confidence thresholds const bool isValidFire = (detectedObj.classId == 0) && (detectedObj.confidence >= _detectionScoreThreshold); const bool isValidSmoke = (detectedObj.classId == 2) && (detectedObj.confidence >= _smokeDetetectionThreshold); if (!isValidFire && !isValidSmoke) { UpdateNoDetectionCondition(); continue; } // Check area overlap const float area_threshold = calculateIoU(_detectedArea, detectedObj.box); if (area_threshold >= MAX_AREA_OVERLAP) { UpdateNoDetectionCondition(); continue; } #ifdef FNS_DEBUG cv::Scalar color = (detectedObj.classId == 0) ? cv::Scalar(0, 255, 255) : cv::Scalar(255, 0, 255); cv::rectangle(draw, detectedObj.box, color, 2); #endif // Check motion if (!skipMotionCheck && !validMovement) { UpdateNoDetectionCondition(); continue; } if (!skipMotionCheck && !IsOverlapping(detectedObj, movementObjects, 0)) { UpdateNoDetectionCondition(); continue; } // Process valid detection if (!ProcessValidDetection(frame, camera_id, draw, detectedObj, output)) { continue; } } return output; } bool ANSCustomFS::ProcessValidDetection( const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw, ANSCENTER::Object& detectedObj, std::vector& output) { if (_realFireCheck > FILTERFRAMES) { AddConfirmedDetection(detectedObj, output); return true; } std::vector excludedObjects = FindExcludedObjects(frame, camera_id, draw); if (excludedObjects.empty()) { AddConfirmedDetection(detectedObj, output); return true; } if (!IsOverlapping(detectedObj, excludedObjects, 0)) { AddConfirmedDetection(detectedObj, output); _realFireCheck++; return true; } _realFireCheck = std::max(0, _realFireCheck - 1); if (_realFireCheck <= 0) { _isRealFireFrame = false; } return false; } void ANSCustomFS::AddConfirmedDetection(ANSCENTER::Object& detectedObj, std::vector& output) { output.push_back(std::move(detectedObj)); _isFireNSmokeDetected = true; _retainDetectedArea = 0; _isRealFireFrame = true; }