2026-03-28 16:54:11 +11:00
|
|
|
#include "ANSOVSEG.h"
|
|
|
|
|
namespace ANSCENTER {
|
|
|
|
|
bool ANSOVSEG::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
if (!ANSODBase::OptimizeModel(fp16, optimizedModelFolder)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (FileExist(_modelFilePath)) {
|
|
|
|
|
std::string modelName = GetFileNameWithoutExtension(_modelFilePath);
|
|
|
|
|
std::string binaryModelName = modelName + ".bin";
|
|
|
|
|
std::string modelFolder = GetParentFolder(_modelFilePath);
|
|
|
|
|
std::string optimizedModelPath = CreateFilePath(modelFolder, binaryModelName);
|
|
|
|
|
if (FileExist(optimizedModelPath)) {
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::OptimizeModel", "This model is optimized. No need other optimization.", __FILE__, __LINE__);
|
|
|
|
|
optimizedModelFolder = modelFolder;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::OptimizeModel", "This model can not be optimized.", __FILE__, __LINE__);
|
|
|
|
|
optimizedModelFolder = modelFolder;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::OptimizeModel", "This model is not exist. Please check the model path again.", __FILE__, __LINE__);
|
|
|
|
|
optimizedModelFolder = "";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool ANSOVSEG::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
2026-04-13 19:48:32 +10:00
|
|
|
ModelLoadingGuard mlg(_modelLoading);
|
2026-03-28 16:54:11 +11:00
|
|
|
try {
|
|
|
|
|
bool result = ANSODBase::LoadModel(modelZipFilePath, modelZipPassword);
|
|
|
|
|
if (!result) return false;
|
|
|
|
|
// 0. Check if the configuration file exist
|
|
|
|
|
if (FileExist(_modelConfigFile)) {
|
|
|
|
|
ModelType modelType;
|
|
|
|
|
std::vector<int> inputShape;
|
|
|
|
|
_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
|
|
|
|
|
if (inputShape.size() == 2) {
|
|
|
|
|
if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
|
|
|
|
|
if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {// This is old version of model zip file
|
|
|
|
|
std::string onnxfile = CreateFilePath(_modelFolder, "train_last.xml");//yolov8n.xml
|
|
|
|
|
if (std::filesystem::exists(onnxfile)) {
|
|
|
|
|
_modelFilePath = onnxfile;
|
|
|
|
|
_classFilePath = CreateFilePath(_modelFolder, "classes.names");
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::Initialize. Model file is not exist", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
std::ifstream isValidFileName(_classFilePath);
|
|
|
|
|
if (!isValidFileName)
|
|
|
|
|
{
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from string", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromString();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from file", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load Model from Here
|
|
|
|
|
InitialModel();
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("OPENVINOCL::LoadModel", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool ANSOVSEG::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig, std::string modelName, std::string className, const std::string& modelFolder, std::string& labelMap) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
2026-04-13 19:48:32 +10:00
|
|
|
ModelLoadingGuard mlg(_modelLoading);
|
2026-03-28 16:54:11 +11:00
|
|
|
try {
|
|
|
|
|
bool result = ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap);
|
|
|
|
|
if (!result) return false;
|
|
|
|
|
std::string _modelName = modelName;
|
|
|
|
|
if (_modelName.empty()) {
|
|
|
|
|
_modelName = "train_last";
|
|
|
|
|
}
|
|
|
|
|
std::string modelFullName = _modelName + ".xml";
|
|
|
|
|
_modelConfig = modelConfig;
|
|
|
|
|
_modelConfig.detectionType = ANSCENTER::DetectionType::SEGMENTATION;
|
|
|
|
|
_modelConfig.modelType = ModelType::OPENVINO;
|
|
|
|
|
_modelConfig.inpHeight = 640;
|
|
|
|
|
_modelConfig.inpWidth = 640;
|
|
|
|
|
if (_modelConfig.modelMNSThreshold < 0.2)
|
|
|
|
|
_modelConfig.modelMNSThreshold = 0.5;
|
|
|
|
|
if (_modelConfig.modelConfThreshold < 0.2)
|
|
|
|
|
_modelConfig.modelConfThreshold = 0.5;
|
|
|
|
|
// 0. Check if the configuration file exist
|
|
|
|
|
if (FileExist(_modelConfigFile)) {
|
|
|
|
|
ModelType modelType;
|
|
|
|
|
std::vector<int> inputShape;
|
|
|
|
|
_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
|
|
|
|
|
if (inputShape.size() == 2) {
|
|
|
|
|
if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
|
|
|
|
|
if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {// This is old version of model zip file
|
|
|
|
|
std::string onnxfile = CreateFilePath(_modelFolder, modelFullName);//yolov8n.xml
|
|
|
|
|
if (std::filesystem::exists(onnxfile)) {
|
|
|
|
|
_modelFilePath = onnxfile;
|
|
|
|
|
_classFilePath = CreateFilePath(_modelFolder, className);
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::Initialize. Model file is not exist", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
std::ifstream isValidFileName(_classFilePath);
|
|
|
|
|
if (!isValidFileName)
|
|
|
|
|
{
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from string", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromString();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from file", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 1. Load labelMap and engine
|
|
|
|
|
labelMap.clear();
|
|
|
|
|
if (!_classes.empty())
|
|
|
|
|
labelMap = VectorToCommaSeparatedString(_classes);
|
|
|
|
|
|
|
|
|
|
// Load Model from Here
|
|
|
|
|
InitialModel();
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("OPENVINOCL::LoadModel", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool ANSOVSEG::Initialize(std::string licenseKey, ModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, std::string& labelMap) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
2026-04-13 19:48:32 +10:00
|
|
|
ModelLoadingGuard mlg(_modelLoading);
|
2026-03-28 16:54:11 +11:00
|
|
|
try {
|
|
|
|
|
std::string openVINOVersion = ov::get_openvino_version().buildNumber;
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. OpenVINO version", openVINOVersion, __FILE__, __LINE__);
|
|
|
|
|
bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
|
|
|
|
|
if (!result) return false;
|
|
|
|
|
// Parsing for YOLO only here
|
|
|
|
|
_modelConfig = modelConfig;
|
|
|
|
|
_modelConfig.detectionType = ANSCENTER::DetectionType::SEGMENTATION;
|
|
|
|
|
_modelConfig.modelType = ModelType::OPENVINO;
|
|
|
|
|
_modelConfig.inpHeight = 640;
|
|
|
|
|
_modelConfig.inpWidth = 640;
|
|
|
|
|
if (_modelConfig.modelMNSThreshold < 0.2)
|
|
|
|
|
_modelConfig.modelMNSThreshold = 0.5;
|
|
|
|
|
if (_modelConfig.modelConfThreshold < 0.2)
|
|
|
|
|
_modelConfig.modelConfThreshold = 0.5;
|
|
|
|
|
// 0. Check if the configuration file exist
|
|
|
|
|
if (FileExist(_modelConfigFile)) {
|
|
|
|
|
ModelType modelType;
|
|
|
|
|
std::vector<int> inputShape;
|
|
|
|
|
_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
|
|
|
|
|
if (inputShape.size() == 2) {
|
|
|
|
|
if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
|
|
|
|
|
if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {// This is old version of model zip file
|
|
|
|
|
std::string onnxfile = CreateFilePath(_modelFolder, "train_last.xml");//yolov8n.xml
|
|
|
|
|
if (std::filesystem::exists(onnxfile)) {
|
|
|
|
|
_modelFilePath = onnxfile;
|
|
|
|
|
_classFilePath = CreateFilePath(_modelFolder, "classes.names");
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::Initialize. Model file is not exist", _modelFilePath, __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
std::ifstream isValidFileName(_classFilePath);
|
|
|
|
|
if (!isValidFileName)
|
|
|
|
|
{
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from string", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromString();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this->_logger.LogDebug("ANSOVSEG::Initialize. Load classes from file", _classFilePath, __FILE__, __LINE__);
|
|
|
|
|
LoadClassesFromFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 1. Load labelMap and engine
|
|
|
|
|
labelMap.clear();
|
|
|
|
|
if (!_classes.empty())
|
|
|
|
|
labelMap = VectorToCommaSeparatedString(_classes);
|
|
|
|
|
|
|
|
|
|
// Load Model from Here
|
|
|
|
|
InitialModel();
|
|
|
|
|
_isInitialized = true;
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::Initialize", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
std::vector<Object> ANSOVSEG::RunInference(const cv::Mat& input) {
|
|
|
|
|
return RunInference(input, "CustomCam");
|
|
|
|
|
}
|
|
|
|
|
std::vector<Object> ANSOVSEG::RunInference(const cv::Mat& input,const std::string& camera_id)
|
|
|
|
|
{
|
2026-04-13 19:48:32 +10:00
|
|
|
if (!PreInferenceCheck("ANSOVSEG::RunInference")) return {};
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Validation
|
|
|
|
|
if (!_licenseValid) {
|
|
|
|
|
_logger.LogError("ANSOVSEG::RunInference", "Invalid License",
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_isInitialized) {
|
|
|
|
|
_logger.LogError("ANSOVSEG::RunInference",
|
|
|
|
|
"Model is not initialized", __FILE__, __LINE__);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input.empty() || input.cols < 10 || input.rows < 10) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Preprocessing
|
|
|
|
|
cv::Mat letterbox_img = PreProcessing(input);
|
|
|
|
|
if (letterbox_img.empty()) {
|
|
|
|
|
_logger.LogError("ANSOVSEG::RunInference", "PreProcessing failed",
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr int imageSize = 640;
|
|
|
|
|
const float scale = static_cast<float>(letterbox_img.rows) / imageSize;
|
|
|
|
|
|
|
|
|
|
cv::Mat blob = cv::dnn::blobFromImage(letterbox_img, 1.0 / 255.0,
|
|
|
|
|
cv::Size(imageSize, imageSize),
|
|
|
|
|
cv::Scalar(), true);
|
|
|
|
|
|
|
|
|
|
// Inference
|
|
|
|
|
auto input_port = compiled_model_.input();
|
|
|
|
|
ov::Tensor input_tensor(input_port.get_element_type(),
|
|
|
|
|
input_port.get_shape(), blob.ptr(0));
|
|
|
|
|
inference_request_.set_input_tensor(input_tensor);
|
|
|
|
|
inference_request_.infer();
|
|
|
|
|
|
|
|
|
|
// Get outputs
|
|
|
|
|
auto output0 = inference_request_.get_output_tensor(0);
|
|
|
|
|
auto output1 = inference_request_.get_output_tensor(1);
|
|
|
|
|
auto output0_shape = output0.get_shape();
|
|
|
|
|
|
|
|
|
|
const int rows = static_cast<int>(output0_shape[2]); // 8400
|
|
|
|
|
const int dimensions = static_cast<int>(output0_shape[1]); // 116
|
|
|
|
|
float* data = output0.data<float>();
|
|
|
|
|
|
|
|
|
|
// Proto masks [32, 25600]
|
|
|
|
|
cv::Mat proto(32, 25600, CV_32F, output1.data<float>());
|
|
|
|
|
|
|
|
|
|
// Parse detections (NO TRANSPOSE!)
|
|
|
|
|
std::vector<int> class_ids;
|
|
|
|
|
std::vector<float> class_scores;
|
|
|
|
|
std::vector<cv::Rect> boxes;
|
|
|
|
|
std::vector<cv::Mat> mask_confs;
|
|
|
|
|
|
|
|
|
|
// Pre-allocate
|
|
|
|
|
class_ids.reserve(rows / 10);
|
|
|
|
|
class_scores.reserve(rows / 10);
|
|
|
|
|
boxes.reserve(rows / 10);
|
|
|
|
|
mask_confs.reserve(rows / 10);
|
|
|
|
|
|
|
|
|
|
const cv::Rect imageBounds(0, 0, input.cols, input.rows);
|
|
|
|
|
|
|
|
|
|
// Process detections without transpose
|
|
|
|
|
for (int i = 0; i < rows; i++) {
|
|
|
|
|
// Find max class score (columns 4-83, total 80 classes)
|
|
|
|
|
float max_score = -1.0f;
|
|
|
|
|
int max_class_id = 0;
|
|
|
|
|
|
|
|
|
|
for (int j = 4; j < 84; j++) {
|
|
|
|
|
const float score = data[j * rows + i];
|
|
|
|
|
if (score > max_score) {
|
|
|
|
|
max_score = score;
|
|
|
|
|
max_class_id = j - 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (max_score > _modelConfig.detectionScoreThreshold) {
|
|
|
|
|
// Extract box coordinates
|
|
|
|
|
const float cx = data[0 * rows + i];
|
|
|
|
|
const float cy = data[1 * rows + i];
|
|
|
|
|
const float w = data[2 * rows + i];
|
|
|
|
|
const float h = data[3 * rows + i];
|
|
|
|
|
|
|
|
|
|
// Convert to pixel coordinates
|
|
|
|
|
cv::Rect box(
|
|
|
|
|
static_cast<int>((cx - 0.5f * w) * scale),
|
|
|
|
|
static_cast<int>((cy - 0.5f * h) * scale),
|
|
|
|
|
static_cast<int>(w * scale),
|
|
|
|
|
static_cast<int>(h * scale)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
box &= imageBounds; // Clamp to bounds
|
|
|
|
|
|
|
|
|
|
if (box.area() > 0) {
|
|
|
|
|
class_ids.push_back(max_class_id);
|
|
|
|
|
class_scores.push_back(max_score);
|
|
|
|
|
boxes.push_back(box);
|
|
|
|
|
|
|
|
|
|
// Extract mask coefficients (columns 84-115, 32 values)
|
|
|
|
|
cv::Mat mask_conf(1, 32, CV_32F);
|
|
|
|
|
for (int k = 0; k < 32; k++) {
|
|
|
|
|
mask_conf.at<float>(0, k) = data[(84 + k) * rows + i];
|
|
|
|
|
}
|
|
|
|
|
mask_confs.push_back(mask_conf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (boxes.empty()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply NMS
|
|
|
|
|
std::vector<int> indices;
|
|
|
|
|
cv::dnn::NMSBoxes(boxes, class_scores,
|
|
|
|
|
_modelConfig.detectionScoreThreshold,
|
|
|
|
|
_modelConfig.modelMNSThreshold, indices);
|
|
|
|
|
|
|
|
|
|
// Process final detections
|
|
|
|
|
std::vector<Object> outputs;
|
|
|
|
|
outputs.reserve(indices.size());
|
|
|
|
|
|
|
|
|
|
const int classNameSize = static_cast<int>(_classes.size());
|
|
|
|
|
|
|
|
|
|
for (int idx : indices) {
|
|
|
|
|
// Generate mask for this detection
|
|
|
|
|
cv::Mat m = mask_confs[idx] * proto; // [1, 25600]
|
|
|
|
|
|
|
|
|
|
// Apply sigmoid vectorized
|
|
|
|
|
cv::exp(-m, m);
|
|
|
|
|
m = 1.0 / (1.0 + m);
|
|
|
|
|
|
|
|
|
|
// Reshape to 160x160
|
|
|
|
|
cv::Mat m1 = m.reshape(1, 160);
|
|
|
|
|
|
|
|
|
|
// Get ROI coordinates
|
|
|
|
|
const cv::Rect& box = boxes[idx];
|
|
|
|
|
int x1 = std::max(0, box.x);
|
|
|
|
|
int y1 = std::max(0, box.y);
|
|
|
|
|
int x2 = std::min(input.cols, box.br().x);
|
|
|
|
|
int y2 = std::min(input.rows, box.br().y);
|
|
|
|
|
|
|
|
|
|
if (x2 <= x1 || y2 <= y1) continue; // Invalid box
|
|
|
|
|
|
|
|
|
|
// Calculate mask ROI (in 160x160 space, 0.25 scale)
|
|
|
|
|
int mx1 = static_cast<int>(x1 / scale * 0.25);
|
|
|
|
|
int my1 = static_cast<int>(y1 / scale * 0.25);
|
|
|
|
|
int mx2 = static_cast<int>(x2 / scale * 0.25);
|
|
|
|
|
int my2 = static_cast<int>(y2 / scale * 0.25);
|
|
|
|
|
|
|
|
|
|
// Clamp mask coordinates
|
|
|
|
|
mx1 = std::clamp(mx1, 0, 160);
|
|
|
|
|
my1 = std::clamp(my1, 0, 160);
|
|
|
|
|
mx2 = std::clamp(mx2, 0, 160);
|
|
|
|
|
my2 = std::clamp(my2, 0, 160);
|
|
|
|
|
|
|
|
|
|
if (mx2 <= mx1 || my2 <= my1) continue; // Invalid mask ROI
|
|
|
|
|
|
|
|
|
|
// Extract and resize mask
|
|
|
|
|
cv::Mat mask_roi = m1(cv::Range(my1, my2), cv::Range(mx1, mx2));
|
|
|
|
|
cv::Mat rm;
|
|
|
|
|
cv::resize(mask_roi, rm, cv::Size(x2 - x1, y2 - y1));
|
|
|
|
|
|
|
|
|
|
// Threshold mask (vectorized)
|
|
|
|
|
cv::threshold(rm, rm, 0.5, 1.0, cv::THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
// Convert to uint8
|
|
|
|
|
cv::Mat det_mask;
|
|
|
|
|
rm.convertTo(det_mask, CV_8UC1, 255.0);
|
|
|
|
|
|
|
|
|
|
// Create full-size mask
|
|
|
|
|
cv::Mat bbox_mask = cv::Mat::zeros(input.size(), CV_8UC1);
|
|
|
|
|
det_mask.copyTo(bbox_mask(cv::Rect(x1, y1, x2 - x1, y2 - y1)));
|
|
|
|
|
|
|
|
|
|
// Extract contours
|
|
|
|
|
std::vector<std::vector<cv::Point>> contours;
|
|
|
|
|
cv::findContours(bbox_mask, contours, cv::RETR_EXTERNAL,
|
|
|
|
|
cv::CHAIN_APPROX_SIMPLE);
|
|
|
|
|
|
|
|
|
|
if (contours.empty()) continue; // No contour found
|
|
|
|
|
|
|
|
|
|
// Find largest contour
|
|
|
|
|
size_t max_contour_idx = 0;
|
|
|
|
|
double max_area = 0;
|
|
|
|
|
for (size_t i = 0; i < contours.size(); i++) {
|
|
|
|
|
double area = cv::contourArea(contours[i]);
|
|
|
|
|
if (area > max_area) {
|
|
|
|
|
max_area = area;
|
|
|
|
|
max_contour_idx = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normalize polygon
|
|
|
|
|
std::vector<cv::Point2f> normalizedPolygon;
|
|
|
|
|
normalizedPolygon.reserve(contours[max_contour_idx].size());
|
|
|
|
|
for (const auto& pt : contours[max_contour_idx]) {
|
|
|
|
|
normalizedPolygon.emplace_back(
|
|
|
|
|
static_cast<float>(pt.x),
|
|
|
|
|
static_cast<float>(pt.y)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build result object
|
|
|
|
|
Object result;
|
|
|
|
|
result.classId = class_ids[idx];
|
|
|
|
|
result.confidence = class_scores[idx];
|
|
|
|
|
result.box = box;
|
|
|
|
|
result.mask = det_mask.clone(); // Only clone final mask
|
|
|
|
|
result.polygon = std::move(normalizedPolygon);
|
|
|
|
|
result.cameraId = camera_id;
|
|
|
|
|
|
|
|
|
|
// Set class name
|
|
|
|
|
if (!_classes.empty()) {
|
|
|
|
|
result.className = (result.classId < classNameSize)
|
|
|
|
|
? _classes[result.classId]
|
|
|
|
|
: _classes.back();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
result.className = "Unknown";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outputs.push_back(std::move(result));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_trackerEnabled) {
|
|
|
|
|
outputs = ApplyTracking(outputs, camera_id);
|
|
|
|
|
if (_stabilizationEnabled) outputs = StabilizeDetections(outputs, camera_id);
|
|
|
|
|
}
|
|
|
|
|
return outputs;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
_logger.LogFatal("ANSOVSEG::RunInference", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ANSOVSEG::~ANSOVSEG() {
|
|
|
|
|
try {
|
|
|
|
|
// We will do it all togther when exit ANSVIS
|
|
|
|
|
if (FolderExist(_modelFolder)) {
|
|
|
|
|
if (!DeleteFolder(_modelFolder)) {
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::~ANSOVSEG()", "Failed to release OPENVINO Models", __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
std::cout << "ANSOVSEG::~ANSOVSEG()" << e.what() << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool ANSOVSEG::Destroy() {
|
|
|
|
|
try {
|
|
|
|
|
// We will do it all togther when exit ANSVIS
|
|
|
|
|
if (FolderExist(_modelFolder)) {
|
|
|
|
|
if (!DeleteFolder(_modelFolder)) {
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::Destroy()", "Failed to release OPENVINO Models", __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
std::cout << "ANSOVSEG::Destroy()" << e.what() << std::endl;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//private
|
|
|
|
|
void ANSOVSEG::InitialModel() {
|
|
|
|
|
try {
|
|
|
|
|
// Step 1: Initialize OpenVINO Runtime Core
|
|
|
|
|
ov::Core core;
|
|
|
|
|
// Step 2: Get Available Devices and Log
|
|
|
|
|
std::vector<std::string> available_devices = core.get_available_devices();
|
|
|
|
|
// Define device priority: NPU > GPU > CPU
|
|
|
|
|
std::vector<std::string> priority_devices = { "NPU", "GPU" };
|
|
|
|
|
bool device_found = false;
|
|
|
|
|
|
|
|
|
|
// Iterate over prioritized devices
|
|
|
|
|
for (const auto& device : priority_devices) {
|
|
|
|
|
if (std::find(available_devices.begin(), available_devices.end(), device) != available_devices.end()) {
|
|
|
|
|
if (device == "NPU") {
|
|
|
|
|
core.set_property("NPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
|
|
|
|
|
core.set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
|
|
|
|
|
compiled_model_ = core.compile_model(_modelFilePath, "AUTO:NPU,GPU");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Configure and compile for individual device
|
|
|
|
|
core.set_property(device, ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
|
|
|
|
|
compiled_model_ = core.compile_model(_modelFilePath, device);
|
|
|
|
|
}
|
|
|
|
|
device_found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Fallback: Default to CPU if no devices found
|
|
|
|
|
if (!device_found) {
|
|
|
|
|
core.set_property("CPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
|
|
|
|
|
compiled_model_ = core.compile_model(_modelFilePath, "CPU");
|
|
|
|
|
}
|
|
|
|
|
// Step 3: Create Inference Request
|
|
|
|
|
inference_request_ = compiled_model_.create_infer_request();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::InitialModel", e.what(), __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
cv::Mat ANSOVSEG::PreProcessing(const cv::Mat& source) {
|
|
|
|
|
try {
|
|
|
|
|
if (source.empty()) {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::PreProcessing", "Empty image provided", __FILE__, __LINE__);
|
|
|
|
|
return cv::Mat();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert grayscale to 3-channel BGR if needed
|
|
|
|
|
cv::Mat processedImage;
|
|
|
|
|
if (source.channels() == 1) {
|
|
|
|
|
cv::cvtColor(source, processedImage, cv::COLOR_GRAY2BGR);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
processedImage = source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int col = processedImage.cols;
|
|
|
|
|
int row = processedImage.rows;
|
|
|
|
|
int maxSize = std::max(col, row);
|
|
|
|
|
|
|
|
|
|
// Create a square padded image with a black background
|
|
|
|
|
cv::Mat result = cv::Mat::zeros(maxSize, maxSize, CV_8UC3);
|
|
|
|
|
|
|
|
|
|
// Copy the original image to the top-left corner of the square matrix
|
|
|
|
|
processedImage.copyTo(result(cv::Rect(0, 0, col, row)));
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("ANSOVSEG::PreProcessing", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return cv::Mat();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cv::Rect ANSOVSEG::GetBoundingBox(const cv::Rect& src) {
|
|
|
|
|
cv::Rect box = src;
|
|
|
|
|
box.x = (box.x - 0.5 * box.width) * factor_.x;
|
|
|
|
|
box.y = (box.y - 0.5 * box.height) * factor_.y;
|
|
|
|
|
box.width *= factor_.x;
|
|
|
|
|
box.height *= factor_.y;
|
|
|
|
|
return box;
|
|
|
|
|
}
|
|
|
|
|
std::vector<Object>ANSOVSEG::PostProcessing(const std::string& camera_id) {
|
|
|
|
|
try {
|
|
|
|
|
std::vector<int> class_list;
|
|
|
|
|
std::vector<float> confidence_list;
|
|
|
|
|
std::vector<cv::Rect> box_list;
|
|
|
|
|
|
|
|
|
|
float* detections = inference_request_.get_output_tensor().data<float>();
|
|
|
|
|
const cv::Mat detection_outputs(model_output_shape_, CV_32F, (float*)detections);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < detection_outputs.cols; ++i) {
|
|
|
|
|
const cv::Mat classes_scores = detection_outputs.col(i).rowRange(4, detection_outputs.rows);
|
|
|
|
|
|
|
|
|
|
cv::Point class_id;
|
|
|
|
|
double score;
|
|
|
|
|
|
|
|
|
|
cv::minMaxLoc(classes_scores, nullptr, &score, nullptr, &class_id);
|
|
|
|
|
|
|
|
|
|
if (score >= _modelConfig.detectionScoreThreshold) {
|
|
|
|
|
class_list.push_back(class_id.y);
|
|
|
|
|
confidence_list.push_back(score);
|
|
|
|
|
|
|
|
|
|
const float x = detection_outputs.at<float>(0, i);
|
|
|
|
|
const float y = detection_outputs.at<float>(1, i);
|
|
|
|
|
const float w = detection_outputs.at<float>(2, i);
|
|
|
|
|
const float h = detection_outputs.at<float>(3, i);
|
|
|
|
|
|
|
|
|
|
cv::Rect box;
|
|
|
|
|
|
|
|
|
|
box.x = static_cast<int>(x);
|
|
|
|
|
box.y = static_cast<int>(y);
|
|
|
|
|
box.width = static_cast<int>(w);
|
|
|
|
|
box.height = static_cast<int>(h);
|
|
|
|
|
|
|
|
|
|
box_list.push_back(box);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<int> NMS_result;
|
|
|
|
|
cv::dnn::NMSBoxes(box_list, confidence_list, _modelConfig.modelConfThreshold, _modelConfig.modelMNSThreshold, NMS_result);
|
|
|
|
|
|
|
|
|
|
std::vector<Object> output;
|
|
|
|
|
for (int i = 0; i < NMS_result.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
Object result;
|
|
|
|
|
int id = NMS_result[i];
|
|
|
|
|
result.classId = class_list[id];
|
|
|
|
|
result.confidence = confidence_list[id];
|
|
|
|
|
result.box = GetBoundingBox(box_list[id]);
|
|
|
|
|
result.cameraId = camera_id;
|
|
|
|
|
output.push_back(result);
|
|
|
|
|
}
|
|
|
|
|
//EnqueueDetection(output,camera_id);
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
std::vector<Object> result;
|
|
|
|
|
result.clear();
|
|
|
|
|
this->_logger.LogError("ANSOVSEG::PostprocessImage", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
float ANSOVSEG::sigmoid_function(float a) {
|
|
|
|
|
float b = 1. / (1. + exp(-a));
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only work for Yolov8 for now, extract polygons does not work as expected
|
|
|
|
|
//std::vector<Object> ANSOVSEG::RunInference(const cv::Mat& input, const std::string& camera_id) {
|
|
|
|
|
// std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
// std::vector<Object> outputs;
|
|
|
|
|
// outputs.clear();
|
|
|
|
|
// if (!_licenseValid) {
|
|
|
|
|
// this->_logger.LogError("ANSOVSEG::RunInference", "Invalid License", __FILE__, __LINE__);
|
|
|
|
|
// return outputs;
|
|
|
|
|
// }
|
|
|
|
|
// if (!_isInitialized) {
|
|
|
|
|
// this->_logger.LogError("ANSOVSEG::RunInference", "Model is not initialized", __FILE__, __LINE__);
|
|
|
|
|
// return outputs;
|
|
|
|
|
// }
|
|
|
|
|
// try {
|
|
|
|
|
// // Step 0: Prepare input
|
|
|
|
|
// if (input.empty()) return outputs;
|
|
|
|
|
// if ((input.cols < 10) || (input.rows < 10)) return outputs;
|
|
|
|
|
// cv::Mat letterbox_img = PreProcessing(input);
|
|
|
|
|
// if (letterbox_img.empty()) {
|
|
|
|
|
// _logger.LogError("OPENVINOOD::RunInference", "PreProcessing failed", __FILE__, __LINE__);
|
|
|
|
|
// return outputs;
|
|
|
|
|
// }
|
|
|
|
|
// int imageSize = 640;
|
|
|
|
|
// int maxImageSize = std::max(letterbox_img.cols, letterbox_img.rows);
|
|
|
|
|
// //if (maxImageSize < imageSize)imageSize = maxImageSize;
|
|
|
|
|
// float scale = static_cast<float>(letterbox_img.rows) / imageSize;
|
|
|
|
|
// cv::Mat blob = cv::dnn::blobFromImage(letterbox_img, 1.0 / 255.0,cv::Size(imageSize, imageSize),cv::Scalar(), true);
|
|
|
|
|
|
|
|
|
|
// // Step 1: Feed blob to the network
|
|
|
|
|
// auto input_port = compiled_model_.input();
|
|
|
|
|
// ov::Tensor input_tensor(input_port.get_element_type(), input_port.get_shape(), blob.ptr(0));
|
|
|
|
|
// inference_request_.set_input_tensor(input_tensor);
|
|
|
|
|
// inference_request_.infer();
|
|
|
|
|
|
|
|
|
|
// // Step 2: Get output
|
|
|
|
|
// auto output0 = inference_request_.get_output_tensor(0); //output0
|
|
|
|
|
// auto output1 = inference_request_.get_output_tensor(1); //otuput1
|
|
|
|
|
// auto output0_shape = output0.get_shape();
|
|
|
|
|
// auto output1_shape = output1.get_shape();
|
|
|
|
|
//
|
|
|
|
|
// // Step 3: Postprocess the inference result
|
|
|
|
|
// cv::Mat output_buffer(output0_shape[1], output0_shape[2], CV_32F, output0.data<float>());
|
|
|
|
|
// cv::Mat proto(32, 25600, CV_32F, output1.data<float>()); //[32,25600]
|
|
|
|
|
// transpose(output_buffer, output_buffer); //[8400,116]
|
|
|
|
|
//
|
|
|
|
|
// std::vector<int> class_ids;
|
|
|
|
|
// std::vector<float> class_scores;
|
|
|
|
|
// std::vector<cv::Rect> boxes;
|
|
|
|
|
// std::vector<cv::Mat> mask_confs;
|
|
|
|
|
// // Figure out the bbox, class_id and class_score
|
|
|
|
|
// for (int i = 0; i < output_buffer.rows; i++) {
|
|
|
|
|
// cv::Mat classes_scores = output_buffer.row(i).colRange(4, 84);
|
|
|
|
|
// cv::Point class_id;
|
|
|
|
|
// double maxClassScore;
|
|
|
|
|
// minMaxLoc(classes_scores, 0, &maxClassScore, 0, &class_id);
|
|
|
|
|
// if (maxClassScore > _modelConfig.detectionScoreThreshold) {
|
|
|
|
|
// class_scores.push_back(maxClassScore);
|
|
|
|
|
// class_ids.push_back(class_id.x);
|
|
|
|
|
// float cx = output_buffer.at<float>(i, 0);
|
|
|
|
|
// float cy = output_buffer.at<float>(i, 1);
|
|
|
|
|
// float w = output_buffer.at<float>(i, 2);
|
|
|
|
|
// float h = output_buffer.at<float>(i, 3);
|
|
|
|
|
|
|
|
|
|
// int left = int((cx - 0.5 * w) * scale);
|
|
|
|
|
// int top = int((cy - 0.5 * h) * scale);
|
|
|
|
|
// int width = int(w * scale);
|
|
|
|
|
// int height = int(h * scale);
|
|
|
|
|
|
|
|
|
|
// cv::Mat mask_conf = output_buffer.row(i).colRange(84, 116);
|
|
|
|
|
// mask_confs.push_back(mask_conf);
|
|
|
|
|
// boxes.push_back(cv::Rect(left, top, width, height));
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// //NMS
|
|
|
|
|
// std::vector<int> indices;
|
|
|
|
|
// cv::dnn::NMSBoxes(boxes, class_scores, _modelConfig.detectionScoreThreshold, _modelConfig.modelMNSThreshold, indices);
|
|
|
|
|
// int classNameSize = static_cast<int>(_classes.size());
|
|
|
|
|
// for (size_t i = 0; i < indices.size(); i++) {
|
|
|
|
|
// // Visualize the objects
|
|
|
|
|
// if (class_scores[i] >= _modelConfig.detectionScoreThreshold) {
|
|
|
|
|
// int index = indices[i];
|
|
|
|
|
// // Visualize the Masks
|
|
|
|
|
// cv::Mat m = mask_confs[i] * proto;
|
|
|
|
|
// for (int col = 0; col < m.cols; col++) {
|
|
|
|
|
// m.at<float>(0, col) = sigmoid_function(m.at<float>(0, col));
|
|
|
|
|
// }
|
|
|
|
|
// cv::Mat m1 = m.reshape(1, 160); // 1x25600 -> 160x160
|
|
|
|
|
// int x1 = std::max(0, boxes[index].x);
|
|
|
|
|
// int y1 = std::max(0, boxes[index].y);
|
|
|
|
|
// int x2 = std::min(input.cols, boxes[index].br().x);
|
|
|
|
|
// int y2 = std::min(input.rows, boxes[index].br().y);
|
|
|
|
|
|
|
|
|
|
// int mx1 = int(x1 / scale * 0.25);
|
|
|
|
|
// int my1 = int(y1 / scale * 0.25);
|
|
|
|
|
// int mx2 = int(x2 / scale * 0.25);
|
|
|
|
|
// int my2 = int(y2 / scale * 0.25);
|
|
|
|
|
|
|
|
|
|
// cv::Mat mask_roi = m1(cv::Range(my1, my2), cv::Range(mx1, mx2));
|
|
|
|
|
// cv::Mat rm, det_mask;
|
|
|
|
|
// cv::resize(mask_roi, rm, cv::Size(x2 - x1, y2 - y1));
|
|
|
|
|
|
|
|
|
|
// for (int r = 0; r < rm.rows; r++) {
|
|
|
|
|
// for (int c = 0; c < rm.cols; c++) {
|
|
|
|
|
// float pv = rm.at<float>(r, c);
|
|
|
|
|
// rm.at<float>(r, c) = (pv > 0.5) ? 1.0 : 0.0;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// rm = rm * 255; // Scale mask to 255 for visualization
|
|
|
|
|
// rm.convertTo(det_mask, CV_8UC1);
|
|
|
|
|
// // Extract contours from the bounding box region
|
|
|
|
|
// cv::Mat bbox_mask = cv::Mat::zeros(input.size(), CV_8UC1);
|
|
|
|
|
// det_mask.copyTo(bbox_mask(cv::Rect(x1, y1, x2 - x1, y2 - y1)));
|
|
|
|
|
|
|
|
|
|
// std::vector<std::vector<cv::Point>> contours;
|
|
|
|
|
// cv::findContours(bbox_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
|
|
|
|
|
|
|
|
|
|
// // Normalize the polygon
|
|
|
|
|
// std::vector<cv::Point2f> normalizedPolygon;
|
|
|
|
|
// for (const auto& pt : contours[0]) {
|
|
|
|
|
// normalizedPolygon.emplace_back(static_cast<float>(pt.x) / 1.0, static_cast<float>(pt.y) / 1.0);
|
|
|
|
|
// }
|
|
|
|
|
// // add to outputs
|
|
|
|
|
// Object result;
|
|
|
|
|
// int class_id = class_ids[i];
|
|
|
|
|
// result.classId = class_id;
|
|
|
|
|
// if (!_classes.empty()) {
|
|
|
|
|
// if (result.classId < classNameSize) {
|
|
|
|
|
// result.className = _classes[result.classId];
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// result.className = _classes[classNameSize - 1]; // Use last valid class name if out of range
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// result.className = "Unknown"; // Fallback if _classes is empty
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// result.confidence = class_scores[i];
|
|
|
|
|
// result.box = boxes[i];
|
|
|
|
|
// result.mask = det_mask(cv::Rect(x1, y1, x2 - x1, y2 - y1)).clone();
|
|
|
|
|
// result.polygon = normalizedPolygon;
|
|
|
|
|
// result.cameraId = camera_id;
|
|
|
|
|
// outputs.push_back(result);
|
|
|
|
|
//
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// return outputs;
|
|
|
|
|
// }
|
|
|
|
|
// catch (std::exception& e) {
|
|
|
|
|
// this->_logger.LogFatal("ANSOVSEG::RunInference", e.what(), __FILE__, __LINE__);
|
|
|
|
|
|
|
|
|
|
// return outputs;
|
|
|
|
|
// }
|
|
|
|
|
//}
|