Commit 9b5aa2a5 authored by Vadim Pisarevsky's avatar Vadim Pisarevsky

Merge pull request #1147 from lhelontra:edgenms

parents 74e439f4 da280ac5
......@@ -102,11 +102,31 @@ public:
The algorithm underlies this function is much more robust to texture presence, than common
approaches, e.g. Sobel
@param src source image (RGB, float, in [0;1]) to detect edges
@param dst destination image (grayscale, float, in [0;1]) where edges are drawn
@param _src source image (RGB, float, in [0;1]) to detect edges
@param _dst destination image (grayscale, float, in [0;1]) where edges are drawn
@sa Sobel, Canny
*/
CV_WRAP virtual void detectEdges(const Mat &src, CV_OUT Mat &dst) const = 0;
CV_WRAP virtual void detectEdges(cv::InputArray _src, cv::OutputArray _dst) const = 0;
/** @brief The function computes orientation from edge image.
@param _src edge image.
@param _dst orientation image.
*/
CV_WRAP virtual void computeOrientation(cv::InputArray _src, cv::OutputArray _dst) const = 0;
/** @brief The function edgenms in edge image and suppress edges where edge is stronger in orthogonal direction.
@param edge_image edge image from detectEdges function.
@param orientation_image orientation image from computeOrientation function.
@param _dst suppressed image (grayscale, float, in [0;1])
@param r radius for NMS suppression.
@param s radius for boundary suppression.
@param m multiplier for conservative suppression.
@param isParallel enables/disables parallel computing.
*/
CV_WRAP virtual void edgesNms(cv::InputArray edge_image, cv::InputArray orientation_image, cv::OutputArray _dst, int r = 2, int s = 0, float m = 1, bool isParallel = true) const = 0;
};
/*!
......
......@@ -59,10 +59,20 @@ int main( int argc, const char** argv )
createStructuredEdgeDetection(modelFilename);
pDollar->detectEdges(image, edges);
// computes orientation from edge map
Mat orientation_map;
pDollar->computeOrientation(edges, orientation_map);
// suppress edges
Mat edge_nms;
pDollar->edgesNms(edges, orientation_map, edge_nms, 2, 0, 1, true);
if ( outFilename.size() == 0 )
{
namedWindow("edges", 1);
imshow("edges", edges);
namedWindow("edges nms", 1);
imshow("edges nms", edge_nms);
waitKey(0);
}
else
......
......@@ -255,6 +255,81 @@ static void gradientHist(const cv::Mat &src, cv::Mat &magnitude, cv::Mat &histog
}
}
/*!
* The class parallelizing the edgenms algorithm.
*
* \param E : edge image
* \param O : orientation image
* \param dst : destination image
* \param r : radius for NMS suppression
* \param s : radius for boundary suppression
* \param m : multiplier for conservative suppression
*/
class NmsInvoker : public cv::ParallelLoopBody
{
private:
const cv::Mat &E;
const cv::Mat &O;
cv::Mat &dst;
const int r;
const float m;
public:
NmsInvoker(const cv::Mat &_E, const cv::Mat &_O, cv::Mat &_dst, const int _r, const float _m)
: E(_E), O(_O), dst(_dst), r(_r), m(_m)
{
}
void operator()(const cv::Range &range) const
{
for (int x = range.start; x < range.end; x++)
{
const float *e_ptr = E.ptr<float>(x);
const float *o_ptr = O.ptr<float>(x);
float *dst_ptr = dst.ptr<float>(x);
for (int y=0; y < E.cols; y++)
{
float e = e_ptr[y];
dst_ptr[y] = e;
if (!e) continue;
e *= m;
float coso = cos(o_ptr[y]);
float sino = sin(o_ptr[y]);
for (int d=-r; d<=r; d++)
{
if (d)
{
float xdcos = x+d*coso;
float ydsin = y+d*sino;
xdcos = xdcos < 0 ? 0 : (xdcos > E.rows - 1.001f ? E.rows - 1.001f : xdcos);
ydsin = ydsin < 0 ? 0 : (ydsin > E.cols - 1.001f ? E.cols - 1.001f : ydsin);
int x0 = (int)xdcos;
int y0 = (int)ydsin;
int x1 = x0 + 1;
int y1 = y0 + 1;
float dx0 = xdcos - x0;
float dy0 = ydsin - y0;
float dx1 = 1 - dx0;
float dy1 = 1 - dy0;
float e0 = E.at<float>(x0, y0) * dx1 * dy1 +
E.at<float>(x1, y0) * dx0 * dy1 +
E.at<float>(x0, y1) * dx1 * dy0 +
E.at<float>(x1, y1) * dx0 * dy0;
if(e < e0)
{
dst_ptr[y] = 0;
break;
}
}
}
}
}
}
};
/********************* RFFeatureGetter class *********************/
namespace cv
......@@ -445,21 +520,23 @@ public:
/*!
* The function detects edges in src and draw them to dst
*
* \param src : source image (RGB, float, in [0;1]) to detect edges
* \param dst : destination image (grayscale, float, in [0;1])
* \param _src : source image (RGB, float, in [0;1]) to detect edges
* \param _dst : destination image (grayscale, float, in [0;1])
* where edges are drawn
*/
void detectEdges(const cv::Mat &src, cv::Mat &dst) const
void detectEdges(cv::InputArray _src, cv::OutputArray _dst) const
{
CV_Assert( src.type() == CV_32FC3 );
CV_Assert( _src.type() == CV_32FC3 );
dst.create( src.size(), cv::DataType<float>::type );
_dst.createSameSize( _src, cv::DataType<float>::type );
_dst.setTo(0);
Mat dst = _dst.getMat();
int padding = ( __rf.options.patchSize
- __rf.options.patchInnerSize )/2;
cv::Mat nSrc;
copyMakeBorder( src, nSrc, padding, padding,
copyMakeBorder( _src, nSrc, padding, padding,
padding, padding, BORDER_REFLECT );
NChannelsMat features;
......@@ -472,6 +549,101 @@ public:
predictEdges( features, dst );
}
/*!
* The function computes orientation from edge image.
*
* \param src : edge image.
* \param dst : orientation image.
* \param r : filter radius.
*/
void computeOrientation(cv::InputArray _src, cv::OutputArray _dst) const
{
CV_Assert( _src.type() == CV_32FC1 );
cv::Mat Oxx, Oxy, Oyy;
_dst.createSameSize( _src, _src.type() );
_dst.setTo(0);
Mat src = _src.getMat();
cv::Mat E_conv = imsmooth(src, __rf.options.gradientNormalizationRadius);
Sobel(E_conv, Oxx, -1, 2, 0);
Sobel(E_conv, Oxy, -1, 1, 1);
Sobel(E_conv, Oyy, -1, 0, 2);
Mat dst = _dst.getMat();
float *o = dst.ptr<float>();
float *oxx = Oxx.ptr<float>();
float *oxy = Oxy.ptr<float>();
float *oyy = Oyy.ptr<float>();
for (int i = 0; i < dst.rows * dst.cols; i++)
{
int xysign = -((oxy[i] > 0) - (oxy[i] < 0));
o[i] = (atan((oyy[i] * xysign / (oxx[i] + 1e-5))) > 0) ? (float) fmod(
atan((oyy[i] * xysign / (oxx[i] + 1e-5))), M_PI) : (float) fmod(
atan((oyy[i] * xysign / (oxx[i] + 1e-5))) + M_PI, M_PI);
}
}
/*!
* The function suppress edges where edge is stronger in orthogonal direction
* \param edge_image : edge image from detectEdges function.
* \param orientation_image : orientation image from computeOrientation function.
* \param _dst : suppressed image (grayscale, float, in [0;1])
* \param r : radius for NMS suppression.
* \param s : radius for boundary suppression.
* \param m : multiplier for conservative suppression.
* \param isParallel: enables/disables parallel computing.
*/
void edgesNms(cv::InputArray edge_image, cv::InputArray orientation_image, cv::OutputArray _dst, int r, int s, float m, bool isParallel) const
{
CV_Assert(edge_image.type() == CV_32FC1);
CV_Assert(orientation_image.type() == CV_32FC1);
cv::Mat E = edge_image.getMat();
cv::Mat O = orientation_image.getMat();
cv::Mat E_t = E.t();
cv::Mat O_t = O.t();
cv::Mat dst = _dst.getMat();
dst.create(E.cols, E.rows, E.type());
dst.setTo(0);
cv::Range sizeRange = cv::Range(0, E_t.rows);
NmsInvoker body = NmsInvoker(E_t, O_t, dst, r, m);
if (isParallel)
{
cv::parallel_for_(sizeRange, body);
} else
{
body(sizeRange);
}
s = s > E_t.rows / 2 ? E_t.rows / 2 : s;
s = s > E_t.cols / 2 ? E_t.cols / 2 : s;
for (int x=0; x<s; x++)
{
for (int y=0; y<E_t.cols; y++)
{
dst.at<float>(x, y) *= x / (float)s;
dst.at<float>(E_t.rows-1-x, y) *= x / (float)s;
}
}
for (int x=0; x < E_t.rows; x++)
{
for (int y=0; y < s; y++)
{
dst.at<float>(x, y) *= y / (float)s;
dst.at<float>(x, E_t.cols-1-y) *= y / (float)s;
}
}
transpose(dst, dst);
dst.copyTo(_dst);
}
protected:
/*!
* Private method used by process method. The function
......
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