Skip to content

Commit 9b5aa2a

Browse files
committed
Merge pull request #1147 from lhelontra:edgenms
2 parents 74e439f + da280ac commit 9b5aa2a

File tree

3 files changed

+211
-9
lines changed

3 files changed

+211
-9
lines changed

modules/ximgproc/include/opencv2/ximgproc/structured_edge_detection.hpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,31 @@ class CV_EXPORTS_W StructuredEdgeDetection : public Algorithm
102102
103103
The algorithm underlies this function is much more robust to texture presence, than common
104104
approaches, e.g. Sobel
105-
@param src source image (RGB, float, in [0;1]) to detect edges
106-
@param dst destination image (grayscale, float, in [0;1]) where edges are drawn
105+
@param _src source image (RGB, float, in [0;1]) to detect edges
106+
@param _dst destination image (grayscale, float, in [0;1]) where edges are drawn
107107
@sa Sobel, Canny
108108
*/
109-
CV_WRAP virtual void detectEdges(const Mat &src, CV_OUT Mat &dst) const = 0;
109+
CV_WRAP virtual void detectEdges(cv::InputArray _src, cv::OutputArray _dst) const = 0;
110+
111+
/** @brief The function computes orientation from edge image.
112+
113+
@param _src edge image.
114+
@param _dst orientation image.
115+
*/
116+
CV_WRAP virtual void computeOrientation(cv::InputArray _src, cv::OutputArray _dst) const = 0;
117+
118+
119+
/** @brief The function edgenms in edge image and suppress edges where edge is stronger in orthogonal direction.
120+
121+
@param edge_image edge image from detectEdges function.
122+
@param orientation_image orientation image from computeOrientation function.
123+
@param _dst suppressed image (grayscale, float, in [0;1])
124+
@param r radius for NMS suppression.
125+
@param s radius for boundary suppression.
126+
@param m multiplier for conservative suppression.
127+
@param isParallel enables/disables parallel computing.
128+
*/
129+
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;
110130
};
111131

