From 57ae3ac7a2db03023d262628579200e14879369d Mon Sep 17 00:00:00 2001 From: take1014 Date: Wed, 12 Sep 2018 22:18:30 +0900 Subject: [PATCH 01/12] fix document about HoughLines --- modules/imgproc/include/opencv2/imgproc.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index b1ad3ca1ce..bfffe1ec84 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1947,10 +1947,10 @@ transform. @param image 8-bit, single-channel binary source image. The image may be modified by the function. @param lines Output vector of lines. Each line is represented by a 2 or 3 element vector -\f$(\rho, \theta)\f$ or \f$(\rho, \theta, \votes)\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of +\f$(\rho, \theta)\f$ or \f$(\rho, \theta, \textrm{votes})\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of the image). \f$\theta\f$ is the line rotation angle in radians ( \f$0 \sim \textrm{vertical line}, \pi/2 \sim \textrm{horizontal line}\f$ ). -\f$\votes\f$ is the value of accumulator. +\f$\textrm{votes}\f$ is the value of accumulator. @param rho Distance resolution of the accumulator in pixels. @param theta Angle resolution of the accumulator in radians. @param threshold Accumulator threshold parameter. Only those lines are returned that get enough From 58ac3e09da332cd23126500b9e49059633522c5a Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Fri, 31 Aug 2018 17:27:10 +0300 Subject: [PATCH 02/12] Change default value of crop argument of blobFromImage from true to false --- modules/dnn/include/opencv2/dnn/dnn.hpp | 12 +++--- modules/dnn/test/test_caffe_importer.cpp | 4 +- modules/dnn/test/test_tf_importer.cpp | 3 +- modules/dnn/test/test_torch_importer.cpp | 4 +- .../opencv_mobilenet/MainActivity.java | 40 +++++-------------- 5 files changed, 22 insertions(+), 41 deletions(-) diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index e418ae4066..01268d7842 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -46,9 +46,9 @@ #include #if !defined CV_DOXYGEN && !defined CV_DNN_DONT_ADD_EXPERIMENTAL_NS -#define CV__DNN_EXPERIMENTAL_NS_BEGIN namespace experimental_dnn_34_v8 { +#define CV__DNN_EXPERIMENTAL_NS_BEGIN namespace experimental_dnn_34_v9 { #define CV__DNN_EXPERIMENTAL_NS_END } -namespace cv { namespace dnn { namespace experimental_dnn_34_v8 { } using namespace experimental_dnn_34_v8; }} +namespace cv { namespace dnn { namespace experimental_dnn_34_v9 { } using namespace experimental_dnn_34_v9; }} #else #define CV__DNN_EXPERIMENTAL_NS_BEGIN #define CV__DNN_EXPERIMENTAL_NS_END @@ -843,7 +843,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN * @returns 4-dimensional Mat with NCHW dimensions order. */ CV_EXPORTS_W Mat blobFromImage(InputArray image, double scalefactor=1.0, const Size& size = Size(), - const Scalar& mean = Scalar(), bool swapRB=true, bool crop=true, + const Scalar& mean = Scalar(), bool swapRB=false, bool crop=false, int ddepth=CV_32F); /** @brief Creates 4-dimensional blob from image. @@ -852,7 +852,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN */ CV_EXPORTS void blobFromImage(InputArray image, OutputArray blob, double scalefactor=1.0, const Size& size = Size(), const Scalar& mean = Scalar(), - bool swapRB=true, bool crop=true, int ddepth=CV_32F); + bool swapRB=false, bool crop=false, int ddepth=CV_32F); /** @brief Creates 4-dimensional blob from series of images. Optionally resizes and @@ -873,7 +873,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN * @returns 4-dimensional Mat with NCHW dimensions order. */ CV_EXPORTS_W Mat blobFromImages(InputArrayOfArrays images, double scalefactor=1.0, - Size size = Size(), const Scalar& mean = Scalar(), bool swapRB=true, bool crop=true, + Size size = Size(), const Scalar& mean = Scalar(), bool swapRB=false, bool crop=false, int ddepth=CV_32F); /** @brief Creates 4-dimensional blob from series of images. @@ -882,7 +882,7 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN */ CV_EXPORTS void blobFromImages(InputArrayOfArrays images, OutputArray blob, double scalefactor=1.0, Size size = Size(), - const Scalar& mean = Scalar(), bool swapRB=true, bool crop=true, + const Scalar& mean = Scalar(), bool swapRB=false, bool crop=false, int ddepth=CV_32F); /** @brief Parse a 4D blob and output the images it contains as 2D arrays through a simpler data structure diff --git a/modules/dnn/test/test_caffe_importer.cpp b/modules/dnn/test/test_caffe_importer.cpp index 85ff7ace21..b6da2f189c 100644 --- a/modules/dnn/test/test_caffe_importer.cpp +++ b/modules/dnn/test/test_caffe_importer.cpp @@ -307,7 +307,7 @@ TEST_P(Reproducibility_SqueezeNet_v1_1, Accuracy) net.setPreferableBackend(DNN_BACKEND_OPENCV); net.setPreferableTarget(targetId); - Mat input = blobFromImage(imread(_tf("googlenet_0.png")), 1.0f, Size(227,227), Scalar(), false); + Mat input = blobFromImage(imread(_tf("googlenet_0.png")), 1.0f, Size(227,227), Scalar(), false, true); ASSERT_TRUE(!input.empty()); Mat out; @@ -403,7 +403,7 @@ TEST_P(Test_Caffe_nets, DenseNet_121) const string model = findDataFile("dnn/DenseNet_121.caffemodel", false); Mat inp = imread(_tf("dog416.png")); - inp = blobFromImage(inp, 1.0 / 255, Size(224, 224)); + inp = blobFromImage(inp, 1.0 / 255, Size(224, 224), Scalar(), true, true); Mat ref = blobFromNPY(_tf("densenet_121_output.npy")); Net net = readNetFromCaffe(proto, model); diff --git a/modules/dnn/test/test_tf_importer.cpp b/modules/dnn/test/test_tf_importer.cpp index b05d1f5440..b10c1388f3 100644 --- a/modules/dnn/test/test_tf_importer.cpp +++ b/modules/dnn/test/test_tf_importer.cpp @@ -62,8 +62,7 @@ TEST(Test_TensorFlow, inception_accuracy) Mat sample = imread(_tf("grace_hopper_227.png")); ASSERT_TRUE(!sample.empty()); - resize(sample, sample, Size(224, 224)); - Mat inputBlob = blobFromImage(sample); + Mat inputBlob = blobFromImage(sample, 1.0, Size(224, 224), Scalar(), /*swapRB*/true); net.setInput(inputBlob, "input"); Mat out = net.forward("softmax2"); diff --git a/modules/dnn/test/test_torch_importer.cpp b/modules/dnn/test/test_torch_importer.cpp index bd5f11249d..dd7d975af6 100644 --- a/modules/dnn/test/test_torch_importer.cpp +++ b/modules/dnn/test/test_torch_importer.cpp @@ -278,7 +278,7 @@ TEST_P(Test_Torch_nets, OpenFace_accuracy) sampleF32 /= 255; resize(sampleF32, sampleF32, Size(96, 96), 0, 0, INTER_NEAREST); - Mat inputBlob = blobFromImage(sampleF32); + Mat inputBlob = blobFromImage(sampleF32, 1.0, Size(), Scalar(), /*swapRB*/true); net.setInput(inputBlob); Mat out = net.forward(); @@ -305,7 +305,7 @@ TEST_P(Test_Torch_nets, ENet_accuracy) net.setPreferableTarget(target); Mat sample = imread(_tf("street.png", false)); - Mat inputBlob = blobFromImage(sample, 1./255); + Mat inputBlob = blobFromImage(sample, 1./255, Size(), Scalar(), /*swapRB*/true); net.setInput(inputBlob, ""); Mat out = net.forward(); diff --git a/samples/android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java b/samples/android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java index 31440e2c85..eed419520d 100644 --- a/samples/android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java +++ b/samples/android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java @@ -86,29 +86,13 @@ public class MainActivity extends AppCompatActivity implements CvCameraViewListe // Forward image through network. Mat blob = Dnn.blobFromImage(frame, IN_SCALE_FACTOR, new Size(IN_WIDTH, IN_HEIGHT), - new Scalar(MEAN_VAL, MEAN_VAL, MEAN_VAL), false); + new Scalar(MEAN_VAL, MEAN_VAL, MEAN_VAL), /*swapRB*/false, /*crop*/false); net.setInput(blob); Mat detections = net.forward(); int cols = frame.cols(); int rows = frame.rows(); - Size cropSize; - if ((float)cols / rows > WH_RATIO) { - cropSize = new Size(rows * WH_RATIO, rows); - } else { - cropSize = new Size(cols, cols / WH_RATIO); - } - - int y1 = (int)(rows - cropSize.height) / 2; - int y2 = (int)(y1 + cropSize.height); - int x1 = (int)(cols - cropSize.width) / 2; - int x2 = (int)(x1 + cropSize.width); - Mat subFrame = frame.submat(y1, y2, x1, x2); - - cols = subFrame.cols(); - rows = subFrame.rows(); - detections = detections.reshape(1, (int)detections.total() / 7); for (int i = 0; i < detections.rows(); ++i) { @@ -116,26 +100,24 @@ public class MainActivity extends AppCompatActivity implements CvCameraViewListe if (confidence > THRESHOLD) { int classId = (int)detections.get(i, 1)[0]; - int xLeftBottom = (int)(detections.get(i, 3)[0] * cols); - int yLeftBottom = (int)(detections.get(i, 4)[0] * rows); - int xRightTop = (int)(detections.get(i, 5)[0] * cols); - int yRightTop = (int)(detections.get(i, 6)[0] * rows); + int left = (int)(detections.get(i, 3)[0] * cols); + int top = (int)(detections.get(i, 4)[0] * rows); + int right = (int)(detections.get(i, 5)[0] * cols); + int bottom = (int)(detections.get(i, 6)[0] * rows); // Draw rectangle around detected object. - Imgproc.rectangle(subFrame, new Point(xLeftBottom, yLeftBottom), - new Point(xRightTop, yRightTop), - new Scalar(0, 255, 0)); + Imgproc.rectangle(frame, new Point(left, top), new Point(right, bottom), + new Scalar(0, 255, 0)); String label = classNames[classId] + ": " + confidence; int[] baseLine = new int[1]; Size labelSize = Imgproc.getTextSize(label, Core.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine); // Draw background for label. - Imgproc.rectangle(subFrame, new Point(xLeftBottom, yLeftBottom - labelSize.height), - new Point(xLeftBottom + labelSize.width, yLeftBottom + baseLine[0]), - new Scalar(255, 255, 255), Core.FILLED); - + Imgproc.rectangle(frame, new Point(left, top - labelSize.height), + new Point(left + labelSize.width, top + baseLine[0]), + new Scalar(255, 255, 255), Imgproc.FILLED); // Write class name and confidence. - Imgproc.putText(subFrame, label, new Point(xLeftBottom, yLeftBottom), + Imgproc.putText(frame, label, new Point(left, top), Core.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 0)); } } From c5687caf1a519a70e20de3f59798c00572499a85 Mon Sep 17 00:00:00 2001 From: Karpushin Vladislav Date: Fri, 14 Sep 2018 13:14:17 +0700 Subject: [PATCH 03/12] doc: add new tutorial anisotropic image segmentation --- doc/opencv.bib | 34 ++++++ .../anisotropic_image_segmentation.markdown | 91 +++++++++++++++ .../images/gst_coherency.jpg | Bin 0 -> 20377 bytes .../images/gst_input.jpg | Bin 0 -> 40733 bytes .../images/gst_orientation.jpg | Bin 0 -> 19463 bytes .../images/gst_result.jpg | Bin 0 -> 33021 bytes .../imgproc/table_of_content_imgproc.markdown | 10 ++ .../anisotropic_image_segmentation.cpp | 104 ++++++++++++++++++ 8 files changed, 239 insertions(+) create mode 100755 doc/tutorials/imgproc/anisotropic_image_segmentation/anisotropic_image_segmentation.markdown create mode 100755 doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_coherency.jpg create mode 100755 doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_input.jpg create mode 100755 doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_orientation.jpg create mode 100755 doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_result.jpg create mode 100755 samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp diff --git a/doc/opencv.bib b/doc/opencv.bib index 7c8303f7f4..de18a4e586 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1030,3 +1030,37 @@ year={2000}, publisher={Изд-во НГТУ Новосибирск} } + +@book{jahne2000computer, + title={Computer vision and applications: a guide for students and practitioners}, + author={Jahne, Bernd}, + year={2000}, + publisher={Elsevier} +} + +@book{bigun2006vision, + title={Vision with direction}, + author={Bigun, Josef}, + year={2006}, + publisher={Springer} +} + +@inproceedings{van1995estimators, + title={Estimators for orientation and anisotropy in digitized images}, + author={Van Vliet, Lucas J and Verbeek, Piet W}, + booktitle={ASCI}, + volume={95}, + pages={16--18}, + year={1995} +} + +@article{yang1996structure, + title={Structure adaptive anisotropic image filtering}, + author={Yang, Guang-Zhong and Burger, Peter and Firmin, David N and Underwood, SR}, + journal={Image and Vision Computing}, + volume={14}, + number={2}, + pages={135--145}, + year={1996}, + publisher={Elsevier} +} diff --git a/doc/tutorials/imgproc/anisotropic_image_segmentation/anisotropic_image_segmentation.markdown b/doc/tutorials/imgproc/anisotropic_image_segmentation/anisotropic_image_segmentation.markdown new file mode 100755 index 0000000000..16df8eedd2 --- /dev/null +++ b/doc/tutorials/imgproc/anisotropic_image_segmentation/anisotropic_image_segmentation.markdown @@ -0,0 +1,91 @@ +Anisotropic image segmentation by a gradient structure tensor {#tutorial_anisotropic_image_segmentation_by_a_gst} +========================== + +Goal +---- + +In this tutorial you will learn: + +- what the gradient structure tensor is +- how to estimate orientation and coherency of an anisotropic image by a gradient structure tensor +- how to segment an anisotropic image with a single local orientation by a gradient structure tensor + +Theory +------ + +@note The explanation is based on the books @cite jahne2000computer, @cite bigun2006vision and @cite van1995estimators. Good physical explanation of a gradient structure tensor is given in @cite yang1996structure. Also, you can refer to a wikipedia page [Structure tensor]. +@note A anisotropic image on this page is a real world image. + +### What is the gradient structure tensor? + +In mathematics, the gradient structure tensor (also referred to as the second-moment matrix, the second order moment tensor, the inertia tensor, etc.) is a matrix derived from the gradient of a function. It summarizes the predominant directions of the gradient in a specified neighborhood of a point, and the degree to which those directions are coherent (coherency). The gradient structure tensor is widely used in image processing and computer vision for 2D/3D image segmentation, motion detection, adaptive filtration, local image features detection, etc. + +Important features of anisotropic images include orientation and coherency of a local anisotropy. In this paper we will show how to estimate orientation and coherency, and how to segment an anisotropic image with a single local orientation by a gradient structure tensor. + +The gradient structure tensor of an image is a 2x2 symmetric matrix. Eigenvectors of the gradient structure tensor indicate local orientation, whereas eigenvalues give coherency (a measure of anisotropism). + +The gradient structure tensor \f$J\f$ of an image \f$Z\f$ can be written as: + +\f[J = \begin{bmatrix} +J_{11} & J_{12} \\ +J_{12} & J_{22} +\end{bmatrix}\f] + +where \f$J_{11} = M[Z_{x}^{2}]\f$, \f$J_{22} = M[Z_{y}^{2}]\f$, \f$J_{12} = M[Z_{x}Z_{y}]\f$ - components of the tensor, \f$M[]\f$ is a symbol of mathematical expectation (we can consider this operation as averaging in a window w), \f$Z_{x}\f$ and \f$Z_{y}\f$ are partial derivatives of an image \f$Z\f$ with respect to \f$x\f$ and \f$y\f$. + +The eigenvalues of the tensor can be found in the below formula: +\f[\lambda_{1,2} = J_{11} + J_{22} \pm \sqrt{(J_{11} - J_{22})^{2} + 4J_{12}^{2}}\f] +where \f$\lambda_1\f$ - largest eigenvalue, \f$\lambda_2\f$ - smallest eigenvalue. + +### How to estimate orientation and coherency of an anisotropic image by gradient structure tensor? + +The orientation of an anisotropic image: +\f[\alpha = 0.5arctg\frac{2J_{12}}{J_{22} - J_{11}}\f] + +Coherency: +\f[C = \frac{\lambda_1 - \lambda_2}{\lambda_1 + \lambda_2}\f] + +The coherency ranges from 0 to 1. For ideal local orientation (\f$\lambda_2\f$ = 0, \f$\lambda_1\f$ > 0) it is one, for an isotropic gray value structure (\f$\lambda_1\f$ = \f$\lambda_2\f$ > 0) it is zero. + +Source code +----------- + +You can find source code in the `samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp` of the OpenCV source code library. + +@include cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp + +Explanation +----------- +An anisotropic image segmentation algorithm consists of a gradient structure tensor calculation, an orientation calculation, a coherency calculation and an orientation and coherency thresholding: +@snippet samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp main + +A function calcGST() calculates orientation and coherency by using a gradient structure tensor. An input parameter w defines a window size: +@snippet samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp calcGST + +The below code applies a thresholds LowThr and HighThr to image orientation and a threshold C_Thr to image coherency calculated by the previous function. LowThr and HighThr define orientation range: +@snippet samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp thresholding + +And finally we combine thresholding results: +@snippet samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp combining + +Result +------ + +Below you can see the real anisotropic image with single direction: +![Anisotropic image with the single direction](images/gst_input.jpg) + +Below you can see the orientation and coherency of the anisotropic image: +![Orientation](images/gst_orientation.jpg) +![Coherency](images/gst_coherency.jpg) + +Below you can see the segmentation result: +![Segmentation result](images/gst_result.jpg) + +The result has been computed with w = 52, C_Thr = 0.43, LowThr = 35, HighThr = 57. We can see that the algorithm selected only the areas with one single direction. + +References +------ +- [Structure tensor] - structure tensor description on the wikipedia + + +[Structure tensor]: https://en.wikipedia.org/wiki/Structure_tensor diff --git a/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_coherency.jpg b/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_coherency.jpg new file mode 100755 index 0000000000000000000000000000000000000000..87d0881cfc7bcdb451c9e987d2dd3b1c84d54da3 GIT binary patch literal 20377 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 literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_input.jpg b/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_input.jpg new file mode 100755 index 0000000000000000000000000000000000000000..5fb3dfe8309dc53e0fa26e49042a74b25a805c75 GIT binary patch literal 40733 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

