diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index 8cba54c8d0..3140709238 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -1222,6 +1222,20 @@ CV__DNN_INLINE_NS_BEGIN CV_PROP_RW DataLayout datalayout; //!< Order of output dimensions. Choose DNN_LAYOUT_NCHW or DNN_LAYOUT_NHWC. CV_PROP_RW ImagePaddingMode paddingmode; //!< Image padding mode. @see ImagePaddingMode. CV_PROP_RW Scalar borderValue; //!< Value used in padding mode for padding. + + /** @brief Get rectangle coordinates in original image system from rectangle in blob coordinates. + * @param rBlob rect in blob coordinates. + * @param size original input image size. + * @returns rectangle in original image coordinates. + */ + CV_WRAP Rect blobRectToImageRect(const Rect &rBlob, const Size &size); + + /** @brief Get rectangle coordinates in original image system from rectangle in blob coordinates. + * @param rBlob rect in blob coordinates. + * @param rImg result rect in image coordinates. + * @param size original input image size. + */ + CV_WRAP void blobRectsToImageRects(const std::vector &rBlob, CV_OUT std::vector& rImg, const Size& size); }; /** @brief Creates 4-dimensional blob from image with given params. diff --git a/modules/dnn/misc/python/test/test_dnn.py b/modules/dnn/misc/python/test/test_dnn.py index a06c02ad2d..da3409c3b3 100644 --- a/modules/dnn/misc/python/test/test_dnn.py +++ b/modules/dnn/misc/python/test/test_dnn.py @@ -127,6 +127,34 @@ class dnn_test(NewOpenCVTests): targets = cv.dnn.getAvailableTargets(cv.dnn.DNN_BACKEND_OPENCV) self.assertTrue(cv.dnn.DNN_TARGET_CPU in targets) + def test_blobRectsToImageRects(self): + paramNet = cv.dnn.Image2BlobParams() + paramNet.size = (226, 226) + paramNet.ddepth = cv.CV_32F + paramNet.mean = [0.485, 0.456, 0.406] + paramNet.scalefactor = [0.229, 0.224, 0.225] + paramNet.swapRB = False + paramNet.datalayout = cv.dnn.DNN_LAYOUT_NCHW + paramNet.paddingmode = cv.dnn.DNN_PMODE_LETTERBOX + rBlob = np.zeros(shape=(20, 4), dtype=np.int32) + rImg = paramNet.blobRectsToImageRects(rBlob, (356, 356)) + self.assertTrue(type(rImg[0, 0])==np.int32) + self.assertTrue(rImg.shape==(20, 4)) + + def test_blobRectToImageRect(self): + paramNet = cv.dnn.Image2BlobParams() + paramNet.size = (226, 226) + paramNet.ddepth = cv.CV_32F + paramNet.mean = [0.485, 0.456, 0.406] + paramNet.scalefactor = [0.229, 0.224, 0.225] + paramNet.swapRB = False + paramNet.datalayout = cv.dnn.DNN_LAYOUT_NCHW + paramNet.paddingmode = cv.dnn.DNN_PMODE_LETTERBOX + rBlob = np.zeros(shape=(20, 4), dtype=np.int32) + rImg = paramNet.blobRectToImageRect((0, 0, 0, 0), (356, 356)) + self.assertTrue(type(rImg[0])==int) + + def test_blobFromImage(self): np.random.seed(324) diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index f70ef130fa..99b4e77e11 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -17,9 +17,9 @@ Image2BlobParams::Image2BlobParams():scalefactor(Scalar::all(1.0)), size(Size()) {} Image2BlobParams::Image2BlobParams(const Scalar& scalefactor_, const Size& size_, const Scalar& mean_, bool swapRB_, - int ddepth_, DataLayout datalayout_, ImagePaddingMode mode_, Scalar borderValue_): - scalefactor(scalefactor_), size(size_), mean(mean_), swapRB(swapRB_), ddepth(ddepth_), - datalayout(datalayout_), paddingmode(mode_), borderValue(borderValue_) + int ddepth_, DataLayout datalayout_, ImagePaddingMode mode_, Scalar borderValue_): + scalefactor(scalefactor_), size(size_), mean(mean_), swapRB(swapRB_), ddepth(ddepth_), + datalayout(datalayout_), paddingmode(mode_), borderValue(borderValue_) {} void getVector(InputArrayOfArrays images_, std::vector& images) { @@ -382,6 +382,66 @@ void imagesFromBlob(const cv::Mat& blob_, OutputArrayOfArrays images_) } } +Rect Image2BlobParams::blobRectToImageRect(const Rect &r, const Size &oriImage) +{ + CV_Assert(!oriImage.empty()); + std::vector rImg, rBlob; + rBlob.push_back(Rect(r)); + rImg.resize(1); + this->blobRectsToImageRects(rBlob, rImg, oriImage); + return Rect(rImg[0]); +} + +void Image2BlobParams::blobRectsToImageRects(const std::vector &rBlob, std::vector& rImg, const Size& imgSize) +{ + Size size = this->size; + rImg.resize(rBlob.size()); + if (size != imgSize) + { + if (this->paddingmode == DNN_PMODE_CROP_CENTER) + { + float resizeFactor = std::max(size.width / (float)imgSize.width, + size.height / (float)imgSize.height); + for (int i = 0; i < rBlob.size(); i++) + { + rImg[i] = Rect((rBlob[i].x + 0.5 * (imgSize.width * resizeFactor - size.width)) / resizeFactor, + (rBlob[i].y + 0.5 * (imgSize.height * resizeFactor - size.height)) / resizeFactor, + rBlob[i].width / resizeFactor, + rBlob[i].height / resizeFactor); + } + } + else if (this->paddingmode == DNN_PMODE_LETTERBOX) + { + float resizeFactor = std::min(size.width / (float)imgSize.width, + size.height / (float)imgSize.height); + int rh = int(imgSize.height * resizeFactor); + int rw = int(imgSize.width * resizeFactor); + + int top = (size.height - rh) / 2; + int left = (size.width - rw) / 2; + for (int i = 0; i < rBlob.size(); i++) + { + rImg[i] = Rect((rBlob[i].x - left) / resizeFactor, + (rBlob[i].y - top) / resizeFactor, + rBlob[i].width / resizeFactor, + rBlob[i].height / resizeFactor); + } + } + else if (this->paddingmode == DNN_PMODE_NULL) + { + for (int i = 0; i < rBlob.size(); i++) + { + rImg[i] = Rect(rBlob[i].x * (float)imgSize.width / size.width, + rBlob[i].y * (float)imgSize.height / size.height, + rBlob[i].width * (float)imgSize.width / size.width, + rBlob[i].height * (float)imgSize.height / size.height); + } + } + else + CV_Error(CV_StsBadArg, "Unknown padding mode"); + } +} + CV__DNN_INLINE_NS_END }} // namespace cv::dnn diff --git a/modules/dnn/test/test_misc.cpp b/modules/dnn/test/test_misc.cpp index 5d72b942f5..12d3964196 100644 --- a/modules/dnn/test/test_misc.cpp +++ b/modules/dnn/test/test_misc.cpp @@ -13,17 +13,83 @@ namespace opencv_test { namespace { +TEST(blobRectToImageRect, DNN_PMODE_NULL) +{ + Size inputSize(50 + (rand() % 100) / 4 * 4, 50 + (rand() % 100) / 4 * 4); + Size imgSize(200 + (rand() % 100) / 4 * 4, 200 + (rand() % 100) / 4 * 4); + Rect rBlob(inputSize.width / 2 - inputSize.width / 4, inputSize.height / 2 - inputSize.height / 4, inputSize.width / 2, inputSize.height / 2); + Image2BlobParams paramNet; + paramNet.scalefactor = Scalar::all(1.f); + paramNet.size = inputSize; + paramNet.ddepth = CV_32F; + paramNet.mean = Scalar(); + paramNet.swapRB = false; + paramNet.datalayout = DNN_LAYOUT_NHWC; + paramNet.paddingmode = DNN_PMODE_NULL; + Rect rOri = paramNet.blobRectToImageRect(rBlob, imgSize); + Rect rImg = Rect(rBlob.x * (float)imgSize.width / inputSize.width, rBlob.y * (float)imgSize.height / inputSize.height, + rBlob.width * (float)imgSize.width / inputSize.width, rBlob.height * (float)imgSize.height / inputSize.height); + ASSERT_EQ(rImg, rOri); +} + +TEST(blobRectToImageRect, DNN_PMODE_CROP_CENTER) +{ + Size inputSize(50 + (rand() % 100) / 4 * 4, 50 + (rand() % 100) / 4 * 4); + Size imgSize(200 + (rand() % 100) / 4 * 4, 200 + (rand() % 100) / 4 * 4); + Rect rBlob(inputSize.width / 2 - inputSize.width / 4, inputSize.height / 2 - inputSize.height / 4, inputSize.width / 2, inputSize.height / 2); + Image2BlobParams paramNet; + paramNet.scalefactor = Scalar::all(1.f); + paramNet.size = inputSize; + paramNet.ddepth = CV_32F; + paramNet.mean = Scalar(); + paramNet.swapRB = false; + paramNet.datalayout = DNN_LAYOUT_NHWC; + paramNet.paddingmode = DNN_PMODE_CROP_CENTER; + Rect rOri = paramNet.blobRectToImageRect(rBlob, imgSize); + float resizeFactor = std::max(inputSize.width / (float)imgSize.width, + inputSize.height / (float)imgSize.height); + Rect rImg = Rect((rBlob.x + 0.5 * (imgSize.width * resizeFactor - inputSize.width)) / resizeFactor, (rBlob.y + 0.5 * (imgSize.height * resizeFactor - inputSize.height)) / resizeFactor, + rBlob.width / resizeFactor, rBlob.height / resizeFactor); + ASSERT_EQ(rImg, rOri); +} + +TEST(blobRectToImageRect, DNN_PMODE_LETTERBOX) +{ + Size inputSize(50 + (rand() % 100) / 4 * 4, 50 + (rand() % 100) / 4 * 4); + Size imgSize(200 + (rand() % 100) / 4 * 4, 200 + (rand() % 100) / 4 * 4); + Rect rBlob(inputSize.width / 2 - inputSize.width / 4, inputSize.height / 2 - inputSize.height / 4, inputSize.width / 2, inputSize.height / 2); + Image2BlobParams paramNet; + paramNet.scalefactor = Scalar::all(1.f); + paramNet.size = inputSize; + paramNet.ddepth = CV_32F; + paramNet.mean = Scalar(); + paramNet.swapRB = false; + paramNet.datalayout = DNN_LAYOUT_NHWC; + paramNet.paddingmode = DNN_PMODE_LETTERBOX; + Rect rOri = paramNet.blobRectToImageRect(rBlob, imgSize); + float resizeFactor = std::min(inputSize.width / (float)imgSize.width, + inputSize.height / (float)imgSize.height); + int rh = int(imgSize.height * resizeFactor); + int rw = int(imgSize.width * resizeFactor); + + int top = (inputSize.height - rh) / 2; + int left = (inputSize.width - rw) / 2; + Rect rImg = Rect((rBlob.x - left) / resizeFactor, (rBlob.y - top) / resizeFactor, rBlob.width / resizeFactor, rBlob.height / resizeFactor); + ASSERT_EQ(rImg, rOri); +} + + TEST(blobFromImage_4ch, Regression) { Mat ch[4]; - for(int i = 0; i < 4; i++) - ch[i] = Mat::ones(10, 10, CV_8U)*i; + for (int i = 0; i < 4; i++) + ch[i] = Mat::ones(10, 10, CV_8U) * i; Mat img; merge(ch, 4, img); Mat blob = dnn::blobFromImage(img, 1., Size(), Scalar(), false, false); - for(int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { ch[i] = Mat(img.rows, img.cols, CV_32F, blob.ptr(0, i)); ASSERT_DOUBLE_EQ(cvtest::norm(ch[i], cv::NORM_INF), i); @@ -32,7 +98,7 @@ TEST(blobFromImage_4ch, Regression) TEST(blobFromImage, allocated) { - int size[] = {1, 3, 4, 5}; + int size[] = { 1, 3, 4, 5 }; Mat img(size[2], size[3], CV_32FC(size[1])); Mat blob(4, size, CV_32F); void* blobData = blob.data; @@ -66,8 +132,8 @@ TEST(imagesFromBlob, Regression) TEST(blobFromImageWithParams_4ch, NHWC_scalar_scale) { - Mat img(10, 10, CV_8UC4, cv::Scalar(0,1,2,3)); - std::vector factorVec = {0.1, 0.2, 0.3, 0.4}; + Mat img(10, 10, CV_8UC4, cv::Scalar(0, 1, 2, 3)); + std::vector factorVec = { 0.1, 0.2, 0.3, 0.4 }; Scalar scalefactor(factorVec[0], factorVec[1], factorVec[2], factorVec[3]); @@ -77,7 +143,7 @@ TEST(blobFromImageWithParams_4ch, NHWC_scalar_scale) Mat blob = dnn::blobFromImageWithParams(img, param); // [1, 10, 10, 4] float* blobPtr = blob.ptr(0); - std::vector targetVec = {(float )factorVec[0] * 0, (float )factorVec[1] * 1, (float )factorVec[2] * 2, (float )factorVec[3] * 3}; // Target Value. + std::vector targetVec = { (float)factorVec[0] * 0, (float)factorVec[1] * 1, (float)factorVec[2] * 2, (float)factorVec[3] * 3 }; // Target Value. for (int hi = 0; hi < 10; hi++) { for (int wi = 0; wi < 10; wi++) @@ -128,16 +194,16 @@ TEST(blobFromImageWithParams_CustomPadding, letter_box) TEST(blobFromImageWithParams_4ch, letter_box) { - Mat img(40, 20, CV_8UC4, cv::Scalar(0,1,2,3)); + Mat img(40, 20, CV_8UC4, cv::Scalar(0, 1, 2, 3)); // Construct target mat. Mat targetCh[4]; // The letterbox will add zero at the left and right of output blob. // After the letterbox, every row data would have same value showing as valVec. - std::vector valVec = {0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0}; + std::vector valVec = { 0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0 }; Mat rowM(1, 20, CV_8UC1, valVec.data()); - for(int i = 0; i < 4; i++) + for (int i = 0; i < 4; i++) { targetCh[i] = rowM * i; } @@ -163,7 +229,7 @@ TEST(blobFromImagesWithParams_4ch, multi_image) param.scalefactor = scalefactor; param.datalayout = DNN_LAYOUT_NHWC; - Mat blobs = blobFromImagesWithParams(std::vector { img, 2*img }, param); + Mat blobs = blobFromImagesWithParams(std::vector { img, 2 * img }, param); vector ranges; ranges.push_back(Range(0, 1)); ranges.push_back(Range(0, blobs.size[1])); @@ -173,7 +239,7 @@ TEST(blobFromImagesWithParams_4ch, multi_image) ranges[0] = Range(1, 2); Mat blob1 = blobs(ranges); - EXPECT_EQ(0, cvtest::norm(2*blob0, blob1, NORM_INF)); + EXPECT_EQ(0, cvtest::norm(2 * blob0, blob1, NORM_INF)); } TEST(readNet, Regression)