112132
/*!

modules/ximgproc/samples/structured_edge_detection.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,20 @@ int main( int argc, const char** argv )
5959
createStructuredEdgeDetection(modelFilename);
6060
pDollar->detectEdges(image, edges);
6161

62+
// computes orientation from edge map
63+
Mat orientation_map;
64+
pDollar->computeOrientation(edges, orientation_map);
65+
66+
// suppress edges
67+
Mat edge_nms;
68+
pDollar->edgesNms(edges, orientation_map, edge_nms, 2, 0, 1, true);
69+
6270
if ( outFilename.size() == 0 )
6371
{
6472
namedWindow("edges", 1);
6573
imshow("edges", edges);
74+
namedWindow("edges nms", 1);
75+
imshow("edges nms", edge_nms);
6676
waitKey(0);
6777
}
6878
else

modules/ximgproc/src/structured_edge_detection.cpp

Lines changed: 178 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,81 @@ static void gradientHist(const cv::Mat &src, cv::Mat &magnitude, cv::Mat &histog
255255
}
256256
}
257257

258+
/*!
259+
* The class parallelizing the edgenms algorithm.
260+
*
261+
* \param E : edge image
262+
* \param O : orientation image
263+
* \param dst : destination image
264+
* \param r : radius for NMS suppression
265+
* \param s : radius for boundary suppression
266+
* \param m : multiplier for conservative suppression
267+
*/
268+
class NmsInvoker : public cv::ParallelLoopBody
269+
{
270+
271+
private:
272+
const cv::Mat &E;
273+
const cv::Mat &O;
274+
cv::Mat &dst;
275+
const int r;
276+
const float m;
277+
278+
public:
279+
NmsInvoker(const cv::Mat &_E, const cv::Mat &_O, cv::Mat &_dst, const int _r, const float _m)
280+
: E(_E), O(_O), dst(_dst), r(_r), m(_m)
281+
{
282+
}
283+
284+
void operator()(const cv::Range &range) const
285+
{
286+
for (int x = range.start; x < range.end; x++)
287+
{
288+
const float *e_ptr = E.ptr<float>(x);
289+
const float *o_ptr = O.ptr<float>(x);
290+
float *dst_ptr = dst.ptr<float>(x);
291+
for (int y=0; y < E.cols; y++)
292+
{
293+
float e = e_ptr[y];
294+
dst_ptr[y] = e;
295+
if (!e) continue;
296+
e *= m;
297+
float coso = cos(o_ptr[y]);
298+
float sino = sin(o_ptr[y]);
299+
for (int d=-r; d<=r; d++)
300+
{
301+
if (d)
302+
{
303+
float xdcos = x+d*coso;
304+
float ydsin = y+d*sino;
305+
xdcos = xdcos < 0 ? 0 : (xdcos > E.rows - 1.001f ? E.rows - 1.001f : xdcos);
306+
ydsin = ydsin < 0 ? 0 : (ydsin > E.cols - 1.001f ? E.cols - 1.001f : ydsin);
307+
int x0 = (int)xdcos;
308+
int y0 = (int)ydsin;
309+
int x1 = x0 + 1;
310+
int y1 = y0 + 1;
311+
float dx0 = xdcos - x0;
312+
float dy0 = ydsin - y0;
313+
float dx1 = 1 - dx0;
314+
float dy1 = 1 - dy0;
315+
float e0 = E.at<float>(x0, y0) * dx1 * dy1 +
316+
E.at<float>(x1, y0) * dx0 * dy1 +
317+
E.at<float>(x0, y1) * dx1 * dy0 +
318+
E.at<float>(x1, y1) * dx0 * dy0;
319+
320+
if(e < e0)
321+
{
322+
dst_ptr[y] = 0;
323+
break;
324+
}
325+
}
326+
}
327+
328+
}
329+
}
330+
}
331+
};
332+
258333
/********************* RFFeatureGetter class *********************/
259334