o^GQ}ky*R6A`0Q&Si`&* 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 YjV>v|y3K2FCSLfys5>IB4Eb;Je*&n3jsO4v literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_orientation.jpg b/doc/tutorials/imgproc/anisotropic_image_segmentation/images/gst_orientation.jpg new file mode 100755 index 0000000000000000000000000000000000000000..976fb24c90a139c19e1fb05256449dbf9d6fb0d8 GIT binary patch literal 19463 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 NKEeN1*oOVYV zrlmHlMBdxI^ul9c@4$Iox03i0xj%?0)kK+58o}ei@ZF?*zxAvwhyy``QHgj}b>-BA zyD&a%Hvc)`R)DM`JitMK7!5)tXW1IA2%?7kRUfaP?kK(d#8{uuHbujcC1)NbeVQ>fkFl5u5o@u(@A{kT@d zCze7U;rnR6`)tg+%20UllO%pQyX3ff=w~BuE->xfV&?S9K9*Nl(5qv|we_nlU+{b# zXLCqp{!I`22DGF?xc647okyg$rg;`KwfB1D+#@$8Labo%>?#)=tut6;JD0Syjzs2#T02HKtkCH zhO5uE@w+zJRyfYsJzy&qWz#xVswi9A_`1xWx@Ce>QEN&kUPdwg^;6P_VJ_m&dR!FM zBj{COP@xsBcsRJuxZdsHa@JjbcMAxdoZ8uLJUDrin7?mnZuN{TgMl3=X26i3HTWyNi+@EVurqMDNufxxM z=ZS$NjWBWIBjl?jHGp2G`$X*hyry7e0BaAofYDR|BG+(?HC&|*z#EH3iWSPce@dBB z13~ehv1cC&fDF$XA3l}(bGECu>Ot7H=(|C(dsQ`SHKo}Zf;ov#ec_}U2h{wnYL~g1)GJDXZ?t%b(B=wO~3)V-ApG(Per?j ztE4`uT_P(&M_2CG6590iRAn$BAG&=CGu}!Y+}2TC`0V(+#Q*r&U9o$H%t%eItWT$o zB#VOa|CS%6)KXzirPoA|jM zje8Gfu$3?-PbpfA(RL)vBzsAR5(;vu!J7i-AW1hmMXHI@a%kkZ*`l+kXT7 zo3VTM3NxNq>it+x{2G|2@MhPXqaxN0d|>={BZgX2(e-1KFYMhiq*MOUP~>V5NAHhk zkA|wW^M|~-a>KvheEKfjB3Qzry=p7tYL5K}t2?ICc|J*6*WrKF3vPa1C zIFiJdz8zt5iW@G1J_7;W?$v;{&ATbw`qh<#N{SV(VbPhnJhCG!io+56m^B9f2ZV4zS9Xe!=B~&z&<=svlLr*DAtTCD81&Lnw?vk zhmS?`nW^}Al!CP{Rjy*P`;7eK_FFj78vD3Lm@c1Z4}se(pzLwXEX&4r#~t%>>y-ma zbgTOuAEzSKZ66coNu<{-OSeWt= z{a&P#lS`9WwCZQ5QMy&nr8pwCc2$#5z{iB80f+V2s2_GW#ld1z*%`}9I@M4AwNc}d zo%fI@TTEV6_(LH9dK?7y#-ZVIkntb#uH zj!Hbd-xtf+-LU%kT5IYOIkU`xPx%S541tYgy0B;6bza0oDWVaQmQ;!Arv2Y?Un?5< z^t&Euuw1L;z>sgRs0XTh8wfnU#rD8yIrQ$XGTrAo&hHTOG(G9Q$%U7Ik#$<8E1vXC zV68`Q9yi1+gN|wuOd;rhPG+FA?+_4R%GPUXn(ya1O*uC@E#xEAJ=0 zGaM2E!rWMjlM|q*?b&0wZn=4e1~#^FP2d~CCiR*ytHdaLPyJE@*)B?}`ZSb`&BAwdyVkrKc;*goTyy#gKdvP!(J;!h|47T7F%fK5 zc-_K~^T+!s7mrlj_ZmU5fzo~z8@1q2KOIx(zKr-6mD#tXQY0iK%Z*T;r7%Yq!)jv! MW)iGw_TT*f0K4^W-2eap literal 0 HcmV?d00001 diff --git a/doc/tutorials/imgproc/table_of_content_imgproc.markdown b/doc/tutorials/imgproc/table_of_content_imgproc.markdown index bea1e1b9ac..badc30d095 100644 --- a/doc/tutorials/imgproc/table_of_content_imgproc.markdown +++ b/doc/tutorials/imgproc/table_of_content_imgproc.markdown @@ -330,3 +330,13 @@ In this section you will learn about the image processing (manipulation) functio *Author:* Karpushin Vladislav You will learn how to recover an image with motion blur distortion using a Wiener filter. + +- @subpage tutorial_anisotropic_image_segmentation_by_a_gst + + *Languages:* C++ + + *Compatibility:* \> OpenCV 2.0 + + *Author:* Karpushin Vladislav + + You will learn how to segment an anisotropic image with a single local orientation by a gradient structure tensor. diff --git a/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp b/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp new file mode 100755 index 0000000000..345fd060a2 --- /dev/null +++ b/samples/cpp/tutorial_code/ImgProc/anisotropic_image_segmentation/anisotropic_image_segmentation.cpp @@ -0,0 +1,104 @@ +/** +* @brief You will learn how to segment an anisotropic image with a single local orientation by a gradient structure tensor (GST) +* @author Karpushin Vladislav, karpushin@ngs.ru, https://github.com/VladKarpushin +*/ + +#include +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" + +using namespace cv; +using namespace std; + +void calcGST(const Mat& inputImg, Mat& imgCoherencyOut, Mat& imgOrientationOut, int w); + +int main() +{ + int W = 52; // window size is WxW + double C_Thr = 0.43; // threshold for coherency + int LowThr = 35; // threshold1 for orientation, it ranges from 0 to 180 + int HighThr = 57; // threshold2 for orientation, it ranges from 0 to 180 + + Mat imgIn = imread("input.jpg", IMREAD_GRAYSCALE); + if (imgIn.empty()) //check whether the image is loaded or not + { + cout << "ERROR : Image cannot be loaded..!!" << endl; + return -1; + } + + //! [main] + Mat imgCoherency, imgOrientation; + calcGST(imgIn, imgCoherency, imgOrientation, W); + + //! [thresholding] + Mat imgCoherencyBin; + imgCoherencyBin = imgCoherency > C_Thr; + Mat imgOrientationBin; + inRange(imgOrientation, Scalar(LowThr), Scalar(HighThr), imgOrientationBin); + //! [thresholding] + + //! [combining] + Mat imgBin; + imgBin = imgCoherencyBin & imgOrientationBin; + //! [combining] + //! [main] + + normalize(imgCoherency, imgCoherency, 0, 255, NORM_MINMAX); + normalize(imgOrientation, imgOrientation, 0, 255, NORM_MINMAX); + imwrite("result.jpg", 0.5*(imgIn + imgBin)); + imwrite("Coherency.jpg", imgCoherency); + imwrite("Orientation.jpg", imgOrientation); + return 0; +} +//! [calcGST] +void calcGST(const Mat& inputImg, Mat& imgCoherencyOut, Mat& imgOrientationOut, int w) +{ + Mat img; + inputImg.convertTo(img, CV_64F); + + // GST components calculation (start) + // J = (J11 J12; J12 J22) - GST + Mat imgDiffX, imgDiffY, imgDiffXY; + Sobel(img, imgDiffX, CV_64F, 1, 0, 3); + Sobel(img, imgDiffY, CV_64F, 0, 1, 3); + multiply(imgDiffX, imgDiffY, imgDiffXY); + + Mat imgDiffXX, imgDiffYY; + multiply(imgDiffX, imgDiffX, imgDiffXX); + multiply(imgDiffY, imgDiffY, imgDiffYY); + + Mat J11, J22, J12; // J11, J22 and J12 are GST components + boxFilter(imgDiffXX, J11, CV_64F, Size(w, w)); + boxFilter(imgDiffYY, J22, CV_64F, Size(w, w)); + boxFilter(imgDiffXY, J12, CV_64F, Size(w, w)); + // GST components calculation (stop) + + // eigenvalue calculation (start) + // lambda1 = J11 + J22 + sqrt((J11-J22)^2 + 4*J12^2) + // lambda2 = J11 + J22 - sqrt((J11-J22)^2 + 4*J12^2) + Mat tmp1, tmp2, tmp3, tmp4; + tmp1 = J11 + J22; + tmp2 = J11 - J22; + multiply(tmp2, tmp2, tmp2); + multiply(J12, J12, tmp3); + sqrt(tmp2 + 4.0 * tmp3, tmp4); + + Mat lambda1, lambda2; + lambda1 = tmp1 + tmp4; // biggest eigenvalue + lambda2 = tmp1 - tmp4; // smallest eigenvalue + // eigenvalue calculation (stop) + + // Coherency calculation (start) + // Coherency = (lambda1 - lambda2)/(lambda1 + lambda2)) - measure of anisotropism + // Coherency is anisotropy degree (consistency of local orientation) + divide(lambda1 - lambda2, lambda1 + lambda2, imgCoherencyOut); + // Coherency calculation (stop) + + // orientation angle calculation (start) + // tan(2*Alpha) = 2*J12/(J22 - J11) + // Alpha = 0.5 atan2(2*J12/(J22 - J11)) + phase(J22 - J11, 2.0*J12, imgOrientationOut, true); + imgOrientationOut = 0.5*imgOrientationOut; + // orientation angle calculation (stop) +} +//! [calcGST] From 79db32bcd92f5906e0d7a5948cb3ae6acbc50662 Mon Sep 17 00:00:00 2001 From: gkaneto Date: Thu, 13 Sep 2018 18:02:50 -0300 Subject: [PATCH 04/12] Update py_trackbar.markdown For some mysterious (for me) reason, two lines of the code appears in my browser (Chrome) in the same line. I've add an "enter" (just pressed enter), but don't know if it's the best solution. --- doc/py_tutorials/py_gui/py_trackbar/py_trackbar.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/py_tutorials/py_gui/py_trackbar/py_trackbar.markdown b/doc/py_tutorials/py_gui/py_trackbar/py_trackbar.markdown index e5e9306d27..d6af059903 100644 --- a/doc/py_tutorials/py_gui/py_trackbar/py_trackbar.markdown +++ b/doc/py_tutorials/py_gui/py_trackbar/py_trackbar.markdown @@ -37,6 +37,7 @@ cv.namedWindow('image') # create trackbars for color change cv.createTrackbar('R','image',0,255,nothing) + cv.createTrackbar('G','image',0,255,nothing) cv.createTrackbar('B','image',0,255,nothing) From 96a50d5eb0dacbd128cf00b6653e544fc6e3e045 Mon Sep 17 00:00:00 2001 From: gkaneto Date: Thu, 13 Sep 2018 22:17:12 -0300 Subject: [PATCH 05/12] Update py_basic_ops.markdown Correcting indentation of border type flags --- .../py_core/py_basic_ops/py_basic_ops.markdown | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.markdown b/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.markdown index b0f92d72c6..1d0ebb3967 100644 --- a/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.markdown +++ b/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.markdown @@ -153,15 +153,15 @@ padding etc. This function takes following arguments: - **borderType** - Flag defining what kind of border to be added. It can be following types: - **cv.BORDER_CONSTANT** - Adds a constant colored border. The value should be given - as next argument. - - **cv.BORDER_REFLECT** - Border will be mirror reflection of the border elements, - like this : *fedcba|abcdefgh|hgfedcb* - - **cv.BORDER_REFLECT_101** or **cv.BORDER_DEFAULT** - Same as above, but with a - slight change, like this : *gfedcb|abcdefgh|gfedcba* - - **cv.BORDER_REPLICATE** - Last element is replicated throughout, like this: - *aaaaaa|abcdefgh|hhhhhhh* - - **cv.BORDER_WRAP** - Can't explain, it will look like this : - *cdefgh|abcdefgh|abcdefg* + as next argument. + - **cv.BORDER_REFLECT** - Border will be mirror reflection of the border elements, + like this : *fedcba|abcdefgh|hgfedcb* + - **cv.BORDER_REFLECT_101** or **cv.BORDER_DEFAULT** - Same as above, but with a + slight change, like this : *gfedcb|abcdefgh|gfedcba* + - **cv.BORDER_REPLICATE** - Last element is replicated throughout, like this: + *aaaaaa|abcdefgh|hhhhhhh* + - **cv.BORDER_WRAP** - Can't explain, it will look like this : + *cdefgh|abcdefgh|abcdefg* - **value** - Color of border if border type is cv.BORDER_CONSTANT From a7b3d2581fe22f2afb9a87782eb1ca1215b5b606 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Mon, 17 Sep 2018 12:31:09 +0300 Subject: [PATCH 06/12] Replace CV_USRTYPE1 for int64 to CV_32SC2 in Torch importer --- modules/dnn/src/torch/torch_importer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/dnn/src/torch/torch_importer.cpp b/modules/dnn/src/torch/torch_importer.cpp index 2338c73d96..47aa1cc822 100644 --- a/modules/dnn/src/torch/torch_importer.cpp +++ b/modules/dnn/src/torch/torch_importer.cpp @@ -214,8 +214,8 @@ struct TorchImporter return CV_16S; else if (typeStr == "Int") return CV_32S; - else if (typeStr == "Long") //Carefully! CV_64S type coded as CV_USRTYPE1 - return CV_USRTYPE1; + else if (typeStr == "Long") //Carefully! CV_64S type coded as CV_32SC2 + return CV_32SC2; else CV_Error(Error::StsNotImplemented, "Unknown type \"" + typeStr + "\" of torch class \"" + str + "\""); } @@ -236,7 +236,7 @@ struct TorchImporter void readTorchStorage(int index, int type = -1) { long size = readLong(); - Mat storageMat(1, size, (type != CV_USRTYPE1) ? type : CV_64F); //handle LongStorage as CV_64F Mat + Mat storageMat(1, size, (type != CV_32SC2) ? type : CV_64F); //handle LongStorage as CV_64F Mat switch (type) { @@ -257,7 +257,7 @@ struct TorchImporter case CV_32S: THFile_readIntRaw(file, (int*)storageMat.data, size); break; - case CV_USRTYPE1: + case CV_32SC2: { double *buf = storageMat.ptr(); THFile_readLongRaw(file, (int64*)buf, size); From 29bee6f07e1105a1ed6492c889bf75f436bcb3b2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 17 Sep 2018 14:55:42 +0300 Subject: [PATCH 07/12] cmake: move Matlab scripts to opencv_contrib (#12541) * matlab: move to opencv_contrib * cmake: preserve variables scope for processing modules - use macro instead of function to avoid scope resets --- CMakeLists.txt | 16 +-- cmake/OpenCVFindMatlab.cmake | 199 ----------------------------------- cmake/OpenCVModule.cmake | 40 +++---- cmake/OpenCVUtils.cmake | 6 +- modules/dnn/CMakeLists.txt | 2 +- 5 files changed, 27 insertions(+), 236 deletions(-) delete mode 100644 cmake/OpenCVFindMatlab.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 86c2b04319..6c53bf80ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,7 +271,6 @@ OCV_OPTION(WITH_OPENCLAMDFFT "Include AMD OpenCL FFT library support" ON OCV_OPTION(WITH_OPENCLAMDBLAS "Include AMD OpenCL BLAS library support" ON IF (NOT ANDROID AND NOT IOS AND NOT WINRT) ) OCV_OPTION(WITH_DIRECTX "Include DirectX support" ON IF (WIN32 AND NOT WINRT) ) OCV_OPTION(WITH_INTELPERC "Include Intel Perceptual Computing support" OFF IF (WIN32 AND NOT WINRT) ) -OCV_OPTION(WITH_MATLAB "Include Matlab support" ON IF (NOT ANDROID AND NOT IOS AND NOT WINRT)) OCV_OPTION(WITH_VA "Include VA support" OFF IF (UNIX AND NOT ANDROID) ) OCV_OPTION(WITH_VA_INTEL "Include Intel VA-API/OpenCL support" OFF IF (UNIX AND NOT ANDROID) ) OCV_OPTION(WITH_MFX "Include Intel Media SDK support" OFF IF ((UNIX AND NOT ANDROID) OR (WIN32 AND NOT WINRT AND NOT MINGW)) ) @@ -693,11 +692,6 @@ if(WITH_DIRECTX) include(cmake/OpenCVDetectDirectX.cmake) endif() -# --- Matlab/Octave --- -if(WITH_MATLAB) - include(cmake/OpenCVFindMatlab.cmake) -endif() - if(WITH_VTK) include(cmake/OpenCVDetectVTK.cmake) endif() @@ -1516,15 +1510,7 @@ if(BUILD_JAVA OR BUILD_opencv_java) status(" Java tests:" BUILD_TESTS AND opencv_test_java_BINARY_DIR THEN YES ELSE NO) endif() -# ========================= matlab ========================= -if(WITH_MATLAB OR MATLAB_FOUND) - status("") - status(" Matlab:" MATLAB_FOUND THEN "YES" ELSE "NO") - if(MATLAB_FOUND) - status(" mex:" MATLAB_MEX_SCRIPT THEN "${MATLAB_MEX_SCRIPT}" ELSE NO) - status(" Compiler/generator:" MEX_WORKS THEN "Working" ELSE "Not working (bindings will not be generated)") - endif() -endif() +ocv_cmake_hook(STATUS_DUMP_EXTRA) # ========================== auxiliary ========================== status("") diff --git a/cmake/OpenCVFindMatlab.cmake b/cmake/OpenCVFindMatlab.cmake deleted file mode 100644 index ffe8857fec..0000000000 --- a/cmake/OpenCVFindMatlab.cmake +++ /dev/null @@ -1,199 +0,0 @@ -# ----- Find Matlab/Octave ----- -# -# OpenCVFindMatlab.cmake attempts to locate the install path of Matlab in order -# to extract the mex headers, libraries and shell scripts. If found -# successfully, the following variables will be defined -# -# MATLAB_FOUND: true/false -# MATLAB_ROOT_DIR: Root of Matlab installation -# MATLAB_BIN: The main Matlab "executable" (shell script) -# MATLAB_MEX_SCRIPT: The mex script used to compile mex files -# MATLAB_INCLUDE_DIRS:Path to "mex.h" -# MATLAB_LIBRARY_DIRS:Path to mex and matrix libraries -# MATLAB_LIBRARIES: The Matlab libs, usually mx, mex, mat -# MATLAB_MEXEXT: The mex library extension. It will be one of: -# mexwin32, mexwin64, mexglx, mexa64, mexmac, -# mexmaci, mexmaci64, mexsol, mexs64 -# MATLAB_ARCH: The installation architecture. It is **usually** -# the MEXEXT with the preceding "mex" removed, -# though it's different for linux distros. -# -# There doesn't appear to be an elegant way to detect all versions of Matlab -# across different platforms. If you know the matlab path and want to avoid -# the search, you can define the path to the Matlab root when invoking cmake: -# -# cmake -DMATLAB_ROOT_DIR='/PATH/TO/ROOT_DIR' .. - - - -# ----- set_library_presuffix ----- -# -# Matlab tends to use some non-standard prefixes and suffixes on its libraries. -# For example, libmx.dll on Windows (Windows does not add prefixes) and -# mkl.dylib on OS X (OS X uses "lib" prefixes). -# On some versions of Windows the .dll suffix also appears to not be checked. -# -# This function modifies the library prefixes and suffixes used by -# find_library when finding Matlab libraries. It does not affect scopes -# outside of this file. -function(set_libarch_prefix_suffix) - if (UNIX AND NOT APPLE) - set(CMAKE_FIND_LIBRARY_PREFIXES "lib" PARENT_SCOPE) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".so" ".a" PARENT_SCOPE) - elseif (APPLE) - set(CMAKE_FIND_LIBRARY_PREFIXES "lib" PARENT_SCOPE) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".a" PARENT_SCOPE) - elseif (WIN32) - set(CMAKE_FIND_LIBRARY_PREFIXES "lib" PARENT_SCOPE) - set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".dll" PARENT_SCOPE) - endif() -endfunction() - - - -# ----- locate_matlab_root ----- -# -# Attempt to find the path to the Matlab installation. If successful, sets -# the absolute path in the variable MATLAB_ROOT_DIR -function(locate_matlab_root) - - # --- UNIX/APPLE --- - if (UNIX) - # possible root locations, in order of likelihood - set(SEARCH_DIRS_ /Applications /usr/local /opt/local /usr /opt) - foreach (DIR_ ${SEARCH_DIRS_}) - file(GLOB MATLAB_ROOT_DIR_ ${DIR_}/MATLAB/R* ${DIR_}/MATLAB_R*) - if (MATLAB_ROOT_DIR_) - # sort in order from highest to lowest - # normally it's in the format MATLAB_R[20XX][A/B] - # TODO: numerical rather than lexicographic sort. However, - # CMake does not support floating-point MATH(EXPR ...) at this time. - list(SORT MATLAB_ROOT_DIR_) - list(REVERSE MATLAB_ROOT_DIR_) - list(GET MATLAB_ROOT_DIR_ 0 MATLAB_ROOT_DIR_) - set(MATLAB_ROOT_DIR ${MATLAB_ROOT_DIR_} PARENT_SCOPE) - return() - endif() - endforeach() - - # --- WINDOWS --- - elseif (WIN32) - # 1. search the path environment variable - find_program(MATLAB_ROOT_DIR_ matlab PATHS ENV PATH) - if (MATLAB_ROOT_DIR_) - # get the root directory from the full path - # /path/to/matlab/rootdir/bin/matlab.exe - get_filename_component(MATLAB_ROOT_DIR_ ${MATLAB_ROOT_DIR_} PATH) - get_filename_component(MATLAB_ROOT_DIR_ ${MATLAB_ROOT_DIR_} PATH) - set(MATLAB_ROOT_DIR ${MATLAB_ROOT_DIR_} PARENT_SCOPE) - return() - endif() - - # 2. search the registry - # determine the available Matlab versions - set(REG_EXTENSION_ "SOFTWARE\\Mathworks\\MATLAB") - set(REG_ROOTS_ "HKEY_LOCAL_MACHINE" "HKEY_CURRENT_USER") - foreach(REG_ROOT_ ${REG_ROOTS_}) - execute_process(COMMAND reg query "${REG_ROOT_}\\${REG_EXTENSION_}" OUTPUT_VARIABLE QUERY_RESPONSE_ ERROR_VARIABLE UNUSED_) - if (QUERY_RESPONSE_) - string(REGEX MATCHALL "[0-9]\\.[0-9]" VERSION_STRINGS_ ${QUERY_RESPONSE_}) - list(APPEND VERSIONS_ ${VERSION_STRINGS_}) - endif() - endforeach() - - # select the highest version - list(APPEND VERSIONS_ "0.0") - list(SORT VERSIONS_) - list(REVERSE VERSIONS_) - list(GET VERSIONS_ 0 VERSION_) - - # request the MATLABROOT from the registry - foreach(REG_ROOT_ ${REG_ROOTS_}) - get_filename_component(QUERY_RESPONSE_ [${REG_ROOT_}\\${REG_EXTENSION_}\\${VERSION_};MATLABROOT] ABSOLUTE) - if (NOT ${QUERY_RESPONSE_} MATCHES "registry$") - set(MATLAB_ROOT_DIR ${QUERY_RESPONSE_} PARENT_SCOPE) - return() - endif() - endforeach() - endif() -endfunction() - - - -# ----- locate_matlab_components ----- -# -# Given a directory MATLAB_ROOT_DIR, attempt to find the Matlab components -# (include directory and libraries) under the root. If everything is found, -# sets the variable MATLAB_FOUND to TRUE -function(locate_matlab_components MATLAB_ROOT_DIR) - # get the mex extension - find_file(MATLAB_MEXEXT_SCRIPT_ NAMES mexext mexext.bat PATHS ${MATLAB_ROOT_DIR}/bin NO_DEFAULT_PATH) - execute_process(COMMAND ${MATLAB_MEXEXT_SCRIPT_} - OUTPUT_VARIABLE MATLAB_MEXEXT_ - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (NOT MATLAB_MEXEXT_) - return() - endif() - - # map the mexext to an architecture extension - set(ARCHITECTURES_ "maci64" "maci" "glnxa64" "glnx64" "sol64" "sola64" "win32" "win64" ) - foreach(ARCHITECTURE_ ${ARCHITECTURES_}) - if(EXISTS ${MATLAB_ROOT_DIR}/bin/${ARCHITECTURE_}) - set(MATLAB_ARCH_ ${ARCHITECTURE_}) - break() - endif() - endforeach() - - # get the path to the libraries - set(MATLAB_LIBRARY_DIRS_ ${MATLAB_ROOT_DIR}/bin/${MATLAB_ARCH_}) - - # get the libraries - set_libarch_prefix_suffix() - find_library(MATLAB_LIB_MX_ mx PATHS ${MATLAB_LIBRARY_DIRS_} NO_DEFAULT_PATH) - find_library(MATLAB_LIB_MEX_ mex PATHS ${MATLAB_LIBRARY_DIRS_} NO_DEFAULT_PATH) - find_library(MATLAB_LIB_MAT_ mat PATHS ${MATLAB_LIBRARY_DIRS_} NO_DEFAULT_PATH) - set(MATLAB_LIBRARIES_ ${MATLAB_LIB_MX_} ${MATLAB_LIB_MEX_} ${MATLAB_LIB_MAT_}) - - # get the include path - find_path(MATLAB_INCLUDE_DIRS_ mex.h ${MATLAB_ROOT_DIR}/extern/include) - - # get the mex shell script - find_program(MATLAB_MEX_SCRIPT_ NAMES mex mex.bat PATHS ${MATLAB_ROOT_DIR}/bin NO_DEFAULT_PATH) - - # get the Matlab executable - find_program(MATLAB_BIN_ NAMES matlab PATHS ${MATLAB_ROOT_DIR}/bin NO_DEFAULT_PATH) - - # export into parent scope - if (MATLAB_MEX_SCRIPT_ AND MATLAB_LIBRARIES_ AND MATLAB_INCLUDE_DIRS_) - set(MATLAB_BIN ${MATLAB_BIN_} PARENT_SCOPE) - set(MATLAB_MEX_SCRIPT ${MATLAB_MEX_SCRIPT_} PARENT_SCOPE) - set(MATLAB_INCLUDE_DIRS ${MATLAB_INCLUDE_DIRS_} PARENT_SCOPE) - set(MATLAB_LIBRARIES ${MATLAB_LIBRARIES_} PARENT_SCOPE) - set(MATLAB_LIBRARY_DIRS ${MATLAB_LIBRARY_DIRS_} PARENT_SCOPE) - set(MATLAB_MEXEXT ${MATLAB_MEXEXT_} PARENT_SCOPE) - set(MATLAB_ARCH ${MATLAB_ARCH_} PARENT_SCOPE) - endif() -endfunction() - - - -# ---------------------------------------------------------------------------- -# FIND MATLAB COMPONENTS -# ---------------------------------------------------------------------------- -if (NOT MATLAB_FOUND) - - # attempt to find the Matlab root folder - if (NOT MATLAB_ROOT_DIR) - locate_matlab_root() - endif() - - # given the matlab root folder, find the library locations - if (MATLAB_ROOT_DIR) - locate_matlab_components(${MATLAB_ROOT_DIR}) - endif() - find_package_handle_standard_args(Matlab DEFAULT_MSG - MATLAB_MEX_SCRIPT MATLAB_INCLUDE_DIRS - MATLAB_ROOT_DIR MATLAB_LIBRARIES - MATLAB_LIBRARY_DIRS MATLAB_MEXEXT - MATLAB_ARCH MATLAB_BIN) -endif() diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 54f100d3cf..a869de5cc8 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -296,28 +296,29 @@ endfunction() # Calls 'add_subdirectory' for each location. # Note: both input lists should have same length. # Usage: _add_modules_1( ) -function(_add_modules_1 paths names) - list(LENGTH ${paths} len) - if(len EQUAL 0) - return() +macro(_add_modules_1 paths names) + ocv_debug_message("_add_modules_1(paths=${paths}, names=${names}, ... " ${ARGN} ")") + list(LENGTH ${paths} __len) + if(NOT __len EQUAL 0) + list(LENGTH ${names} __len_verify) + if(NOT __len EQUAL __len_verify) + message(FATAL_ERROR "Bad configuration! ${__len} != ${__len_verify}") + endif() + math(EXPR __len "${__len} - 1") + foreach(i RANGE ${__len}) + list(GET ${paths} ${i} __path) + list(GET ${names} ${i} __name) + #message(STATUS "First pass: ${__name} => ${__path}") + include("${__path}/cmake/init.cmake" OPTIONAL) + add_subdirectory("${__path}" "${CMAKE_CURRENT_BINARY_DIR}/.firstpass/${__name}") + endforeach() endif() - list(LENGTH ${names} len_verify) - if(NOT len EQUAL len_verify) - message(FATAL_ERROR "Bad configuration! ${len} != ${len_verify}") - endif() - math(EXPR len "${len} - 1") - foreach(i RANGE ${len}) - list(GET ${paths} ${i} path) - list(GET ${names} ${i} name) - #message(STATUS "First pass: ${name} => ${path}") - include("${path}/cmake/init.cmake" OPTIONAL) - add_subdirectory("${path}" "${CMAKE_CURRENT_BINARY_DIR}/.firstpass/${name}") - endforeach() -endfunction() +endmacro() # Calls 'add_subdirectory' for each module name. # Usage: _add_modules_2([ ...]) -function(_add_modules_2) +macro(_add_modules_2) + ocv_debug_message("_add_modules_2(" ${ARGN} ")") foreach(m ${ARGN}) set(the_module "${m}") ocv_cmake_hook(PRE_MODULES_CREATE_${the_module}) @@ -333,7 +334,8 @@ function(_add_modules_2) endif() ocv_cmake_hook(POST_MODULES_CREATE_${the_module}) endforeach() -endfunction() + unset(the_module) +endmacro() # Check if list of input items is unique. # Usage: _assert_uniqueness( [ ...]) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 60c20192dc..e0c740caf9 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -121,8 +121,10 @@ macro(ocv_assert) endmacro() macro(ocv_debug_message) -# string(REPLACE ";" " " __msg "${ARGN}") -# message(STATUS "${__msg}") + if(OPENCV_CMAKE_DEBUG_MESSAGES) + string(REPLACE ";" " " __msg "${ARGN}") + message(STATUS "${__msg}") + endif() endmacro() macro(ocv_check_environment_variables) diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index 52416731ff..1cb7c467f9 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -10,7 +10,7 @@ set(the_description "Deep neural network module. It allows to load models from d ocv_add_dispatched_file_force_all("layers/layers_common" AVX AVX2 AVX512_SKX) -ocv_add_module(dnn opencv_core opencv_imgproc WRAP python matlab java js) +ocv_add_module(dnn opencv_core opencv_imgproc WRAP python java js) ocv_option(OPENCV_DNN_OPENCL "Build with OpenCL support" HAVE_OPENCL AND NOT APPLE) From d259eb28bb6d6789ea55cc28d7f94af19939c9f7 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 13 Sep 2018 16:31:31 +0300 Subject: [PATCH 08/12] Add python tests for dnn module --- modules/python/test/test_dnn.py | 179 ++++++++++++++++++++++++++++ modules/python/test/tests_common.py | 29 ++--- 2 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 modules/python/test/test_dnn.py diff --git a/modules/python/test/test_dnn.py b/modules/python/test/test_dnn.py new file mode 100644 index 0000000000..b726c29849 --- /dev/null +++ b/modules/python/test/test_dnn.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +import os +import cv2 as cv +import numpy as np + +from tests_common import NewOpenCVTests + +def normAssert(test, a, b, lInf=1e-5): + test.assertLess(np.max(np.abs(a - b)), lInf) + +def inter_area(box1, box2): + x_min, x_max = max(box1[0], box2[0]), min(box1[2], box2[2]) + y_min, y_max = max(box1[1], box2[1]), min(box1[3], box2[3]) + return (x_max - x_min) * (y_max - y_min) + +def area(box): + return (box[2] - box[0]) * (box[3] - box[1]) + +def box2str(box): + left, top = box[0], box[1] + width, height = box[2] - left, box[3] - top + return '[%f x %f from (%f, %f)]' % (width, height, left, top) + +def normAssertDetections(test, ref, out, confThreshold=0.0, scores_diff=1e-5, boxes_iou_diff=1e-4): + ref = np.array(ref, np.float32) + refClassIds, testClassIds = ref[:, 1], out[:, 1] + refScores, testScores = ref[:, 2], out[:, 2] + refBoxes, testBoxes = ref[:, 3:], out[:, 3:] + + matchedRefBoxes = [False] * len(refBoxes) + errMsg = '' + for i in range(len(refBoxes)): + testScore = testScores[i] + if testScore < confThreshold: + continue + + testClassId, testBox = testClassIds[i], testBoxes[i] + matched = False + for j in range(len(refBoxes)): + if (not matchedRefBoxes[j]) and testClassId == refClassIds[j] and \ + abs(testScore - refScores[j]) < scores_diff: + interArea = inter_area(testBox, refBoxes[j]) + iou = interArea / (area(testBox) + area(refBoxes[j]) - interArea) + if abs(iou - 1.0) < boxes_iou_diff: + matched = True + matchedRefBoxes[j] = True + if not matched: + errMsg += '\nUnmatched prediction: class %d score %f box %s' % (testClassId, testScore, box2str(testBox)) + + for i in range(len(refBoxes)): + if (not matchedRefBoxes[i]) and refScores[i] > confThreshold: + errMsg += '\nUnmatched reference: class %d score %f box %s' % (refClassIds[i], refScores[i], box2str(refBoxes[i])) + if errMsg: + test.fail(errMsg) + + +# Returns a simple one-layer network created from Caffe's format +def getSimpleNet(): + prototxt = """ + name: "simpleNet" + input: "data" + layer { + type: "Identity" + name: "testLayer" + top: "testLayer" + bottom: "data" + } + """ + return cv.dnn.readNetFromCaffe(bytearray(prototxt, 'utf8')) + + +def testBackendAndTarget(backend, target): + net = getSimpleNet() + net.setPreferableBackend(backend) + net.setPreferableTarget(target) + inp = np.random.standard_normal([1, 2, 3, 4]).astype(np.float32) + try: + net.setInput(inp) + net.forward() + except BaseException as e: + return False + return True + + +haveInfEngine = testBackendAndTarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_CPU) +dnnBackendsAndTargets = [ + [cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_CPU], +] + +if haveInfEngine: + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_CPU]) + if testBackendAndTarget(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_MYRIAD): + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_MYRIAD]) + +if cv.ocl.haveOpenCL() and cv.ocl.useOpenCL(): + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL]) + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL_FP16]) + if haveInfEngine: + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL]) + dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL_FP16]) + + +def printParams(backend, target): + backendNames = { + cv.dnn.DNN_BACKEND_OPENCV: 'OCV', + cv.dnn.DNN_BACKEND_INFERENCE_ENGINE: 'DLIE' + } + targetNames = { + cv.dnn.DNN_TARGET_CPU: 'CPU', + cv.dnn.DNN_TARGET_OPENCL: 'OCL', + cv.dnn.DNN_TARGET_OPENCL_FP16: 'OCL_FP16', + cv.dnn.DNN_TARGET_MYRIAD: 'MYRIAD' + } + print('%s/%s' % (backendNames[backend], targetNames[target])) + + +class dnn_test(NewOpenCVTests): + + def find_dnn_file(self, filename): + return self.find_file(filename, [os.environ['OPENCV_DNN_TEST_DATA_PATH']]) + + def test_blobFromImage(self): + np.random.seed(324) + + width = 6 + height = 7 + scale = 1.0/127.5 + mean = (10, 20, 30) + + # Test arguments names. + img = np.random.randint(0, 255, [4, 5, 3]).astype(np.uint8) + blob = cv.dnn.blobFromImage(img, scale, (width, height), mean, True, False) + blob_args = cv.dnn.blobFromImage(img, scalefactor=scale, size=(width, height), + mean=mean, swapRB=True, crop=False) + normAssert(self, blob, blob_args) + + # Test values. + target = cv.resize(img, (width, height), interpolation=cv.INTER_LINEAR) + target = target.astype(np.float32) + target = target[:,:,[2, 1, 0]] # BGR2RGB + target[:,:,0] -= mean[0] + target[:,:,1] -= mean[1] + target[:,:,2] -= mean[2] + target *= scale + target = target.transpose(2, 0, 1).reshape(1, 3, height, width) # to NCHW + normAssert(self, blob, target) + + + def test_face_detection(self): + proto = self.find_dnn_file('dnn/opencv_face_detector.prototxt') + model = self.find_dnn_file('dnn/opencv_face_detector.caffemodel') + + img = self.get_sample('gpu/lbpcascade/er.png') + blob = cv.dnn.blobFromImage(img, mean=(104, 177, 123), swapRB=False, crop=False) + + ref = [[0, 1, 0.99520785, 0.80997437, 0.16379407, 0.87996572, 0.26685631], + [0, 1, 0.9934696, 0.2831718, 0.50738752, 0.345781, 0.5985168], + [0, 1, 0.99096733, 0.13629119, 0.24892329, 0.19756334, 0.3310290], + [0, 1, 0.98977017, 0.23901358, 0.09084064, 0.29902688, 0.1769477], + [0, 1, 0.97203469, 0.67965847, 0.06876482, 0.73999709, 0.1513494], + [0, 1, 0.95097077, 0.51901293, 0.45863652, 0.5777427, 0.5347801]] + + print('\n') + for backend, target in dnnBackendsAndTargets: + printParams(backend, target) + + net = cv.dnn.readNet(proto, model) + net.setPreferableBackend(backend) + net.setPreferableTarget(target) + net.setInput(blob) + out = net.forward().reshape(-1, 7) + + scoresDiff = 4e-3 if target in [cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD] else 1e-5 + iouDiff = 2e-2 if target in [cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD] else 1e-4 + + normAssertDetections(self, ref, out, 0.5, scoresDiff, iouDiff) + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/python/test/tests_common.py b/modules/python/test/tests_common.py index e6539ae7f4..4e3b4ef1bf 100644 --- a/modules/python/test/tests_common.py +++ b/modules/python/test/tests_common.py @@ -26,23 +26,24 @@ class NewOpenCVTests(unittest.TestCase): # github repository url repoUrl = 'https://raw.github.com/opencv/opencv/master' + def find_file(self, filename, searchPaths=[]): + searchPaths = searchPaths if searchPaths else [self.repoPath, self.extraTestDataPath] + for path in searchPaths: + if path is not None: + candidate = path + '/' + filename + if os.path.isfile(candidate): + return candidate + self.fail('File ' + filename + ' not found') + return None + + def get_sample(self, filename, iscolor = None): if iscolor is None: iscolor = cv.IMREAD_COLOR if not filename in self.image_cache: - filedata = None - if NewOpenCVTests.repoPath is not None: - candidate = NewOpenCVTests.repoPath + '/' + filename - if os.path.isfile(candidate): - with open(candidate, 'rb') as f: - filedata = f.read() - if NewOpenCVTests.extraTestDataPath is not None: - candidate = NewOpenCVTests.extraTestDataPath + '/' + filename - if os.path.isfile(candidate): - with open(candidate, 'rb') as f: - filedata = f.read() - if filedata is None: - return None#filedata = urlopen(NewOpenCVTests.repoUrl + '/' + filename).read() + filepath = self.find_file(filename) + with open(filepath, 'rb') as f: + filedata = f.read() self.image_cache[filename] = cv.imdecode(np.fromstring(filedata, dtype=np.uint8), iscolor) return self.image_cache[filename] @@ -102,4 +103,4 @@ def isPointInRect(p, rect): if rect[0] <= p[0] and rect[1] <=p[1] and p[0] <= rect[2] and p[1] <= rect[3]: return True else: - return False \ No newline at end of file + return False From ecc9bd0925cd15190495ee1f607fc2e4df7ebd07 Mon Sep 17 00:00:00 2001 From: Hamdi Sahloul Date: Mon, 17 Sep 2018 23:31:54 +0900 Subject: [PATCH 09/12] Support GpuMat in copyTo() functions --- modules/core/src/copy.cpp | 8 ++++++++ modules/core/src/matrix_wrap.cpp | 8 ++++++++ modules/core/src/umatrix.cpp | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/modules/core/src/copy.cpp b/modules/core/src/copy.cpp index 321c54b5c3..38264cc58f 100644 --- a/modules/core/src/copy.cpp +++ b/modules/core/src/copy.cpp @@ -238,6 +238,14 @@ void Mat::copyTo( OutputArray _dst ) const { CV_INSTRUMENT_REGION(); +#ifdef HAVE_CUDA + if (_dst.isGpuMat()) + { + _dst.getGpuMat().upload(*this); + return; + } +#endif + int dtype = _dst.type(); if( _dst.fixedType() && dtype != type() ) { diff --git a/modules/core/src/matrix_wrap.cpp b/modules/core/src/matrix_wrap.cpp index b5b4514ada..e64d097aad 100644 --- a/modules/core/src/matrix_wrap.cpp +++ b/modules/core/src/matrix_wrap.cpp @@ -1146,6 +1146,10 @@ void _InputArray::copyTo(const _OutputArray& arr) const } else if( k == UMAT ) ((UMat*)obj)->copyTo(arr); +#ifdef HAVE_CUDA + else if (k == CUDA_GPU_MAT) + ((cuda::GpuMat*)obj)->copyTo(arr); +#endif else CV_Error(Error::StsNotImplemented, ""); } @@ -1163,6 +1167,10 @@ void _InputArray::copyTo(const _OutputArray& arr, const _InputArray & mask) cons } else if( k == UMAT ) ((UMat*)obj)->copyTo(arr, mask); +#ifdef HAVE_CUDA + else if (k == CUDA_GPU_MAT) + ((cuda::GpuMat*)obj)->copyTo(arr, mask); +#endif else CV_Error(Error::StsNotImplemented, ""); } diff --git a/modules/core/src/umatrix.cpp b/modules/core/src/umatrix.cpp index 248b679379..27d587b186 100644 --- a/modules/core/src/umatrix.cpp +++ b/modules/core/src/umatrix.cpp @@ -874,6 +874,14 @@ void UMat::copyTo(OutputArray _dst) const { CV_INSTRUMENT_REGION(); +#ifdef HAVE_CUDA + if (_dst.isGpuMat()) + { + _dst.getGpuMat().upload(*this); + return; + } +#endif + int dtype = _dst.type(); if( _dst.fixedType() && dtype != type() ) { From 7d7552637351f8da6d73d68798b04d57709511dd Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Mon, 17 Sep 2018 17:28:26 +0300 Subject: [PATCH 10/12] Use TorchType enum --- modules/dnn/src/torch/torch_importer.cpp | 56 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/modules/dnn/src/torch/torch_importer.cpp b/modules/dnn/src/torch/torch_importer.cpp index 47aa1cc822..cbcc83bf6c 100644 --- a/modules/dnn/src/torch/torch_importer.cpp +++ b/modules/dnn/src/torch/torch_importer.cpp @@ -74,6 +74,18 @@ enum LuaType LEGACY_TYPE_RECUR_FUNCTION = 7 }; +// We use OpenCV's types to manage CV_ELEM_SIZE. +enum TorchType +{ + TYPE_DOUBLE = CV_64F, + TYPE_FLOAT = CV_32F, + TYPE_BYTE = CV_8U, + TYPE_CHAR = CV_8S, + TYPE_SHORT = CV_16S, + TYPE_INT = CV_32S, + TYPE_LONG = CV_32SC2 +}; + template static String toString(const T &v) { @@ -203,19 +215,19 @@ struct TorchImporter String typeStr = str.substr(strlen(prefix), str.length() - strlen(prefix) - strlen(suffix)); if (typeStr == "Double") - return CV_64F; + return TYPE_DOUBLE; else if (typeStr == "Float" || typeStr == "Cuda") - return CV_32F; + return TYPE_FLOAT; else if (typeStr == "Byte") - return CV_8U; + return TYPE_BYTE; else if (typeStr == "Char") - return CV_8S; + return TYPE_CHAR; else if (typeStr == "Short") - return CV_16S; + return TYPE_SHORT; else if (typeStr == "Int") - return CV_32S; - else if (typeStr == "Long") //Carefully! CV_64S type coded as CV_32SC2 - return CV_32SC2; + return TYPE_INT; + else if (typeStr == "Long") + return TYPE_LONG; else CV_Error(Error::StsNotImplemented, "Unknown type \"" + typeStr + "\" of torch class \"" + str + "\""); } @@ -236,36 +248,44 @@ struct TorchImporter void readTorchStorage(int index, int type = -1) { long size = readLong(); - Mat storageMat(1, size, (type != CV_32SC2) ? type : CV_64F); //handle LongStorage as CV_64F Mat + Mat storageMat; switch (type) { - case CV_32F: + case TYPE_FLOAT: + storageMat.create(1, size, CV_32F); THFile_readFloatRaw(file, (float*)storageMat.data, size); break; - case CV_64F: + case TYPE_DOUBLE: + storageMat.create(1, size, CV_64F); THFile_readDoubleRaw(file, (double*)storageMat.data, size); break; - case CV_8S: - case CV_8U: + case TYPE_CHAR: + storageMat.create(1, size, CV_8S); THFile_readByteRaw(file, (uchar*)storageMat.data, size); break; - case CV_16S: - case CV_16U: + case TYPE_BYTE: + storageMat.create(1, size, CV_8U); + THFile_readByteRaw(file, (uchar*)storageMat.data, size); + break; + case TYPE_SHORT: + storageMat.create(1, size, CV_16S); THFile_readShortRaw(file, (short*)storageMat.data, size); break; - case CV_32S: + case TYPE_INT: + storageMat.create(1, size, CV_32S); THFile_readIntRaw(file, (int*)storageMat.data, size); break; - case CV_32SC2: + case TYPE_LONG: { + storageMat.create(1, size, CV_64F); //handle LongStorage as CV_64F Mat double *buf = storageMat.ptr(); THFile_readLongRaw(file, (int64*)buf, size); for (size_t i = (size_t)size; i-- > 0; ) buf[i] = ((int64*)buf)[i]; - } break; + } default: CV_Error(Error::StsInternal, ""); break; From 43f889ae1f3cef6509f5cdd59b9875e68de92721 Mon Sep 17 00:00:00 2001 From: Lubov Batanina Date: Mon, 17 Sep 2018 20:26:17 +0300 Subject: [PATCH 11/12] Merge pull request #12519 from l-bat:l-bat/onnx_parser Support asymmetric padding in pooling layer (#12519) * Add Inception_V1 support in ONNX * Add asymmetric padding in OpenCL and Inference engine * Refactoring --- .../dnn/include/opencv2/dnn/all_layers.hpp | 4 +- modules/dnn/src/layers/convolution_layer.cpp | 33 +++++++- modules/dnn/src/layers/layers_common.cpp | 36 +++++--- modules/dnn/src/layers/layers_common.hpp | 7 +- modules/dnn/src/layers/pooling_layer.cpp | 83 +++++++++++-------- modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp | 11 +-- modules/dnn/src/ocl4dnn/src/ocl4dnn_pool.cpp | 16 ++-- modules/dnn/src/onnx/onnx_importer.cpp | 7 +- modules/dnn/src/opencl/ocl4dnn_pooling.cl | 12 +-- modules/dnn/src/opencl/pooling.cl | 14 ++-- modules/dnn/test/test_onnx_importer.cpp | 4 + 11 files changed, 142 insertions(+), 85 deletions(-) diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index b5416142c9..cc2e2e35d0 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -234,7 +234,9 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN { public: int type; - Size kernel, stride, pad; + Size kernel, stride; + int pad_l, pad_t, pad_r, pad_b; + CV_DEPRECATED Size pad; bool globalPooling; bool computeMaxIdx; String padMode; diff --git a/modules/dnn/src/layers/convolution_layer.cpp b/modules/dnn/src/layers/convolution_layer.cpp index 40719f3764..a948c6ef9d 100644 --- a/modules/dnn/src/layers/convolution_layer.cpp +++ b/modules/dnn/src/layers/convolution_layer.cpp @@ -64,10 +64,17 @@ public: BaseConvolutionLayerImpl(const LayerParams ¶ms) { setParamsFrom(params); - getConvolutionKernelParams(params, kernel.height, kernel.width, pad.height, - pad.width, stride.height, stride.width, dilation.height, + int pad_t = 0, pad_l = 0, pad_r = 0, pad_b = 0; + getConvolutionKernelParams(params, kernel.height, kernel.width, pad_t, + pad_l, pad_b, pad_r, stride.height, stride.width, dilation.height, dilation.width, padMode); + if (pad_t != pad_b || pad_l != pad_r) + CV_Error(Error::StsNotImplemented, "Unsupported asymmetric padding in convolution layer"); + + pad.width = pad_l; + pad.height = pad_t; + numOutput = params.get("num_output"); int ngroups = params.get("group", 1); @@ -100,8 +107,18 @@ public: } Size outSize = Size(outputs[0].size[3], outputs[0].size[2]); + + int pad_t = pad.height, pad_l = pad.width, pad_b = pad.height, pad_r = pad.width; + getConvPoolPaddings(Size(input.size[3], input.size[2]), outSize, - kernel, stride, padMode, dilation, pad); + kernel, stride, padMode, dilation, pad_t, pad_l, pad_b, pad_r); + + + if (pad_t != pad_b || pad_l != pad_r) + CV_Error(Error::StsNotImplemented, "Unsupported asymmetric padding in convolution layer"); + + pad.width = pad_l; + pad.height = pad_t; } bool hasBias() const @@ -1156,9 +1173,17 @@ public: std::vector inputs, outputs; inputs_arr.getMatVector(inputs); outputs_arr.getMatVector(outputs); + + int pad_t = pad.height, pad_l = pad.width, pad_b = pad.height, pad_r = pad.width; getConvPoolPaddings(Size(outputs[0].size[3], outputs[0].size[2]), Size(inputs[0].size[3], inputs[0].size[2]), - kernel, stride, padMode, dilation, pad); + kernel, stride, padMode, dilation, pad_t, pad_l, pad_b, pad_r); + + if (pad_t != pad_b || pad_l != pad_r) + CV_Error(Error::StsNotImplemented, "Unsupported asymmetric padding in convolution layer"); + + pad.width = pad_l; + pad.height = pad_t; } class MatMulInvoker : public ParallelLoopBody diff --git a/modules/dnn/src/layers/layers_common.cpp b/modules/dnn/src/layers/layers_common.cpp index bf5834c864..2dbb12109d 100644 --- a/modules/dnn/src/layers/layers_common.cpp +++ b/modules/dnn/src/layers/layers_common.cpp @@ -118,9 +118,19 @@ void getKernelSize(const LayerParams ¶ms, int &kernelH, int &kernelW) CV_Assert(kernelH > 0 && kernelW > 0); } -void getStrideAndPadding(const LayerParams ¶ms, int &padH, int &padW, int &strideH, int &strideW, cv::String& padMode) +void getStrideAndPadding(const LayerParams ¶ms, int &padT, int &padL, int &padB, int &padR, int &strideH, int &strideW, cv::String& padMode) { - util::getParameter(params, "pad", "pad", padH, padW, true, 0); + if (params.has("pad_l") && params.has("pad_t") && params.has("pad_r") && params.has("pad_b")) { + padT = params.get("pad_t"); + padL = params.get("pad_l"); + padB = params.get("pad_b"); + padR = params.get("pad_r"); + } + else { + util::getParameter(params, "pad", "pad", padT, padL, true, 0); + padB = padT; + padR = padL; + } util::getParameter(params, "stride", "stride", strideH, strideW, true, 1); padMode = ""; @@ -129,15 +139,15 @@ void getStrideAndPadding(const LayerParams ¶ms, int &padH, int &padW, int &s padMode = params.get("pad_mode"); } - CV_Assert(padH >= 0 && padW >= 0 && strideH > 0 && strideW > 0); + CV_Assert(padT >= 0 && padL >= 0 && padB >= 0 && padR >= 0 && strideH > 0 && strideW > 0); } } void getPoolingKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, bool &globalPooling, - int &padH, int &padW, int &strideH, int &strideW, cv::String &padMode) + int &padT, int &padL, int &padB, int &padR, int &strideH, int &strideW, cv::String &padMode) { - util::getStrideAndPadding(params, padH, padW, strideH, strideW, padMode); + util::getStrideAndPadding(params, padT, padL, padB, padR, strideH, strideW, padMode); globalPooling = params.has("global_pooling") && params.get("global_pooling"); @@ -148,9 +158,9 @@ void getPoolingKernelParams(const LayerParams ¶ms, int &kernelH, int &kernel { CV_Error(cv::Error::StsBadArg, "In global_pooling mode, kernel_size (or kernel_h and kernel_w) cannot be specified"); } - if(padH != 0 || padW != 0 || strideH != 1 || strideW != 1) + if(padT != 0 || padL != 0 || padB != 0 || padR != 0 || strideH != 1 || strideW != 1) { - CV_Error(cv::Error::StsBadArg, "In global_pooling mode, pad_h and pad_w must be = 0, and stride_h and stride_w must be = 1"); + CV_Error(cv::Error::StsBadArg, "In global_pooling mode, pads must be = 0, and stride_h and stride_w must be = 1"); } } else @@ -159,12 +169,11 @@ void getPoolingKernelParams(const LayerParams ¶ms, int &kernelH, int &kernel } } -void getConvolutionKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, int &padH, int &padW, +void getConvolutionKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, int &padT, int &padL, int &padB, int &padR, int &strideH, int &strideW, int &dilationH, int &dilationW, cv::String &padMode) { util::getKernelSize(params, kernelH, kernelW); - util::getStrideAndPadding(params, padH, padW, strideH, strideW, padMode); - + util::getStrideAndPadding(params, padT, padL, padB, padR, strideH, strideW, padMode); util::getParameter(params, "dilation", "dilation", dilationH, dilationW, true, 1); CV_Assert(dilationH > 0 && dilationW > 0); @@ -201,11 +210,11 @@ void getConvPoolOutParams(const Size& inp, const Size &kernel, void getConvPoolPaddings(const Size& inp, const Size& out, const Size &kernel, const Size &stride, - const String &padMode, const Size &dilation, Size &pad) + const String &padMode, const Size &dilation, int &padT, int &padL, int &padB, int &padR) { if (padMode == "VALID") { - pad = cv::Size(0,0); + padT = padL = padB = padR = 0; } else if (padMode == "SAME") { @@ -213,7 +222,8 @@ void getConvPoolPaddings(const Size& inp, const Size& out, int Pw = std::max(0, (out.width - 1) * stride.width + (dilation.width * (kernel.width - 1) + 1) - inp.width); // For odd values of total padding, add more padding at the 'right' // side of the given dimension. - pad = cv::Size(Pw / 2, Ph / 2); + padT= padB = Ph / 2; + padL = padR = Pw / 2; } } diff --git a/modules/dnn/src/layers/layers_common.hpp b/modules/dnn/src/layers/layers_common.hpp index 4bb4c317e4..7fce183d6e 100644 --- a/modules/dnn/src/layers/layers_common.hpp +++ b/modules/dnn/src/layers/layers_common.hpp @@ -60,19 +60,20 @@ namespace cv namespace dnn { -void getConvolutionKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, int &padH, int &padW, +void getConvolutionKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, int &padT, int &padL, int &padB, int &padR, int &strideH, int &strideW, int &dilationH, int &dilationW, cv::String& padMode); void getPoolingKernelParams(const LayerParams ¶ms, int &kernelH, int &kernelW, bool &globalPooling, - int &padH, int &padW, int &strideH, int &strideW, cv::String& padMode); + int &padT, int &padL, int &padB, int &padR, int &strideH, int &strideW, cv::String& padMode); void getConvPoolOutParams(const Size& inp, const Size &kernel, const Size &stride, const String &padMode, const Size &dilation, Size& out); + void getConvPoolPaddings(const Size& inp, const Size& out, const Size &kernel, const Size &stride, - const String &padMode, const Size &dilation, Size &pad); + const String &padMode, const Size &dilation, int &padT, int &padL, int &padB, int &padR); } } diff --git a/modules/dnn/src/layers/pooling_layer.cpp b/modules/dnn/src/layers/pooling_layer.cpp index 37933c777d..028f4f8b6e 100644 --- a/modules/dnn/src/layers/pooling_layer.cpp +++ b/modules/dnn/src/layers/pooling_layer.cpp @@ -85,8 +85,12 @@ public: type = STOCHASTIC; else CV_Error(Error::StsBadArg, "Unknown pooling type \"" + pool + "\""); + getPoolingKernelParams(params, kernel.height, kernel.width, globalPooling, - pad.height, pad.width, stride.height, stride.width, padMode); + pad_t, pad_l, pad_b, pad_r, stride.height, stride.width, padMode); + + pad.width = pad_l; + pad.height = pad_t; } else if (params.has("pooled_w") || params.has("pooled_h")) { @@ -130,7 +134,9 @@ public: kernel = inp; } - getConvPoolPaddings(inp, out, kernel, stride, padMode, Size(1, 1), pad); + getConvPoolPaddings(inp, out, kernel, stride, padMode, Size(1, 1), pad_t, pad_l, pad_b, pad_r); + pad.width = pad_l; + pad.height = pad_t; #ifdef HAVE_OPENCL poolOp.release(); @@ -149,7 +155,7 @@ public: else return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE && haveHalide() && - (type == MAX || type == AVE && !pad.width && !pad.height); + (type == MAX || type == AVE && !pad_t && !pad_l && !pad_b && !pad_r); } #ifdef HAVE_OPENCL @@ -169,7 +175,10 @@ public: config.in_shape = shape(inputs[0]); config.out_shape = shape(outputs[0]); config.kernel = kernel; - config.pad = pad; + config.pad_l = pad_l; + config.pad_t = pad_t; + config.pad_r = pad_r; + config.pad_b = pad_b; config.stride = stride; config.channels = inputs[0].size[1]; config.pool_method = type == MAX ? LIBDNN_POOLING_METHOD_MAX : @@ -193,7 +202,6 @@ public: if (!poolOp->Forward(inpMat, outMat, maskMat)) return false; } - return true; } #endif @@ -264,8 +272,10 @@ public: poolLayer->_kernel_y = kernel.height; poolLayer->_stride_x = stride.width; poolLayer->_stride_y = stride.height; - poolLayer->_padding_x = pad.width; - poolLayer->_padding_y = pad.height; + poolLayer->_padding_x = pad_l; + poolLayer->_padding_y = pad_t; + poolLayer->params["pad-r"] = format("%d", pad_r); + poolLayer->params["pad-b"] = format("%d", pad_b); poolLayer->_exclude_pad = type == AVE && padMode == "SAME"; poolLayer->params["rounding-type"] = ceilMode ? "ceil" : "floor"; poolLayer->_type = type == MAX ? InferenceEngine::PoolingLayer::PoolType::MAX : @@ -296,12 +306,14 @@ public: return Ptr(); } + class PoolingInvoker : public ParallelLoopBody { public: const Mat* src, *rois; Mat *dst, *mask; - Size kernel, stride, pad; + Size kernel, stride; + int pad_l, pad_t, pad_r, pad_b; bool avePoolPaddedArea; int nstripes; bool computeMaxIdx; @@ -313,7 +325,7 @@ public: computeMaxIdx(0), poolingType(MAX), spatialScale(0) {} static void run(const Mat& src, const Mat& rois, Mat& dst, Mat& mask, Size kernel, - Size stride, Size pad, bool avePoolPaddedArea, int poolingType, float spatialScale, + Size stride, int pad_l, int pad_t, int pad_r, int pad_b, bool avePoolPaddedArea, int poolingType, float spatialScale, bool computeMaxIdx, int nstripes) { CV_Assert_N( @@ -332,7 +344,10 @@ public: p.mask = &mask; p.kernel = kernel; p.stride = stride; - p.pad = pad; + p.pad_l = pad_l; + p.pad_t = pad_t; + p.pad_r = pad_r; + p.pad_b = pad_b; p.avePoolPaddedArea = avePoolPaddedArea; p.nstripes = nstripes; p.computeMaxIdx = computeMaxIdx; @@ -359,7 +374,6 @@ public: size_t stripeStart = r.start*stripeSize; size_t stripeEnd = std::min(r.end*stripeSize, total); int kernel_w = kernel.width, kernel_h = kernel.height; - int pad_w = pad.width, pad_h = pad.height; int stride_w = stride.width, stride_h = stride.height; bool compMaxIdx = computeMaxIdx; @@ -411,8 +425,8 @@ public: } else { - ystart = y0 * stride_h - pad_h; - yend = min(ystart + kernel_h, inp_height + pad_h); + ystart = y0 * stride_h - pad_t; + yend = min(ystart + kernel_h, inp_height + pad_b); srcData = src->ptr(n, c); } int ydelta = yend - ystart; @@ -428,7 +442,7 @@ public: if( poolingType == MAX) for( ; x0 < x1; x0++ ) { - int xstart = x0 * stride_w - pad_w; + int xstart = x0 * stride_w - pad_l; int xend = min(xstart + kernel_w, inp_width); xstart = max(xstart, 0); if (xstart >= xend || ystart >= yend) @@ -439,7 +453,7 @@ public: continue; } #if CV_SIMD128 - if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_w + kernel_w < inp_width ) + if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) { if( compMaxIdx ) { @@ -578,15 +592,15 @@ public: { for( ; x0 < x1; x0++ ) { - int xstart = x0 * stride_w - pad_w; - int xend = min(xstart + kernel_w, inp_width + pad_w); + int xstart = x0 * stride_w - pad_l; + int xend = min(xstart + kernel_w, inp_width + pad_r); int xdelta = xend - xstart; xstart = max(xstart, 0); xend = min(xend, inp_width); float inv_kernel_area = avePoolPaddedArea ? xdelta * ydelta : ((yend - ystart) * (xend - xstart)); inv_kernel_area = 1.0 / inv_kernel_area; #if CV_SIMD128 - if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_w + kernel_w < inp_width ) + if( xstart > 0 && x0 + 7 < x1 && (x0 + 7) * stride_w - pad_l + kernel_w < inp_width ) { v_float32x4 sum_val0 = v_setzero_f32(), sum_val1 = v_setzero_f32(); v_float32x4 ikarea = v_setall_f32(inv_kernel_area); @@ -695,21 +709,21 @@ public: { const int nstripes = getNumThreads(); Mat rois; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } void avePooling(Mat &src, Mat &dst) { const int nstripes = getNumThreads(); Mat rois, mask; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } void roiPooling(const Mat &src, const Mat &rois, Mat &dst) { const int nstripes = getNumThreads(); Mat mask; - PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); + PoolingInvoker::run(src, rois, dst, mask, kernel, stride, pad_l, pad_t, pad_r, pad_b, avePoolPaddedArea, type, spatialScale, computeMaxIdx, nstripes); } virtual Ptr initMaxPoolingHalide(const std::vector > &inputs) @@ -723,10 +737,10 @@ public: Halide::Func top = (name.empty() ? Halide::Func() : Halide::Func(name)); Halide::RDom r(0, kernel.width, 0, kernel.height); Halide::Expr kx, ky; - if (pad.width || pad.height) + if(pad_l || pad_t) { - kx = clamp(x * stride.width + r.x - pad.width, 0, inWidth - 1); - ky = clamp(y * stride.height + r.y - pad.height, 0, inHeight - 1); + kx = clamp(x * stride.width + r.x - pad_l, 0, inWidth - 1); + ky = clamp(y * stride.height + r.y - pad_t, 0, inHeight - 1); } else { @@ -739,11 +753,11 @@ public: // Compute offset from argmax in range [0, kernel_size). Halide::Expr max_index; - if (pad.width || pad.height) + if(pad_l || pad_t) { - max_index = clamp(y * stride.height + res[1] - pad.height, + max_index = clamp(y * stride.height + res[1] - pad_t, 0, inHeight - 1) * inWidth + - clamp(x * stride.width + res[0] - pad.width, + clamp(x * stride.width + res[0] - pad_l, 0, inWidth - 1); } else @@ -852,21 +866,21 @@ public: } else if (padMode.empty()) { - float height = (float)(in.height + 2 * pad.height - kernel.height) / stride.height; - float width = (float)(in.width + 2 * pad.width - kernel.width) / stride.width; + float height = (float)(in.height + pad_t + pad_b - kernel.height) / stride.height; + float width = (float)(in.width + pad_l + pad_r - kernel.width) / stride.width; out.height = 1 + (ceilMode ? ceil(height) : floor(height)); out.width = 1 + (ceilMode ? ceil(width) : floor(width)); - if (pad.height || pad.width) + if (pad_r || pad_b) { // If we have padding, ensure that the last pooling starts strictly // inside the image (instead of at the padding); otherwise clip the last. - if ((out.height - 1) * stride.height >= in.height + pad.height) + if ((out.height - 1) * stride.height >= in.height + pad_b) --out.height; - if ((out.width - 1) * stride.width >= in.width + pad.width) + if ((out.width - 1) * stride.width >= in.width + pad_r) --out.width; - CV_Assert((out.height - 1) * stride.height < in.height + pad.height); - CV_Assert((out.width - 1) * stride.width < in.width + pad.width); + CV_Assert((out.height - 1) * stride.height < in.height + pad_b); + CV_Assert((out.width - 1) * stride.width < in.width + pad_r); } } else @@ -888,6 +902,7 @@ public: dims[1] = psRoiOutChannels; } outputs.assign(type == MAX ? 2 : 1, shape(dims, 4)); + return false; } diff --git a/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp b/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp index e0ca5ca98c..eda2e837c0 100644 --- a/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp +++ b/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp @@ -345,7 +345,7 @@ struct OCL4DNNPoolConfig { OCL4DNNPoolConfig() : kernel(1, 1), - pad(0, 0), + pad_l(0), pad_t(0), pad_r(0), pad_b(0), stride(1, 1), dilation(1, 1), channels(0), @@ -358,7 +358,7 @@ struct OCL4DNNPoolConfig MatShape in_shape; MatShape out_shape; Size kernel; - Size pad; + int pad_l, pad_t, pad_r, pad_b; Size stride; Size dilation; @@ -381,7 +381,6 @@ class OCL4DNNPool UMat& top_mask); private: // Pooling parameters - std::vector pad_; std::vector stride_; std::vector kernel_shape_; std::vector im_in_shape_; @@ -394,8 +393,10 @@ class OCL4DNNPool int32_t kernel_w_; int32_t stride_h_; int32_t stride_w_; - int32_t pad_h_; - int32_t pad_w_; + int32_t pad_t_; + int32_t pad_l_; + int32_t pad_b_; + int32_t pad_r_; int32_t height_; int32_t width_; int32_t pooled_height_; diff --git a/modules/dnn/src/ocl4dnn/src/ocl4dnn_pool.cpp b/modules/dnn/src/ocl4dnn/src/ocl4dnn_pool.cpp index 77cd3a6337..47b40cc6c2 100644 --- a/modules/dnn/src/ocl4dnn/src/ocl4dnn_pool.cpp +++ b/modules/dnn/src/ocl4dnn/src/ocl4dnn_pool.cpp @@ -62,7 +62,6 @@ OCL4DNNPool::OCL4DNNPool(OCL4DNNPoolConfig config) for (int i = 0; i < spatial_dims; ++i) { kernel_shape_.push_back(i == 0 ? config.kernel.height : config.kernel.width); - pad_.push_back(i == 0 ? config.pad.height : config.pad.width); stride_.push_back(i == 0 ? config.stride.height : config.stride.width); im_in_shape_.push_back(config.in_shape[dims - spatial_dims + i]); im_out_shape_.push_back(config.out_shape[dims - spatial_dims + i]); @@ -72,8 +71,10 @@ OCL4DNNPool::OCL4DNNPool(OCL4DNNPoolConfig config) kernel_w_ = kernel_shape_[1]; stride_h_ = stride_[0]; stride_w_ = stride_[1]; - pad_h_ = pad_[0]; - pad_w_ = pad_[1]; + pad_t_ = config.pad_t; + pad_l_ = config.pad_l; + pad_r_ = config.pad_r; + pad_b_ = config.pad_b; height_ = im_in_shape_[0]; width_ = im_in_shape_[1]; pooled_height_ = im_out_shape_[0]; @@ -113,14 +114,13 @@ bool OCL4DNNPool::Forward(const UMat& bottom, ocl::dnn::ocl4dnn_pooling_oclsrc, format(" -D Dtype=%s -D KERNEL_MAX_POOL=1 -D KERNEL_W=%d -D KERNEL_H=%d" " -D STRIDE_W=%d -D STRIDE_H=%d" - " -D PAD_W=%d -D PAD_H=%d%s", + " -D PAD_L=%d -D PAD_T=%d -D PAD_R=%d -D PAD_B=%d%s", (use_half) ? "half" : "float", kernel_w_, kernel_h_, stride_w_, stride_h_, - pad_w_, pad_h_, + pad_l_, pad_t_, pad_r_, pad_b_, computeMaxIdx ? " -D HAVE_MASK=1" : "" )); - if (oclk_max_pool_forward.empty()) return false; @@ -150,11 +150,11 @@ bool OCL4DNNPool::Forward(const UMat& bottom, ocl::dnn::ocl4dnn_pooling_oclsrc, format(" -D Dtype=%s -D KERNEL_AVE_POOL=1 -D KERNEL_W=%d -D KERNEL_H=%d" " -D STRIDE_W=%d -D STRIDE_H=%d" - " -D PAD_W=%d -D PAD_H=%d%s", + " -D PAD_L=%d -D PAD_T=%d -D PAD_R=%d -D PAD_B=%d%s", (use_half) ? "half" : "float", kernel_w_, kernel_h_, stride_w_, stride_h_, - pad_w_, pad_h_, + pad_l_, pad_t_, pad_r_, pad_b_, avePoolPaddedArea ? " -D AVE_POOL_PADDING_AREA" : "" )); diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index bd10e1dedf..04b56f8df2 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -174,9 +174,8 @@ LayerParams ONNXImporter::getLayerParams(const opencv_onnx::NodeProto& node_prot else if(attribute_name == "pads") { CV_Assert(attribute_proto.ints_size() == 4); - lp.set("pad_h", saturate_cast(attribute_proto.ints(0))); - lp.set("pad_w", saturate_cast(attribute_proto.ints(1))); - // push pad_b and pad_r for compute ceil_mode + lp.set("pad_t", saturate_cast(attribute_proto.ints(0))); + lp.set("pad_l", saturate_cast(attribute_proto.ints(1))); lp.set("pad_b", saturate_cast(attribute_proto.ints(2))); lp.set("pad_r", saturate_cast(attribute_proto.ints(3))); } @@ -306,6 +305,7 @@ void ONNXImporter::populateNet(Net dstNet) std::string layer_type = node_proto.op_type(); layerParams.type = layer_type; + if (layer_type == "MaxPool") { layerParams.type = "Pooling"; @@ -551,7 +551,6 @@ void ONNXImporter::populateNet(Net dstNet) for (int j = 0; j < node_proto.input_size(); j++) { layerId = layer_id.find(node_proto.input(j)); - if (layerId != layer_id.end()) { dstNet.connect(layerId->second.layerId, layerId->second.outputId, id, j); } diff --git a/modules/dnn/src/opencl/ocl4dnn_pooling.cl b/modules/dnn/src/opencl/ocl4dnn_pooling.cl index 77d2e5ba33..53c61e4bd2 100644 --- a/modules/dnn/src/opencl/ocl4dnn_pooling.cl +++ b/modules/dnn/src/opencl/ocl4dnn_pooling.cl @@ -73,8 +73,8 @@ __kernel void const int xx = index / pooled_width; const int ph = xx % pooled_height; const int ch = xx / pooled_height; - int hstart = ph * STRIDE_H - PAD_H; - int wstart = pw * STRIDE_W - PAD_W; + int hstart = ph * STRIDE_H - PAD_T; + int wstart = pw * STRIDE_W - PAD_L; Dtype maxval = -FLT_MAX; int maxidx = -1; int in_offset = ch * height * width; @@ -117,10 +117,10 @@ __kernel void TEMPLATE(ave_pool_forward, Dtype)( const int xx = index / pooled_width; const int ph = xx % pooled_height; const int ch = xx / pooled_height; - int hstart = ph * STRIDE_H - PAD_H; - int wstart = pw * STRIDE_W - PAD_W; - int hend = min(hstart + KERNEL_H, height + PAD_H); - int wend = min(wstart + KERNEL_W, width + PAD_W); + int hstart = ph * STRIDE_H - PAD_T; + int wstart = pw * STRIDE_W - PAD_L; + int hend = min(hstart + KERNEL_H, height + PAD_B); + int wend = min(wstart + KERNEL_W, width + PAD_R); int pool_size; #ifdef AVE_POOL_PADDING_AREA pool_size = (hend - hstart) * (wend - wstart); diff --git a/modules/dnn/src/opencl/pooling.cl b/modules/dnn/src/opencl/pooling.cl index adfd59e6d9..2a92cb2f01 100644 --- a/modules/dnn/src/opencl/pooling.cl +++ b/modules/dnn/src/opencl/pooling.cl @@ -27,7 +27,7 @@ __kernel void MaxPoolForward(const int nthreads, __global T* bottom_data, const int num, const int channels, const int height, const int width, const int pooled_height, const int pooled_width, const int kernel_h, const int kernel_w, - const int stride_h, const int stride_w, const int pad_h, const int pad_w, + const int stride_h, const int stride_w, const int pad_t, const int pad_l, const int pad_b, const int pad_r, __global T* top_data #ifdef MASK , __global float* mask @@ -41,8 +41,8 @@ __kernel void MaxPoolForward(const int nthreads, int ph = (index / pooled_width) % pooled_height; int c = (index / pooled_width / pooled_height) % channels; int n = index / pooled_width / pooled_height / channels; - int hstart = ph * stride_h - pad_h; - int wstart = pw * stride_w - pad_w; + int hstart = ph * stride_h - pad_t; + int wstart = pw * stride_w - pad_l; const int hend = min(hstart + kernel_h, height); const int wend = min(wstart + kernel_w, width); hstart = max(hstart, 0); @@ -71,7 +71,7 @@ __kernel void MaxPoolForward(const int nthreads, __kernel void AvePoolForward(const int nthreads, __global T* bottom_data, const int num, const int channels, const int height, const int width, const int pooled_height, const int pooled_width, const int kernel_h, const int kernel_w, - const int stride_h, const int stride_w, const int pad_h, const int pad_w, + const int stride_h, const int stride_w, const int pad_t, const int pad_l, const int pad_b, const int pad_r, __global T* top_data #ifdef MASK , __global float* mask // NOT USED @@ -84,9 +84,9 @@ __kernel void AvePoolForward(const int nthreads, int pw = index % pooled_width; int ph = (index / pooled_width) % pooled_height; int c = (index / pooled_width / pooled_height) % channels; - int n = index / pooled_width / pooled_height / channels; int hstart = ph * stride_h - pad_h; int wstart = pw * stride_w - pad_w; - int hend = min(hstart + kernel_h, height + pad_h); - int wend = min(wstart + kernel_w, width + pad_w); + int n = index / pooled_width / pooled_height / channels; int hstart = ph * stride_h - pad_t; int wstart = pw * stride_w - pad_l; + int hend = min(hstart + kernel_h, height + pad_b); + int wend = min(wstart + kernel_w, width + pad_r); const int pool_size = (hend - hstart) * (wend - wstart); hstart = max(hstart, 0); wstart = max(wstart, 0); diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 8d53b63eab..85405803d6 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -346,6 +346,10 @@ TEST_P(Test_ONNX_nets, DenseNet121) testONNXModels("densenet121", pb, l1, lInf); } +TEST_P(Test_ONNX_nets, Inception_v1) +{ + testONNXModels("inception_v1", pb); +} INSTANTIATE_TEST_CASE_P(/**/, Test_ONNX_nets, dnnBackendsAndTargets()); From 3cab9e7a9c79736614dbbed441c029b22539604b Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Mon, 17 Sep 2018 22:13:01 +0000 Subject: [PATCH 12/12] 3.4: fixes --- modules/python/test/test_dnn.py | 15 +++++++++------ modules/python/test/tests_common.py | 5 +++-- modules/videoio/src/cap_ffmpeg_impl.hpp | 4 ++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/python/test/test_dnn.py b/modules/python/test/test_dnn.py index b726c29849..a1b55f4358 100644 --- a/modules/python/test/test_dnn.py +++ b/modules/python/test/test_dnn.py @@ -3,7 +3,7 @@ import os import cv2 as cv import numpy as np -from tests_common import NewOpenCVTests +from tests_common import NewOpenCVTests, unittest def normAssert(test, a, b, lInf=1e-5): test.assertLess(np.max(np.abs(a - b)), lInf) @@ -95,7 +95,7 @@ if haveInfEngine: if cv.ocl.haveOpenCL() and cv.ocl.useOpenCL(): dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL]) dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_TARGET_OPENCL_FP16]) - if haveInfEngine: + if haveInfEngine: # FIXIT Check Intel iGPU only dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL]) dnnBackendsAndTargets.append([cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_TARGET_OPENCL_FP16]) @@ -116,8 +116,8 @@ def printParams(backend, target): class dnn_test(NewOpenCVTests): - def find_dnn_file(self, filename): - return self.find_file(filename, [os.environ['OPENCV_DNN_TEST_DATA_PATH']]) + def find_dnn_file(self, filename, required=True): + return self.find_file(filename, [os.environ.get('OPENCV_DNN_TEST_DATA_PATH', os.getcwd())], required=required) def test_blobFromImage(self): np.random.seed(324) @@ -147,8 +147,11 @@ class dnn_test(NewOpenCVTests): def test_face_detection(self): - proto = self.find_dnn_file('dnn/opencv_face_detector.prototxt') - model = self.find_dnn_file('dnn/opencv_face_detector.caffemodel') + testdata_required = bool(os.environ.get('OPENCV_DNN_TEST_REQUIRE_TESTDATA', False)) + proto = self.find_dnn_file('dnn/opencv_face_detector.prototxt2', required=testdata_required) + model = self.find_dnn_file('dnn/opencv_face_detector.caffemodel', required=testdata_required) + if proto is None or model is None: + raise unittest.SkipTest("Missing DNN test files (dnn/opencv_face_detector.{prototxt/caffemodel}). Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.") img = self.get_sample('gpu/lbpcascade/er.png') blob = cv.dnn.blobFromImage(img, mean=(104, 177, 123), swapRB=False, crop=False) diff --git a/modules/python/test/tests_common.py b/modules/python/test/tests_common.py index 4e3b4ef1bf..a938a8e2cb 100644 --- a/modules/python/test/tests_common.py +++ b/modules/python/test/tests_common.py @@ -26,14 +26,15 @@ class NewOpenCVTests(unittest.TestCase): # github repository url repoUrl = 'https://raw.github.com/opencv/opencv/master' - def find_file(self, filename, searchPaths=[]): + def find_file(self, filename, searchPaths=[], required=True): searchPaths = searchPaths if searchPaths else [self.repoPath, self.extraTestDataPath] for path in searchPaths: if path is not None: candidate = path + '/' + filename if os.path.isfile(candidate): return candidate - self.fail('File ' + filename + ' not found') + if required: + self.fail('File ' + filename + ' not found') return None diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index e0198be5f8..ce337ea10f 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -58,6 +58,10 @@ # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif +#ifndef CV_UNUSED // Required for standalone compilation mode (OpenCV defines this in base.hpp) +#define CV_UNUSED(name) (void)name +#endif + #ifdef __cplusplus extern "C" { #endif