Files
ANSCustomModels/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp

1147 lines
33 KiB
C++

#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<cv::Rect>& 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<ImageSection> 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<CustomObject> ANSCustomFS::ConvertToCustomObjects(const std::vector<ANSCENTER::Object>& objects) {
std::vector<CustomObject> 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<ANSCENTER::Object> ANSCustomFS::ProcessExistingDetectedArea(
const cv::Mat& frame,
const std::string& camera_id,
const std::vector<cv::Rect>& 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<ANSCENTER::Object> detectedObjects;
_detector->RunInference(activeROI, camera_id.c_str(), detectedObjects);
if (detectedObjects.empty()) {
UpdateNoDetectionCondition();
return {};
}
std::vector<ANSCENTER::Object> 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<cv::Rect>& fireNSmokeRects,
std::vector<ANSCENTER::Object>& 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<ANSCENTER::Object>& 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<ANSCENTER::Object> filteredObjects;
_filter->RunInference(frame, camera_id.c_str(), filteredObjects);
std::vector<ANSCENTER::Object> 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<ANSCENTER::Object> ANSCustomFS::FindNewDetectedArea(
const cv::Mat& frame,
const std::string& camera_id, cv::Mat& draw)
{
std::vector<ANSCENTER::Object> 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<ANSCENTER::Object> 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<ANSCENTER::Object> 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<ANSCENTER::Object> movementObjects;
_motiondetector->RunInference(activeROI, camera_id.c_str(), movementObjects);
std::vector<ANSCENTER::Object> fireNSmokeRects;
float movementarea_threshold = 0;
int totalMovementObjects = static_cast<int>(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<ANSCENTER::Object>{};
}
std::vector<ANSCENTER::Object> 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<ANSCENTER::Object> filteredObjects;
_filter->RunInference(paddedFrame, camera_id.c_str(), filteredObjects);
if (!filteredObjects.empty()) {
std::vector<ANSCENTER::Object> 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<CustomObject> 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<ANSCENTER::Object>& 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<cv::Mat>& 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<cv::Point2f> prevCorners, currCorners;
cv::goodFeaturesToTrack(prevGray, prevCorners, 100, 0.01, 10);
if (prevCorners.empty()) continue;
std::vector<uchar> status;
std::vector<float> 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<cv::Mat>& frames) {
if (frames.size() < 10) return false;
std::vector<double> 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<int>(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<double>(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<Range> 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<Range>& 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<float>(matched_area) / static_cast<float>(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<float>(intersectionArea) / unionArea;
}
bool ANSCustomFS::IsOverlapping(const ANSCENTER::Object& obj, const std::vector<ANSCENTER::Object>& 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<cv::Rect>& 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<cv::Rect>& 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::ImageSection> 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<int>(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<std::pair<double, ImageSection>> 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<double, ImageSection>& a, const std::pair<double, ImageSection>& 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<CustomObject> ANSCustomFS::RunInference(const cv::Mat& input, const std::string& camera_id) {
std::lock_guard<std::recursive_mutex> 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<ANSCENTER::Object> 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<CustomObject> results = ConvertToCustomObjects(output);
if (results.empty()) {
UpdateNoDetectionCondition();
}
return results;
}
catch (const std::exception& e) {
return {};
}
}
// New helper function to process detected area
std::vector<ANSCENTER::Object> ANSCustomFS::ProcessExistingDetectedArea(
const cv::Mat& frame,
const std::string& camera_id,
cv::Mat& draw)
{
std::vector<ANSCENTER::Object> output;
cv::Mat activeROI = frame(_detectedArea);
// Detect movement and objects
std::vector<ANSCENTER::Object> movementObjects = FindMovementObjects(frame, camera_id, draw);
std::vector<ANSCENTER::Object> 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<ANSCENTER::Object>& output)
{
if (_realFireCheck > FILTERFRAMES) {
AddConfirmedDetection(detectedObj, output);
return true;
}
std::vector<ANSCENTER::Object> 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<ANSCENTER::Object>& output) {
output.push_back(std::move(detectedObj));
_isFireNSmokeDetected = true;
_retainDetectedArea = 0;
_isRealFireFrame = true;
}