260335
namespace cv
@@ -445,21 +520,23 @@ class StructuredEdgeDetectionImpl : public StructuredEdgeDetection
445520
/*!
446521
* The function detects edges in src and draw them to dst
447522
*
448-
* \param src : source image (RGB, float, in [0;1]) to detect edges
449-
* \param dst : destination image (grayscale, float, in [0;1])
523+
* \param _src : source image (RGB, float, in [0;1]) to detect edges
524+
* \param _dst : destination image (grayscale, float, in [0;1])
450525
* where edges are drawn
451526
*/
452-
void detectEdges(const cv::Mat &src, cv::Mat &dst) const
527+
void detectEdges(cv::InputArray _src, cv::OutputArray _dst) const
453528
{
454-
CV_Assert( src.type() == CV_32FC3 );
529+
CV_Assert( _src.type() == CV_32FC3 );
455530

456-
dst.create( src.size(), cv::DataType<float>::type );
531+
_dst.createSameSize( _src, cv::DataType<float>::type );
532+
_dst.setTo(0);
533+
Mat dst = _dst.getMat();
457534

458535
int padding = ( __rf.options.patchSize
459536
- __rf.options.patchInnerSize )/2;
460537

461538
cv::Mat nSrc;
462-
copyMakeBorder( src, nSrc, padding, padding,
539+
copyMakeBorder( _src, nSrc, padding, padding,
463540
padding, padding, BORDER_REFLECT );
464541

465542
NChannelsMat features;
@@ -472,6 +549,101 @@ class StructuredEdgeDetectionImpl : public StructuredEdgeDetection
472549
predictEdges( features, dst );
473550
}
474551

552+
/*!
553+
* The function computes orientation from edge image.
554+
*
555+
* \param src : edge image.
556+
* \param dst : orientation image.
557+
* \param r : filter radius.
558+
*/
559+
void computeOrientation(cv::InputArray _src, cv::OutputArray _dst) const
560+
{
561+
CV_Assert( _src.type() == CV_32FC1 );
562+
563+
cv::Mat Oxx, Oxy, Oyy;
564+
565+
_dst.createSameSize( _src, _src.type() );
566+
_dst.setTo(0);
567+
568+
Mat src = _src.getMat();
569+
cv::Mat E_conv = imsmooth(src, __rf.options.gradientNormalizationRadius);
570+
571+
Sobel(E_conv, Oxx, -1, 2, 0);
572+
Sobel(E_conv, Oxy, -1, 1, 1);
573+
Sobel(E_conv, Oyy, -1, 0, 2);
574+
575+
Mat dst = _dst.getMat();
576+
float *o = dst.ptr<float>();
577+
float *oxx = Oxx.ptr<float>();
578+
float *oxy = Oxy.ptr<float>();
579+
float *oyy = Oyy.ptr<float>();
580+
for (int i = 0; i < dst.rows * dst.cols; i++)
581+
{
582+
int xysign = -((oxy[i] > 0) - (oxy[i] < 0));
583+
o[i] = (atan((oyy[i] * xysign / (oxx[i] + 1e-5))) > 0) ? (float) fmod(
584+
atan((oyy[i] * xysign / (oxx[i] + 1e-5))), M_PI) : (float) fmod(
585+
atan((oyy[i] * xysign / (oxx[i] + 1e-5))) + M_PI, M_PI);
586+
}
587+
}
588+
589+
/*!
590+
* The function suppress edges where edge is stronger in orthogonal direction
591+
* \param edge_image : edge image from detectEdges function.
592+
* \param orientation_image : orientation image from computeOrientation function.
593+
* \param _dst : suppressed image (grayscale, float, in [0;1])
594+
* \param r : radius for NMS suppression.
595+
* \param s : radius for boundary suppression.
596+
* \param m : multiplier for conservative suppression.
597+
* \param isParallel: enables/disables parallel computing.
598+
*/
599+
void edgesNms(cv::InputArray edge_image, cv::InputArray orientation_image, cv::OutputArray _dst, int r, int s, float m, bool isParallel) const
600+
{
601+
CV_Assert(edge_image.type() == CV_32FC1);
602+
CV_Assert(orientation_image.type() == CV_32FC1);
603+
604+
cv::Mat E = edge_image.getMat();
605+
cv::Mat O = orientation_image.getMat();
606+
cv::Mat E_t = E.t();
607+
cv::Mat O_t = O.t();
608+
609+
cv::Mat dst = _dst.getMat();
610+
dst.create(E.cols, E.rows, E.type());
611+
dst.setTo(0);
612+
613+
cv::Range sizeRange = cv::Range(0, E_t.rows);
614+
NmsInvoker body = NmsInvoker(E_t, O_t, dst, r, m);
615+
if (isParallel)
616+
{
617+
cv::parallel_for_(sizeRange, body);
618+
} else
619+
{
620+
body(sizeRange);
621+
}
622+
623+
s = s > E_t.rows / 2 ? E_t.rows / 2 : s;
624+
s = s > E_t.cols / 2 ? E_t.cols / 2 : s;
625+
for (int x=0; x<s; x++)
626+
{
627+
for (int y=0; y<E_t.cols; y++)
628+
{
629+
dst.at<float>(x, y) *= x / (float)s;
630+
dst.at<float>(E_t.rows-1-x, y) *= x / (float)s;
631+
}
632+
}
633+
634+
for (int x=0; x < E_t.rows; x++)
635+
{
636+
for (int y=0; y < s; y++)
637+
{
638+
dst.at<float>(x, y) *= y / (float)s;
639+
dst.at<float>(x, E_t.cols-1-y) *= y / (float)s;
640+
}
641+
}
642+
transpose(dst, dst);
643+
dst.copyTo(_dst);
644+
}
645+
646+
475647
protected:
476648
/*!
477649
* Private method used by process method. The function

0 commit comments

Comments
 (0)