commit fed40b0c9016587e5d5ae2215692a52be0ccbfbd Author: Tuan Nghia Nguyen Date: Mon Mar 30 21:43:47 2026 +1100 Initial commit to add all 3 custom projects diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..03457ee --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "Bash(find C:ProjectsANLSANSLIBANSCustomHelmetDetection -type f -name *)", + "mcp__desktop-commander__read_file", + "Bash(grep -E \"\\\\.\\(cpp|h|c|vcxproj|sln|props\\)$\")", + "Bash(find /c/Projects/CLionProjects/ANSCustomModels -name *.cmake -o -name CMakeLists.txt)", + "Bash(cp \"C:/Projects/ANLS/ANSLIB/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp\" \"C:/Projects/CLionProjects/ANSCustomModels/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp\")", + "Bash(cp \"C:/Projects/ANLS/ANSLIB/ANSCustomFireNSmokeDetection/dllmain.cpp\" \"C:/Projects/CLionProjects/ANSCustomModels/ANSCustomFireNSmokeDetection/dllmain.cpp\")", + "Bash(cp \"C:/Projects/ANLS/ANSLIB/ANSCustomFireNSmokeDetection/framework.h\" \"C:/Projects/CLionProjects/ANSCustomModels/ANSCustomFireNSmokeDetection/framework.h\")", + "Bash(cp \"C:/Projects/ANLS/ANSLIB/ANSCustomFireNSmokeDetection/pch.h\" \"C:/Projects/CLionProjects/ANSCustomModels/ANSCustomFireNSmokeDetection/pch.h\")", + "Bash(cp \"C:/Projects/ANLS/ANSLIB/ANSCustomFireNSmokeDetection/pch.cpp\" \"C:/Projects/CLionProjects/ANSCustomModels/ANSCustomFireNSmokeDetection/pch.cpp\")" + ] + } +} diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ANSCustomModels.iml b/.idea/ANSCustomModels.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/ANSCustomModels.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..fef3201 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,249 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9966412 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp b/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp new file mode 100644 index 0000000..23c811e --- /dev/null +++ b/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.cpp @@ -0,0 +1,1146 @@ +#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; +} diff --git a/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.h b/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.h new file mode 100644 index 0000000..0463f46 --- /dev/null +++ b/ANSCustomFireNSmokeDetection/ANSCustomFireNSmoke.h @@ -0,0 +1,156 @@ +#include "ANSLIB.h" +#include +#include + +#define RETAINFRAMES 80 +#define FILTERFRAMES 10 +//#define FNS_DEBUG +class CUSTOM_API ANSCustomFS : public IANSCustomClass +{ + typedef std::pair Range; + struct ImageSection { + cv::Rect region; + int priority; + ImageSection(const cv::Rect& r) : region(r), priority(0) {} + }; +private: + using ANSLIBPtr = std::unique_ptr; + + int engineType{ 0 }; + ANSLIBPtr _motiondetector{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + ANSLIBPtr _detector{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + ANSLIBPtr _filter{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + + std::recursive_mutex _mutex; + cv::Rect _detectedArea; + cv::Rect _previousDetectedArea; + int _retainDetectedArea{ 0 }; + bool _isFireNSmokeDetected{ false }; + float _detectionScoreThreshold{ 0.5 }; + std::vector _frameHistory; + std::deque _frameQueue; + std::string _filterLabels; + int _realFireCheck{ 0 }; + bool _isRealFireFrame{ false }; + double _hsvThreshold; + std::vector _smoke_colour; + std::vector _fire_colour; + + // ----- Model config ----- + int _detectorModelType; + int _detectorDetectionType; + int _filterModelType; + int _filterDetectionType; + int _motionModelType; + int _motionDetectionType; + + // Add these constants at class level + int MIN_ROI_SIZE = 50; + float SMOKE_CONFIDENCE_THRESHOLD = 0.7f; + float DETECTION_CONFIDENCE_THRESHOLD = 0.5f; + float MAX_AREA_OVERLAP = 0.5f; + int MAX_MOTION_TRACKING = 10; + int FILTER_VERIFICATION_FRAMES = 10; + int MIN_IMAGE_SIZE = 10; + float INCLUSIVE_IOU_THRESHOLD = 0.001f; + float MOTION_SENSITIVITY = 0.5f; + + // Excluded filter class IDs + const std::unordered_set EXCLUDED_FILTER_CLASSES = + { + 13, // bench + 59, // bed + 60, // dining table + 68, // microwave + 69, // oven + 70 // toaster + }; + + cv::Size previousImageSize = cv::Size(0, 0); + std::vector cachedSections; + int _currentPriority{ 0 }; + bool _readROIs{ false }; + std::vector _exclusiveROIs; + float _smokeDetetectionThreshold{ 0 }; + float _motionSpecificity{ 0 }; + + cv::Rect GenerateMinimumSquareBoundingBox(const std::vector& detectedObjects, int minSize = 640); + void UpdateNoDetectionCondition(); + bool detectStaticFire(std::deque& frameQueue); + bool detectScreenFlicker(const std::vector& frames); + bool detectReflection(const cv::Mat& frame); + bool MajorityColourInFrame(cv::Mat frame, Range range, float area_threshold); + bool MajorityColourInFrame(cv::Mat frame, std::vector ranges, float area_threshold); + bool DetectFireNSmokeColourInFrame(const cv::Mat& frame, const cv::Rect& bBox, const std::vector& ranges, float area_threshold); + bool IsFireDetected(const cv::Mat image, const cv::Rect bBox); + bool IsSmokeDetected(const cv::Mat image, const cv::Rect bBox); + float calculateIoU(const cv::Rect& box1, const cv::Rect& box2); + bool IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold = 0.5); + bool IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold = 0.5); + + bool IsROIOverlapping(const cv::Rect& obj, const std::vector& objectList, float iouThreshold = 0.5); + bool IsDetectedAreaValid(const cv::Rect& area) const; + bool IsFireOrSmoke(int classId, float confidence) const; + bool ValidateMotionCorrelation(const std::vector& fireNSmokeRects) const; + void UpdatePositiveDetection(); + void ResetDetectedArea(); + void UpdatePriorityRegion(const cv::Mat& frame); + int CalculateOptimalCropSize(const cv::Mat& frame) const; + void RefineDetectedArea(const cv::Mat& frame, const ANSCENTER::Object& detection); + std::vector ConvertToCustomObjects(const std::vector& objects); + void ResetDetectionState(); + void GetModelParameters(); + std::vector ProcessExistingDetectedArea(const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw); + bool ProcessValidDetection(const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw, ANSCENTER::Object& detectedObj, std::vector& output); + void AddConfirmedDetection(ANSCENTER::Object& detectedObj, std::vector& output); +#ifdef FNS_DEBUG + void DisplayDebugFrame(cv::Mat& draw) { + cv::resize(draw, draw, cv::Size(1920, (1920 * draw.rows) / draw.cols)); + cv::imshow("Combined Detected Areas", draw); + cv::waitKey(1); + } +#endif + // Function to separate screen size + double calculateDistanceToCenter(const cv::Point& center, const cv::Rect& rect); + std::vector divideImage(const cv::Mat& image); + int getHighestPriorityRegion(); + int getLowestPriorityRegion(); + cv::Rect getRegionByPriority(int priority); + + std::vector ProcessExistingDetectedArea( + const cv::Mat& frame, + const std::string& camera_id, + const std::vector& fireNSmokeRects, cv::Mat& draw); + bool ProcessDetectedObject( + const cv::Mat& frame, + ANSCENTER::Object& detectedObj, + const std::string& camera_id, + const std::vector& fireNSmokeRects, + std::vector& output, cv::Mat& draw); + bool ValidateWithFilter( + const cv::Mat& frame, + const ANSCENTER::Object& detectedObj, + const std::string& camera_id, + std::vector& output, cv::Mat& draw); + std::vector FindNewDetectedArea( + const cv::Mat& frame, + const std::string& camera_id, cv::Mat& draw); + + std::vector FindMovementObjects( + const cv::Mat& frame, + const std::string& camera_id, cv::Mat& draw); + + std::vector FindExcludedObjects( + const cv::Mat& frame, + const std::string& camera_id, cv::Mat& draw); + +public: + bool Initialize(const std::string& modelDiretory, float detectionScoreThreshold, std::string& labelMap) override; + bool OptimizeModel(bool fp16) override; + bool ConfigureParameters(CustomParams& param) override; + std::vector RunInference(const cv::Mat& input) override; + std::vector RunInference(const cv::Mat& input, const std::string& camera_id) override; + bool Destroy() override; + ANSCustomFS(); + ~ANSCustomFS(); +}; diff --git a/ANSCustomFireNSmokeDetection/CMakeLists.txt b/ANSCustomFireNSmokeDetection/CMakeLists.txt new file mode 100644 index 0000000..f4a9cc6 --- /dev/null +++ b/ANSCustomFireNSmokeDetection/CMakeLists.txt @@ -0,0 +1,60 @@ +project(ANSCustomFireNSmokeDetection LANGUAGES CXX) + +# ---------- sources ---------- +set(SOURCES + ANSCustomFireNSmoke.cpp + dllmain.cpp + pch.cpp +) + +set(HEADERS + ANSCustomFireNSmoke.h + framework.h + pch.h +) + +# ---------- shared library (DLL) ---------- +add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS}) + +# ---------- preprocessor definitions ---------- +target_compile_definitions(${PROJECT_NAME} PRIVATE + ANSCUSTOMFIRENSMOKE_EXPORTS + _WINDOWS + _USRDLL + WIN32_LEAN_AND_MEAN + NOMINMAX + $<$:_DEBUG> + $<$:NDEBUG> +) + +# ---------- include directories ---------- +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + C:/Projects/ANLS/ANSLIB/ANSLIB + C:/ANSLibs/opencv/include +) + +# ---------- library directories & linking ---------- +target_link_directories(${PROJECT_NAME} PRIVATE + C:/ProgramData/ANSCENTER/Shared + C:/ANSLibs/opencv/x64/vc17/lib +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + ANSLIB + opencv_world4130 +) + +# ---------- compiler options (MSVC) ---------- +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /W3 # Warning level 3 + /sdl # SDL checks + /permissive- # Conformance mode + $<$:/O2 /Oi /GL> # Optimize + intrinsics + whole-program opt + ) + + target_link_options(${PROJECT_NAME} PRIVATE + $<$:/OPT:REF /OPT:ICF /LTCG> # Optimize refs, COMDAT folding, link-time codegen + ) +endif() diff --git a/ANSCustomFireNSmokeDetection/dllmain.cpp b/ANSCustomFireNSmokeDetection/dllmain.cpp new file mode 100644 index 0000000..86be386 --- /dev/null +++ b/ANSCustomFireNSmokeDetection/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ANSCustomFireNSmoke.h" +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// expose the class to the outside world +extern "C" __declspec(dllexport) IANSCustomClass* Create() { + return new ANSCustomFS(); +} \ No newline at end of file diff --git a/ANSCustomFireNSmokeDetection/framework.h b/ANSCustomFireNSmokeDetection/framework.h new file mode 100644 index 0000000..4cae05b --- /dev/null +++ b/ANSCustomFireNSmokeDetection/framework.h @@ -0,0 +1,7 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX // Prevent windows.h from defining min/max macros + // which break std::min / std::max (C2589) +// Windows Header Files +#include diff --git a/ANSCustomFireNSmokeDetection/pch.cpp b/ANSCustomFireNSmokeDetection/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/ANSCustomFireNSmokeDetection/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/ANSCustomFireNSmokeDetection/pch.h b/ANSCustomFireNSmokeDetection/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/ANSCustomFireNSmokeDetection/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.cpp b/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.cpp new file mode 100644 index 0000000..6f0136d --- /dev/null +++ b/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.cpp @@ -0,0 +1,257 @@ +#include "ANSCustomCodeHelmetDetection.h" + +static std::string toUpperCase(const std::string& input) { + std::string result = input; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::toupper(c); }); + return result; +} + +ANSCustomHMD::ANSCustomHMD() { + _isInitialized = false; + _readROIs = false; +} + +ANSCustomHMD::~ANSCustomHMD() { + Destroy(); +} + +bool ANSCustomHMD::Destroy() { + try { + _detector.reset(); + _classifier.reset(); + _isInitialized = false; + return true; + } + catch (...) { + return false; + } +} + +bool ANSCustomHMD::OptimizeModel(bool fp16) { + try { + if (!_detector || !_classifier) return false; + int detectorResult = _detector->Optimize(fp16); + int classifierResult = _classifier->Optimize(fp16); + if ((detectorResult != 1) || (classifierResult != 1)) return false; + else return true; + } + catch (...) { + return false; + } +} + +bool ANSCustomHMD::Initialize(const std::string& modelDirectory, float detectionScoreThreshold, std::string& labelMap) { + try { + _modelDirectory = modelDirectory; + _detectionScoreThreshold = detectionScoreThreshold; + _isInitialized = false; + + // Create model instances using factory pattern (ABI-safe) + _detector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); + _classifier = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); + + // NVIDIA GPU: Use TensorRT + _detectorModelType = 31; // TENSORRT + _detectorDetectionType = 1; // DETECTION + _classifierModelType = 31; // TENSORRT + _classifierDetectionType = 0; // CLASSIFICATION + + //Check the hardware type + engineType = _detector->GetEngineType(); + if (engineType == 1) { + // NVIDIA GPU: Use TensorRT + _detectorModelType = 31; // TENSORRT + _detectorDetectionType = 1; // DETECTION + _classifierModelType = 31; // TENSORRT + _classifierDetectionType = 0; // CLASSIFICATION + std::cout << "NVIDIA GPU detected. Using TensorRT" << std::endl; + } + else { + // CPU/Other: Use YOLO + _detectorModelType = 3; // YOLOV8/YOLOV11 + _detectorDetectionType = 1; // DETECTION + _classifierModelType = 20; // ANSONNXCL + _classifierDetectionType = 0; // CLASSIFICATION + std::cout << "CPU detected. Using YOLO/ANSONNXCL" << std::endl; + } + + if (_detectionScoreThreshold < 0.25f) _detectionScoreThreshold = 0.25f; + + // classId: 0=license plate, 1=motorcyclist, 2=helmet, 3=no_helmet + labelMap = "license plate,motorcyclist,helmet,no_helmet"; + +#ifdef FNS_DEBUG + this->_loadEngineOnCreate = true; +#endif + int loadEngineOnCreation = _loadEngineOnCreate ? 1 : 0; + int autoEngineDetection = 1; + std::string licenseKey = ""; + + // Load detector model + float detScoreThreshold = _detectionScoreThreshold; + float detConfThreshold = 0.5f; + float detNMSThreshold = 0.5f; + std::string detLabelMap; + int detResult = _detector->LoadModelFromFolder( + licenseKey.c_str(), + "detector", "detector.names", + detScoreThreshold, detConfThreshold, detNMSThreshold, + autoEngineDetection, + _detectorModelType, _detectorDetectionType, + loadEngineOnCreation, + modelDirectory.c_str(), + detLabelMap); + if (detResult != 1) { + std::cerr << "ANSCustomHMD::Initialize: Failed to load detector model." << std::endl; + return false; + } + + // Load classifier model + float clsScoreThreshold = 0.25f; + float clsConfThreshold = 0.5f; + float clsNMSThreshold = 0.5f; + int clsResult = _classifier->LoadModelFromFolder( + licenseKey.c_str(), + "classifier", "classifier.names", + clsScoreThreshold, clsConfThreshold, clsNMSThreshold, + autoEngineDetection, + _classifierModelType, _classifierDetectionType, + loadEngineOnCreation, + modelDirectory.c_str(), + _classifierLabels); + if (clsResult != 1) { + std::cerr << "ANSCustomHMD::Initialize: Failed to load classifier model." << std::endl; + return false; + } + + _isInitialized = true; + return true; + } + catch (const std::exception& e) { + std::cerr << "ANSCustomHMD::Initialize: Exception: " << e.what() << std::endl; + return false; + } + catch (...) { + std::cerr << "ANSCustomHMD::Initialize: Unknown exception." << std::endl; + return false; + } +} + +bool ANSCustomHMD::ConfigureParameters(CustomParams& param) { + param.ROI_Config.clear(); + param.ROI_Options.clear(); + param.Parameters.clear(); + param.ROI_Values.clear(); + return true; +} + +std::vector ANSCustomHMD::RunInference(const cv::Mat& input) { + return RunInference(input, "CustomCam"); +} + +std::vector ANSCustomHMD::RunInference(const cv::Mat& input, const std::string& camera_id) { + std::lock_guard lock(_mutex); + + if (!_isInitialized || !_detector) { + return {}; + } + + if (input.empty() || input.cols < 10 || input.rows < 10) { + return {}; + } + + try { + // One-time parameter reading + if (!_readROIs) { + for (const auto& param : _params.Parameters) { + if (param.Name.find("ALPR") != std::string::npos) { + std::string paramValue = toUpperCase(param.Value); + // ALPR feature currently disabled + } + } + _readROIs = true; + } + + // Run object detection + std::vector detectionResults; + _detector->RunInference(input, camera_id.c_str(), detectionResults); + + if (detectionResults.empty()) { + return {}; + } + + std::vector results; + results.reserve(detectionResults.size()); + + const cv::Rect frameBounds(0, 0, input.cols, input.rows); + const float scoreThreshold = _detectionScoreThreshold; + + for (const auto& obj : detectionResults) { + if (obj.confidence < scoreThreshold) { + continue; + } + + CustomObject customObject; + customObject.confidence = obj.confidence; + customObject.box = obj.box; + customObject.cameraId = camera_id; + + switch (obj.classId) { + case 0: { // License plate + customObject.classId = 0; + customObject.className = "license plate"; + customObject.extraInfo = "license plate"; + results.push_back(std::move(customObject)); + break; + } + + case 1: { // Motorcycle + customObject.classId = 1; + customObject.className = "motorcyclist"; + results.push_back(std::move(customObject)); + break; + } + + case 2: // Helmet + case 3: { // No helmet - classify to confirm + // Validate bounding box + if ((obj.box & frameBounds) != obj.box) { + continue; + } + + if (!_classifier) { + continue; + } + + cv::Mat croppedImage = input(obj.box); + std::vector classifierResults; + _classifier->RunInference(croppedImage, camera_id.c_str(), classifierResults); + + if (classifierResults.empty()) { + continue; + } + + const bool isNoHelmet = (classifierResults[0].classId == 1); + customObject.classId = isNoHelmet ? 3 : 2; + customObject.className = isNoHelmet ? "no_helmet" : "helmet"; + results.push_back(std::move(customObject)); + break; + } + + default: + break; + } + } + + return results; + } + catch (const std::exception& e) { + std::cerr << "ANSCustomHMD::RunInference: Exception: " << e.what() << std::endl; + } + catch (...) { + std::cerr << "ANSCustomHMD::RunInference: Unknown exception." << std::endl; + } + + return {}; +} diff --git a/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.h b/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.h new file mode 100644 index 0000000..41f205c --- /dev/null +++ b/ANSCustomHelmetDetection/ANSCustomCodeHelmetDetection.h @@ -0,0 +1,33 @@ +#include "ANSLIB.h" + +//#define FNS_DEBUG +class CUSTOM_API ANSCustomHMD : public IANSCustomClass +{ +private: + using ANSLIBPtr = std::unique_ptr; + + std::recursive_mutex _mutex; + int engineType{ 0 }; + ANSLIBPtr _detector{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + ANSLIBPtr _classifier{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + + // ----- Model config ----- + int _detectorModelType; + int _detectorDetectionType; + int _classifierModelType; + int _classifierDetectionType; + + std::string _classifierLabels; + bool _isInitialized{ false }; + bool _readROIs{ false }; + +public: + bool Initialize(const std::string& modelDiretory, float detectionScoreThreshold, std::string& labelMap) override; + bool OptimizeModel(bool fp16) override; + bool ConfigureParameters(CustomParams& param) override; + std::vector RunInference(const cv::Mat& input) override; + std::vector RunInference(const cv::Mat& input, const std::string& camera_id) override; + bool Destroy() override; + ANSCustomHMD(); + ~ANSCustomHMD(); +}; diff --git a/ANSCustomHelmetDetection/CMakeLists.txt b/ANSCustomHelmetDetection/CMakeLists.txt new file mode 100644 index 0000000..2299646 --- /dev/null +++ b/ANSCustomHelmetDetection/CMakeLists.txt @@ -0,0 +1,60 @@ +project(ANSCustomHelmetDetection LANGUAGES CXX) + +# ---------- sources ---------- +set(SOURCES + ANSCustomCodeHelmetDetection.cpp + dllmain.cpp + pch.cpp +) + +set(HEADERS + ANSCustomCodeHelmetDetection.h + framework.h + pch.h +) + +# ---------- shared library (DLL) ---------- +add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS}) + +# ---------- preprocessor definitions ---------- +target_compile_definitions(${PROJECT_NAME} PRIVATE + ANSCUSTOMHELMETDETECTION_EXPORTS + _WINDOWS + _USRDLL + WIN32_LEAN_AND_MEAN + NOMINMAX + $<$:_DEBUG> + $<$:NDEBUG> +) + +# ---------- include directories ---------- +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + C:/Projects/ANLS/ANSLIB/ANSLIB + C:/ANSLibs/opencv/include +) + +# ---------- library directories & linking ---------- +target_link_directories(${PROJECT_NAME} PRIVATE + C:/ProgramData/ANSCENTER/Shared + C:/ANSLibs/opencv/x64/vc17/lib +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + ANSLIB + opencv_world4130 +) + +# ---------- compiler options (MSVC) ---------- +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /W3 # Warning level 3 + /sdl # SDL checks + /permissive- # Conformance mode + $<$:/O2 /Oi /GL> # Optimize + intrinsics + whole-program opt + ) + + target_link_options(${PROJECT_NAME} PRIVATE + $<$:/OPT:REF /OPT:ICF /LTCG> # Optimize refs, COMDAT folding, link-time codegen + ) +endif() diff --git a/ANSCustomHelmetDetection/dllmain.cpp b/ANSCustomHelmetDetection/dllmain.cpp new file mode 100644 index 0000000..ca0efb9 --- /dev/null +++ b/ANSCustomHelmetDetection/dllmain.cpp @@ -0,0 +1,23 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ANSCustomCodeHelmetDetection.h" +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// expose the class to the outside world +extern "C" __declspec(dllexport) IANSCustomClass* Create() { + return new ANSCustomHMD(); +} diff --git a/ANSCustomHelmetDetection/framework.h b/ANSCustomHelmetDetection/framework.h new file mode 100644 index 0000000..4cae05b --- /dev/null +++ b/ANSCustomHelmetDetection/framework.h @@ -0,0 +1,7 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX // Prevent windows.h from defining min/max macros + // which break std::min / std::max (C2589) +// Windows Header Files +#include diff --git a/ANSCustomHelmetDetection/pch.cpp b/ANSCustomHelmetDetection/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/ANSCustomHelmetDetection/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/ANSCustomHelmetDetection/pch.h b/ANSCustomHelmetDetection/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/ANSCustomHelmetDetection/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.cpp b/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.cpp new file mode 100644 index 0000000..147fa6d --- /dev/null +++ b/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.cpp @@ -0,0 +1,810 @@ +#include "ANSCustomCodeWeaponDetection.h" + +//#define FNS_DEBUG +ANSCustomWD::ANSCustomWD() +{ + // Initialize the model +} +bool ANSCustomWD::OptimizeModel(bool fp16) +{ + try { + if (this->_detector == nullptr || this->_filter == nullptr) + { + std::cout << "Model is not initialized." << std::endl; + return false; + } + int vResult = _detector->Optimize(fp16); + int tResult = _filter->Optimize(fp16); + if (vResult != 1 || tResult != 1) + { + std::cout << "Model optimization failed." << std::endl; + return false; + } + std::cout << "Model optimization successful." << std::endl; + return true; + } + catch (const std::exception& e) { + return false; + } + catch (...) { + return false; + } +} + +std::vector ANSCustomWD::RunInference(const cv::Mat& input) +{ + return RunInference(input, "CustomCam"); +} +bool ANSCustomWD::Destroy() +{ + try { + this->_detector.reset(); + this->_filter.reset(); + return true; + } + catch (const std::exception& e) { + return false; + } + catch (...) { + return false; + } +} +bool ANSCustomWD::Initialize(const std::string& modelDirectory, float detectionScoreThreshold, std::string& labelMap) +{ + //1. The modelDirectory is supplied by ANSVIS and contains the path to the model files + _modelDirectory = modelDirectory; + _detectionScoreThreshold = detectionScoreThreshold; + + // Detector model configuration + _detectorModelName = "train_last"; + _detectorClassName = "classes.names"; + _detectorModelType = 4; // Assuming 4 represents TensorRT YoloV11 + _detectorDetectionType = 1; // Assuming 1 represents object detection + + // Fileter model configuration + _filterModelName = "filter"; + _filterClassName = "filter.names"; + _filterModelType = 4; // Assuming 4 represents TensorRT YoloV11 + _filterDetectionType = 1; // Assuming 1 represents object detection + + // Create model instances + _detector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); + _filter = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy); + + _detectorModelType = 4; // Assuming 4 represents TensorRT YoloV11 + _detectorDetectionType = 1; // Assuming 1 represents object detection + _filterModelType = 4; // Assuming 4 represents TensorRT YoloV12 + _filterDetectionType = 1; // Assuming 1 represents object detection + + // Check the hardware type + engineType = _detector->GetEngineType(); + if (engineType == 1) { + // detector will be TensorRT (v8, v11) and filter will be TensorRT (v12) + _detectorModelType = 4; // Assuming 4 represents TensorRT YoloV11 + _detectorDetectionType = 1; // Assuming 1 represents object detection + _filterModelType = 4; // Assuming 4 represents TensorRT YoloV12 + _filterDetectionType = 1; // Assuming 1 represents object detection + std::cout << "NVIDIA GPU detected. Using TensorRT" << std::endl; + } + else { + // detetector will be OpenVINO (v8, v11) and filter will be Yolo (v12) + _detectorModelType = 5; // Assuming 5 represents OpenVINO YoloV11 + _detectorDetectionType = 1; // Assuming 1 represents object detection + _filterModelType = 17; // Assuming 17 represents ONNX YoloV12 + _filterDetectionType = 1; // Assuming 1 represents object detection + std::cout << "CPU detected. Using OpenVINO and ONNX" << std::endl; + } + + //Clear parameters + this->_params.ROI_Config.clear(); + this->_params.ROI_Options.clear(); + this->_params.Parameters.clear(); + this->_params.ROI_Values.clear(); + + + //2. User can start impelementing the initialization logic here + + double _modelConfThreshold = 0.5; + double _modelNMSThreshold = 0.5; + std::string licenseKey = ""; +#ifdef FNS_DEBUG // Corrected preprocessor directive + _loadEngineOnCreate = true; +#endif + int loadEngineOnCreation = 0; // Load engine on creation + if (_loadEngineOnCreate)loadEngineOnCreation = 1; + int autoEngineDetection = 1; // Auto engine detection + int detectorResult = _detector->LoadModelFromFolder(licenseKey.c_str(), + _detectorModelName.c_str(), + _detectorClassName.c_str(), + detectionScoreThreshold, + _modelConfThreshold, + _modelNMSThreshold, autoEngineDetection, + _detectorModelType, + _detectorDetectionType, + loadEngineOnCreation, + _modelDirectory.c_str(), + labelMap); + + int filterResult = _filter->LoadModelFromFolder(licenseKey.c_str(), + _filterModelName.c_str(), + _filterClassName.c_str(), + _detectionScoreThreshold, + _modelConfThreshold, + _modelNMSThreshold, autoEngineDetection, + _filterModelType, + _filterDetectionType, + loadEngineOnCreation, + _modelDirectory.c_str(), + _filterLabelMap); + + if ((detectorResult == 1) && (filterResult == 1)) return true; + return false; +} +ANSCustomWD::~ANSCustomWD() +{ + Destroy(); +} + +std::vector ANSCustomWD::RunInference(const cv::Mat& input, const std::string& camera_id) { + std::lock_guard lock(_mutex); + + // Early validation + if (input.empty() || input.cols < 10 || input.rows < 10) { + return {}; + } + + if (!_detector) { + return {}; + } + + try { +#ifdef FNS_DEBUG + cv::Mat draw = input.clone(); +#endif + + std::vector output; + + // A. Check if detected area is retained and valid + if (_detectedArea.width > 50 && _detectedArea.height > 50) { + output = ProcessExistingDetectedArea(input, camera_id +#ifdef FNS_DEBUG + , draw +#endif + ); + } + else { + // B. Find new detected area + ProcessNewDetectedArea(input, camera_id, output +#ifdef FNS_DEBUG + , draw +#endif + ); + } + +#ifdef FNS_DEBUG + cv::imshow("Combined Detected Areas", draw); + cv::waitKey(1); +#endif + + // Convert to CustomObjects + std::vector results; + if (!output.empty()) { + results.reserve(output.size()); + const float scoreThreshold = _detectionScoreThreshold; + + for (const auto& obj : output) { + if (obj.confidence >= scoreThreshold) { + 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(scoreThreshold); + results.push_back(std::move(customObj)); + } + } + } + + if (results.empty()) { + UpdateNoDetectionCondition(); + } + + return results; + + } + catch (const std::exception& e) { + return {}; + } +} + +std::vector ANSCustomWD::RunFilterGetPersons(const cv::Mat& frame) { + std::vector persons; + if (!_filter) return persons; + + std::vector filteredObjects; + _filter->RunInference(frame, "cam", filteredObjects); + + for (const auto& obj : filteredObjects) { + if (obj.classId == 0) { // person + persons.push_back(obj); + } + } + return persons; +} + +std::vector ANSCustomWD::ProcessExistingDetectedArea( + const cv::Mat& frame, + const std::string& camera_id +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif +) { + std::vector output; + + // Validate _detectedArea is within frame bounds + cv::Rect frameRect(0, 0, frame.cols, frame.rows); + _detectedArea &= frameRect; + if (_detectedArea.width <= 0 || _detectedArea.height <= 0) { + _detectedArea = cv::Rect(); + UpdateNoDetectionCondition(); + return output; + } + +#ifdef FNS_DEBUG + cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 2); // RED +#endif + + cv::Mat activeROI = frame(_detectedArea); + + // Run weapon detection on cropped ROI + std::vector detectedObjects; + int detectorDetectionResult = _detector->RunInference(activeROI, camera_id.c_str(), detectedObjects); + + if (detectedObjects.empty()) { + UpdateNoDetectionCondition(); + return output; + } + + // Run filter once for all candidates in this frame + std::vector personDetections = RunFilterGetPersons(frame); + bool filterHadResults = !personDetections.empty() || !_filter; + + const float scoreThreshold = _detectionScoreThreshold; + std::vector movementObjects; + int detectedMovementResult = _detector->DetectMovement(activeROI, camera_id.c_str(), movementObjects); + + // Convert movement objects to frame coordinates + std::vector weaponRects; + weaponRects.reserve(movementObjects.size()); + for (const auto& rect : movementObjects) { + ANSCENTER::Object mObj; + mObj.box = rect.box; + mObj.box.x += _detectedArea.x; + mObj.box.y += _detectedArea.y; + weaponRects.push_back(mObj); + } + +#ifdef FNS_DEBUG + for (const auto& rect : weaponRects) { + cv::rectangle(draw, rect.box, cv::Scalar(0, 255, 0), 2); // GREEN + } +#endif + + for (auto& detectedObj : detectedObjects) { + // Adjust to frame coordinates + detectedObj.box.x += _detectedArea.x; + detectedObj.box.y += _detectedArea.y; + detectedObj.cameraId = camera_id; + + if (detectedObj.confidence <= scoreThreshold) { + UpdateNoDetectionCondition(); + continue; + } + + const float area_threshold = calculateIoU(_detectedArea, detectedObj.box); + if (area_threshold >= 0.5f) { + UpdateNoDetectionCondition(); + continue; + } + +#ifdef FNS_DEBUG + cv::rectangle(draw, detectedObj.box, cv::Scalar(0, 255, 255), 2); // Yellow +#endif + + if (weaponRects.empty()) { + UpdateNoDetectionCondition(); + continue; + } + + if (!IsOverlapping(detectedObj, weaponRects, 0)) { + UpdateNoDetectionCondition(); + continue; + } + + // Process valid detection + if (ProcessWeaponDetection(frame, detectedObj, output, personDetections, filterHadResults +#ifdef FNS_DEBUG + , draw +#endif + )) { + continue; + } + } + + return output; +} + +bool ANSCustomWD::ProcessWeaponDetection( + const cv::Mat& frame, + ANSCENTER::Object& detectedObj, + std::vector& output, + const std::vector& personDetections, + bool filterHadResults +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif +) { + // Already established as real weapon after FILTERFRAMES consecutive confirmations + if (_realWeaponCheck > FILTERFRAMES) { + AddConfirmedWeaponDetection(frame, detectedObj, output); + return true; + } + + // Filter not available - confirm directly + if (!_filter) { + AddConfirmedWeaponDetection(frame, detectedObj, output); + return true; + } + + // Filter ran but detected nothing at all - treat as inconclusive + // Require more consecutive frames before confirming without person validation + if (!filterHadResults) { + _realWeaponCheck++; + if (_realWeaponCheck > FILTERFRAMES / 2) { + AddConfirmedWeaponDetection(frame, detectedObj, output); + return true; + } + return false; + } + +#ifdef FNS_DEBUG + for (const auto& person : personDetections) { + cv::rectangle(draw, person.box, cv::Scalar(23, 25, 0), 2); + cv::putText(draw, "person", + cv::Point(person.box.x, person.box.y - 10), + cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2); + } +#endif + + // No persons found - weapon without person context + if (personDetections.empty()) { + _realWeaponCheck = std::max(0, _realWeaponCheck - 1); + if (_realWeaponCheck <= 0) _isRealWeaponFrame = false; + return false; + } + + // Check if weapon overlaps with ANY person (use meaningful IoU threshold) + if (IsOverlapping(detectedObj, personDetections, 0.05f)) { + AddConfirmedWeaponDetection(frame, detectedObj, output); + _realWeaponCheck++; + return true; + } + + // Detection doesn't overlap with any person + _realWeaponCheck = std::max(0, _realWeaponCheck - 1); + if (_realWeaponCheck <= 0) { + _isRealWeaponFrame = false; + } + return false; +} + +void ANSCustomWD::AddConfirmedWeaponDetection( + const cv::Mat& frame, + ANSCENTER::Object& detectedObj, + std::vector& output) +{ + output.push_back(detectedObj); + UpdateActiveROI(frame, detectedObj); + _isWeaponDetected = true; + _retainDetectedArea = 0; + _isRealWeaponFrame = true; +} + +void ANSCustomWD::ProcessNewDetectedArea( + const cv::Mat& frame, + const std::string& camera_id, + std::vector& output +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif +) { + // Decay _realWeaponCheck gradually instead of hard reset + _isRealWeaponFrame = false; + _realWeaponCheck = std::max(0, _realWeaponCheck - 1); + + // Divide image and get priority region + std::vector sections = divideImage(frame); + const int lowestPriority = getLowestPriorityRegion(); + + if (_currentPriority > lowestPriority || _currentPriority == 0) { + _currentPriority = getHighestPriorityRegion(); + } + else { + _currentPriority++; + } + + _detectedArea = getRegionByPriority(_currentPriority); + +#ifdef FNS_DEBUG + cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 4); // RED +#endif + + // Validate _detectedArea is within frame bounds + cv::Rect frameRect(0, 0, frame.cols, frame.rows); + _detectedArea &= frameRect; + + if (_detectedArea.width <= 50 || _detectedArea.height <= 50) { + _detectedArea = cv::Rect(); + return; + } + + cv::Mat detectedROI = frame(_detectedArea); + std::vector detectedObjects; + int detectorResult = _detector->RunInference(detectedROI, camera_id.c_str(), detectedObjects); + + if (detectedObjects.empty()) { + _detectedArea = cv::Rect(); + return; + } + + // Use configurable threshold instead of hardcoded 0.35 + const float scanThreshold = std::min(_detectionScoreThreshold, 0.35f); + ANSCENTER::Object* bestDetection = nullptr; + float maxScore = 0.0f; + + for (auto& detectedObj : detectedObjects) { + detectedObj.box.x += _detectedArea.x; + detectedObj.box.y += _detectedArea.y; + detectedObj.cameraId = camera_id; + + if (detectedObj.confidence <= scanThreshold) { + continue; + } + + if (detectedObj.confidence > maxScore) { + maxScore = detectedObj.confidence; + bestDetection = &detectedObj; + } + } + + if (!bestDetection) { + _detectedArea = cv::Rect(); + return; + } + + // Set up tracking area around the best detection + UpdateDetectedAreaFromObject(frame, *bestDetection); + _isWeaponDetected = true; + _retainDetectedArea = 0; + + // Validate in the same frame — run filter to check for person overlap + std::vector personDetections = RunFilterGetPersons(frame); + bool filterHadResults = !personDetections.empty() || !_filter; + + ProcessWeaponDetection(frame, *bestDetection, output, personDetections, filterHadResults +#ifdef FNS_DEBUG + , draw +#endif + ); +} + +void ANSCustomWD::UpdateDetectedAreaFromObject(const cv::Mat& frame, const ANSCENTER::Object& detectedObj) { + const int imageSize = std::max(frame.cols, frame.rows); + int cropSize; + + if (imageSize > 1920) cropSize = 640; + else if (imageSize > 1280) cropSize = 480; + else if (imageSize > 640) cropSize = 320; + else cropSize = 224; + + // Cap cropSize to frame dimensions to avoid negative clamp range (UB) + cropSize = std::min(cropSize, std::min(frame.cols, frame.rows)); + if (cropSize <= 0) { + _detectedArea = cv::Rect(); + return; + } + + const int xc = detectedObj.box.x + detectedObj.box.width / 2; + const int yc = detectedObj.box.y + detectedObj.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.x = x1_new; + _detectedArea.y = y1_new; + _detectedArea.width = std::min(cropSize, frame.cols - _detectedArea.x); + _detectedArea.height = std::min(cropSize, frame.rows - _detectedArea.y); +} +// Functions for screen size division +double ANSCustomWD::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 ANSCustomWD::divideImage(const cv::Mat& image) { + if (image.empty()) { + std::cerr << "Error: Empty image!" << std::endl; + return cachedSections; + } + cv::Size currentSize(image.cols, image.rows); + + // Check if the image size has changed + if (currentSize == previousImageSize) { + return cachedSections; // Return cached sections if size is the same + } + + // Update previous size + previousImageSize = currentSize; + cachedSections.clear(); + + int width = image.cols; + int height = image.rows; + int maxDimension = std::max(width, height); + int numSections = 10;// std::max(1, numSections); // Ensure at least 1 section + if (maxDimension <= 2560)numSections = 8; + if (maxDimension <= 1280)numSections = 6; + if (maxDimension <= 960)numSections = 4; + if (maxDimension <= 640)numSections = 2; + if (maxDimension <= 320)numSections = 1; + + int gridRows = std::sqrt(numSections); + int gridCols = (numSections + gridRows - 1) / gridRows; // Ensure all sections are covered + + int sectionWidth = width / gridCols; + int sectionHeight = height / gridRows; + + cv::Point imageCenter(width / 2, height / 2); + std::vector> distancePriorityList; + + // Create sections and store their distance from the center + 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); + } + } + + // Sort sections based on distance from center, then top-to-bottom, then left-to-right + 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; // Sort by closest distance to center + } + // If distance is the same, prioritize top to bottom, then left to right + 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; + }); + + // Assign priority + int priority = 1; + for (auto& entry : distancePriorityList) { + entry.second.priority = priority++; + cachedSections.push_back(entry.second); + } + + return cachedSections; +} +int ANSCustomWD::getHighestPriorityRegion() { + if (!cachedSections.empty()) { + return cachedSections.front().priority; // First element has the highest priority + } + return 0; // Return empty rect if no sections exist +} +int ANSCustomWD::getLowestPriorityRegion() { + if (!cachedSections.empty()) { + return cachedSections.back().priority; // Last element has the lowest priority + } + return 0; // Return empty rect if no sections exist +} +cv::Rect ANSCustomWD::getRegionByPriority(int priority) { + for (const auto& section : cachedSections) { + if (section.priority == priority) { + return section.region; + } + } + return cv::Rect(); // Return empty rect if priority not found +} +void ANSCustomWD::UpdateNoDetectionCondition() { + _isRealWeaponFrame = false; + _realWeaponCheck = 0; + if (_isWeaponDetected) { + _retainDetectedArea++; + if (_retainDetectedArea >= RETAINFRAMES) {// Reset detected area after 80 frames + _detectedArea.width = 0; + _detectedArea.height = 0; + _retainDetectedArea = 0; + _isWeaponDetected = false; + } + } + else { + _detectedArea.width = 0; + _detectedArea.height = 0; + _retainDetectedArea = 0; + } +} + +float ANSCustomWD::calculateIoU(const cv::Rect& box1, const cv::Rect& box2) { + int x1 = std::max(box1.x, box2.x); + int y1 = std::max(box1.y, box2.y); + int x2 = std::min(box1.x + box1.width, box2.x + box2.width); + int y2 = std::min(box1.y + box1.height, box2.y + box2.height); + + int intersectionArea = std::max(0, x2 - x1) * std::max(0, y2 - y1); + int box1Area = box1.width * box1.height; + int box2Area = box2.width * box2.height; + int unionArea = box1Area + box2Area - intersectionArea; + if (unionArea <= 0) return 0.0f; + + return static_cast(intersectionArea) / unionArea; +} +bool ANSCustomWD::IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold) +{ + for (const auto& otherObj : objectList) + { + float iou = calculateIoU(obj.box, otherObj.box); + //std::cout << "IoU: " << iou << std::endl; + if (iou > iouThreshold) + { + return true; // Overlapping found + } + } + return false; // No overlapping object found +} + +void ANSCustomWD::UpdateActiveROI(const cv::Mat& frame, ANSCENTER::Object detectedObj) { + int cropSize = 640; + int imagegSize = std::max(frame.cols, frame.rows); + if (imagegSize > 1920) cropSize = 640; + else if (imagegSize > 1280) cropSize = 480; + else if (imagegSize > 640) cropSize = 320; + else cropSize = 224; + + // Cap cropSize to frame dimensions to avoid negative coordinates + cropSize = std::min(cropSize, std::min(frame.cols, frame.rows)); + if (cropSize <= 0) { + _detectedArea = cv::Rect(); + return; + } + + int xc = detectedObj.box.x + detectedObj.box.width / 2; + int yc = detectedObj.box.y + detectedObj.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.x = x1_new; + _detectedArea.y = y1_new; + _detectedArea.width = std::min(cropSize, frame.cols - _detectedArea.x); + _detectedArea.height = std::min(cropSize, frame.rows - _detectedArea.y); +} + +bool ANSCustomWD::ConfigureParameters(CustomParams& param) +{ + std::lock_guard lock(_mutex); + try { + // Clear current parameters + param.ROI_Config.clear(); + param.ROI_Options.clear(); + param.Parameters.clear(); + param.ROI_Values.clear(); + + //// Fill ROI_Config + //if (this->_params.ROI_Config.empty()) { + // // Traffic Light ROIs + // param.ROI_Config.push_back({ true, false, false, 0, 10, "TrafficLight_One", "All Corners" }); + // param.ROI_Config.push_back({ true, false, false, 0, 10, "TrafficLight_Two", "All Corners" }); + // param.ROI_Config.push_back({ true, false, false, 0, 10, "TrafficLight_Three", "All Corners" }); + + // // Vehicle Detector Zones + // param.ROI_Config.push_back({ true, true, false, 0, 10, "CarZone_One", "All Corners" }); + // param.ROI_Config.push_back({ true, true, false, 0, 10, "CarZone_Two", "All Corners" }); + // param.ROI_Config.push_back({ true, true, false, 0, 10, "CarZone_Three", "All Corners" }); + + // // Cross Line ROIs + // param.ROI_Config.push_back({ false, false, true, 0, 10, "CrossLine_One", "All Corners" }); + // param.ROI_Config.push_back({ false, false, true, 0, 10, "CrossLine_Two", "All Corners" }); + // param.ROI_Config.push_back({ false, false, true, 0, 10, "CrossLine_Three", "All Corners" }); + //} + //else { + // // Reuse existing ROI_Config + // param.ROI_Config = this->_params.ROI_Config; + //} + + //// Safely reuse ROI_Values only if valid + //size_t suspiciousCap = this->_params.ROI_Values.capacity(); + //if (!this->_params.ROI_Values.empty() && suspiciousCap < 10000) { + // for (const auto& roi : this->_params.ROI_Values) { + // CustomROIValue roiValue; + // roiValue.Name = roi.Name; + // roiValue.ROIPoints = roi.ROIPoints; + // roiValue.ROIMatch = roi.ROIMatch; + // roiValue.Option = roi.Option; + // roiValue.OriginalImageSize = roi.OriginalImageSize; + // param.ROI_Values.push_back(roiValue); + // } + //} + //else { + // // Use default harcoded values + // param.ROI_Values = { + // // TrafficLight + // {"Centre Point", {{700, 100}, {950, 100}, {950, 200}, {700, 200}}, "Left side", "TrafficLight_One_1", 1920}, + // {"Centre Point", {{1000, 100}, {2000, 100}, {2000, 200}, {1000, 200}}, "Left side", "TrafficLight_Two_1", 1920}, + // {"Centre Point", {{2100, 100}, {2200, 100}, {2200, 200}, {2100, 200}}, "Left side", "TrafficLight_Three_1", 1920}, + // // VehicleDetector + // {"Centre Point", {{700, 650}, {950, 650}, {950, 700}, {600, 700}}, "Inside ROI", "CarZone_One_1", 1920}, + // {"Centre Point", {{950, 650}, {1900, 650}, {1900, 770}, {950, 700}}, "Inside ROI", "CarZone_Two_1", 1920}, + // {"Centre Point", {{1900, 650}, {2150, 650}, {2150, 770}, {1900, 770}}, "Inside ROI", "CarZone_Three_1", 1920}, + // // CrossLine + // {"Centre Point", {{600, 670}, {2150, 750}}, "Above", "CrossLine_One_1", 1920}, + // {"Centre Point", {{600, 670}, {2150, 750}}, "Left side", "CrossLine_Two_1", 1920}, + // {"Centre Point", {{600, 670}, {2150, 750}}, "Left side", "CrossLine_Three_1", 1920} + // }; + //} + + //// Add ALPR parameter if ALPR is available + //if (_ALPRVisible) { + // CustomParameter stALPRParam; + // stALPRParam.Name = "ALPR"; + // stALPRParam.DataType = "Boolean"; + // stALPRParam.NoOfDecimals = 0; + // stALPRParam.MaxValue = 0; + // stALPRParam.MinValue = 0; + // stALPRParam.StartValue = "false"; + // stALPRParam.ListItems.clear(); + // stALPRParam.DefaultValue = "false"; + // stALPRParam.Value = _useALPR ? "true" : "false"; + // param.Parameters.push_back(stALPRParam); + //} + + //// Add Display TL parameter + //CustomParameter stTLParam; + //stTLParam.Name = "Show Traffic Light"; + //stTLParam.DataType = "Boolean"; + //stTLParam.NoOfDecimals = 0; + //stTLParam.MaxValue = 0; + //stTLParam.MinValue = 0; + //stTLParam.StartValue = "false"; + //stTLParam.ListItems.clear(); + //stTLParam.DefaultValue = "false"; + //stTLParam.Value = _displayTL ? "true" : "false"; + //param.Parameters.push_back(stTLParam); + + ////Add Traffic Light AI parameter + //CustomParameter stTrafficLightAI; + //stTrafficLightAI.Name = "TrafficLightAI"; + //stTrafficLightAI.DataType = "Boolean"; + //stTrafficLightAI.NoOfDecimals = 0; + //stTrafficLightAI.MaxValue = 0; + //stTrafficLightAI.MinValue = 0; + //stTrafficLightAI.StartValue = "false"; + //stTrafficLightAI.ListItems.clear(); + //stTrafficLightAI.DefaultValue = "false"; + //stTrafficLightAI.Value = m_bTrafficLightAI ? "true" : "false"; + //param.Parameters.push_back(stTrafficLightAI); + + return true; + } + catch (const std::exception& e) { + std::cerr << "Error in ConfigureParamaters: " << e.what() << std::endl; + return false; + } + catch (...) { + std::cerr << "Unknown error in ConfigureParamaters." << std::endl; + return false; + } +} diff --git a/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.h b/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.h new file mode 100644 index 0000000..6ff7e45 --- /dev/null +++ b/ANSCustomWeaponDetection/ANSCustomCodeWeaponDetection.h @@ -0,0 +1,91 @@ +#include "ANSLIB.h" +#define RETAINFRAMES 80 +#define FILTERFRAMES 10 + +class CUSTOM_API ANSCustomWD : public IANSCustomClass +{ + struct ImageSection { + cv::Rect region; + int priority; + ImageSection(const cv::Rect& r) : region(r), priority(0) {} + }; + private: + using ANSLIBPtr = std::unique_ptr; + ANSLIBPtr _detector{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + ANSLIBPtr _filter{ nullptr, &ANSCENTER::ANSLIB::Destroy }; + int engineType; + std::string _detectorModelName; + std::string _detectorClassName; + int _detectorModelType; // Assuming 4 represents TensorRT YoloV11 + int _detectorDetectionType; // Assuming 1 represents object detection + + std::string _filterModelName; + std::string _filterClassName; + std::string _filterLabelMap; + + int _filterModelType; // Assuming 1 represents OpenVINO YoloV12 + int _filterDetectionType; // Assuming 1 represents object detection + + std::recursive_mutex _mutex; + cv::Rect _detectedArea;// Area where weapon is detected + int _retainDetectedArea{ 0 }; + bool _isWeaponDetected{ false }; + float _detectionScoreThreshold{ 0.5 }; + cv::Size previousImageSize = cv::Size(0, 0); + std::vector cachedSections; + int _currentPriority{ 0 }; // None + int _realWeaponCheck{ 0 }; + bool _isRealWeaponFrame{ false }; + void UpdateNoDetectionCondition(); + std::vector RunFilterGetPersons(const cv::Mat& frame); + // Function to seperate screen size + double calculateDistanceToCenter(const cv::Point& center, const cv::Rect& rect); + std::vector divideImage(const cv::Mat& image); + int getHighestPriorityRegion(); + int getLowestPriorityRegion(); + cv::Rect getRegionByPriority(int priority); + + // Utilities + float calculateIoU(const cv::Rect& box1, const cv::Rect& box2); + bool IsOverlapping(const ANSCENTER::Object& obj, const std::vector& objectList, float iouThreshold); + void UpdateActiveROI(const cv::Mat& frame, ANSCENTER::Object detectedObj); + std::vector ProcessExistingDetectedArea( + const cv::Mat& frame, const std::string& camera_id +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif + ); + bool ProcessWeaponDetection( + const cv::Mat& frame, + ANSCENTER::Object& detectedObj, + std::vector& output, + const std::vector& personDetections, + bool filterHadResults +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif + ); + void AddConfirmedWeaponDetection( + const cv::Mat& frame, + ANSCENTER::Object& detectedObj, + std::vector& output); + + void ProcessNewDetectedArea( + const cv::Mat& frame, + const std::string& camera_id, + std::vector& output +#ifdef FNS_DEBUG + , cv::Mat& draw +#endif + ); + void UpdateDetectedAreaFromObject(const cv::Mat& frame, const ANSCENTER::Object& detectedObj); + public: + bool Initialize(const std::string& modelDiretory,float detectionScoreThreshold, std::string& labelMap)override; + bool OptimizeModel(bool fp16)override; + std::vector RunInference(const cv::Mat& input)override; + std::vector RunInference(const cv::Mat& input, const std::string& camera_id)override; + bool Destroy()override; + virtual bool ConfigureParameters(CustomParams& param) override; + ANSCustomWD(); + ~ANSCustomWD(); +}; \ No newline at end of file diff --git a/ANSCustomWeaponDetection/CMakeLists.txt b/ANSCustomWeaponDetection/CMakeLists.txt new file mode 100644 index 0000000..539c749 --- /dev/null +++ b/ANSCustomWeaponDetection/CMakeLists.txt @@ -0,0 +1,60 @@ +project(ANSCustomWeaponDetection LANGUAGES CXX) + +# ---------- sources ---------- +set(SOURCES + ANSCustomCodeWeaponDetection.cpp + dllmain.cpp + pch.cpp +) + +set(HEADERS + ANSCustomCodeWeaponDetection.h + framework.h + pch.h +) + +# ---------- shared library (DLL) ---------- +add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS}) + +# ---------- preprocessor definitions ---------- +target_compile_definitions(${PROJECT_NAME} PRIVATE + ANSCUSTOMCODE_EXPORTS + _WINDOWS + _USRDLL + WIN32_LEAN_AND_MEAN + NOMINMAX + $<$:_DEBUG> + $<$:NDEBUG> +) + +# ---------- include directories ---------- +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + C:/Projects/ANLS/ANSLIB/ANSLIB + C:/ANSLibs/opencv/include +) + +# ---------- library directories & linking ---------- +target_link_directories(${PROJECT_NAME} PRIVATE + C:/ProgramData/ANSCENTER/Shared + C:/ANSLibs/opencv/x64/vc17/lib +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + ANSLIB + opencv_world4130 +) + +# ---------- compiler options (MSVC) ---------- +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE + /W3 # Warning level 3 + /sdl # SDL checks + /permissive- # Conformance mode + $<$:/O2 /Oi /GL> # Optimize + intrinsics + whole-program opt + ) + + target_link_options(${PROJECT_NAME} PRIVATE + $<$:/OPT:REF /OPT:ICF /LTCG> # Optimize refs, COMDAT folding, link-time codegen + ) +endif() diff --git a/ANSCustomWeaponDetection/dllmain.cpp b/ANSCustomWeaponDetection/dllmain.cpp new file mode 100644 index 0000000..c05ea41 --- /dev/null +++ b/ANSCustomWeaponDetection/dllmain.cpp @@ -0,0 +1,27 @@ +#include "pch.h" +#include "ANSCustomCodeWeaponDetection.h" + + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + + + + +// expose the class to the outside world +extern "C" __declspec(dllexport) IANSCustomClass* Create() { + return new ANSCustomWD(); +} \ No newline at end of file diff --git a/ANSCustomWeaponDetection/framework.h b/ANSCustomWeaponDetection/framework.h new file mode 100644 index 0000000..4cae05b --- /dev/null +++ b/ANSCustomWeaponDetection/framework.h @@ -0,0 +1,7 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX // Prevent windows.h from defining min/max macros + // which break std::min / std::max (C2589) +// Windows Header Files +#include diff --git a/ANSCustomWeaponDetection/pch.cpp b/ANSCustomWeaponDetection/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/ANSCustomWeaponDetection/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/ANSCustomWeaponDetection/pch.h b/ANSCustomWeaponDetection/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/ANSCustomWeaponDetection/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e1b2b4c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(ANSCustomModels LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_subdirectory(ANSCustomHelmetDetection) +add_subdirectory(ANSCustomFireNSmokeDetection) +add_subdirectory(ANSCustomWeaponDetection)