From 7790ac5b918f9656f5a2c39363c51dadb11126f2 Mon Sep 17 00:00:00 2001 From: Louis Jean Date: Wed, 13 Jul 2022 12:50:12 +0000 Subject: [PATCH] feat: add image rotation to input connector --- src/dto/input_connector.hpp | 34 +++++++++++++ src/imginputfileconn.h | 96 +++++++++++++++++++++++++++++-------- tests/ut-torchapi.cc | 4 +- 3 files changed, 112 insertions(+), 22 deletions(-) diff --git a/src/dto/input_connector.hpp b/src/dto/input_connector.hpp index 061dc7cc2..18b8c86dd 100644 --- a/src/dto/input_connector.hpp +++ b/src/dto/input_connector.hpp @@ -55,19 +55,53 @@ namespace dd DTO_FIELD(Int32, height); DTO_FIELD(Int32, crop_width); DTO_FIELD(Int32, crop_height); + + DTO_FIELD_INFO(bw) + { + info->description = "whether to convert to black & white."; + } DTO_FIELD(Boolean, bw); + + DTO_FIELD_INFO(rgb) + { + info->description = "whether to convert to rgb."; + } DTO_FIELD(Boolean, rgb); + + DTO_FIELD_INFO(histogram_equalization) + { + info->description = "whether to apply histogram equalizer."; + } DTO_FIELD(Boolean, histogram_equalization); + DTO_FIELD(Boolean, unchanged_data); DTO_FIELD(Boolean, shuffle); DTO_FIELD(Int32, seed); DTO_FIELD(Float64, test_split); + + DTO_FIELD_INFO(mean) + { + info->description = "mean image pixels, to be subtracted from images."; + } DTO_FIELD(Vector, mean); + + DTO_FIELD_INFO(std) + { + info->description = "std, to divide image values."; + } DTO_FIELD(Vector, std); + DTO_FIELD(Any, scale); // bool for csv/csvts, float for img DTO_FIELD(Boolean, scaled); DTO_FIELD(Int32, scale_min); DTO_FIELD(Int32, scale_max); + + DTO_FIELD_INFO(rotate) + { + info->description = "Rotate input image of 90, 180 or 270 degrees."; + } + DTO_FIELD(Int32, rotate); + DTO_FIELD(Boolean, keep_orig); DTO_FIELD(String, interp); diff --git a/src/imginputfileconn.h b/src/imginputfileconn.h index a86c8227f..129a530b3 100644 --- a/src/imginputfileconn.h +++ b/src/imginputfileconn.h @@ -96,38 +96,63 @@ namespace dd void prepare(const cv::Mat &src, cv::Mat &dst, const std::string &img_name) const { + dst = src; + try { - if (_scaled) - scale(src, dst); - else if (_width == 0 || _height == 0) + if (_rotate != 0) { - if (_width == 0 && _height == 0) + if (_rotate == 90) + { + cv::transpose(dst, dst); + cv::flip(dst, dst, 1); + } + else if (_rotate == 180) { - // Do nothing and keep native resolution. May cause issues if - // batched images are different resolutions - dst = src; + cv::flip(dst, dst, -1); + } + else if (_rotate == 270) + { + cv::transpose(dst, dst); + cv::flip(dst, dst, 0); } else + { + throw InputConnectorBadParamException( + "Bad rotation value: " + std::to_string(_rotate)); + } + } + + if (_scaled) + scale(dst, dst); + else if (_width == 0 || _height == 0) + { + if (_width != 0 || _height != 0) { // Resize so that the larger dimension is set to whichever // (width or height) is non-zero, maintaining aspect ratio // XXX - This may cause issues if batch images are different // resolutions - size_t currMaxDim = std::max(src.rows, src.cols); + size_t currMaxDim = std::max(dst.rows, dst.cols); double scale = static_cast(std::max(_width, _height)) / static_cast(currMaxDim); - cv::resize(src, dst, cv::Size(), scale, scale, + cv::resize(dst, dst, cv::Size(), scale, scale, select_cv_interp()); } + // Otherwise do nothing and keep native resolution. May cause + // issues if batched images are different resolutions } else { // Resize normally to the specified width and height - cv::resize(src, dst, cv::Size(_width, _height), 0, 0, + cv::resize(dst, dst, cv::Size(_width, _height), 0, 0, select_cv_interp()); } } + catch (InputConnectorBadParamException &e) + { + throw e; + } catch (...) { throw InputConnectorBadParamException("failed resizing image " @@ -195,38 +220,63 @@ namespace dd void prepare_cuda(const cv::cuda::GpuMat &src, cv::cuda::GpuMat &dst, const std::string &img_name) const { + dst = src; + try { - if (_scaled) - scale_cuda(src, dst); - else if (_width == 0 || _height == 0) + if (_rotate != 0) { - if (_width == 0 && _height == 0) + if (_rotate == 90) + { + cv::cuda::transpose(dst, dst); + cv::cuda::flip(dst, dst, 1); + } + else if (_rotate == 180) { - // Do nothing and keep native resolution. May cause issues if - // batched images are different resolutions - dst = src; + cv::cuda::flip(dst, dst, -1); + } + else if (_rotate == 270) + { + cv::cuda::transpose(dst, dst); + cv::cuda::flip(dst, dst, 0); } else + { + throw InputConnectorBadParamException( + "Bad rotation value: " + std::to_string(_rotate)); + } + } + + if (_scaled) + scale_cuda(dst, dst); + else if (_width == 0 || _height == 0) + { + if (_width != 0 || _height != 0) { // Resize so that the larger dimension is set to whichever // (width or height) is non-zero, maintaining aspect ratio // XXX - This may cause issues if batch images are different // resolutions - size_t currMaxDim = std::max(src.rows, src.cols); + size_t currMaxDim = std::max(dst.rows, dst.cols); double scale = static_cast(std::max(_width, _height)) / static_cast(currMaxDim); - cv::cuda::resize(src, dst, cv::Size(), scale, scale, + cv::cuda::resize(dst, dst, cv::Size(), scale, scale, select_cv_interp(), *_cuda_stream); } + // Otherwise do nothing and keep native resolution. May cause + // issues if batched images are different resolutions } else { // Resize normally to the specified width and height - cv::cuda::resize(src, dst, cv::Size(_width, _height), 0, 0, + cv::cuda::resize(dst, dst, cv::Size(_width, _height), 0, 0, select_cv_interp(), *_cuda_stream); } } + catch (InputConnectorBadParamException &e) + { + throw e; + } catch (...) { throw InputConnectorBadParamException("failed resizing image " @@ -533,6 +583,7 @@ namespace dd bool _scaled = false; int _scale_min = 600; int _scale_max = 1000; + int _rotate = 0; bool _keep_orig = false; bool _b64 = false; std::string _interp = "cubic"; @@ -667,6 +718,9 @@ namespace dd _scale_max = params->scale_max; } + if (params->rotate) + _rotate = params->rotate; + // whether to keep original image (for chained ops, e.g. cropping) _keep_orig |= params->keep_orig; @@ -697,6 +751,7 @@ namespace dd dimg._scaled = _scaled; dimg._scale_min = _scale_min; dimg._scale_max = _scale_max; + dimg._rotate = _rotate; dimg._keep_orig = _keep_orig; dimg._interp = _interp; #ifdef USE_CUDA_CV @@ -1105,6 +1160,7 @@ namespace dd bool _scaled = false; int _scale_min = 600; int _scale_max = 1000; + int _rotate = 0; /**< rotate the input image of 90, 180 or 270 degrees. */ bool _keep_orig = false; std::string _interp = "cubic"; #ifdef USE_CUDA_CV diff --git a/tests/ut-torchapi.cc b/tests/ut-torchapi.cc index 9d0aea972..a1fbe5a37 100644 --- a/tests/ut-torchapi.cc +++ b/tests/ut-torchapi.cc @@ -333,10 +333,10 @@ TEST(torchapi, service_predict_object_detection) // Check confidence threshold ASSERT_TRUE(preds[preds.Size() - 1]["prob"].GetDouble() >= 0.8); - // best + // best + rotate jpredictstr = "{\"service\":\"detectserv\",\"parameters\":{" "\"input\":{\"height\":224," - "\"width\":224},\"output\":{\"bbox\":true, " + "\"width\":224, \"rotate\": 90},\"output\":{\"bbox\":true, " "\"best_bbox\":3}},\"data\":[\"" + detect_repo + "cat.jpg\"]}"; joutstr = japi.jrender(japi.service_predict(jpredictstr));