diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 81531cfebbd0d311428c12c59bc7ee5a693a0785..bca1143eee6c931216347d6ee1be66b84ae03abf 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -158,6 +158,7 @@ IF (OpenCV_FOUND) TENGINE_EXAMPLE_CV (tm_movenet tm_movenet.cpp) TENGINE_EXAMPLE_CV (tm_picodet tm_picodet.cpp) TENGINE_EXAMPLE_CV (tm_handpose tm_handpose.cpp) + TENGINE_EXAMPLE_CV (tm_nanodet_plus tm_nanodet_plus.cpp) IF(TENGINE_ENABLE_TIM_VX) TENGINE_EXAMPLE_CV (tm_yolov3_timvx tm_yolov3_timvx.cpp) TENGINE_EXAMPLE_CV (tm_yolov4_tiny_timvx tm_yolov4_tiny_timvx.cpp) diff --git a/examples/tm_nanodet_plus.cpp b/examples/tm_nanodet_plus.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b139142c22983a275cb9229c1a5ebda31820b91a --- /dev/null +++ b/examples/tm_nanodet_plus.cpp @@ -0,0 +1,562 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * License); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +/* + * Copyright (c) 2021, OPEN AI LAB + * Author: 774074168@qq.com + * original model: https://github.com/RangiLyu/nanodet + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "tengine/c_api.h" +#include "tengine_operations.h" + +const int num_class = 80; +const int reg_max = 7; +// allow none square letterbox, set default letterbox size +const int letterbox_rows = 416; +const int letterbox_cols = 416; +struct Object +{ + cv::Rect_ rect; + int label; + float prob; +}; +struct CenterPrior +{ + int x; + int y; + int stride; +}; + +typedef struct BoxInfo +{ + float x1; + float y1; + float x2; + float y2; + float score; + int label; +} BoxInfo; +inline float fast_exp(float x) +{ + union + { + uint32_t i; + float f; + } v{}; + v.i = (1 << 23) * (1.4426950409 * x + 126.93490512f); + return v.f; +} + +static inline float sigmoid(float x) +{ + return 1.0f / (1.0f + fast_exp(-x)); +} + +template +static int activation_function_softmax(const _Tp* src, _Tp* dst, int length) +{ + const _Tp alpha = *std::max_element(src, src + length); + _Tp denominator{0}; + + for (int i = 0; i < length; ++i) + { + dst[i] = fast_exp(src[i] - alpha); + denominator += dst[i]; + } + + for (int i = 0; i < length; ++i) + { + dst[i] /= denominator; + } + + return 0; +} + +static void generate_grid_center_priors(const int input_height, const int input_width, std::vector& strides, std::vector& center_priors) +{ + for (int i = 0; i < (int)strides.size(); i++) + { + int stride = strides[i]; + int feat_w = ceil((float)input_width / stride); + int feat_h = ceil((float)input_height / stride); + for (int y = 0; y < feat_h; y++) + { + for (int x = 0; x < feat_w; x++) + { + CenterPrior ct; + ct.x = x; + ct.y = y; + ct.stride = stride; + center_priors.push_back(ct); + } + } + } +} + +static void nms(std::vector& input_boxes, float NMS_THRESH) +{ + std::sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) { return a.score > b.score; }); + std::vector vArea(input_boxes.size()); + for (int i = 0; i < int(input_boxes.size()); ++i) + { + vArea[i] = (input_boxes.at(i).x2 - input_boxes.at(i).x1 + 1) + * (input_boxes.at(i).y2 - input_boxes.at(i).y1 + 1); + } + for (int i = 0; i < int(input_boxes.size()); ++i) + { + for (int j = i + 1; j < int(input_boxes.size());) + { + float xx1 = (std::max)(input_boxes[i].x1, input_boxes[j].x1); + float yy1 = (std::max)(input_boxes[i].y1, input_boxes[j].y1); + float xx2 = (std::min)(input_boxes[i].x2, input_boxes[j].x2); + float yy2 = (std::min)(input_boxes[i].y2, input_boxes[j].y2); + float w = (std::max)(float(0), xx2 - xx1 + 1); + float h = (std::max)(float(0), yy2 - yy1 + 1); + float inter = w * h; + float ovr = inter / (vArea[i] + vArea[j] - inter); + if (ovr >= NMS_THRESH) + { + input_boxes.erase(input_boxes.begin() + j); + vArea.erase(vArea.begin() + j); + } + else + { + j++; + } + } + } +} + +static BoxInfo disPred2Bbox(const float* dfl_det, int label, float score, int x, int y, int stride) +{ + float ct_x = x * stride; + float ct_y = y * stride; + std::vector dis_pred; + dis_pred.resize(4); + for (int i = 0; i < 4; i++) + { + float dis = 0; + float* dis_after_sm = new float[8]; + activation_function_softmax(dfl_det + i * (reg_max + 1), dis_after_sm, reg_max + 1); + for (int j = 0; j < reg_max + 1; j++) + { + dis += j * dis_after_sm[j]; + } + dis *= stride; + //std::cout << "dis:" << dis << std::endl; + dis_pred[i] = dis; + delete[] dis_after_sm; + } + float xmin = (std::max)(ct_x - dis_pred[0], .0f); + float ymin = (std::max)(ct_y - dis_pred[1], .0f); + float xmax = (std::min)(ct_x + dis_pred[2], (float)letterbox_cols); + float ymax = (std::min)(ct_y + dis_pred[3], (float)letterbox_rows); + + //std::cout << xmin << "," << ymin << "," << xmax << "," << xmax << "," << std::endl; + return BoxInfo{xmin, ymin, xmax, ymax, score, label}; +} + +static void decode_infer(const float* feats_ptr, std::vector& center_priors, float threshold, std::vector >& results) +{ + const int num_points = center_priors.size(); + + for (int idx = 0; idx < num_points; idx++) + { + const int ct_x = center_priors[idx].x; + const int ct_y = center_priors[idx].y; + const int stride = center_priors[idx].stride; + + const float* scores = feats_ptr + idx * (num_class + 4 * (reg_max + 1)); + float score = 0; + int cur_label = 0; + for (int label = 0; label < num_class; label++) + { + if (scores[label] > score) + { + score = scores[label]; + cur_label = label; + } + } + if (score > threshold) + { + const float* bbox_pred = feats_ptr + idx * (num_class + +4 * (reg_max + 1)) + num_class; + results[cur_label].push_back(disPred2Bbox(bbox_pred, cur_label, score, ct_x, ct_y, stride)); + } + } +} + +static void draw_objects(const cv::Mat& bgr, const std::vector& objects) +{ + static const char* class_names[] = { + "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", + "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", + "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", + "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", + "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", + "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", + "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", + "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", + "hair drier", "toothbrush"}; + + cv::Mat image = bgr.clone(); + + for (size_t i = 0; i < objects.size(); i++) + { + const Object& obj = objects[i]; + + fprintf(stderr, "%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]); + + cv::rectangle(image, obj.rect, cv::Scalar(255, 0, 0)); + + char text[256]; + sprintf(text, "%s %.1f%%", class_names[obj.label], obj.prob * 100); + + int baseLine = 0; + cv::Size label_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine); + + int x = obj.rect.x; + int y = obj.rect.y - label_size.height - baseLine; + if (y < 0) + y = 0; + if (x + label_size.width > image.cols) + x = image.cols - label_size.width; + + cv::rectangle(image, cv::Rect(cv::Point(x, y), cv::Size(label_size.width, label_size.height + baseLine)), + cv::Scalar(255, 255, 255), -1); + + cv::putText(image, text, cv::Point(x, y + label_size.height), cv::FONT_HERSHEY_SIMPLEX, 0.5, + cv::Scalar(0, 0, 0)); + } + + cv::imwrite("nanodet_out.jpg", image); +} + +static int get_input_data_letter(const char* image_file, float* input_data, int letterbox_rows, int letterbox_cols, const float* mean, const float* scale) +{ + cv::Mat sample = cv::imread(image_file, 1); + if (sample.empty()) + { + fprintf(stderr, "cv::imread %s failed\n", image_file); + return -1; + } + + cv::Mat img = sample.clone(); + + if (sample.channels() == 1) + cv::cvtColor(sample, img, cv::COLOR_GRAY2BGR); + + /* letterbox process to support different letterbox size */ + float scale_letterbox; + int resize_rows; + int resize_cols; + if ((letterbox_rows * 1.0 / img.rows) < (letterbox_cols * 1.0 / img.cols)) + { + scale_letterbox = letterbox_rows * 1.0 / img.rows; + } + else + { + scale_letterbox = letterbox_cols * 1.0 / img.cols; + } + resize_cols = int(scale_letterbox * img.cols); + resize_rows = int(scale_letterbox * img.rows); + + cv::resize(img, img, cv::Size(resize_cols, resize_rows)); + + img.convertTo(img, CV_32FC3); + // Generate a gray image for letterbox using opencv + cv::Mat img_new(letterbox_rows, letterbox_cols, CV_32FC3, cv::Scalar(0, 0, 0)); + int top = (letterbox_rows - resize_rows) / 2; + int bot = (letterbox_rows - resize_rows + 1) / 2; + int left = (letterbox_cols - resize_cols) / 2; + int right = (letterbox_cols - resize_cols + 1) / 2; + + // Letterbox filling + cv::copyMakeBorder(img, img_new, top, bot, left, right, cv::BORDER_CONSTANT, cv::Scalar(114.f, 114.f, 114.f)); + + img_new.convertTo(img_new, CV_32FC3); + float* img_data = (float*)img_new.data; + + /* nhwc to nchw */ + for (int h = 0; h < letterbox_rows; h++) + { + for (int w = 0; w < letterbox_cols; w++) + { + for (int c = 0; c < 3; c++) + { + int in_index = h * letterbox_cols * 3 + w * 3 + c; + int out_index = c * letterbox_rows * letterbox_cols + h * letterbox_cols + w; + input_data[out_index] = (img_data[in_index] - mean[c]) * scale[c]; + } + } + } + + return 0; +} + +void show_usage() +{ + fprintf( + stderr, + "[Usage]: [-h]\n [-m model_file] [-i image_file] [-r repeat_count] [-t thread_count]\n"); +} + +int main(int argc, char* argv[]) +{ + const char* model_file = nullptr; + const char* image_file = nullptr; + + int img_c = 3; + const float mean[3] = {103.53f, 116.28f, 123.675f}; + const float scale[3] = {0.017429f, 0.017507f, 0.017125f}; + + int repeat_count = 1; + int num_thread = 1; + + int res; + while ((res = getopt(argc, argv, "m:i:r:t:h:")) != -1) + { + switch (res) + { + case 'm': + model_file = optarg; + break; + case 'i': + image_file = optarg; + break; + case 'r': + repeat_count = std::strtoul(optarg, nullptr, 10); + break; + case 't': + num_thread = std::strtoul(optarg, nullptr, 10); + break; + case 'h': + show_usage(); + return 0; + default: + break; + } + } + + /* check files */ + if (nullptr == model_file) + { + fprintf(stderr, "Error: Tengine model file not specified!\n"); + show_usage(); + return -1; + } + + if (nullptr == image_file) + { + fprintf(stderr, "Error: Image file not specified!\n"); + show_usage(); + return -1; + } + + if (!check_file_exist(model_file) || !check_file_exist(image_file)) + return -1; + + cv::Mat img = cv::imread(image_file, 1); + if (img.empty()) + { + fprintf(stderr, "cv::imread %s failed\n", image_file); + return -1; + } + + /* set runtime options */ + struct options opt; + opt.num_thread = num_thread; + opt.cluster = TENGINE_CLUSTER_ALL; + opt.precision = TENGINE_MODE_FP32; + opt.affinity = 0; + + /* inital tengine */ + if (init_tengine() != 0) + { + fprintf(stderr, "Initial tengine failed.\n"); + return -1; + } + fprintf(stderr, "tengine-lite library version: %s\n", get_tengine_version()); + + /* create graph, load tengine model xxx.tmfile */ + graph_t graph = create_graph(nullptr, "tengine", model_file); + if (graph == nullptr) + { + fprintf(stderr, "Create graph failed.\n"); + return -1; + } + + int img_size = letterbox_rows * letterbox_cols * img_c; + int dims[] = {1, 3, letterbox_rows, letterbox_cols}; + std::vector input_data(img_size); + + tensor_t input_tensor = get_graph_input_tensor(graph, 0, 0); + + if (input_tensor == nullptr) + { + fprintf(stderr, "Get input tensor failed\n"); + return -1; + } + + if (set_tensor_shape(input_tensor, dims, 4) < 0) + { + fprintf(stderr, "Set input tensor shape failed\n"); + return -1; + } + + if (set_tensor_buffer(input_tensor, input_data.data(), img_size * 4) < 0) + { + fprintf(stderr, "Set input tensor buffer failed\n"); + return -1; + } + + /* prerun graph, set work options(num_thread, cluster, precision) */ + if (prerun_graph_multithread(graph, opt) < 0) + { + fprintf(stderr, "Prerun multithread graph failed.\n"); + return -1; + } + + /* prepare process input data, set the data mem to input tensor */ + get_input_data_letter(image_file, input_data.data(), letterbox_rows, letterbox_cols, mean, scale); + + /* run graph */ + double min_time = DBL_MAX; + double max_time = DBL_MIN; + double total_time = 0.; + for (int i = 0; i < repeat_count; i++) + { + double start = get_current_time(); + if (run_graph(graph, 1) < 0) + { + fprintf(stderr, "Run graph failed\n"); + return -1; + } + double end = get_current_time(); + double cur = end - start; + total_time += cur; + min_time = std::min(min_time, cur); + max_time = std::max(max_time, cur); + } + fprintf(stderr, "Repeat %d times, thread %d, avg time %.2f ms, max_time %.2f ms, min_time %.2f ms\n", repeat_count, num_thread, + total_time / repeat_count, max_time, min_time); + fprintf(stderr, "--------------------------------------\n"); + + tensor_t p_output = get_graph_output_tensor(graph, 0, 0); + float* p_data = (float*)get_tensor_buffer(p_output); + + const float prob_threshold = 0.4f; + const float nms_threshold = 0.5f; + + /* postprocess */ + std::vector center_priors; + std::vector strides = {8, 16, 32, 64}; + generate_grid_center_priors(letterbox_rows, letterbox_cols, strides, center_priors); + + std::vector > results; + results.resize(num_class); + decode_infer(p_data, center_priors, prob_threshold, results); + + std::vector objects; + for (int i = 0; i < (int)results.size(); i++) + { + nms(results[i], nms_threshold); + if (results.size() == 0) + continue; + else + { + for (int j = 0; j < results[i].size(); j++) + { + Object obj; + obj.rect.x = results[i][j].x1; + obj.rect.y = results[i][j].y1; + obj.rect.width = results[i][j].x2 - results[i][j].x1; + obj.rect.height = results[i][j].y2 - results[i][j].y1; + obj.label = results[i][j].label; + obj.prob = results[i][j].score; + objects.push_back(obj); + } + } + } + /* draw the result */ + + float scale_letterbox; + int resize_rows; + int resize_cols; + if ((letterbox_rows * 1.0 / img.rows) < (letterbox_cols * 1.0 / img.cols)) + { + scale_letterbox = letterbox_rows * 1.0 / img.rows; + } + else + { + scale_letterbox = letterbox_cols * 1.0 / img.cols; + } + resize_cols = int(scale_letterbox * img.cols); + resize_rows = int(scale_letterbox * img.rows); + + int tmp_h = (letterbox_rows - resize_rows) / 2; + int tmp_w = (letterbox_cols - resize_cols) / 2; + + float ratio_x = (float)img.rows / resize_rows; + float ratio_y = (float)img.cols / resize_cols; + + int count = objects.size(); + fprintf(stderr, "detection num: %d\n", count); + + for (int i = 0; i < count; i++) + { + float x0 = (objects[i].rect.x); + float y0 = (objects[i].rect.y); + float x1 = (objects[i].rect.x + objects[i].rect.width); + float y1 = (objects[i].rect.y + objects[i].rect.height); + + x0 = (x0 - tmp_w) * ratio_x; + y0 = (y0 - tmp_h) * ratio_y; + x1 = (x1 - tmp_w) * ratio_x; + y1 = (y1 - tmp_h) * ratio_y; + + x0 = (std::max)((std::min)(x0, (float)(img.cols - 1)), 0.f); + y0 = (std::max)((std::min)(y0, (float)(img.rows - 1)), 0.f); + x1 = (std::max)((std::min)(x1, (float)(img.cols - 1)), 0.f); + y1 = (std::max)((std::min)(y1, (float)(img.rows - 1)), 0.f); + + objects[i].rect.x = x0; + objects[i].rect.y = y0; + objects[i].rect.width = x1 - x0; + objects[i].rect.height = y1 - y0; + } + + draw_objects(img, objects); + + /* release tengine */ + postrun_graph(graph); + destroy_graph(graph); + release_tengine(); +} diff --git a/tools/convert_tool/onnx/onnx2tengine.cpp b/tools/convert_tool/onnx/onnx2tengine.cpp index beb8d22027aa64e9f307ea2fa07cd5874e3b3838..b0d269623a93ed90621fd5e7037e3e989a41da3b 100644 --- a/tools/convert_tool/onnx/onnx2tengine.cpp +++ b/tools/convert_tool/onnx/onnx2tengine.cpp @@ -318,13 +318,10 @@ int onnx_serializer::load_constant_tensor(ir_graph_t* graph, const onnx::GraphPr const std::string& op = node.op_type(); - if ((op == "Reshape" || op == "Gather" || - op == "Div" || op == "Resize" || - op == "Upsample" || op == "Clip") && - (node.input_size() > 1)) + if ((op == "Reshape" || op == "Gather" || op == "Div" || op == "Resize" || op == "Upsample" || op == "Clip") && (node.input_size() > 1)) { // iter over constant inputs and create ir_tensor for constant tensor - for(int inp_idx = 0; inp_idx < node.input_size(); ++inp_idx) + for (int inp_idx = 0; inp_idx < node.input_size(); ++inp_idx) { if (node_tensor.count(node.input(inp_idx)) == 0) continue; @@ -377,7 +374,7 @@ int onnx_serializer::load_constant_tensor(ir_graph_t* graph, const onnx::GraphPr } } } - else if(tensor_data_type == TENGINE_DT_FP32) + else if (tensor_data_type == TENGINE_DT_FP32) { // to support float type constant data loading int tensor_size = ir_tensor->elem_num * sizeof(float_t); diff --git a/tools/convert_tool/utils/graph_optimizer/graph_opt.cpp b/tools/convert_tool/utils/graph_optimizer/graph_opt.cpp index 53ce527f1ef456b19bb0b3d8255beb53d4d87f0a..bbdbe63ed6f413b11d5253eacba9f1b915dd36ba 100644 --- a/tools/convert_tool/utils/graph_optimizer/graph_opt.cpp +++ b/tools/convert_tool/utils/graph_optimizer/graph_opt.cpp @@ -628,9 +628,7 @@ static int fuse_conv_relu_common(ir_graph_t* graph) for (size_t i = 0; i < graph->node_num; i++) { ir_node_t* relu_node = get_ir_graph_node(graph, i); - if (relu_node->op.type != OP_RELU && - relu_node->op.type != OP_RELU6 && - relu_node->op.type != OP_CLIP) + if (relu_node->op.type != OP_RELU && relu_node->op.type != OP_RELU6 && relu_node->op.type != OP_CLIP) continue; if (relu_node->op.type == OP_RELU) { @@ -662,8 +660,7 @@ static int fuse_conv_relu_common(ir_graph_t* graph) struct conv_param* conv_param = (struct conv_param*)conv_node->op.param_mem; if (relu_node->op.type == OP_RELU) conv_param->activation = 0; - if (relu_node->op.type == OP_RELU6 || - relu_node->op.type == OP_CLIP) + if (relu_node->op.type == OP_RELU6 || relu_node->op.type == OP_CLIP) conv_param->activation = 6; /* delete relu node */ diff --git a/tools/quantize/algorithm/quant_dfq.cpp b/tools/quantize/algorithm/quant_dfq.cpp index 7b81d369478d28374b1c31a7255408ec0f739ee4..25ffe4d4b21fac0dd4a3178e60eb50ea30e6f7c1 100644 --- a/tools/quantize/algorithm/quant_dfq.cpp +++ b/tools/quantize/algorithm/quant_dfq.cpp @@ -59,7 +59,7 @@ int QuantTool::data_free_quant() struct graph* graphn = (struct graph*)graph; // struct node_graph* node_proto = (struct node_graph*)sys_malloc(sizeof(struct node_graph) * graphn->node_num); // crash access node_proto.input_node_list std::vector node_proto(graphn->node_num); - + for (int i = 0; i < graphn->node_num; i++) { struct node* n = graphn->node_list[i]; //ir node @@ -261,8 +261,8 @@ int QuantTool::data_free_quant() float* S01 = new float[dims1]; float* S01_F = new float[dims1]; float* S12 = new float[dims1]; - float* S12_F = new float[dims1]; - + float* S12_F = new float[dims1]; + for (int ops = 0; ops < dims1; ops++) { if (ops_range[ops] == 0) @@ -343,7 +343,7 @@ int QuantTool::data_free_quant() } } } - delete[] S01; // free the memory + delete[] S01; // free the memory S01 = NULL; delete[] S01_F; S01_F = NULL; @@ -452,7 +452,7 @@ int QuantTool::data_free_quant() // float S01_F[dims1]; float* S01 = new float[dims1]; float* S01_F = new float[dims1]; - + for (int ops = 0; ops < dims1; ops++) { if (ops_range[ops] == 0) @@ -506,7 +506,7 @@ int QuantTool::data_free_quant() } } } - delete[] S01; // free the memory + delete[] S01; // free the memory S01 = NULL; delete[] S01_F; S01_F = NULL; diff --git a/tools/quantize/quant_utils.cpp b/tools/quantize/quant_utils.cpp index cb69263737594bd4f6ffa3662d0e9d774ef194bb..a6eaa4babf039b4de7e91aa294edbb5c75adeb48 100644 --- a/tools/quantize/quant_utils.cpp +++ b/tools/quantize/quant_utils.cpp @@ -270,7 +270,7 @@ void get_input_data_cv(const char* image_file, float* input_data, int img_c, int cv::resize(img, img, cv::Size(resize_cols, resize_rows)); img.convertTo(img, CV_32FC3); - + // Letterbox filling cv::Mat resize_img; int top = (letterbox_rows - resize_rows) / 2; @@ -279,7 +279,7 @@ void get_input_data_cv(const char* image_file, float* input_data, int img_c, int int right = (letterbox_cols - resize_cols + 1) / 2; cv::copyMakeBorder(img, resize_img, top, bot, left, right, cv::BORDER_CONSTANT, cv::Scalar(0.5 / scale[0] + mean[0], 0.5 / scale[1] + mean[1], 0.5 / scale[2] + mean[2])); - + if (img_c == 3) resize_img.convertTo(resize_img, CV_32FC3); else if (img_c == 1)