| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #ifndef _EDGE_IMPULSE_NMS_H_ |
| #define _EDGE_IMPULSE_NMS_H_ |
|
|
| #include "model-parameters/model_metadata.h" |
| #include "edge-impulse-sdk/classifier/ei_model_types.h" |
| #include "edge-impulse-sdk/classifier/ei_classifier_types.h" |
| #include "edge-impulse-sdk/porting/ei_classifier_porting.h" |
|
|
| #if (EI_HAS_YOLOV5 || EI_HAS_YOLOX || EI_HAS_TAO_DECODE_DETECTIONS || EI_HAS_TAO_YOLOV3 || EI_HAS_TAO_YOLOV4 || EI_HAS_YOLOV2 || EI_HAS_YOLO_PRO || EI_HAS_YOLOV11 || EI_HAS_QC_FACE_DET_LITE || EI_HAS_QC_YOLOX) |
|
|
| |
| |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <deque> |
| #include <queue> |
|
|
| |
| struct BoxCornerEncoding { |
| float y1; |
| float x1; |
| float y2; |
| float x2; |
| }; |
|
|
| static inline float ComputeIntersectionOverUnion(const float* boxes, const int i, |
| const int j) { |
| auto& box_i = reinterpret_cast<const BoxCornerEncoding*>(boxes)[i]; |
| auto& box_j = reinterpret_cast<const BoxCornerEncoding*>(boxes)[j]; |
| const float box_i_y_min = std::min<float>(box_i.y1, box_i.y2); |
| const float box_i_y_max = std::max<float>(box_i.y1, box_i.y2); |
| const float box_i_x_min = std::min<float>(box_i.x1, box_i.x2); |
| const float box_i_x_max = std::max<float>(box_i.x1, box_i.x2); |
| const float box_j_y_min = std::min<float>(box_j.y1, box_j.y2); |
| const float box_j_y_max = std::max<float>(box_j.y1, box_j.y2); |
| const float box_j_x_min = std::min<float>(box_j.x1, box_j.x2); |
| const float box_j_x_max = std::max<float>(box_j.x1, box_j.x2); |
|
|
| const float area_i = |
| (box_i_y_max - box_i_y_min) * (box_i_x_max - box_i_x_min); |
| const float area_j = |
| (box_j_y_max - box_j_y_min) * (box_j_x_max - box_j_x_min); |
| if (area_i <= 0 || area_j <= 0) return 0.0; |
| const float intersection_ymax = std::min<float>(box_i_y_max, box_j_y_max); |
| const float intersection_xmax = std::min<float>(box_i_x_max, box_j_x_max); |
| const float intersection_ymin = std::max<float>(box_i_y_min, box_j_y_min); |
| const float intersection_xmin = std::max<float>(box_i_x_min, box_j_x_min); |
| const float intersection_area = |
| std::max<float>(intersection_ymax - intersection_ymin, 0.0) * |
| std::max<float>(intersection_xmax - intersection_xmin, 0.0); |
| return intersection_area / (area_i + area_j - intersection_area); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static inline void NonMaxSuppression(const float* boxes, const int num_boxes, |
| const float* scores, const int max_output_size, |
| const float iou_threshold, |
| const float score_threshold, |
| const float soft_nms_sigma, int* selected_indices, |
| float* selected_scores, |
| int* num_selected_indices) { |
| struct Candidate { |
| int index; |
| float score; |
| int suppress_begin_index; |
| }; |
|
|
| |
| auto cmp = [](const Candidate bs_i, const Candidate bs_j) { |
| return bs_i.score < bs_j.score; |
| }; |
| std::priority_queue<Candidate, std::deque<Candidate>, decltype(cmp)> |
| candidate_priority_queue(cmp); |
| |
| for (int i = 0; i < num_boxes; ++i) { |
| if (scores[i] > score_threshold) { |
| candidate_priority_queue.emplace(Candidate({i, scores[i], 0})); |
| } |
| } |
|
|
| *num_selected_indices = 0; |
| int num_outputs = std::min(static_cast<int>(candidate_priority_queue.size()), |
| max_output_size); |
| if (num_outputs == 0) return; |
|
|
| |
| float scale = 0; |
| if (soft_nms_sigma > 0.0) { |
| scale = -0.5 / soft_nms_sigma; |
| } |
| while (*num_selected_indices < num_outputs && |
| !candidate_priority_queue.empty()) { |
| Candidate next_candidate = candidate_priority_queue.top(); |
| const float original_score = next_candidate.score; |
| candidate_priority_queue.pop(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| bool should_hard_suppress = false; |
| for (int j = *num_selected_indices - 1; |
| j >= next_candidate.suppress_begin_index; --j) { |
| const float iou = ComputeIntersectionOverUnion( |
| boxes, next_candidate.index, selected_indices[j]); |
|
|
| |
| if (iou >= iou_threshold) { |
| should_hard_suppress = true; |
| break; |
| } |
|
|
| |
| if (soft_nms_sigma > 0.0) { |
| next_candidate.score = |
| next_candidate.score * std::exp(scale * iou * iou); |
| } |
|
|
| |
| |
| if (next_candidate.score <= score_threshold) break; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| next_candidate.suppress_begin_index = *num_selected_indices; |
|
|
| if (!should_hard_suppress) { |
| if (next_candidate.score == original_score) { |
| |
| selected_indices[*num_selected_indices] = next_candidate.index; |
| if (selected_scores) { |
| selected_scores[*num_selected_indices] = next_candidate.score; |
| } |
| ++*num_selected_indices; |
| } |
| if ((soft_nms_sigma > 0.0) && (next_candidate.score > score_threshold)) { |
| |
| |
| |
| candidate_priority_queue.push(next_candidate); |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| EI_IMPULSE_ERROR ei_run_nms( |
| const ei_impulse_t *impulse, |
| std::vector<ei_impulse_result_bounding_box_t> *results, |
| float *boxes, |
| float *scores, |
| int *classes, |
| size_t bb_count, |
| bool clip_boxes, |
| const ei_object_detection_nms_config_t *nms_config) { |
|
|
| if (bb_count < 1) { |
| return EI_IMPULSE_OK; |
| } |
|
|
| int *selected_indices = (int*)ei_malloc(1 * bb_count * sizeof(int)); |
| float *selected_scores = (float*)ei_malloc(1 * bb_count * sizeof(float)); |
|
|
| if (!scores || !boxes || !selected_indices || !selected_scores || !classes) { |
| ei_free(selected_indices); |
| ei_free(selected_scores); |
| return EI_IMPULSE_OUT_OF_MEMORY; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| int num_selected_indices; |
|
|
| NonMaxSuppression( |
| (const float*)boxes, |
| bb_count, |
| (const float*)scores, |
| bb_count, |
| nms_config->iou_threshold, |
| nms_config->confidence_threshold, |
| 0.0f, |
| selected_indices, |
| selected_scores, |
| &num_selected_indices); |
|
|
| std::vector<ei_impulse_result_bounding_box_t> new_results; |
|
|
| for (size_t ix = 0; ix < (size_t)num_selected_indices; ix++) { |
|
|
| int out_ix = selected_indices[ix]; |
| ei_impulse_result_bounding_box_t bb; |
| bb.label = impulse->categories[classes[out_ix]]; |
| bb.value = selected_scores[ix]; |
|
|
| float ymin = boxes[(out_ix * 4) + 0]; |
| float xmin = boxes[(out_ix * 4) + 1]; |
| float ymax = boxes[(out_ix * 4) + 2]; |
| float xmax = boxes[(out_ix * 4) + 3]; |
|
|
| if (clip_boxes) { |
| ymin = std::min(std::max(ymin, 0.0f), (float)impulse->input_height); |
| xmin = std::min(std::max(xmin, 0.0f), (float)impulse->input_width); |
| ymax = std::min(std::max(ymax, 0.0f), (float)impulse->input_height); |
| xmax = std::min(std::max(xmax, 0.0f), (float)impulse->input_width); |
| } |
|
|
| bb.y = static_cast<uint32_t>(ymin); |
| bb.x = static_cast<uint32_t>(xmin); |
| bb.height = static_cast<uint32_t>(ymax) - bb.y; |
| bb.width = static_cast<uint32_t>(xmax) - bb.x; |
| new_results.push_back(bb); |
|
|
| EI_LOGD("Found bb with label %s\n", bb.label); |
| } |
|
|
| results->clear(); |
|
|
| for (size_t ix = 0; ix < new_results.size(); ix++) { |
| results->push_back(new_results[ix]); |
| } |
|
|
| ei_free(selected_indices); |
| ei_free(selected_scores); |
|
|
| return EI_IMPULSE_OK; |
|
|
| } |
|
|
| |
| |
| |
| EI_IMPULSE_ERROR ei_run_nms( |
| const ei_impulse_t *impulse, |
| const ei_object_detection_nms_config_t *nms_config, |
| std::vector<ei_impulse_result_bounding_box_t> *results, |
| bool clip_boxes = true |
| ) { |
|
|
| size_t bb_count = 0; |
| for (size_t ix = 0; ix < results->size(); ix++) { |
| auto bb = results->at(ix); |
| if (bb.value == 0) { |
| continue; |
| } |
| bb_count++; |
| } |
|
|
| if (bb_count < 1) { |
| return EI_IMPULSE_OK; |
| } |
|
|
| float *boxes = (float*)ei_malloc(4 * bb_count * sizeof(float)); |
| float *scores = (float*)ei_malloc(1 * bb_count * sizeof(float)); |
| int *classes = (int*) ei_malloc(bb_count * sizeof(int)); |
|
|
| if (!scores || !boxes || !classes) { |
| ei_free(boxes); |
| ei_free(scores); |
| ei_free(classes); |
| return EI_IMPULSE_OUT_OF_MEMORY; |
| } |
|
|
| size_t box_ix = 0; |
| for (size_t ix = 0; ix < results->size(); ix++) { |
| auto bb = results->at(ix); |
| if (bb.value == 0) { |
| continue; |
| } |
| boxes[(box_ix * 4) + 0] = bb.y; |
| boxes[(box_ix * 4) + 1] = bb.x; |
| boxes[(box_ix * 4) + 2] = bb.y + bb.height; |
| boxes[(box_ix * 4) + 3] = bb.x + bb.width; |
| scores[box_ix] = bb.value; |
|
|
| for (size_t j = 0; j < impulse->label_count; j++) { |
| if (strcmp(impulse->categories[j], bb.label) == 0) |
| classes[box_ix] = j; |
| } |
|
|
| box_ix++; |
| } |
|
|
| EI_IMPULSE_ERROR nms_res = ei_run_nms(impulse, |
| results, |
| boxes, |
| scores, |
| classes, |
| bb_count, |
| clip_boxes, |
| nms_config); |
|
|
|
|
| ei_free(boxes); |
| ei_free(scores); |
| ei_free(classes); |
|
|
| return nms_res; |
|
|
| } |
|
|
| #endif |
|
|
| #if (EI_HAS_TAO_DECODE_DETECTIONS || EI_HAS_TAO_YOLO || EI_HAS_YOLO_PRO || EI_HAS_YOLOV11 || EI_HAS_QC_FACE_DET_LITE || EI_HAS_QC_YOLOX) |
|
|
| __attribute__((unused)) static void prepare_nms_results_common(size_t object_detection_count, |
| ei_impulse_result_t *result, |
| std::vector<ei_impulse_result_bounding_box_t> *results) { |
| #define EI_CLASSIFIER_OBJECT_DETECTION_KEEP_TOPK 200 |
|
|
| |
| size_t added_boxes_count = results->size(); |
| if (added_boxes_count < object_detection_count) { |
| results->resize(object_detection_count); |
| for (size_t ix = added_boxes_count; ix < object_detection_count; ix++) { |
| (*results)[ix].value = 0.0f; |
| } |
| } |
|
|
| |
| |
| std::sort(results->begin(), results->end(), [ ]( const ei_impulse_result_bounding_box_t& lhs, const ei_impulse_result_bounding_box_t& rhs ) |
| { |
| return lhs.value > rhs.value; |
| }); |
|
|
| |
| if (results->size() > EI_CLASSIFIER_OBJECT_DETECTION_KEEP_TOPK) { |
| results->erase(results->begin() + EI_CLASSIFIER_OBJECT_DETECTION_KEEP_TOPK, results->end()); |
| } |
|
|
| result->bounding_boxes = results->data(); |
| result->bounding_boxes_count = added_boxes_count; |
| } |
|
|
| #endif |
| #endif |
|
|