Commit 5146e0d2 authored by Vladislav Samsonov's avatar Vladislav Samsonov
parents 17831add 159534a2
<!--
If you have a question rather than reporting a bug please go to http://answers.opencv.org where you get much faster responses.
If you need further assistance please read [How To Contribute](https://github.com/opencv/opencv/wiki/How_to_contribute).
This is a template helping you to create an issue which can be processed as quickly as possible. This is the bug reporting section for the OpenCV library.
-->
##### System information (version)
<!-- Example
- OpenCV => 3.1
- Operating System / Platform => Windows 64 Bit
- Compiler => Visual Studio 2015
-->
- OpenCV => :grey_question:
- Operating System / Platform => :grey_question:
- Compiler => :grey_question:
##### Detailed description
<!-- your description -->
##### Steps to reproduce
<!-- to add code example fence it with triple backticks and optional file extension
```.cpp
// C++ code example
```
or attach as .txt or .zip file
-->
\ No newline at end of file
<!-- Please use this line to close one or multiple issues when this pullrequest gets merged
You can add another line right under the first one:
resolves #1234
resolves #1235
-->
### This pullrequest changes
<!-- Please describe what your pullrequest is changing -->
......@@ -4,7 +4,7 @@ compiler:
- clang
before_script:
- cd ../
- git clone https://github.com/Itseez/opencv.git
- git clone https://github.com/opencv/opencv.git
- mkdir build-opencv
- cd build-opencv
- cmake -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules ../opencv
......
## Contributing guidelines
All guidelines for contributing to the OpenCV repository can be found at [`How to contribute guideline`](https://github.com/Itseez/opencv/wiki/How_to_contribute).
All guidelines for contributing to the OpenCV repository can be found at [`How to contribute guideline`](https://github.com/opencv/opencv/wiki/How_to_contribute).
cmake_minimum_required(VERSION 2.8)
if(APPLE_FRAMEWORK OR WINRT)
if(APPLE_FRAMEWORK OR WINRT
OR AARCH64 # protobuf doesn't know this platform
)
ocv_module_disable(dnn)
endif()
......
......@@ -3,7 +3,7 @@ Build opencv_contrib with dnn module {#tutorial_dnn_build}
Introduction
------------
opencv_dnn module is placed in the secondary [opencv_contrib](https://github.com/Itseez/opencv_contrib) repository,
opencv_dnn module is placed in the secondary [opencv_contrib](https://github.com/opencv/opencv_contrib) repository,
which isn't distributed in binary form, therefore you need to build it manually.
To do this you need to have installed: [CMake](http://www.cmake.org/download), git, and build system (*gcc* with *make* for Linux or *MS Visual Studio* for Windows)
......@@ -12,12 +12,12 @@ Steps
-----
-# Make any directory, for example **opencv_root**
-# Clone [opencv](https://github.com/Itseez/opencv) and [opencv_contrib](https://github.com/Itseez/opencv_contrib) repos to the **opencv_root**.
-# Clone [opencv](https://github.com/opencv/opencv) and [opencv_contrib](https://github.com/opencv/opencv_contrib) repos to the **opencv_root**.
You can do it in terminal like here:
@code
cd opencv_root
git clone https://github.com/Itseez/opencv
git clone https://github.com/Itseez/opencv_contrib
git clone https://github.com/opencv/opencv
git clone https://github.com/opencv/opencv_contrib
@endcode
-# Run [CMake-gui] and set source and build directories:
......
......@@ -305,6 +305,25 @@ public:
/** @copybrief getGradientDescentIterations @see getGradientDescentIterations */
CV_WRAP virtual void setVariationalRefinementIterations(int val) = 0;
/** @brief Weight of the smoothness term
@see setVariationalRefinementAlpha */
CV_WRAP virtual float getVariationalRefinementAlpha() const = 0;
/** @copybrief getVariationalRefinementAlpha @see getVariationalRefinementAlpha */
CV_WRAP virtual void setVariationalRefinementAlpha(float val) = 0;
/** @brief Weight of the color constancy term
@see setVariationalRefinementDelta */
CV_WRAP virtual float getVariationalRefinementDelta() const = 0;
/** @copybrief getVariationalRefinementDelta @see getVariationalRefinementDelta */
CV_WRAP virtual void setVariationalRefinementDelta(float val) = 0;
/** @brief Weight of the gradient constancy term
@see setVariationalRefinementGamma */
CV_WRAP virtual float getVariationalRefinementGamma() const = 0;
/** @copybrief getVariationalRefinementGamma @see getVariationalRefinementGamma */
CV_WRAP virtual void setVariationalRefinementGamma(float val) = 0;
/** @brief Whether to use mean-normalization of patches when computing patch distance. It is turned on
by default as it typically provides a noticeable quality boost because of increased robustness to
illumanition variations. Turn it off if you are certain that your sequence does't contain any changes
......
......@@ -67,26 +67,12 @@ protected:
float gamma; // gradient constancy weight
float omega; // relaxation factor in SOR
float zeta; // added to the denomimnator of theta_0 (normaliation of the data term)
float epsilon; // robust penalizer const
int maxLayers; // max amount of layers in the pyramid
int interpolationType;
private:
void calcOneLevel( const Mat I0, const Mat I1, Mat W );
Mat warpImage( const Mat input, const Mat flow );
void dataTerm( const Mat W, const Mat dW, const Mat Ix, const Mat Iy, const Mat Iz,
const Mat Ixx, const Mat Ixy, const Mat Iyy, const Mat Ixz, const Mat Iyz,
Mat a11, Mat a12, Mat a22, Mat b1, Mat b2 );
void smoothnessWeights( const Mat W, Mat weightsX, Mat weightsY );
void smoothnessTerm( const Mat W, const Mat weightsX, const Mat weightsY, Mat b1, Mat b2 );
void sorSolve( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
const Mat smoothX, const Mat smoothY, Mat dW );
void sorUnfolded( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
const Mat smoothX, const Mat smoothY, Mat dW );
std::vector<Mat> buildPyramid( const Mat& src );
int interpolationType;
};
OpticalFlowDeepFlow::OpticalFlowDeepFlow()
......@@ -104,8 +90,6 @@ OpticalFlowDeepFlow::OpticalFlowDeepFlow()
//consts
interpolationType = INTER_LINEAR;
zeta = 0.1f;
epsilon = 0.001f;
maxLayers = 200;
}
......@@ -130,31 +114,7 @@ std::vector<Mat> OpticalFlowDeepFlow::buildPyramid( const Mat& src )
}
return pyramid;
}
Mat OpticalFlowDeepFlow::warpImage( const Mat input, const Mat flow )
{
// warps the image "backwards"
// if flow = computeFlow( I0, I1 ), then
// I0 = warpImage( I1, flow ) - approx.
Mat output;
Mat mapX = Mat(flow.size(), CV_32FC1);
Mat mapY = Mat(flow.size(), CV_32FC1);
const float *pFlow;
float *pMapX, *pMapY;
for ( int j = 0; j < flow.rows; ++j )
{
pFlow = flow.ptr<float>(j);
pMapX = mapX.ptr<float>(j);
pMapY = mapY.ptr<float>(j);
for ( int i = 0; i < flow.cols; ++i )
{
pMapX[i] = i + pFlow[2 * i];
pMapY[i] = j + pFlow[2 * i + 1];
}
}
remap(input, output, mapX, mapY, interpolationType);
return output;
}
void OpticalFlowDeepFlow::calc( InputArray _I0, InputArray _I1, InputOutputArray _flow )
{
Mat I0temp = _I0.getMat();
......@@ -189,7 +149,16 @@ void OpticalFlowDeepFlow::calc( InputArray _I0, InputArray _I1, InputOutputArray
for ( int level = levelCount - 1; level >= 0; --level )
{ //iterate through all levels, beginning with the most coarse
calcOneLevel(pyramid_I0[level], pyramid_I1[level], W);
Ptr<VariationalRefinement> var = createVariationalFlowRefinement();
var->setAlpha(4 * alpha);
var->setDelta(delta / 3);
var->setGamma(gamma / 3);
var->setFixedPointIterations(fixedPointIterations);
var->setSorIterations(sorIterations);
var->setOmega(omega);
var->calc(pyramid_I0[level], pyramid_I1[level], W);
if ( level > 0 ) //not the last level
{
Mat temp;
......@@ -201,666 +170,9 @@ void OpticalFlowDeepFlow::calc( InputArray _I0, InputArray _I1, InputOutputArray
W.copyTo(_flow);
}
void OpticalFlowDeepFlow::calcOneLevel( const Mat I0, const Mat I1, Mat W )
{
CV_DbgAssert( I0.size() == I1.size() );CV_DbgAssert( I0.type() == I1.type() );CV_DbgAssert( W.size() == I0.size() );
// linear equation systems
Size s = I0.size();
int t = CV_32F; // data type
Mat a11, a12, a22, b1, b2;
a11.create(s, t);
a12.create(s, t);
a22.create(s, t);
b1.create(s, t);
b2.create(s, t);
// diffusivity coeffs
Mat weightsX, weightsY;
weightsX.create(s, t);
weightsY.create(s, t);
Mat warpedI1 = warpImage(I1, W); // warped second image
Mat averageFrame = 0.5 * (I0 + warpedI1); // mean value of 2 frames - to compute derivatives on
//computing derivatives, notation as in Brox's paper
Mat Ix, Iy, Iz, Ixx, Ixy, Iyy, Ixz, Iyz;
int ddepth = -1; //as source image
int kernel_size = 1;
Sobel(averageFrame, Ix, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
Sobel(averageFrame, Iy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
Iz.create(I1.size(), I1.type());
Iz = warpedI1 - I0;
Sobel(Ix, Ixx, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
Sobel(Ix, Ixy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
Sobel(Iy, Iyy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
Sobel(Iz, Ixz, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
Sobel(Iz, Iyz, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
Mat tempW = W.clone(); // flow version to be modified in each iteration
Mat dW = Mat::zeros(W.size(), W.type()); // flow increment
//fixed-point iterations
for ( int i = 0; i < fixedPointIterations; ++i )
{
dataTerm(W, dW, Ix, Iy, Iz, Ixx, Ixy, Iyy, Ixz, Iyz, a11, a12, a22, b1, b2);
smoothnessWeights(tempW, weightsX, weightsY);
smoothnessTerm(W, weightsX, weightsY, b1, b2);
sorSolve(a11, a12, a22, b1, b2, weightsX, weightsY, dW);
tempW = W + dW;
}
tempW.copyTo(W);
}
void OpticalFlowDeepFlow::dataTerm( const Mat W, const Mat dW, const Mat Ix, const Mat Iy,
const Mat Iz, const Mat Ixx, const Mat Ixy, const Mat Iyy, const Mat Ixz,
const Mat Iyz, Mat a11, Mat a12, Mat a22, Mat b1, Mat b2 )
{
const float zeta_squared = zeta * zeta; // added in normalization factor to be non-zero
const float epsilon_squared = epsilon * epsilon;
const float *pIx, *pIy, *pIz;
const float *pIxx, *pIxy, *pIyy, *pIxz, *pIyz;
const float *pdU, *pdV; // accessing 2 layers of dW. Succesive columns interleave u and v
float *pa11, *pa12, *pa22, *pb1, *pb2; // linear equation sys. coeffs for each pixel
float derivNorm; //denominator of the spatial-derivative normalizing factor (theta_0)
float derivNorm2;
float Ik1z, Ik1zx, Ik1zy; // approximations of I^(k+1) values by Taylor expansions
float temp;
for ( int j = 0; j < W.rows; j++ ) //for each row
{
pIx = Ix.ptr<float>(j);
pIy = Iy.ptr<float>(j);
pIz = Iz.ptr<float>(j);
pIxx = Ixx.ptr<float>(j);
pIxy = Ixy.ptr<float>(j);
pIyy = Iyy.ptr<float>(j);
pIxz = Ixz.ptr<float>(j);
pIyz = Iyz.ptr<float>(j);
pa11 = a11.ptr<float>(j);
pa12 = a12.ptr<float>(j);
pa22 = a22.ptr<float>(j);
pb1 = b1.ptr<float>(j);
pb2 = b2.ptr<float>(j);
pdU = dW.ptr<float>(j);
pdV = pdU + 1;
for ( int i = 0; i < W.cols; i++ ) //for each pixel in the row
{ // TODO: implement masking of points warped out of the image
//color constancy component
derivNorm = (*pIx) * (*pIx) + (*pIy) * (*pIy) + zeta_squared;
Ik1z = *pIz + (*pIx * *pdU) + (*pIy * *pdV);
temp = (0.5f*delta/3) / sqrt(Ik1z * Ik1z / derivNorm + epsilon_squared);
*pa11 = *pIx * *pIx * temp / derivNorm;
*pa12 = *pIx * *pIy * temp / derivNorm;
*pa22 = *pIy * *pIy * temp / derivNorm;
*pb1 = -*pIz * *pIx * temp / derivNorm;
*pb2 = -*pIz * *pIy * temp / derivNorm;
// gradient constancy component
derivNorm = *pIxx * *pIxx + *pIxy * *pIxy + zeta_squared;
derivNorm2 = *pIyy * *pIyy + *pIxy * *pIxy + zeta_squared;
Ik1zx = *pIxz + *pIxx * *pdU + *pIxy * *pdV;
Ik1zy = *pIyz + *pIxy * *pdU + *pIyy * *pdV;
temp = (0.5f*gamma/3)
/ sqrt(
Ik1zx * Ik1zx / derivNorm + Ik1zy * Ik1zy / derivNorm2
+ epsilon_squared);
*pa11 += temp * (*pIxx * *pIxx / derivNorm + *pIxy * *pIxy / derivNorm2);
*pa12 += temp * (*pIxx * *pIxy / derivNorm + *pIxy * *pIyy / derivNorm2);
*pa22 += temp * (*pIxy * *pIxy / derivNorm + *pIyy * *pIyy / derivNorm2);
*pb1 += -temp * (*pIxx * *pIxz / derivNorm + *pIxy * *pIyz / derivNorm2);
*pb2 += -temp * (*pIxy * *pIxz / derivNorm + *pIyy * *pIyz / derivNorm2);
++pIx;
++pIy;
++pIz;
++pIxx;
++pIxy;
++pIyy;
++pIxz;
++pIyz;
pdU += 2;
pdV += 2;
++pa11;
++pa12;
++pa22;
++pb1;
++pb2;
}
}
}
void OpticalFlowDeepFlow::smoothnessWeights( const Mat W, Mat weightsX, Mat weightsY )
{
float k[] = { -0.5, 0, 0.5 };
const float epsilon_squared = epsilon * epsilon;
Mat kernel_h = Mat(1, 3, CV_32FC1, k);
Mat kernel_v = Mat(3, 1, CV_32FC1, k);
Mat Wx, Wy; // partial derivatives of the flow
Mat S = Mat(W.size(), CV_32FC1); // sum of squared derivatives
weightsX = Mat::zeros(W.size(), CV_32FC1); //output - weights of smoothness terms in x and y directions
weightsY = Mat::zeros(W.size(), CV_32FC1);
filter2D(W, Wx, CV_32FC2, kernel_h);
filter2D(W, Wy, CV_32FC2, kernel_v);
const float * ux, *uy, *vx, *vy;
float * pS, *pWeight, *temp;
for ( int j = 0; j < S.rows; ++j )
{
ux = Wx.ptr<float>(j);
vx = ux + 1;
uy = Wy.ptr<float>(j);
vy = uy + 1;
pS = S.ptr<float>(j);
for ( int i = 0; i < S.cols; ++i )
{
*pS = alpha / sqrt(*ux * *ux + *vx * *vx + *uy * *uy + *vy * *vy + epsilon_squared);
ux += 2;
vx += 2;
uy += 2;
vy += 2;
++pS;
}
}
// horizontal weights
for ( int j = 0; j < S.rows; ++j )
{
pWeight = weightsX.ptr<float>(j);
pS = S.ptr<float>(j);
for ( int i = 0; i < S.cols - 1; ++i )
{
*pWeight = *pS + *(pS + 1);
++pS;
++pWeight;
}
}
//vertical weights
for ( int j = 0; j < S.rows - 1; ++j )
{
pWeight = weightsY.ptr<float>(j);
pS = S.ptr<float>(j);
temp = S.ptr<float>(j + 1); // next row pointer for easy access
for ( int i = 0; i < S.cols; ++i )
{
*pWeight = *(pS++) + *(temp++);
++pWeight;
}
}
}
void OpticalFlowDeepFlow::smoothnessTerm( const Mat W, const Mat weightsX, const Mat weightsY,
Mat b1, Mat b2 )
{
float *pB1, *pB2;
const float *pU, *pV, *pWeight;
float iB1, iB2; // increments of b1 and b2
//horizontal direction - both U and V (b1 and b2)
for ( int j = 0; j < W.rows; j++ )
{
pB1 = b1.ptr<float>(j);
pB2 = b2.ptr<float>(j);
pU = W.ptr<float>(j);
pV = pU + 1;
pWeight = weightsX.ptr<float>(j);
for ( int i = 0; i < W.cols - 1; i++ )
{
iB1 = (*(pU + 2) - *pU) * *pWeight;
iB2 = (*(pV + 2) - *pV) * *pWeight;
*pB1 += iB1;
*(pB1 + 1) -= iB1;
*pB2 += iB2;
*(pB2 + 1) -= iB2;
pB1++;
pB2++;
pU += 2;
pV += 2;
pWeight++;
}
}
const float *pUnext, *pVnext; // temp pointers for next row
float *pB1next, *pB2next;
//vertical direction - both U and V
for ( int j = 0; j < W.rows - 1; j++ )
{
pB1 = b1.ptr<float>(j);
pB2 = b2.ptr<float>(j);
pU = W.ptr<float>(j);
pV = pU + 1;
pUnext = W.ptr<float>(j + 1);
pVnext = pUnext + 1;
pB1next = b1.ptr<float>(j + 1);
pB2next = b2.ptr<float>(j + 1);
pWeight = weightsY.ptr<float>(j);
for ( int i = 0; i < W.cols; i++ )
{
iB1 = (*pUnext - *pU) * *pWeight;
iB2 = (*pVnext - *pV) * *pWeight;
*pB1 += iB1;
*pB1next -= iB1;
*pB2 += iB2;
*pB2next -= iB2;
pB1++;
pB2++;
pU += 2;
pV += 2;
pWeight++;
pUnext += 2;
pVnext += 2;
pB1next++;
pB2next++;
}
}
}
void OpticalFlowDeepFlow::sorSolve( const Mat a11, const Mat a12, const Mat a22, const Mat b1,
const Mat b2, const Mat smoothX, const Mat smoothY, Mat dW )
{
CV_Assert(a11.isContinuous());
CV_Assert(a12.isContinuous());
CV_Assert(a22.isContinuous());
CV_Assert(b1.isContinuous());
CV_Assert(b2.isContinuous());
CV_Assert(smoothX.isContinuous());
CV_Assert(smoothY.isContinuous());
if(dW.cols > 2 && dW.rows > 2)
{
sorUnfolded(a11, a12, a22, b1, b2, smoothX, smoothY, dW );
//more efficient version - this one is mostly for future reference and readability
return;
}
std::vector<Mat> dWChannels(2);
split(dW, dWChannels);
Mat *du = &(dWChannels[0]);
Mat *dv = &(dWChannels[1]);
CV_Assert(du->isContinuous());
CV_Assert(dv->isContinuous());
const float *pa11, *pa12, *pa22, *pb1, *pb2, *psmoothX, *psmoothY;
float *pdu, *pdv;
psmoothX = smoothX.ptr<float>(0);
psmoothY = smoothY.ptr<float>(0);
pdu = du->ptr<float>(0);
pdv = dv->ptr<float>(0);
float sigmaU, sigmaV, dPsi, A11, A22, A12, B1, B2, det;
int cols = dW.cols;
int rows = dW.rows;
int s = dW.cols; // step between rows
for ( int iter = 0; iter < sorIterations; ++iter )
{
pa11 = a11.ptr<float>(0);
pa12 = a12.ptr<float>(0);
pa22 = a22.ptr<float>(0);
pb1 = b1.ptr<float>(0);
pb2 = b2.ptr<float>(0);
for ( int j = 0; j < rows; ++j )
{
for ( int i = 0; i < cols; ++i )
{
int o = j * s + i;
if ( i == 0 && j == 0 )
{
dPsi = psmoothX[o] + psmoothY[o];
sigmaU = psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
} else if ( i == cols - 1 && j == 0 )
{
dPsi = psmoothX[o - 1] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o] * pdv[o + s];
} else if ( j == 0 )
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
} else if ( i == 0 && j == rows - 1 )
{
dPsi = psmoothX[o] + psmoothY[o - s];
sigmaU = psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s];
} else if ( i == cols - 1 && j == rows - 1 )
{
dPsi = psmoothX[o - 1] + psmoothY[o - s];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o - s] * pdv[o - s];
} else if ( j == rows - 1 )
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s];
} else if ( i == 0 )
{
dPsi = psmoothX[o] + psmoothY[o - s] + psmoothY[o];
sigmaU = psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
} else if ( i == cols - 1 )
{
dPsi = psmoothX[o - 1] + psmoothY[o - s] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
} else
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s]
+ psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
}
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
}
}
}
merge(dWChannels, dW);
}
void OpticalFlowDeepFlow::sorUnfolded( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
const Mat smoothX, const Mat smoothY, Mat dW )
{
// the same effect as sorSolve(), but written more efficiently
std::vector<Mat> dWChannels(2);
split(dW, dWChannels);
Mat *du = &(dWChannels[0]);
Mat *dv = &(dWChannels[1]);
CV_Assert(du->isContinuous());
CV_Assert(dv->isContinuous());
const float *pa11, *pa12, *pa22, *pb1, *pb2, *psmoothX, *psmoothY;
float *pdu, *pdv;
float sigmaU, sigmaV, dPsi, A11, A22, A12, B1, B2, det;
int cols = dW.cols;
int rows = dW.rows;
int s = dW.cols; // step between rows
int j, i, o; //row, column, offset
for ( int iter = 0; iter < sorIterations; ++iter )
{
pa11 = a11.ptr<float>(0);
pa12 = a12.ptr<float>(0);
pa22 = a22.ptr<float>(0);
pb1 = b1.ptr<float>(0);
pb2 = b2.ptr<float>(0);
psmoothX = smoothX.ptr<float>(0);
psmoothY = smoothY.ptr<float>(0);
pdu = du->ptr<float>(0);
pdv = dv->ptr<float>(0);
// first row
// first column
o=0;
dPsi = psmoothX[o] + psmoothY[o];
sigmaU = psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
// middle rows
for ( o = 1; o < cols-1; ++o )
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
}
// last column
dPsi = psmoothX[o - 1] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
//middle rows
for ( j = 1; j < rows - 1; ++j)
{
// first column
dPsi = psmoothX[o] + psmoothY[o - s] + psmoothY[o];
sigmaU = psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
// middle columns
for ( i = 1; i < cols - 1; ++i)
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s]
+ psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
}
//last column
dPsi = psmoothX[o - 1] + psmoothY[o - s] + psmoothY[o];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o - s] * pdu[o - s]
+ psmoothY[o] * pdu[o + s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o - s] * pdv[o - s]
+ psmoothY[o] * pdv[o + s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
}
//last row
//first column
dPsi = psmoothX[o] + psmoothY[o - s];
sigmaU = psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
//middle columns
for ( i = 1; i < cols - 1; ++i)
{
dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothX[o] * pdu[o + 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothX[o] * pdv[o + 1]
+ psmoothY[o - s] * pdv[o - s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
++o;
}
//last column
dPsi = psmoothX[o - 1] + psmoothY[o - s];
sigmaU = psmoothX[o - 1] * pdu[o - 1]
+ psmoothY[o - s] * pdu[o - s];
sigmaV = psmoothX[o - 1] * pdv[o - 1]
+ psmoothY[o - s] * pdv[o - s];
A11 = *pa22 + dPsi;
A12 = -*pa12;
A22 = *pa11 + dPsi;
det = A11 * A22 - A12 * A12;
A11 /= det;
A12 /= det;
A22 /= det;
B1 = *pb1 + sigmaU;
B2 = *pb2 + sigmaV;
pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
}
merge(dWChannels, dW);
}
void OpticalFlowDeepFlow::collectGarbage()
{
}
//
//CV_INIT_ALGORITHM(OpticalFlowDeepFlow, "DenseOpticalFlow.DeepFlow",
// obj.info()->addParam(obj, "sigma", obj.sigma, false, 0, 0, "Gaussian blur parameter");
// obj.info()->addParam(obj, "alpha", obj.alpha, false, 0, 0, "Smoothness assumption weight");
// obj.info()->addParam(obj, "delta", obj.delta, false, 0, 0, "Color constancy weight");
// obj.info()->addParam(obj, "gamma", obj.gamma, false, 0, 0, "Gradient constancy weight");
// obj.info()->addParam(obj, "omega", obj.omega, false, 0, 0, "Relaxation factor in SOR");
// obj.info()->addParam(obj, "minSize", obj.minSize, false, 0, 0, "Min. image size in the pyramid");
// obj.info()->addParam(obj, "fixedPointIterations", obj.fixedPointIterations, false, 0, 0, "Fixed point iterations");
// obj.info()->addParam(obj, "sorIterations", obj.sorIterations, false, 0, 0, "SOR iterations");
// obj.info()->addParam(obj, "downscaleFactor", obj.downscaleFactor, false, 0, 0,"Downscale factor"))
void OpticalFlowDeepFlow::collectGarbage() {}
Ptr<DenseOpticalFlow> createOptFlow_DeepFlow()
{
return makePtr<OpticalFlowDeepFlow>();
}
Ptr<DenseOpticalFlow> createOptFlow_DeepFlow() { return makePtr<OpticalFlowDeepFlow>(); }
}//optflow
}//cv
......@@ -65,6 +65,9 @@ class DISOpticalFlowImpl : public DISOpticalFlow
int patch_stride;
int grad_descent_iter;
int variational_refinement_iter;
float variational_refinement_alpha;
float variational_refinement_gamma;
float variational_refinement_delta;
bool use_mean_normalization;
bool use_spatial_propagation;
......@@ -84,6 +87,13 @@ class DISOpticalFlowImpl : public DISOpticalFlow
void setGradientDescentIterations(int val) { grad_descent_iter = val; }
int getVariationalRefinementIterations() const { return variational_refinement_iter; }
void setVariationalRefinementIterations(int val) { variational_refinement_iter = val; }
float getVariationalRefinementAlpha() const { return variational_refinement_alpha; }
void setVariationalRefinementAlpha(float val) { variational_refinement_alpha = val; }
float getVariationalRefinementDelta() const { return variational_refinement_delta; }
void setVariationalRefinementDelta(float val) { variational_refinement_delta = val; }
float getVariationalRefinementGamma() const { return variational_refinement_gamma; }
void setVariationalRefinementGamma(float val) { variational_refinement_gamma = val; }
bool getUseMeanNormalization() const { return use_mean_normalization; }
void setUseMeanNormalization(bool val) { use_mean_normalization = val; }
bool getUseSpatialPropagation() const { return use_spatial_propagation; }
......@@ -161,6 +171,10 @@ DISOpticalFlowImpl::DISOpticalFlowImpl()
patch_stride = 4;
grad_descent_iter = 16;
variational_refinement_iter = 5;
variational_refinement_alpha = 20.f;
variational_refinement_gamma = 10.f;
variational_refinement_delta = 5.f;
border_size = 16;
use_mean_normalization = true;
use_spatial_propagation = true;
......@@ -234,9 +248,9 @@ void DISOpticalFlowImpl::prepareBuffers(Mat &I0, Mat &I1)
spatialGradient(I0s[i], I0xs[i], I0ys[i]);
Ux[i].create(cur_rows, cur_cols);
Uy[i].create(cur_rows, cur_cols);
variational_refinement_processors[i]->setAlpha(20.0f);
variational_refinement_processors[i]->setDelta(5.0f);
variational_refinement_processors[i]->setGamma(10.0f);
variational_refinement_processors[i]->setAlpha(variational_refinement_alpha);
variational_refinement_processors[i]->setDelta(variational_refinement_delta);
variational_refinement_processors[i]->setGamma(variational_refinement_gamma);
variational_refinement_processors[i]->setSorIterations(5);
variational_refinement_processors[i]->setFixedPointIterations(variational_refinement_iter);
}
......
......@@ -1074,9 +1074,10 @@ void VariationalRefinementImpl::RedBlackSOR_ParBody::operator()(const Range &ran
void VariationalRefinementImpl::calc(InputArray I0, InputArray I1, InputOutputArray flow)
{
CV_Assert(!I0.empty() && I0.depth() == CV_8U && I0.channels() == 1);
CV_Assert(!I1.empty() && I1.depth() == CV_8U && I1.channels() == 1);
CV_Assert(!I0.empty() && I0.channels() == 1);
CV_Assert(!I1.empty() && I1.channels() == 1);
CV_Assert(I0.sameSize(I1));
CV_Assert((I0.depth() == CV_8U && I1.depth() == CV_8U) || (I0.depth() == CV_32F && I1.depth() == CV_32F));
CV_Assert(!flow.empty() && flow.depth() == CV_32F && flow.channels() == 2);
CV_Assert(I0.sameSize(flow));
......@@ -1089,9 +1090,10 @@ void VariationalRefinementImpl::calc(InputArray I0, InputArray I1, InputOutputAr
void VariationalRefinementImpl::calcUV(InputArray I0, InputArray I1, InputOutputArray flow_u, InputOutputArray flow_v)
{
CV_Assert(!I0.empty() && I0.depth() == CV_8U && I0.channels() == 1);
CV_Assert(!I1.empty() && I1.depth() == CV_8U && I1.channels() == 1);
CV_Assert(!I0.empty() && I0.channels() == 1);
CV_Assert(!I1.empty() && I1.channels() == 1);
CV_Assert(I0.sameSize(I1));
CV_Assert((I0.depth() == CV_8U && I1.depth() == CV_8U) || (I0.depth() == CV_32F && I1.depth() == CV_32F));
CV_Assert(!flow_u.empty() && flow_u.depth() == CV_32F && flow_u.channels() == 1);
CV_Assert(!flow_v.empty() && flow_v.depth() == CV_32F && flow_v.channels() == 1);
CV_Assert(I0.sameSize(flow_u));
......
......@@ -92,7 +92,7 @@ grouping horizontally aligned text, and the method proposed by Lluis Gomez and D
in [Gomez13][Gomez14] for grouping arbitrary oriented text (see erGrouping).
To see the text detector at work, have a look at the textdetection demo:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/textdetection.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/textdetection.cpp>
@defgroup text_recognize Scene Text Recognition
@}
......
......@@ -345,7 +345,7 @@ single vector\<Point\>, the function separates them in two different vectors (th
ERStats where extracted from two different channels).
An example of MSERsToERStats in use can be found in the text detection webcam_demo:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
*/
CV_EXPORTS void MSERsToERStats(InputArray image, std::vector<std::vector<Point> > &contours,
std::vector<std::vector<ERStat> > &regions);
......
......@@ -81,10 +81,10 @@ Notice that it is compiled only when tesseract-ocr is correctly installed.
@note
- (C++) An example of OCRTesseract recognition combined with scene text detection can be found
at the end_to_end_recognition demo:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/end_to_end_recognition.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/end_to_end_recognition.cpp>
- (C++) Another example of OCRTesseract recognition combined with scene text detection can be
found at the webcam_demo:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
*/
class CV_EXPORTS_W OCRTesseract : public BaseOCR
{
......@@ -152,7 +152,7 @@ enum decoder_mode
@note
- (C++) An example on using OCRHMMDecoder recognition combined with scene text detection can
be found at the webcam_demo sample:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/webcam_demo.cpp>
*/
class CV_EXPORTS_W OCRHMMDecoder : public BaseOCR
{
......@@ -165,7 +165,7 @@ public:
The default character classifier and feature extractor can be loaded using the utility funtion
loadOCRHMMClassifierNM and KNN model provided in
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/OCRHMM_knn_model_data.xml.gz>.
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/OCRHMM_knn_model_data.xml.gz>.
*/
class CV_EXPORTS_W ClassifierCallback
{
......@@ -321,7 +321,7 @@ CV_EXPORTS_W Ptr<OCRHMMDecoder::ClassifierCallback> loadOCRHMMClassifierCNN(cons
* The function calculate frequency statistics of character pairs from the given lexicon and fills the output transition_probabilities_table with them. The transition_probabilities_table can be used as input in the OCRHMMDecoder::create() and OCRBeamSearchDecoder::create() methods.
* @note
* - (C++) An alternative would be to load the default generic language transition table provided in the text module samples folder (created from ispell 42869 english words list) :
* <https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/OCRHMM_transitions_table.xml>
* <https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/OCRHMM_transitions_table.xml>
**/
CV_EXPORTS void createOCRHMMTransitionsTable(std::string& vocabulary, std::vector<std::string>& lexicon, OutputArray transition_probabilities_table);
......@@ -335,7 +335,7 @@ CV_EXPORTS_W Mat createOCRHMMTransitionsTable(const String& vocabulary, std::vec
@note
- (C++) An example on using OCRBeamSearchDecoder recognition combined with scene text detection can
be found at the demo sample:
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/word_recognition.cpp>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/word_recognition.cpp>
*/
class CV_EXPORTS_W OCRBeamSearchDecoder : public BaseOCR
{
......@@ -348,7 +348,7 @@ public:
The default character classifier and feature extractor can be loaded using the utility funtion
loadOCRBeamSearchClassifierCNN with all its parameters provided in
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/OCRBeamSearch_CNN_model_data.xml.gz>.
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/OCRBeamSearch_CNN_model_data.xml.gz>.
*/
class CV_EXPORTS_W ClassifierCallback
{
......
......@@ -2820,12 +2820,12 @@ bool guo_hall_thinning(const Mat1b & img, Mat& skeleton)
p8 = (skeleton.data[row * skeleton.cols + col-1]) > 0;
p9 = (skeleton.data[(row-1) * skeleton.cols + col-1]) > 0;
int C = (!p2 & (p3 | p4)) + (!p4 & (p5 | p6)) +
(!p6 & (p7 | p8)) + (!p8 & (p9 | p2));
int N1 = (p9 | p2) + (p3 | p4) + (p5 | p6) + (p7 | p8);
int N2 = (p2 | p3) + (p4 | p5) + (p6 | p7) + (p8 | p9);
int C = (!p2 && (p3 || p4)) + (!p4 && (p5 || p6)) +
(!p6 && (p7 || p8)) + (!p8 && (p9 || p2));
int N1 = (p9 || p2) + (p3 || p4) + (p5 || p6) + (p7 || p8);
int N2 = (p2 || p3) + (p4 || p5) + (p6 || p7) + (p8 || p9);
int N = N1 < N2 ? N1 : N2;
int m = iter == 0 ? ((p6 | p7 | !p9) & p8) : ((p2 | p3 | !p5) & p4);
int m = iter == 0 ? ((p6 || p7 || !p9) && p8) : ((p2 || p3 || !p5) && p4);
if ((C == 1) && (N >= 2) && (N <= 3) && (m == 0))
{
......
......@@ -1206,7 +1206,7 @@ the output transition_probabilities_table with them.
The transition_probabilities_table can be used as input in the OCRHMMDecoder::create() and OCRBeamSearchDecoder::create() methods.
@note
- (C++) An alternative would be to load the default generic language transition table provided in the text module samples folder (created from ispell 42869 english words list) :
<https://github.com/Itseez/opencv_contrib/blob/master/modules/text/samples/OCRHMM_transitions_table.xml>
<https://github.com/opencv/opencv_contrib/blob/master/modules/text/samples/OCRHMM_transitions_table.xml>
*/
void createOCRHMMTransitionsTable(string& vocabulary, vector<string>& lexicon, OutputArray _transitions)
{
......
......@@ -28,8 +28,8 @@ Explanation
as shown in help. In the help, it means that the image files are numbered with 4 digits
(e.g. the file naming will be 0001.jpg, 0002.jpg, and so on).
You can find video samples in Itseez/opencv_extra/testdata/cv/tracking
<https://github.com/Itseez/opencv_extra/tree/master/testdata/cv/tracking>
You can find video samples in opencv_extra/testdata/cv/tracking
<https://github.com/opencv/opencv_extra/tree/master/testdata/cv/tracking>
-# **Declares the required variables**
......
......@@ -9,3 +9,4 @@ Extended Image Processing
6. Superpixels
7. Graph segmentation
8. Selective search from segmentation
10. Paillou Filter
......@@ -166,3 +166,13 @@
year={2014},
organization={IEEE}
}
@article{paillou1997detecting,
title={Detecting step edges in noisy SAR images: a new linear operator},
author={Paillou, Philippe},
journal={IEEE transactions on geoscience and remote sensing},
volume={35},
number={1},
pages={191--196},
year={1997}
}
......@@ -48,6 +48,8 @@
#include "ximgproc/weighted_median_filter.hpp"
#include "ximgproc/slic.hpp"
#include "ximgproc/lsc.hpp"
#include "ximgproc/paillou_filter.hpp"
/** @defgroup ximgproc Extended Image Processing
@{
......
/*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
*
* License Agreement
* For Open Source Computer Vision Library
* (3 - clause BSD License)
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met :
*
* *Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution.
*
* * Neither the names of the copyright holders nor the names of the contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* This software is provided by the copyright holders and contributors "as is" and
* any express or implied warranties, including, but not limited to, the implied
* warranties of merchantability and fitness for a particular purpose are disclaimed.
* In no event shall copyright holders or contributors be liable for any direct,
* indirect, incidental, special, exemplary, or consequential damages
* (including, but not limited to, procurement of substitute goods or services;
* loss of use, data, or profits; or business interruption) however caused
* and on any theory of liability, whether in contract, strict liability,
* or tort(including negligence or otherwise) arising in any way out of
* the use of this software, even if advised of the possibility of such damage.
*/
#ifndef __OPENCV_PAILLOUFILTER_HPP__
#define __OPENCV_PAILLOUFILTER_HPP__
#ifdef __cplusplus
#include <opencv2/core.hpp>
namespace cv {
namespace ximgproc {
//! @addtogroup ximgproc_filters
//! @{
/**
* @brief Applies Paillou filter to an image.
*
* For more details about this implementation, please see @cite paillou1997detecting
*
* @param op Source 8-bit or 16bit image, 1-channel or 3-channel image.
* @param _dst result CV_32F image with same numeber of channel than op.
* @param omega double see paper
* @param alpha double see paper
*
* @sa GradientPaillouX, GradientPaillouY
*/
CV_EXPORTS void GradientPaillouY(InputArray op, OutputArray _dst, double alpha, double omega);
CV_EXPORTS void GradientPaillouX(InputArray op, OutputArray _dst, double alpha, double omega);
}
}
#endif
#endif
/*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
*
* License Agreement
* For Open Source Computer Vision Library
* (3 - clause BSD License)
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met :
*
* *Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution.
*
* * Neither the names of the copyright holders nor the names of the contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* This software is provided by the copyright holders and contributors "as is" and
* any express or implied warranties, including, but not limited to, the implied
* warranties of merchantability and fitness for a particular purpose are disclaimed.
* In no event shall copyright holders or contributors be liable for any direct,
* indirect, incidental, special, exemplary, or consequential damages
* (including, but not limited to, procurement of substitute goods or services;
* loss of use, data, or profits; or business interruption) however caused
* and on any theory of liability, whether in contract, strict liability,
* or tort(including negligence or otherwise) arising in any way out of
* the use of this software, even if advised of the possibility of such damage.
*/
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/ximgproc.hpp>
#include "opencv2/ximgproc/paillou_filter.hpp"
using namespace cv;
using namespace cv::ximgproc;
#include <iostream>
using namespace std;
int aa = 100, ww = 10;
Mat dx, dy;
UMat img;
const char* window_name = "Gradient Modulus";
static void DisplayImage(Mat x,string s)
{
vector<Mat> sx;
split(x, sx);
vector<double> minVal(3), maxVal(3);
for (int i = 0; i < static_cast<int>(sx.size()); i++)
{
minMaxLoc(sx[i], &minVal[i], &maxVal[i]);
}
maxVal[0] = *max_element(maxVal.begin(), maxVal.end());
minVal[0] = *min_element(minVal.begin(), minVal.end());
Mat uc;
x.convertTo(uc, CV_8U,255/(maxVal[0]-minVal[0]),-255*minVal[0]/(maxVal[0]-minVal[0]));
imshow(s, uc);
}
/**
* @function paillouFilter
* @brief Trackbar callback
*/
static void PaillouFilter(int, void*)
{
Mat dst;
double a=aa/100.0,w=ww/100.0;
Mat rx,ry;
GradientPaillouX(img,rx,a,w);
GradientPaillouY(img,ry,a,w);
DisplayImage(rx, "Gx");
DisplayImage(ry, "Gy");
add(rx.mul(rx),ry.mul(ry),dst);
sqrt(dst,dst);
DisplayImage(dst, window_name );
}
int main(int argc, char* argv[])
{
if (argc==2)
imread(argv[1]).copyTo(img);
if (img.empty())
{
cout << "File not found or empty image\n";
}
imshow("Original",img);
namedWindow( window_name, WINDOW_AUTOSIZE );
/// Create a Trackbar for user to enter threshold
createTrackbar( "a:",window_name, &aa, 400, PaillouFilter );
createTrackbar( "w:", window_name, &ww, 400, PaillouFilter );
PaillouFilter(0,NULL);
waitKey();
return 0;
}
\ No newline at end of file
/**************************************************************************************
The structered edge demo requires you to provide a model.
This model can be found at the opencv_extra repository on Github on the following link:
https://github.com/Itseez/opencv_extra/blob/master/testdata/cv/ximgproc/model.yml.gz
https://github.com/opencv/opencv_extra/blob/master/testdata/cv/ximgproc/model.yml.gz
***************************************************************************************/
#include <opencv2/ximgproc.hpp>
......
#include "precomp.hpp"
#include "opencv2/highgui.hpp"
#include <math.h>
#include <vector>
#include <iostream>
namespace cv {
namespace ximgproc {
/*
If you use this code please cite this @cite paillou1997detecting
Detecting step edges in noisy SAR images: a new linear operator IEEE Transactions on Geoscience and Remote Sensing (Volume:35 , Issue: 1 ) 1997
*/
class ParallelGradientPaillouYCols: public ParallelLoopBody
{
private:
Mat &img;
Mat &dst;
double a;
double w;
bool verbose;
public:
ParallelGradientPaillouYCols(Mat& imgSrc, Mat &d,double aa,double ww):
img(imgSrc),
dst(d),
a(aa),
w(ww),
verbose(false)
{}
void Verbose(bool b){verbose=b;}
virtual void operator()(const Range& range) const
{
CV_Assert(img.depth()==CV_8UC1 || img.depth()==CV_16SC1 || img.depth()==CV_16UC1);
CV_Assert(dst.depth()==CV_32FC1);
if (verbose)
std::cout << getThreadNum()<<"# :Start from row " << range.start << " to " << range.end-1<<" ("<<range.end-range.start<<" loops)" << std::endl;
float *f2;
int tailleSequence=(img.rows>img.cols)?img.rows:img.cols;
Mat matYp(1,tailleSequence,CV_64FC1), matYm(1,tailleSequence,CV_64FC1);
double *yp=matYp.ptr<double>(0), *ym=matYm.ptr<double>(0);
int rows=img.rows,cols=img.cols;
// Equation 12 p193
double b1=-2*exp(-a)*cosh(w);
double a1=2*exp(-a)*cosh(w)-exp(-2*a)-1;
double b2=exp(-2*a);
switch(img.depth()){
case CV_8U :
for (int j=range.start;j<range.end;j++)
{
// Equation 26 p194
uchar *c1 = img.ptr(0)+j;
f2 = dst.ptr<float>(0)+j;
double border=*c1;
yp[0] = *c1 ;
c1+=cols;
yp[1] = *c1 - b1*yp[0]-b2*border;
c1+=cols;
for (int i=2;i<rows;i++,c1+=cols)
yp[i] = *c1-b1*yp[i-1]-b2*yp[i-2];
// Equation 27 p194
c1 = img.ptr(rows-1)+j;
border=*c1;
ym[rows - 1] = *c1;
c1 -= cols;
ym[rows-2] =*c1 - b1*ym[rows-1];
c1 -= cols;
for (int i=rows-3;i>=0;i--,c1-=cols)
ym[i]=*c1-b1*ym[i+1]-b2*ym[i+2];
// Equation 25 p193
for (int i=0;i<rows;i++,f2+=cols)
*f2 = (float)(a1*(ym[i]-yp[i]));
}
break;
case CV_16S :
for (int j = range.start; j<range.end; j++)
{
// Equation 26 p194
short *c1 = img.ptr<short>(0) + j;
f2 = dst.ptr<float>(0) + j;
double border = *c1;
yp[0] = *c1;
c1 += cols;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1 += cols;
for (int i = 2; i<rows; i++, c1 += cols)
yp[i] = *c1 - b1*yp[i - 1] - b2*yp[i - 2];
// Equation 27 p194
c1 = img.ptr<short>(rows - 1) + j;
border = *c1;
ym[rows - 1] = *c1;
c1 -= cols;
ym[rows - 2] = *c1 - b1*ym[rows - 1];
c1 -= cols;
for (int i = rows - 3; i >= 0; i--, c1 -= cols)
ym[i] = *c1 - b1*ym[i + 1] - b2*ym[i + 2];
// Equation 25 p193
for (int i = 0; i<rows; i++, f2 += cols)
*f2 = (float)(a1*(ym[i] - yp[i]));
}
break;
case CV_16U :
for (int j = range.start; j<range.end; j++)
{
// Equation 26 p194
ushort *c1 = img.ptr<ushort>(0) + j;
f2 = dst.ptr<float>(0) + j;
double border = *c1;
yp[0] = *c1;
c1 += cols;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1 += cols;
for (int i = 2; i<rows; i++, c1 += cols)
yp[i] = *c1 - b1*yp[i - 1] - b2*yp[i - 2];
// Equation 27 p194
c1 = img.ptr<ushort>(rows - 1) + j;
border = *c1;
ym[rows - 1] = *c1;
c1 -= cols;
ym[rows - 2] = *c1 - b1*ym[rows - 1];
c1 -= cols;
for (int i = rows - 3; i >= 0; i--, c1 -= cols)
ym[i] = *c1 - b1*ym[i + 1] - b2*ym[i + 2];
// Equation 25 p193
for (int i = 0; i<rows; i++, f2 += cols)
*f2 = (float)(a1*(ym[i] - yp[i]));
}
break;
default :
return ;
}
};
ParallelGradientPaillouYCols& operator=(const ParallelGradientPaillouYCols &) {
return *this;
};
};
class ParallelGradientPaillouYRows: public ParallelLoopBody
{
private:
Mat &img;
Mat &dst;
double a;
double w;
bool verbose;
public:
ParallelGradientPaillouYRows(Mat& imgSrc, Mat &d,double aa,double ww):
img(imgSrc),
dst(d),
a(aa),
w(ww),
verbose(false)
{}
void Verbose(bool b){verbose=b;}
virtual void operator()(const Range& range) const
{
CV_Assert(img.depth()==CV_32FC1);
if (verbose)
std::cout << getThreadNum()<<"# :Start from row " << range.start << " to " << range.end-1<<" ("<<range.end-range.start<<" loops)" << std::endl;
float *iy,*iy0;
int tailleSequence=(img.rows>img.cols)?img.rows:img.cols;
Mat matIym(1,tailleSequence,CV_64FC1), matIyp(1,tailleSequence,CV_64FC1);
double *iym=matIym.ptr<double>(0), *iyp=matIyp.ptr<double>(0);
int cols=img.cols;
// Equation 13 p193
double d=(1-2*exp(-a)*cosh(w)+exp(-2*a))/(2*a*exp(-a)*sinh(w)+w*(1-exp(-2*a)));
double c1=a*d;
double c2=w*d;
// Equation 12 p193
double b1=-2*exp(-a)*cosh(w);
double b2=exp(-2*a);
// Equation 14 p193
double a0p=c2;
double a1p=(c1*sinh(w)-c2*cosh(w))*exp(-a);
double a1m=a1p-c2*b1;
double a2m=-c2*b2;
for (int i=range.start;i<range.end;i++)
{
iy0 = img.ptr<float>(i);
int j=0;
iyp[0] = a0p*iy0[0] ;
iyp[1] = a0p*iy0[1] + a1p*iy0[0] - b1*iyp[0];
iy0 += 2;
for (j=2;j<cols;j++,iy0++)
iyp[j] = a0p*iy0[0] + a1p*iy0[-1] - b1*iyp[j-1] - b2*iyp[j-2];
iy0 = img.ptr<float>(i)+cols-1;
iym[cols-1] = 0;
iy0--;
iym[cols-2] = a1m*iy0[1] - b1*iym[cols-1];
iy0--;
for (j=cols-3;j>=0;j--,iy0--)
iym[j] = a1m*iy0[1] + a2m*iy0[2] - b1*iym[j+1] - b2*iym[j+2];
iy = dst.ptr<float>(i);
for (j=0;j<cols;j++,iy++)
*iy = (float)(iym[j]+iyp[j]);
}
};
ParallelGradientPaillouYRows& operator=(const ParallelGradientPaillouYRows &) {
return *this;
};
};
class ParallelGradientPaillouXCols: public ParallelLoopBody
{
private:
Mat &img;
Mat &dst;
double a;
double w;
bool verbose;
public:
ParallelGradientPaillouXCols(Mat& imgSrc, Mat &d,double aa,double ww):
img(imgSrc),
dst(d),
a(aa),
w(ww),
verbose(false)
{}
void Verbose(bool b){verbose=b;}
virtual void operator()(const Range& range) const
{
CV_Assert(img.depth()==CV_32FC1);
if (verbose)
std::cout << getThreadNum() << "# :Start from row " << range.start << " to " << range.end - 1 << " (" << range.end - range.start << " loops)" << std::endl;
float *iy, *iy0;
int tailleSequence = (img.rows>img.cols) ? img.rows : img.cols;
Mat matIym(1,tailleSequence,CV_64FC1), matIyp(1,tailleSequence,CV_64FC1);
double *iym=matIym.ptr<double>(0), *iyp=matIyp.ptr<double>(0);
int rows = img.rows,cols=img.cols;
// Equation 13 p193
double d = (1 - 2 * exp(-a)*cosh(w) + exp(-2 * a)) / (2 * a*exp(-a)*sinh(w) + w*(1 - exp(-2 * a)));
double c1 = a*d;
double c2 = w*d;
// Equation 12 p193
double b1 = -2 * exp(-a)*cosh(w);
double b2 = exp(-2 * a);
// Equation 14 p193
double a0p = c2;
double a1p = (c1*sinh(w) - c2*cosh(w))*exp(-a);
double a1m = a1p - c2*b1;
double a2m = -c2*b2;
for (int j = range.start; j<range.end; j++)
{
iy0 = img.ptr<float>(0)+j;
iyp[0] = a0p*iy0[0];
iy0 +=cols;
iyp[1] = a0p*iy0[0] + a1p*iy0[-cols] - b1*iyp[0];
iy0 +=cols;
for (int i = 2; i<rows; i++, iy0+=cols)
iyp[i] = a0p*iy0[0] + a1p*iy0[-cols] - b1*iyp[i - 1] - b2*iyp[i - 2];
iy0 = img.ptr<float>(rows-1) + j;
iym[rows - 1] = 0;
iy0 -=cols;
iym[rows - 2] = a1m*iy0[cols] - b1*iym[rows-1];
iy0-=cols;
for (int i = rows - 3; i >= 0; i--, iy0-=cols)
iym[i] = a1m*iy0[cols] + a2m*iy0[2*cols] - b1*iym[i + 1] - b2*iym[i + 2];
iy = dst.ptr<float>(0)+j;
for (int i = 0; i<rows; i++, iy+=cols)
*iy = (float)(iym[i] + iyp[i]);
}
};
ParallelGradientPaillouXCols& operator=(const ParallelGradientPaillouXCols &) {
return *this;
};
};
class ParallelGradientPaillouXRows: public ParallelLoopBody
{
private:
Mat &img;
Mat &im1;
double a;
double w;
bool verbose;
public:
ParallelGradientPaillouXRows(Mat& imgSrc, Mat &d,double aa,double ww):
img(imgSrc),
im1(d),
a(aa),
w(ww),
verbose(false)
{}
void Verbose(bool b){verbose=b;}
virtual void operator()(const Range& range) const
{
if (verbose)
std::cout << getThreadNum()<<"# :Start from row " << range.start << " to " << range.end-1<<" ("<<range.end-range.start<<" loops)" << std::endl;
float *f2;
int tailleSequence = (img.rows>img.cols) ? img.rows : img.cols;
Mat matYp(1,tailleSequence,CV_64FC1), matYm(1,tailleSequence,CV_64FC1);
double *yp=matYp.ptr<double>(0), *ym=matYm.ptr<double>(0);
int cols = img.cols;
// Equation 12 p193
double b1 = -2 * exp(-a)*cosh(w);
double a1 = 2 * exp(-a)*cosh(w) - exp(-2 * a) - 1;
double b2 = exp(-2 * a);
switch(img.depth()){
case CV_8U :
for (int i = range.start; i<range.end; i++)
{
// Equation 26 p194
uchar *c1 = img.ptr(i);
double border = *c1;
yp[0] = *c1;
c1++;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1++;
for (int j = 2; j<cols; j++, c1++)
yp[j] = *c1 - b1*yp[j - 1] - b2*yp[j - 2];
// Equation 27 p194
c1 = img.ptr(i)+cols-1;
border = *c1;
ym[cols - 1] = *c1;
c1--;
ym[cols - 2] = *c1 - b1*ym[cols - 1];
c1--;
for (int j = cols - 3; j >= 0; j--, c1--)
ym[j] = *c1 - b1*ym[j + 1] - b2*ym[j + 2];
// Equation 25 p193
f2 = im1.ptr<float>(i);
for (int j = 0; j<cols; j++, f2 ++)
*f2 = (float)(a1*(ym[j] - yp[j]));
}
break;
case CV_8S :
for (int i = range.start; i<range.end; i++)
{
// Equation 26 p194
char *c1 = img.ptr<char>(i);
double border = *c1;
yp[0] = *c1;
c1++;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1++;
for (int j = 2; j<cols; j++, c1++)
yp[j] = *c1 - b1*yp[j - 1] - b2*yp[j - 2];
// Equation 27 p194
c1 = img.ptr<char>(i)+cols-1;
border = *c1;
ym[cols - 1] = *c1;
c1--;
ym[cols - 2] = *c1 - b1*ym[cols - 1];
c1--;
for (int j = cols - 3; j >= 0; j--, c1--)
ym[j] = *c1 - b1*ym[j + 1] - b2*ym[j + 2];
// Equation 25 p193
f2 = im1.ptr<float>(i);
for (int j = 0; j<cols; j++, f2 ++)
*f2 = (float)(a1*(ym[j] - yp[j]));
}
break;
case CV_16S :
for (int i = range.start; i<range.end; i++)
{
// Equation 26 p194
short *c1 = img.ptr<short>(i);
f2 = im1.ptr<float>(i);
double border = *c1;
yp[0] = *c1;
c1++;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1++;
for (int j = 2; j<cols; j++, c1++)
yp[j] = *c1 - b1*yp[j - 1] - b2*yp[j - 2];
// Equation 27 p194
c1 = img.ptr<short>(i) + cols - 1;
border = *c1;
ym[cols - 1] = *c1;
c1--;
ym[cols - 2] = *c1 - b1*ym[cols - 1];
c1--;
for (int j = cols - 3; j >= 0; j--, c1--)
ym[j] = *c1 - b1*ym[j + 1] - b2*ym[j + 2];
// Equation 25 p193
for (int j = 0; j<cols; j++, f2++)
*f2 = (float)(a1*(ym[i] - yp[i]));
}
break;
case CV_16U :
for (int i = range.start; i<range.end; i++)
{
// Equation 26 p194
ushort *c1 = img.ptr<ushort>(i);
f2 = im1.ptr<float>(i);
double border = *c1;
yp[0] = *c1;
c1++;
yp[1] = *c1 - b1*yp[0] - b2*border;
c1++;
for (int j = 2; j<cols; j++, c1++)
yp[j] = *c1 - b1*yp[j - 1] - b2*yp[j - 2];
// Equation 27 p194
c1 = img.ptr<ushort>(i) + cols - 1;
border = *c1;
ym[cols - 1] = *c1;
c1--;
ym[cols - 2] = *c1 - b1*ym[cols - 1];
c1--;
for (int j = cols - 3; j >= 0; j--, c1--)
ym[j] = *c1 - b1*ym[j + 1] - b2*ym[j + 2];
// Equation 25 p193
for (int j = 0; j<cols; j++, f2++)
*f2 = (float)(a1*(ym[i] - yp[i]));
}
break;
default :
return ;
}
};
ParallelGradientPaillouXRows& operator=(const ParallelGradientPaillouXRows &) {
return *this;
};
};
void GradientPaillouY(InputArray _op, OutputArray _dst, double alpha, double omega)
{
Mat tmp(_op.size(),CV_32FC(_op.channels()));
_dst.create( _op.size(),CV_32FC(tmp.channels()) );
cv::Mat opSrc = _op.getMat();
std::vector<Mat> planSrc;
split(opSrc,planSrc);
std::vector<Mat> planTmp;
split(tmp,planTmp);
std::vector<Mat> planDst;
split(_dst,planDst);
for (int i = 0; i < static_cast<int>(planSrc.size()); i++)
{
if (planSrc[i].isContinuous() && planTmp[i].isContinuous() && planDst[i].isContinuous())
{
ParallelGradientPaillouYCols x(planSrc[i],planTmp[i],alpha,omega);
parallel_for_(Range(0,opSrc.cols), x,getNumThreads());
ParallelGradientPaillouYRows xr(planTmp[i],planDst[i],alpha,omega);
parallel_for_(Range(0,opSrc.rows), xr,getNumThreads());
}
else
std::cout << "PB";
}
merge(planDst,_dst);
}
void GradientPaillouX(InputArray _op, OutputArray _dst, double alpha, double omega)
{
Mat tmp(_op.size(),CV_32FC(_op.channels()));
_dst.create( _op.size(),CV_32FC(tmp.channels()) );
Mat opSrc = _op.getMat();
std::vector<Mat> planSrc;
split(opSrc,planSrc);
std::vector<Mat> planTmp;
split(tmp,planTmp);
std::vector<Mat> planDst;
split(_dst,planDst);
for (int i = 0; i < static_cast<int>(planSrc.size()); i++)
{
if (planSrc[i].isContinuous() && planTmp[i].isContinuous() && planDst[i].isContinuous())
{
ParallelGradientPaillouXRows x(planSrc[i],planTmp[i],alpha,omega);
parallel_for_(Range(0,opSrc.rows), x,getNumThreads());
ParallelGradientPaillouXCols xr(planTmp[i],planDst[i],alpha,omega);
parallel_for_(Range(0,opSrc.cols), xr,getNumThreads());
}
else
std::cout << "PB";
}
merge(planDst,_dst);
}
}
}
......@@ -27,7 +27,7 @@ Source Stereoscopic Image
Source Code
-----------
We will be using snippets from the example application, that can be downloaded [here ](https://github.com/Itseez/opencv_contrib/blob/master/modules/ximgproc/samples/disparity_filtering.cpp).
We will be using snippets from the example application, that can be downloaded [here ](https://github.com/opencv/opencv_contrib/blob/master/modules/ximgproc/samples/disparity_filtering.cpp).
Explanation
-----------
......
......@@ -19,7 +19,7 @@ file(GLOB ${the_target}_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
add_executable(${the_target} ${${the_target}_SOURCES})
target_link_libraries(${the_target} ${OPENCV_${the_target}_DEPS})
ocv_target_link_libraries(${the_target} ${OPENCV_${the_target}_DEPS})
set_target_properties(${the_target} PROPERTIES
DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment