diff --git a/docs/ax650/yolov8s_obb_out.jpg b/docs/ax650/yolov8s_obb_out.jpg new file mode 100644 index 0000000..998f6ca Binary files /dev/null and b/docs/ax650/yolov8s_obb_out.jpg differ diff --git a/examples/ax650/CMakeLists.txt b/examples/ax650/CMakeLists.txt index 14541a4..f14c41a 100644 --- a/examples/ax650/CMakeLists.txt +++ b/examples/ax650/CMakeLists.txt @@ -33,6 +33,7 @@ axera_example(ax_yolov8 ax_yolov8_steps.cc) axera_example(ax_yolov8_nv12 ax_yolov8_nv12_steps.cc) axera_example(ax_yolov8_seg ax_yolov8_seg_steps.cc) axera_example(ax_yolov8_pose ax_yolov8_pose_steps.cc) +axera_example(ax_yolov8_obb ax_yolov8_obb_steps.cc) axera_example(ax_yolox ax_yolox_steps.cc) axera_example(ax_yolo_nas ax_yolo_nas_steps.cc) diff --git a/examples/ax650/README.md b/examples/ax650/README.md index ae608d7..6623b4b 100644 --- a/examples/ax650/README.md +++ b/examples/ax650/README.md @@ -605,4 +605,62 @@ detection num: 5 16: 69%, [ 144, 203, 195, 343], dog -------------------------------------- ``` - \ No newline at end of file + + +### YOLOV8-OBB +``` +/opt/test # ./ax_yolov8_obb -m ./yolov8s-obb.axmodel -i ./dota_demo.jpg -r 10 +-------------------------------------- +model file : ./yolov8s-obb.axmodel +image file : ./dota_demo.jpg +img_h, img_w : 1024 1024 +-------------------------------------- +Engine creating handle is done. +Engine creating context is done. +Engine get io info is done. +Engine alloc io is done. +Engine push input is done. +-------------------------------------- +post process cost time:6.04 ms +-------------------------------------- +Repeat 10 times, avg time 26.66 ms, max_time 26.71 ms, min_time 26.62 ms +-------------------------------------- +detection num: 35 + 0: 93%, [ 691, 632, 766, 697], plane + 0: 93%, [ 642, 579, 701, 634], plane + 0: 93%, [ 392, 318, 466, 382], plane + 0: 93%, [ 272, 191, 375, 281], plane + 0: 91%, [ 342, 260, 443, 347], plane + 0: 91%, [ 421, 593, 498, 660], plane + 0: 91%, [ 182, 409, 279, 501], plane + 0: 91%, [ 591, 522, 693, 608], plane + 0: 91%, [ 832, 781, 942, 857], plane +10: 84%, [ 99, 710, 120, 720], small vehicle +10: 84%, [ 25, 834, 45, 843], small vehicle +10: 79%, [ 173, 724, 192, 733], small vehicle +10: 79%, [ 29, 715, 50, 725], small vehicle +10: 79%, [ 26, 823, 47, 832], small vehicle +10: 79%, [ 101, 733, 119, 743], small vehicle +10: 79%, [ 171, 704, 191, 714], small vehicle +10: 79%, [ 100, 662, 120, 671], small vehicle +10: 79%, [ 101, 686, 119, 696], small vehicle +10: 79%, [ 23, 867, 42, 876], small vehicle +10: 73%, [ 25, 884, 44, 895], small vehicle +10: 73%, [ 167, 835, 185, 845], small vehicle +10: 73%, [ 100, 672, 119, 682], small vehicle +10: 73%, [ 100, 697, 119, 707], small vehicle +10: 73%, [ 31, 702, 53, 712], small vehicle +10: 73%, [ 25, 800, 41, 811], small vehicle +10: 73%, [ 31, 757, 60, 767], small vehicle +10: 66%, [ 23, 845, 40, 853], small vehicle +10: 66%, [ 165, 903, 187, 914], small vehicle +10: 58%, [ 98, 450, 122, 461], small vehicle +10: 58%, [ 23, 856, 40, 865], small vehicle +10: 50%, [ 170, 771, 186, 780], small vehicle +10: 50%, [ 28, 733, 48, 745], small vehicle +10: 50%, [ 168, 925, 188, 936], small vehicle +10: 50%, [ 102, 746, 120, 755], small vehicle +10: 27%, [ 91, 461, 116, 474], small vehicle +-------------------------------------- +``` +![yolov8-obb](../../docs/ax650/yolov8s_obb_out.jpg) \ No newline at end of file diff --git a/examples/ax650/ax_yolov8_obb_steps.cc b/examples/ax650/ax_yolov8_obb_steps.cc new file mode 100644 index 0000000..95bf254 --- /dev/null +++ b/examples/ax650/ax_yolov8_obb_steps.cc @@ -0,0 +1,244 @@ +/* +* AXERA is pleased to support the open source community by making ax-samples available. +* +* Copyright (c) 2022, AXERA Semiconductor (Shanghai) Co., Ltd. All rights reserved. +* +* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +* in compliance with the License. You may obtain a copy of the License at +* +* https://opensource.org/licenses/BSD-3-Clause +* +* Unless required by applicable law or agreed to in writing, software distributed +* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +* CONDITIONS OF ANY KIND, either express or implied. See the License for the +* specific language governing permissions and limitations under the License. +*/ + +/* +* Author: FeiGeChuanShu +*/ + +#include +#include +#include + +#include +#include "base/common.hpp" +#include "base/detection.hpp" +#include "middleware/io.hpp" + +#include "utilities/args.hpp" +#include "utilities/cmdline.hpp" +#include "utilities/file.hpp" +#include "utilities/timer.hpp" + +#include +#include + +const int DEFAULT_IMG_H = 1024; +const int DEFAULT_IMG_W = 1024; + +static const char* CLASS_NAMES[] = { + "plane", "ship", "storage tank", "baseball diamond", "tennis court", + "basketball court", "ground track field", "harbor", "bridge", "large vehicle", + "small vehicle", "helicopter", "roundabout", "soccer ball field", "swimming pool" +}; + +int NUM_CLASS = 15; + +const int DEFAULT_LOOP_COUNT = 1; + +const float PROB_THRESHOLD = 0.25f; +const float NMS_THRESHOLD = 0.45f; +namespace ax +{ + void post_process(AX_ENGINE_IO_INFO_T* io_info, AX_ENGINE_IO_T* io_data, const cv::Mat& mat, int input_w, int input_h, const std::vector& time_costs) + { + std::vector proposals; + std::vector objects; + timer timer_postprocess; + + std::vector strides = { 8, 16, 32 }; + std::vector grid_strides; + detection::generate_grids_and_stride(input_w, input_h, strides, grid_strides); + + auto feat_ptr = (float*)io_data->pOutputs[0].pVirAddr; + detection::obb::generate_proposals_yolov8_obb_native(grid_strides, feat_ptr, PROB_THRESHOLD, proposals, input_w, input_h, NUM_CLASS); + detection::obb::get_out_obb_bbox(proposals, objects, NMS_THRESHOLD, input_h, input_w, mat.rows, mat.cols); + + fprintf(stdout, "post process cost time:%.2f ms \n", timer_postprocess.cost()); + fprintf(stdout, "--------------------------------------\n"); + auto total_time = std::accumulate(time_costs.begin(), time_costs.end(), 0.f); + auto min_max_time = std::minmax_element(time_costs.begin(), time_costs.end()); + fprintf(stdout, + "Repeat %d times, avg time %.2f ms, max_time %.2f ms, min_time %.2f ms\n", + (int)time_costs.size(), + total_time / (float)time_costs.size(), + *min_max_time.second, + *min_max_time.first); + fprintf(stdout, "--------------------------------------\n"); + fprintf(stdout, "detection num: %zu\n", objects.size()); + + detection::obb::draw_objects_obb(mat, objects, CLASS_NAMES, "yolov8s_obb_out", 1); + } + + bool run_model(const std::string& model, const std::vector& data, const int& repeat, cv::Mat& mat, int input_h, int input_w) + { + // 1. init engine +#ifdef AXERA_TARGET_CHIP_AX620E + auto ret = AX_ENGINE_Init(); +#else + AX_ENGINE_NPU_ATTR_T npu_attr; + memset(&npu_attr, 0, sizeof(npu_attr)); + npu_attr.eHardMode = AX_ENGINE_VIRTUAL_NPU_DISABLE; + auto ret = AX_ENGINE_Init(&npu_attr); +#endif + if (0 != ret) + { + return ret; + } + + // 2. load model + std::vector model_buffer; + if (!utilities::read_file(model, model_buffer)) + { + fprintf(stderr, "Read Run-Joint model(%s) file failed.\n", model.c_str()); + return false; + } + + // 3. create handle + AX_ENGINE_HANDLE handle; + ret = AX_ENGINE_CreateHandle(&handle, model_buffer.data(), model_buffer.size()); + SAMPLE_AX_ENGINE_DEAL_HANDLE + fprintf(stdout, "Engine creating handle is done.\n"); + + // 4. create context + ret = AX_ENGINE_CreateContext(handle); + SAMPLE_AX_ENGINE_DEAL_HANDLE + fprintf(stdout, "Engine creating context is done.\n"); + + // 5. set io + AX_ENGINE_IO_INFO_T* io_info; + ret = AX_ENGINE_GetIOInfo(handle, &io_info); + SAMPLE_AX_ENGINE_DEAL_HANDLE + fprintf(stdout, "Engine get io info is done. \n"); + + // 6. alloc io + AX_ENGINE_IO_T io_data; + ret = middleware::prepare_io(io_info, &io_data, std::make_pair(AX_ENGINE_ABST_DEFAULT, AX_ENGINE_ABST_CACHED)); + SAMPLE_AX_ENGINE_DEAL_HANDLE + fprintf(stdout, "Engine alloc io is done. \n"); + + // 7. insert input + ret = middleware::push_input(data, &io_data, io_info); + SAMPLE_AX_ENGINE_DEAL_HANDLE_IO + fprintf(stdout, "Engine push input is done. \n"); + fprintf(stdout, "--------------------------------------\n"); + + // 8. warn up + for (int i = 0; i < 5; ++i) + { + AX_ENGINE_RunSync(handle, &io_data); + } + + // 9. run model + std::vector time_costs(repeat, 0); + for (int i = 0; i < repeat; ++i) + { + timer tick; + ret = AX_ENGINE_RunSync(handle, &io_data); + time_costs[i] = tick.cost(); + SAMPLE_AX_ENGINE_DEAL_HANDLE_IO + } + + // 10. get result + post_process(io_info, &io_data, mat, input_w, input_h, time_costs); + fprintf(stdout, "--------------------------------------\n"); + + middleware::free_io(&io_data); + return AX_ENGINE_DestroyHandle(handle); + } +} // namespace ax + +int main(int argc, char* argv[]) +{ + cmdline::parser cmd; + cmd.add("model", 'm', "joint file(a.k.a. joint model)", true, ""); + cmd.add("image", 'i', "image file", true, ""); + cmd.add("size", 'g', "input_h, input_w", false, std::to_string(DEFAULT_IMG_H) + "," + std::to_string(DEFAULT_IMG_W)); + + cmd.add("repeat", 'r', "repeat count", false, DEFAULT_LOOP_COUNT); + cmd.parse_check(argc, argv); + + // 0. get app args, can be removed from user's app + auto model_file = cmd.get("model"); + auto image_file = cmd.get("image"); + + auto model_file_flag = utilities::file_exist(model_file); + auto image_file_flag = utilities::file_exist(image_file); + + if (!model_file_flag | !image_file_flag) + { + auto show_error = [](const std::string& kind, const std::string& value) { + fprintf(stderr, "Input file %s(%s) is not exist, please check it.\n", kind.c_str(), value.c_str()); + }; + + if (!model_file_flag) { show_error("model", model_file); } + if (!image_file_flag) { show_error("image", image_file); } + + return -1; + } + + auto input_size_string = cmd.get("size"); + + std::array input_size = {DEFAULT_IMG_H, DEFAULT_IMG_W}; + + auto input_size_flag = utilities::parse_string(input_size_string, input_size); + + if (!input_size_flag) + { + auto show_error = [](const std::string& kind, const std::string& value) { + fprintf(stderr, "Input %s(%s) is not allowed, please check it.\n", kind.c_str(), value.c_str()); + }; + + show_error("size", input_size_string); + + return -1; + } + + auto repeat = cmd.get("repeat"); + + // 1. print args + fprintf(stdout, "--------------------------------------\n"); + fprintf(stdout, "model file : %s\n", model_file.c_str()); + fprintf(stdout, "image file : %s\n", image_file.c_str()); + fprintf(stdout, "img_h, img_w : %d %d\n", input_size[0], input_size[1]); + fprintf(stdout, "--------------------------------------\n"); + + // 2. read image & resize & transpose + std::vector image(input_size[0] * input_size[1] * 3, 0); + cv::Mat mat = cv::imread(image_file); + if (mat.empty()) + { + fprintf(stderr, "Read image failed.\n"); + return -1; + } + common::get_input_data_letterbox(mat, image, input_size[0], input_size[1]); + + // 3. sys_init + AX_SYS_Init(); + + // 4. - engine model - can only use AX_ENGINE** inside + { + // AX_ENGINE_NPUReset(); // todo ?? + ax::run_model(model_file, image, repeat, mat, input_size[0], input_size[1]); + + // 4.3 engine de init + AX_ENGINE_Deinit(); + // AX_ENGINE_NPUReset(); + } + // 4. - engine model - + + AX_SYS_Deinit(); + return 0; +} diff --git a/examples/base/detection.hpp b/examples/base/detection.hpp index 72cae5a..575b0c0 100644 --- a/examples/base/detection.hpp +++ b/examples/base/detection.hpp @@ -45,6 +45,8 @@ namespace detection cv::Mat mask; std::vector mask_feat; std::vector kps_feat; + /* for yolov8-obb */ + float angle; } Object; /* for palm detection */ @@ -1311,7 +1313,7 @@ namespace detection } } } - + static void generate_proposals_yolov8_native(int stride, const float* feat, float prob_threshold, std::vector& objects, int letterbox_cols, int letterbox_rows, int cls_num = 80) { @@ -2668,4 +2670,268 @@ namespace detection } } // namespace mmyolo + namespace obb + { + static inline float fast_exp(const float& x) + { + union + { + uint32_t i; + float f; + } v; + v.i = (1 << 23) * (1.4426950409 * x + 126.93490512f); + return v.f; + } + + static inline float clamp_(float v, float min = 0.f, float max = std::numeric_limits::infinity()) + { + return std::max(std::min(v, max), min); + } + static inline void get_covariance_matrix( + const std::vector& objects, + std::vector& covar_maxtirx) + { + int n = objects.size(); + for (size_t i = 0; i < n; ++i) { + + float a = objects[i].rect.width * objects[i].rect.width * 0.0833333333f; + float b = objects[i].rect.height * objects[i].rect.height * 0.0833333333f; + float c = objects[i].angle; + + float c_sin = std::sin(c); + float c_cos = std::cos(c); + + float x = a * c_cos * c_cos + b * c_sin * c_sin; + float y = a * c_sin * c_sin + b * c_cos * c_cos; + float z = a * c_cos * c_sin - b * c_sin * c_cos; + covar_maxtirx.push_back(cv::Point3f(x, y, z)); + } + } + + static inline float fast_log2 (float x) + { + union { + float f; + int i; + } v; + v.f = x; + int* const exp_ptr = &v.i; + int m = *exp_ptr; + const int log_2 = ((m >> 23) & 255) - 128; + m &= ~(255 << 23); + m += 127 << 23; + *exp_ptr = m; + return (((-1.f / 3) * v.f + 2) * v.f - 2.f / 3 + log_2); + } + + static inline float fast_log (const float x) + { + return 0.69314718f * fast_log2 (x); + } + + static inline float probiou( + const Object& obj1, + const Object& obj2, + const cv::Point3f& covar1, + const cv::Point3f& covar2) + { + + float v_x1_x2 = covar1.x + covar2.x; + float v_y1_y2 = covar1.y + covar2.y; + float x1_x2 = obj1.rect.x - obj2.rect.x; + float y1_y2 = obj1.rect.y - obj2.rect.y; + float dem1 = clamp_(covar1.x * covar1.y - covar1.z * covar1.z); + float dem2 = clamp_(covar2.x * covar2.y - covar2.z * covar2.z); + float dem_num = v_x1_x2 * v_y1_y2 - (covar1.z + covar2.z) * (covar1.z + covar2.z); + + float t1 = (v_x1_x2 * y1_y2 * y1_y2 + v_y1_y2 * x1_x2 * x1_x2) / (dem_num + 1e-7) * 0.25f; + float t2 = ((covar1.z + covar2.z) * (-x1_x2) * y1_y2) / (dem_num + 1e-7) * 0.5f; + float t3 = fast_log(0.25f * dem_num / (std::sqrt(dem1 * dem2) + 1e-7) + 1e-7) * 0.5f; + + float bd = t1 + t2 + t3; + float iou = fast_exp(-clamp_(bd, 1e-7, 100.f)); + return iou; + } + + static inline void nms_rotated_sorted_bboxes( + const std::vector& objects, + std::vector& picked, + float nms_threshold) + { + std::vector covar_maxtrix; + get_covariance_matrix(objects, covar_maxtrix); + + float nms_threshold_ = (1.f - nms_threshold) * (1.f - nms_threshold); + int n = objects.size(); + + for (size_t i = 0; i < n; ++i) { + float max_iou = 0.f; + const Object& obj1 = objects[i]; + + for (size_t j = 0; j < i; ++j) { + const Object& obj2 = objects[j]; + + float iou = probiou(obj1, obj2, covar_maxtrix[i], covar_maxtrix[j]); + if (iou > max_iou){ + max_iou = iou; + } + + } + if (max_iou < nms_threshold_) + picked.push_back(i); + } + } + static void get_out_obb_bbox(std::vector& proposals, std::vector& objects, const float nms_threshold, int letterbox_rows, int letterbox_cols, int src_rows, int src_cols) + { + qsort_descent_inplace(proposals); + std::vector picked; + obb::nms_rotated_sorted_bboxes(proposals, picked, nms_threshold); + + /* yolov5 draw the result */ + float scale_letterbox; + int resize_rows; + int resize_cols; + if ((letterbox_rows * 1.0 / src_rows) < (letterbox_cols * 1.0 / src_cols)) + { + scale_letterbox = letterbox_rows * 1.0 / src_rows; + } + else + { + scale_letterbox = letterbox_cols * 1.0 / src_cols; + } + resize_cols = int(scale_letterbox * src_cols); + resize_rows = int(scale_letterbox * src_rows); + + int tmp_h = (letterbox_rows - resize_rows) / 2; + int tmp_w = (letterbox_cols - resize_cols) / 2; + + float ratio_x = (float)src_rows / resize_rows; + float ratio_y = (float)src_cols / resize_cols; + + int count = picked.size(); + double pi = M_PI; + double pi_2 = M_PI_2; + objects.resize(count); + for (int i = 0; i < count; i++) { + objects[i] = proposals[picked[i]]; + + float w_ = objects[i].rect.width > objects[i].rect.height ? objects[i].rect.width : objects[i].rect.height; + float h_ = objects[i].rect.width > objects[i].rect.height ? objects[i].rect.height : objects[i].rect.width; + float a_ = (float)std::fmod((objects[i].rect.width > objects[i].rect.height ? objects[i].angle : objects[i].angle + pi_2), pi); + + float xc = (objects[i].rect.x - tmp_w) * ratio_x; + float yc = (objects[i].rect.y - tmp_h) * ratio_y; + float w = w_ * ratio_x; + float h = h_ * ratio_y; + + // clip + xc = std::max(std::min(xc, (float)(src_cols - 1)), 0.f); + yc = std::max(std::min(yc, (float)(src_rows - 1)), 0.f); + w = std::max(std::min(w, (float)(src_cols - 1)), 0.f); + h = std::max(std::min(h, (float)(src_rows - 1)), 0.f); + + + objects[i].rect.x = xc; + objects[i].rect.y = yc; + objects[i].rect.width = w; + objects[i].rect.height = h; + objects[i].angle = a_; + } + } + static void generate_proposals_yolov8_obb_native(const std::vector& grid_strides, const float* feat, float prob_threshold, std::vector& objects, + int letterbox_cols, int letterbox_rows, int cls_num = 15) + { + const int num_points = grid_strides.size(); + int reg_max = 16; + auto feat_ptr = feat; + std::vector dis_after_sm(reg_max, 0.f); + for (int i = 0; i < num_points; i++) + { + // process cls score + int class_index = 0; + float class_score = -FLT_MAX; + for (int s = 0; s < cls_num; s++) + { + float score = feat_ptr[s + 4 * reg_max]; + if (score > class_score) + { + class_index = s; + class_score = score; + } + } + + float box_prob = sigmoid(class_score); + if (box_prob > prob_threshold) + { + float pred_ltrb[4]; + for (int k = 0; k < 4; k++) + { + float dis = softmax(feat_ptr + k * reg_max, dis_after_sm.data(), reg_max); + pred_ltrb[k] = dis * grid_strides[i].stride; + } + + float angle = feat_ptr[4 * reg_max + cls_num]; + + float pb_cx = (grid_strides[i].grid0 + 0.5f) * grid_strides[i].stride; + float pb_cy = (grid_strides[i].grid1 + 0.5f) * grid_strides[i].stride; + + float cos = std::cos(angle); + float sin = std::sin(angle); + + float x = (pred_ltrb[2] - pred_ltrb[0]) * 0.5f; + float y = (pred_ltrb[3] - pred_ltrb[1]) * 0.5f; + float xc = x * cos - y * sin + pb_cx; + float yc = x * sin + y * cos + pb_cy; + float w = pred_ltrb[2] + pred_ltrb[0]; + float h = pred_ltrb[3] + pred_ltrb[1]; + + Object obj; + obj.rect.x = xc; //center x + obj.rect.y = yc; //center y + obj.rect.width = w; + obj.rect.height = h; + obj.label = class_index; + obj.prob = box_prob; + obj.angle = angle; + + objects.push_back(obj); + } + feat_ptr += (cls_num + 4 * reg_max + 1); + } + } + static void draw_objects_obb(const cv::Mat& bgr, const std::vector& objects, const char** class_names, const char* output_name, int thickness = 1) + { + static const std::vector COCO_COLORS = { + {128, 56, 0, 255}, {128, 226, 255, 0}, {128, 0, 94, 255}, {128, 0, 37, 255}, {128, 0, 255, 94}, {128, 255, 226, 0}, {128, 0, 18, 255}, {128, 255, 151, 0}, {128, 170, 0, 255}, {128, 0, 255, 56}, {128, 255, 0, 75}, {128, 0, 75, 255}, {128, 0, 255, 169}, {128, 255, 0, 207}, {128, 75, 255, 0}, {128, 207, 0, 255}, {128, 37, 0, 255}, {128, 0, 207, 255}, {128, 94, 0, 255}, {128, 0, 255, 113}, {128, 255, 18, 0}, {128, 255, 0, 56}, {128, 18, 0, 255}, {128, 0, 255, 226}, {128, 170, 255, 0}, {128, 255, 0, 245}, {128, 151, 255, 0}, {128, 132, 255, 0}, {128, 75, 0, 255}, {128, 151, 0, 255}, {128, 0, 151, 255}, {128, 132, 0, 255}, {128, 0, 255, 245}, {128, 255, 132, 0}, {128, 226, 0, 255}, {128, 255, 37, 0}, {128, 207, 255, 0}, {128, 0, 255, 207}, {128, 94, 255, 0}, {128, 0, 226, 255}, {128, 56, 255, 0}, {128, 255, 94, 0}, {128, 255, 113, 0}, {128, 0, 132, 255}, {128, 255, 0, 132}, {128, 255, 170, 0}, {128, 255, 0, 188}, {128, 113, 255, 0}, {128, 245, 0, 255}, {128, 113, 0, 255}, {128, 255, 188, 0}, {128, 0, 113, 255}, {128, 255, 0, 0}, {128, 0, 56, 255}, {128, 255, 0, 113}, {128, 0, 255, 188}, {128, 255, 0, 94}, {128, 255, 0, 18}, {128, 18, 255, 0}, {128, 0, 255, 132}, {128, 0, 188, 255}, {128, 0, 245, 255}, {128, 0, 169, 255}, {128, 37, 255, 0}, {128, 255, 0, 151}, {128, 188, 0, 255}, {128, 0, 255, 37}, {128, 0, 255, 0}, {128, 255, 0, 170}, {128, 255, 0, 37}, {128, 255, 75, 0}, {128, 0, 0, 255}, {128, 255, 207, 0}, {128, 255, 0, 226}, {128, 255, 245, 0}, {128, 188, 255, 0}, {128, 0, 255, 18}, {128, 0, 255, 75}, {128, 0, 255, 151}, {128, 255, 56, 0}, {128, 245, 255, 0}}; + cv::Mat image = bgr.clone(); + + for (size_t i = 0; i < objects.size(); i++){ + const Object& obj = objects[i]; + + fprintf(stdout, "%2d: %3.0f%%, [%4.0f, %4.0f, %4.0f, %4.0f], %s\n", obj.label, obj.prob * 100, obj.rect.x, + obj.rect.y, obj.rect.x + obj.rect.width, obj.rect.y + obj.rect.height, class_names[obj.label]); + { + float xc = obj.rect.x; + float yc = obj.rect.y; + float w = obj.rect.width; + float h = obj.rect.height; + float ag = obj.angle; + float wx = w / 2 * std::cos(ag); + float wy = w / 2 * std::sin(ag); + float hx = -h / 2 * std::sin(ag); + float hy = h / 2 * std::cos(ag); + cv::Point2f p1{ xc - wx - hx, yc - wy - hy }; + cv::Point2f p2{ xc + wx - hx, yc + wy - hy }; + cv::Point2f p3{ xc + wx + hx, yc + wy + hy }; + cv::Point2f p4{ xc - wx + hx, yc - wy + hy }; + std::vector points = { p1, p2, p3, p4, p1 }; + std::vector> contours= { points }; + cv::polylines(image, contours, true, COCO_COLORS[obj.label], thickness, cv::LINE_AA); + } + } + + cv::imwrite(std::string(output_name) + ".jpg", image); + } + } // namespace obb + } // namespace detection