From 43f3eb9ffc91e03458d391eed0dbb8001f34ce9a Mon Sep 17 00:00:00 2001 From: biagio montesano Date: Tue, 29 Jul 2014 16:51:25 +0200 Subject: [PATCH] New warnings corrected --- modules/line_descriptor/doc/LSDDetector.rst | 52 + .../line_descriptor/doc/line_descriptor.rst | 2 + .../doc/pics/lines_cameraman_edl.png | Bin 0 -> 167929 bytes .../include/opencv2/line_descriptor.hpp | 1 - .../opencv2/line_descriptor/descriptor.hpp | 113 +- .../samples/compute_descriptors.cpp | 25 +- .../samples/lines_extraction.cpp | 9 +- .../samples/radius_matching.cpp | 9 - modules/line_descriptor/src/LSDDetector.cpp | 215 ++ .../line_descriptor/src/binary_descriptor.cpp | 914 +----- .../src/binary_descriptor_matcher.cpp | 4 +- .../line_descriptor/src/ed_line_detector.cpp | 2597 +++++++++-------- .../src/line_descriptor_init.cpp | 2 + modules/line_descriptor/src/precomp.hpp | 2 - 14 files changed, 1786 insertions(+), 2159 deletions(-) create mode 100644 modules/line_descriptor/doc/LSDDetector.rst create mode 100644 modules/line_descriptor/doc/pics/lines_cameraman_edl.png create mode 100644 modules/line_descriptor/src/LSDDetector.cpp diff --git a/modules/line_descriptor/doc/LSDDetector.rst b/modules/line_descriptor/doc/LSDDetector.rst new file mode 100644 index 000000000..d4cd1467a --- /dev/null +++ b/modules/line_descriptor/doc/LSDDetector.rst @@ -0,0 +1,52 @@ +.. _LSDDetector: + +Line Segments Detector +====================== + + +Lines extraction methodology +---------------------------- + +The lines extraction methodology described in the following is mainly based on [EDL]_. +The extraction starts with a Gaussian pyramid generated from an original image, downsampled N-1 times, blurred N times, to obtain N layers (one for each octave), with layer 0 corresponding to input image. Then, from each layer (octave) in the pyramid, lines are extracted using LSD algorithm. + +Differently from EDLine lines extractor used in original article, LSD furnishes information only about lines extremes; thus, additional information regarding slope and equation of line are computed via analytic methods. The number of pixels is obtained using `LineIterator `_. Extracted lines are returned in the form of KeyLine objects, but since extraction is based on a method different from the one used in `BinaryDescriptor `_ class, data associated to a line's extremes in original image and in octave it was extracted from, coincide. KeyLine's field *class_id* is used as an index to indicate the order of extraction of a line inside a single octave. + + +LSDDetector::createLSDDetector +------------------------------ + +Creates ad LSDDetector object, using smart pointers. + +.. ocv:function:: Ptr LSDDetector::createLSDDetector() + + +LSDDetector::detect +------------------- + +Detect lines inside an image. + +.. ocv:function:: void LSDDetector::detect( const Mat& image, std::vector& keylines, int scale, int numOctaves, const Mat& mask=Mat()) + +.. ocv:function:: void LSDDetector::detect( const std::vector& images, std::vector >& keylines, int scale, int numOctaves, const std::vector& masks=std::vector() ) const + + :param image: input image + + :param images: input images + + :param keylines: vector or set of vectors that will store extracted lines for one or more images + + :param mask: mask matrix to detect only KeyLines of interest + + :param masks: vector of mask matrices to detect only KeyLines of interest from each input image + + :param scale: scale factor used in pyramids generation + + :param numOctaves: number of octaves inside pyramid + + +References +---------- + +.. [EDL] Von Gioi, R. Grompone, et al. *LSD: A fast line segment detector with a false detection control*, IEEE Transactions on Pattern Analysis and Machine Intelligence 32.4 (2010): 722-732. + diff --git a/modules/line_descriptor/doc/line_descriptor.rst b/modules/line_descriptor/doc/line_descriptor.rst index 1c7a45434..1eaeace17 100644 --- a/modules/line_descriptor/doc/line_descriptor.rst +++ b/modules/line_descriptor/doc/line_descriptor.rst @@ -125,6 +125,8 @@ References .. [LBD] Zhang, Lilian, and Reinhard Koch. *An efficient and robust line segment matching approach based on LBD descriptor and pairwise geometric consistency*, Journal of Visual Communication and Image Representation 24.7 (2013): 794-805. +.. [EDL] Von Gioi, R. Grompone, et al. *LSD: A fast line segment detector with a false detection control*, IEEE Transactions on Pattern Analysis and Machine Intelligence 32.4 (2010): 722-732. + Summary ------- diff --git a/modules/line_descriptor/doc/pics/lines_cameraman_edl.png b/modules/line_descriptor/doc/pics/lines_cameraman_edl.png new file mode 100644 index 0000000000000000000000000000000000000000..8542495fb3365643d70d96af1dfe1ef4e6ae5c13 GIT binary patch literal 167929 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. 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�d+~H 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

q-=Ps|%+ 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 keypoints, const Mat& mask = Mat(), int flags = LineDetectionMode::LSD_DETECTOR ); + void detect( const Mat& image, CV_OUT std::vector& keypoints, const Mat& mask = Mat() ); /* requires line detection (more than one image) */ - void detect( const std::vector& images, std::vector >& keylines, const std::vector& masks = std::vector(), - int flags = LineDetectionMode::LSD_DETECTOR ) const; + void detect( const std::vector& images, std::vector >& keylines, const std::vector& masks = + std::vector() ) const; /* requires descriptors computation (only one image) */ CV_WRAP - void compute( const Mat& image, CV_OUT CV_IN_OUT std::vector& keylines, CV_OUT Mat& descriptors, bool returnFloatDescr = false, int flags = - LineDetectionMode::LSD_DETECTOR ) const; + void compute( const Mat& image, CV_OUT CV_IN_OUT std::vector& keylines, CV_OUT Mat& descriptors, bool returnFloatDescr = false ) const; /* requires descriptors computation (more than one image) */ void compute( const std::vector& images, std::vector >& keylines, std::vector& descriptors, bool returnFloatDescr = - false, - int flags = LineDetectionMode::LSD_DETECTOR ) const; + false ) const; - /*return descriptor size */ + /* returns descriptor size */ int descriptorSize() const; - /* return data type */ + /* returns data type */ int descriptorType() const; - /* return norm mode */ + /* returns norm mode */ int defaultNorm() const; - /* check whether Gaussian pyramids were created */ - bool empty() const; - /* definition of operator () */ CV_WRAP_AS(detectAndCompute) virtual void operator()( InputArray image, InputArray mask, CV_OUT std::vector& keylines, OutputArray descriptors, - bool useProvidedKeyLines = false, bool returnFloatDescr = false, int flags = LineDetectionMode::LSD_DETECTOR ) const; + bool useProvidedKeyLines = false, bool returnFloatDescr = false ) const; protected: /* implementation of line detection */ - virtual void detectImpl( const Mat& imageSrc, std::vector& keylines, int flags, const Mat& mask = Mat() ) const; + virtual void detectImpl( const Mat& imageSrc, std::vector& keylines, const Mat& mask = Mat() ) const; /* implementation of descriptors' computation */ - virtual void computeImpl( const Mat& imageSrc, std::vector& keylines, Mat& descriptors, bool returnFloatDescr, int flags ) const; + virtual void computeImpl( const Mat& imageSrc, std::vector& keylines, Mat& descriptors, bool returnFloatDescr ) const; /* function inherited from Algorithm */ AlgorithmInfo* info() const; @@ -217,49 +203,24 @@ class CV_EXPORTS_W BinaryDescriptor : public Algorithm /* conversion of an LBD descriptor to its binary representation */ unsigned char binaryConversion( float* f1, float* f2 ); - /* compute LBD descriptors */ - int computeLBD( ScaleLines &keyLines, int flags ); - /* compute LBD descriptors using EDLine extractor */ - int computeLBD_EDL( ScaleLines &keyLines ); + int computeLBD( ScaleLines &keyLines ); - /* compute Gaussian pyramid of input image */ - void computeGaussianPyramid( const Mat& image ); - - /* gather lines in groups. + /* gathers lines in groups using EDLine extractor. Each group contains the same line, detected in different octaves */ - int OctaveKeyLines( ScaleLines &keyLines ); - - /* gather lines in groups using EDLine extractor. - Each group contains the same line, detected in different octaves */ - int OctaveKeyLines_EDL( cv::Mat& image, ScaleLines &keyLines ); - - /* get coefficients of line passing by two points (in line_extremes) */ - void getLineParameters( cv::Vec4i &line_extremes, cv::Vec3i &lineParams ); - - /* compute the angle between line and X axis */ - float getLineDirection( cv::Vec3i &lineParams ); + int OctaveKeyLines( cv::Mat& image, ScaleLines &keyLines ); /* the local gaussian coefficient applied to the orthogonal line direction within each band */ std::vector gaussCoefL_; - /* the global gaussian coefficient applied to each Row within line support region */ + /* the global gaussian coefficient applied to each row within line support region */ std::vector gaussCoefG_; - /* vector to store horizontal and vertical derivatives of octave images */ - std::vector dxImg_vector, dyImg_vector; - - /* vectot to store sizes of octave images */ - std::vector images_sizes; - - /* structure to store lines extracted from each octave image */ - std::vector > extractedLines; - /* descriptor parameters */ Params params; - /* vector to store the Gaussian pyramid od an input image */ - std::vector octaveImages; + /* vector of sizes of downsampled and blurred images */ + std::vector images_sizes; /*For each octave of image, we define an EDLineDetector, because we can get gradient images (dxImg, dyImg, gImg) *from the EDLineDetector class without extra computation cost. Another reason is that, if we use @@ -269,6 +230,42 @@ class CV_EXPORTS_W BinaryDescriptor : public Algorithm }; +class CV_EXPORTS_W LSDDetector : public Algorithm +{ + public: + + /* constructor */ + LSDDetector() + { + } + ; + + /* constructor with smart pointer */ + static Ptr createLSDDetector(); + + /* requires line detection (only one image) */ + CV_WRAP + void detect( const Mat& image, CV_OUT std::vector& keypoints, int scale, int numOctaves, const Mat& mask = Mat() ); + + /* requires line detection (more than one image) */ + void detect( const std::vector& images, std::vector >& keylines, int scale, int numOctaves, + const std::vector& masks = std::vector() ) const; + + private: + /* compute Gaussian pyramid of input image */ + void computeGaussianPyramid( const Mat& image, int numOctaves, int scale ); + + /* implementation of line detection */ + void detectImpl( const Mat& imageSrc, std::vector& keylines, int numOctaves, int scale, const Mat& mask ) const; + + /* matrices for Gaussian pyramids */ + std::vector gaussianPyrs; + + protected: + /* function inherited from Algorithm */ + AlgorithmInfo* info() const; +}; + class CV_EXPORTS_W BinaryDescriptorMatcher : public Algorithm { diff --git a/modules/line_descriptor/samples/compute_descriptors.cpp b/modules/line_descriptor/samples/compute_descriptors.cpp index 96c05a222..c7a6249aa 100644 --- a/modules/line_descriptor/samples/compute_descriptors.cpp +++ b/modules/line_descriptor/samples/compute_descriptors.cpp @@ -61,20 +61,19 @@ static void help() << std::endl; } -inline void writeMat(cv::Mat m, std::string name, int n) +inline void writeMat( cv::Mat m, std::string name, int n ) { - std::stringstream ss; - std::string s; - ss << n; - ss >> s; - std::string fileNameConf = name + s; - cv::FileStorage fsConf(fileNameConf, cv::FileStorage::WRITE); - fsConf << "m" << m; + std::stringstream ss; + std::string s; + ss << n; + ss >> s; + std::string fileNameConf = name + s; + cv::FileStorage fsConf( fileNameConf, cv::FileStorage::WRITE ); + fsConf << "m" << m; - fsConf.release(); + fsConf.release(); } - int main( int argc, char** argv ) { /* get parameters from command line */ @@ -102,7 +101,7 @@ int main( int argc, char** argv ) /* compute lines */ std::vector keylines; - bd->detect( imageMat, keylines, mask, 1 ); + bd->detect( imageMat, keylines, mask ); /* select only lines from first octave */ std::vector octave0; @@ -115,7 +114,7 @@ int main( int argc, char** argv ) /* compute descriptors */ cv::Mat descriptors; - bd->compute( imageMat, octave0, descriptors, false, 1 ); - writeMat(descriptors, "bd_descriptors", 0); + bd->compute( imageMat, octave0, descriptors, 1); + writeMat( descriptors, "bd_descriptors", 0 ); } diff --git a/modules/line_descriptor/samples/lines_extraction.cpp b/modules/line_descriptor/samples/lines_extraction.cpp index b4e2167f1..227bbc9c7 100644 --- a/modules/line_descriptor/samples/lines_extraction.cpp +++ b/modules/line_descriptor/samples/lines_extraction.cpp @@ -80,7 +80,7 @@ int main( int argc, char** argv ) std::cout << "Error, image could not be loaded. Please, check its path" << std::endl; } - /* create a ramdom binary mask */ + /* create a random binary mask */ cv::Mat mask = Mat::ones( imageMat.size(), CV_8UC1 ); /* create a pointer to a BinaryDescriptor object with deafult parameters */ @@ -91,16 +91,15 @@ int main( int argc, char** argv ) /* extract lines */ cv::Mat output = imageMat.clone(); - bd->detect( imageMat, lines, mask, 1 ); + bd->detect( imageMat, lines, mask ); /* draw lines extracted from octave 0 */ - if( output.channels() == 1 ) cvtColor( output, output, COLOR_GRAY2BGR ); for ( size_t i = 0; i < lines.size(); i++ ) { KeyLine kl = lines[i]; - if( kl.octave == 0 /*&& kl.response >0.08*/) + if( kl.octave == 0) { /* get a random color */ int R = ( rand() % (int) ( 255 + 1 ) ); @@ -112,7 +111,7 @@ int main( int argc, char** argv ) Point pt2 = Point( kl.endPointX, kl.endPointY ); /* draw line */ - line( output, pt1, pt2, Scalar( B, G, R ), 5 ); + line( output, pt1, pt2, Scalar( B, G, R ), 3 ); } } diff --git a/modules/line_descriptor/samples/radius_matching.cpp b/modules/line_descriptor/samples/radius_matching.cpp index 164fab5ac..e00aac099 100644 --- a/modules/line_descriptor/samples/radius_matching.cpp +++ b/modules/line_descriptor/samples/radius_matching.cpp @@ -128,13 +128,4 @@ int main( int argc, char** argv ) std::vector > matches; bdm->radiusMatch( queries, matches, 30 ); - /* print matches */ - for ( size_t q = 0; q < matches.size(); q++ ) - { - for ( size_t m = 0; m < matches[q].size(); m++ ) - { - DMatch dm = matches[q][m]; - std::cout << "Descriptor: " << q << " Image: " << dm.imgIdx << " Distance: " << dm.distance << std::endl; - } - } } diff --git a/modules/line_descriptor/src/LSDDetector.cpp b/modules/line_descriptor/src/LSDDetector.cpp new file mode 100644 index 000000000..0c174b741 --- /dev/null +++ b/modules/line_descriptor/src/LSDDetector.cpp @@ -0,0 +1,215 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// + // + // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. + // + // 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 + // + // Copyright (C) 2014, Biagio Montesano, all rights reserved. + // Third party copyrights are property of their respective owners. + // + // Redistribution and use in source and binary forms, with or without modification, + // are permitted provided that the following conditions are met: + // + // * Redistribution's of source code must retain the above copyright notice, + // this list of conditions and the following disclaimer. + // + // * Redistribution's 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. + // + // * The name of the copyright holders may not 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 the Intel Corporation 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. + // + //M*/ + +#include "precomp.hpp" + +using namespace cv; + +Ptr LSDDetector::createLSDDetector() +{ + return Ptr( new LSDDetector() ); +} + +/* compute Gaussian pyramid of input image */ +void LSDDetector::computeGaussianPyramid( const Mat& image, int numOctaves, int scale ) +{ + /* clear class fields */ + gaussianPyrs.clear(); + + /* insert input image into pyramid */ + cv::Mat currentMat = image.clone(); + cv::GaussianBlur( currentMat, currentMat, cv::Size( 5, 5 ), 1 ); + gaussianPyrs.push_back( currentMat ); + + /* fill Gaussian pyramid */ + for ( int pyrCounter = 1; pyrCounter < numOctaves; pyrCounter++ ) + { + /* compute and store next image in pyramid and its size */ + pyrDown( currentMat, currentMat, Size( currentMat.cols / scale, currentMat.rows / scale ) ); + gaussianPyrs.push_back( currentMat ); + } +} + +/* check lines' extremes */ +inline void checkLineExtremes( cv::Vec4i& extremes, cv::Size imageSize ) +{ + + if( extremes[0] < 0 ) + extremes[0] = 0; + + if( extremes[0] >= imageSize.width ) + extremes[0] = imageSize.width - 1; + + if( extremes[2] < 0 ) + extremes[2] = 0; + + if( extremes[2] >= imageSize.width ) + extremes[2] = imageSize.width - 1; + + if( extremes[1] < 0 ) + extremes[1] = 0; + + if( extremes[1] >= imageSize.height ) + extremes[1] = imageSize.height - 1; + + if( extremes[3] < 0 ) + extremes[3] = 0; + + if( extremes[3] >= imageSize.height ) + extremes[3] = imageSize.height - 1; +} + +/* requires line detection (only one image) */ +void LSDDetector::detect( const Mat& image, CV_OUT std::vector& keylines, int scale, int numOctaves, const Mat& mask ) +{ + if( mask.data != NULL && ( mask.size() != image.size() || mask.type() != CV_8UC1 ) ) + { + std::cout << "Mask error while detecting lines: " << "please check its dimensions and that data type is CV_8UC1" << std::endl; + CV_Assert( false ); + } + + else + detectImpl( image, keylines, numOctaves, scale, mask ); +} + +/* requires line detection (more than one image) */ +void LSDDetector::detect( const std::vector& images, std::vector >& keylines, int scale, int numOctaves, + const std::vector& masks ) const +{ + /* detect lines from each image */ + for ( size_t counter = 0; counter < images.size(); counter++ ) + { + if( masks[counter].data != NULL && ( masks[counter].size() != images[counter].size() || masks[counter].type() != CV_8UC1 ) ) + { + std::cout << "Masks error while detecting lines: " << "please check their dimensions and that data types are CV_8UC1" << std::endl; + CV_Assert( false ); + } + + detectImpl( images[counter], keylines[counter], numOctaves, scale, masks[counter] ); + } +} + +/* implementation of line detection */ +void LSDDetector::detectImpl( const Mat& imageSrc, std::vector& keylines, int numOctaves, int scale, const Mat& mask ) const +{ + cv::Mat image; + if( imageSrc.channels() != 1 ) + cvtColor( imageSrc, image, COLOR_BGR2GRAY ); + else + image = imageSrc.clone(); + + /*check whether image depth is different from 0 */ + if( image.depth() != 0 ) + { + std::cout << "Warning, depth image!= 0" << std::endl; + CV_Assert( false ); + } + + /* create a pointer to self */ + LSDDetector *lsd = const_cast( this ); + + /* compute Gaussian pyramids */ + lsd->computeGaussianPyramid( image, numOctaves, scale ); + + /* create an LSD extractor */ + cv::Ptr ls = cv::createLineSegmentDetector( cv::LSD_REFINE_ADV ); + + /* prepare a vector to host extracted segments */ + std::vector > lines_lsd; + + /* extract lines */ + for ( int i = 0; i < numOctaves; i++ ) + { + std::vector octave_lines; + ls->detect( gaussianPyrs[i], octave_lines ); + lines_lsd.push_back( octave_lines ); + } + + /* create keylines */ + for ( int j = 0; j < (int) lines_lsd.size(); j++ ) + { + for ( int k = 0; k < (int) lines_lsd[j].size(); k++ ) + { + KeyLine kl; + cv::Vec4i extremes = lines_lsd[j][k]; + + /* check data validity */ + checkLineExtremes( extremes, gaussianPyrs[j].size() ); + + /* fill KeyLine's fields */ + kl.startPointX = extremes[0]; + kl.startPointY = extremes[1]; + kl.endPointX = extremes[2]; + kl.endPointY = extremes[3]; + kl.sPointInOctaveX = extremes[0]; + kl.sPointInOctaveY = extremes[1]; + kl.ePointInOctaveX = extremes[2]; + kl.ePointInOctaveY = extremes[3]; + kl.lineLength = sqrt( pow( extremes[0] - extremes[2], 2 ) + pow( extremes[1] - extremes[3], 2 ) ); + + /* compute number of pixels covered by line */ + LineIterator li( gaussianPyrs[j], Point( extremes[0], extremes[1] ), Point( extremes[2], extremes[3] ) ); + kl.numOfPixels = li.count; + + kl.angle = atan2( ( kl.endPointY - kl.startPointY ), ( kl.endPointX - kl.startPointX ) ); + kl.class_id = k; + kl.octave = j; + kl.size = ( kl.endPointX - kl.startPointX ) * ( kl.endPointY - kl.startPointY ); + kl.response = kl.lineLength / max( gaussianPyrs[j].cols, gaussianPyrs[j].rows ); + kl.pt = Point( ( kl.endPointX + kl.startPointX ) / 2, ( kl.endPointY + kl.startPointY ) / 2 ); + + keylines.push_back( kl ); + } + } + + /* delete undesired KeyLines, according to input mask */ + if( !mask.empty() ) + { + for ( size_t keyCounter = 0; keyCounter < keylines.size(); keyCounter++ ) + { + KeyLine kl = keylines[keyCounter]; + if( mask.at( kl.startPointY, kl.startPointX ) == 0 && mask.at( kl.endPointY, kl.endPointX ) == 0 ) + keylines.erase( keylines.begin() + keyCounter ); + } + } + +} + diff --git a/modules/line_descriptor/src/binary_descriptor.cpp b/modules/line_descriptor/src/binary_descriptor.cpp index 724a38882..e9113f0f9 100644 --- a/modules/line_descriptor/src/binary_descriptor.cpp +++ b/modules/line_descriptor/src/binary_descriptor.cpp @@ -154,9 +154,10 @@ BinaryDescriptor::BinaryDescriptor( const BinaryDescriptor::Params ¶meters ) params( parameters ) { - /* reserve enough space for EDLine objects */ + /* reserve enough space for EDLine objects and images in Gaussian pyramid */ edLineVec_.resize( params.numOfOctave_ ); images_sizes.resize( params.numOfOctave_ ); + for ( unsigned int i = 0; i < params.numOfOctave_; i++ ) edLineVec_[i] = new EDLineDetector; @@ -196,7 +197,7 @@ BinaryDescriptor::BinaryDescriptor( const BinaryDescriptor::Params ¶meters ) /* definition of operator () */ void BinaryDescriptor::operator()( InputArray image, InputArray mask, CV_OUT std::vector& keylines, OutputArray descriptors, - bool useProvidedKeyLines, bool returnFloatDescr, int flags ) const + bool useProvidedKeyLines, bool returnFloatDescr ) const { /* create some matrix objects */ @@ -214,10 +215,10 @@ void BinaryDescriptor::operator()( InputArray image, InputArray mask, CV_OUT std /* require drawing KeyLines detection if demanded */ if( !useProvidedKeyLines ) - detectImpl( imageMat, keylines, flags, maskMat ); + detectImpl( imageMat, keylines, maskMat ); /* compute descriptors */ - computeImpl( imageMat, keylines, descrMat, returnFloatDescr, flags ); + computeImpl( imageMat, keylines, descrMat, returnFloatDescr ); } BinaryDescriptor::~BinaryDescriptor() @@ -254,12 +255,6 @@ int BinaryDescriptor::descriptorSize() const return 32 * 8; } -/* check whether Gaussian pyramids were created */ -bool BinaryDescriptor::empty() const -{ - return octaveImages.empty(); -} - /* power function with error management */ static inline int get2Pow( int i ) { @@ -287,109 +282,8 @@ unsigned char BinaryDescriptor::binaryConversion( float* f1, float* f2 ) } -/* get coefficients of line passing by two points in (line_extremes) */ -void BinaryDescriptor::getLineParameters( cv::Vec4i& line_extremes, cv::Vec3i& lineParams ) -{ - int x1 = line_extremes[0]; - int x2 = line_extremes[2]; - int y1 = line_extremes[1]; - int y2 = line_extremes[3]; - - /* line is parallel to Y axis */ - if( x1 == x2 ) - { - lineParams[0] = 1; - lineParams[1] = 0; - lineParams[2] = x1 /* or x2 */; - } - - /* line is parallel to X axis */ - else if( y1 == y2 ) - { - lineParams[0] = 0; - lineParams[1] = 1; - lineParams[2] = y1 /* or y2 */; - } - - /* line is not parallel to any axis */ - else - { - lineParams[0] = y1 - y2; - lineParams[1] = x2 - x1; - lineParams[2] = -y1 * ( x2 - x1 ) + x1 * ( y2 - y1 ); - } -} -/* compute the angle between line and X axis */ -float BinaryDescriptor::getLineDirection( cv::Vec3i &lineParams ) -{ - /* line is parallel to X axis */ - if( lineParams[0] == 0 ) - return 0; - - /* line is parallel to Y axis */ - else if( lineParams[1] == 0 ) - return M_PI / 2; - - /* line is not parallel to any axis */ - else - return atan2( -lineParams[0], lineParams[1] ); -} - -/* compute Gaussian pyramid of input image */ -void BinaryDescriptor::computeGaussianPyramid( const Mat& image ) -{ - /* clear class fields */ - images_sizes.clear(); - octaveImages.clear(); - extractedLines.clear(); - - /* insert input image into pyramid */ - cv::Mat currentMat = image.clone(); - cv::GaussianBlur( currentMat, currentMat, cv::Size( 5, 5 ), 1 ); - octaveImages.push_back( currentMat ); - images_sizes.push_back( currentMat.size() ); - - /* fill Gaussian pyramid */ - for ( int pyrCounter = 1; pyrCounter < params.numOfOctave_; pyrCounter++ ) - { - /* compute and store next image in pyramid and its size */ - pyrDown( currentMat, currentMat, Size( currentMat.cols / params.reductionRatio, currentMat.rows / params.reductionRatio ) ); - octaveImages.push_back( currentMat ); - images_sizes.push_back( currentMat.size() ); - } -} - -/* check lines' extremes */ -inline void checkLineExtremes( cv::Vec4i& extremes, cv::Size imageSize ) -{ - - if( extremes[0] < 0 ) - extremes[0] = 0; - - if( extremes[0] >= imageSize.width ) - extremes[0] = imageSize.width - 1; - - if( extremes[2] < 0 ) - extremes[2] = 0; - - if( extremes[2] >= imageSize.width ) - extremes[2] = imageSize.width - 1; - - if( extremes[1] < 0 ) - extremes[1] = 0; - - if( extremes[1] >= imageSize.height ) - extremes[1] = imageSize.height - 1; - - if( extremes[3] < 0 ) - extremes[3] = 0; - - if( extremes[3] >= imageSize.height ) - extremes[3] = imageSize.height - 1; -} - /* requires line detection (only one image) */ -void BinaryDescriptor::detect( const Mat& image, CV_OUT std::vector& keylines, const Mat& mask, int flags ) +void BinaryDescriptor::detect( const Mat& image, CV_OUT std::vector& keylines, const Mat& mask ) { if( mask.data != NULL && ( mask.size() != image.size() || mask.type() != CV_8UC1 ) ) { @@ -400,12 +294,11 @@ void BinaryDescriptor::detect( const Mat& image, CV_OUT std::vector& ke } else - detectImpl( image, keylines, flags, mask ); + detectImpl( image, keylines, mask ); } /* requires line detection (more than one image) */ -void BinaryDescriptor::detect( const std::vector& images, std::vector >& keylines, const std::vector& masks, - int flags ) const +void BinaryDescriptor::detect( const std::vector& images, std::vector >& keylines, const std::vector& masks ) const { /* detect lines from each image */ for ( size_t counter = 0; counter < images.size(); counter++ ) @@ -418,11 +311,11 @@ void BinaryDescriptor::detect( const std::vector& images, std::vector& keylines, int flags, const Mat& mask ) const +void BinaryDescriptor::detectImpl( const Mat& imageSrc, std::vector& keylines, const Mat& mask ) const { cv::Mat image; @@ -441,33 +334,9 @@ void BinaryDescriptor::detectImpl( const Mat& imageSrc, std::vector& ke /* create a pointer to self */ BinaryDescriptor *bn = const_cast( this ); - /* compute Gaussian pyramid */ - if( flags == 0 ) - bn->computeGaussianPyramid( image ); - /* detect and arrange lines across octaves */ ScaleLines sl; - Mat m = image.clone(); - cvtColor( m, m, COLOR_GRAY2BGR ); - if( flags == 0 ) - bn->OctaveKeyLines( sl ); - - else - bn->OctaveKeyLines_EDL( image, sl ); - - Mat temp = image.clone(); - cvtColor( temp, temp, COLOR_GRAY2BGR ); - for ( size_t i = 0; i < sl.size(); i++ ) - { - for ( size_t j = 0; j < sl[i].size(); j++ ) - { - OctaveSingleLine tempOSL = sl[i][j]; - line( m, Point( tempOSL.startPointX, tempOSL.startPointY ), Point( tempOSL.endPointX, tempOSL.endPointY ), Scalar( 255, 0, 0 ), 5 ); - } - } - - imshow( "Immagine", m ); - waitKey(); + bn->OctaveKeyLines( image, sl ); /* fill KeyLines vector */ for ( int i = 0; i < (int) sl.size(); i++ ) @@ -480,9 +349,6 @@ void BinaryDescriptor::detectImpl( const Mat& imageSrc, std::vector& ke /* create a KeyLine object */ KeyLine kl; - /* check data validity */ -// cv::Vec4i extremes( osl.startPointX, osl.startPointY, osl.endPointX, osl.endPointY ); -// checkLineExtremes( extremes, imageSrc.size() ); /* fill KeyLine's fields */ kl.startPointX = osl.startPointX; //extremes[0]; kl.startPointY = osl.startPointY; //extremes[1]; @@ -522,22 +388,22 @@ void BinaryDescriptor::detectImpl( const Mat& imageSrc, std::vector& ke } /* requires descriptors computation (only one image) */ -void BinaryDescriptor::compute( const Mat& image, CV_OUT CV_IN_OUT std::vector& keylines, CV_OUT Mat& descriptors, bool returnFloatDescr, - int flags ) const +void BinaryDescriptor::compute( const Mat& image, CV_OUT CV_IN_OUT std::vector& keylines, CV_OUT Mat& descriptors, + bool returnFloatDescr ) const { - computeImpl( image, keylines, descriptors, returnFloatDescr, flags ); + computeImpl( image, keylines, descriptors, returnFloatDescr ); } /* requires descriptors computation (more than one image) */ void BinaryDescriptor::compute( const std::vector& images, std::vector >& keylines, std::vector& descriptors, - bool returnFloatDescr, int flags ) const + bool returnFloatDescr ) const { for ( size_t i = 0; i < images.size(); i++ ) - computeImpl( images[i], keylines[i], descriptors[i], returnFloatDescr, flags ); + computeImpl( images[i], keylines[i], descriptors[i], returnFloatDescr ); } /* implementation of descriptors computation */ -void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& keylines, Mat& descriptors, bool returnFloatDescr, int flags ) const +void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& keylines, Mat& descriptors, bool returnFloatDescr ) const { /* convert input image to gray scale */ cv::Mat image; @@ -620,34 +486,9 @@ void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& k } } - /* compute Gaussian pyramid, if image is new or pyramid was not - computed before */ - BinaryDescriptor *bn = const_cast( this ); - /* all structures cleared in computeGaussianPyramid */ - bn->computeGaussianPyramid( image ); - - /* compute Sobel's derivatives */ - bn->dxImg_vector.clear(); - bn->dyImg_vector.clear(); - - bn->dxImg_vector.resize( params.numOfOctave_ ); - bn->dyImg_vector.resize( params.numOfOctave_ ); - - for ( size_t sobelCnt = 0; sobelCnt < octaveImages.size(); sobelCnt++ ) - { - bn->dxImg_vector[sobelCnt].create( images_sizes[sobelCnt].height, images_sizes[sobelCnt].width, CV_16SC1 ); - bn->dyImg_vector[sobelCnt].create( images_sizes[sobelCnt].height, images_sizes[sobelCnt].width, CV_16SC1 ); - - cv::Sobel( octaveImages[sobelCnt], bn->dxImg_vector[sobelCnt], CV_16SC1, 1, 0, 3 ); - cv::Sobel( octaveImages[sobelCnt], bn->dyImg_vector[sobelCnt], CV_16SC1, 0, 1, 3 ); - } - /* compute LBD descriptors */ - if(flags == 0) - bn->computeLBD( sl, flags ); - - else - bn->computeLBD_EDL(sl); + BinaryDescriptor* bd = const_cast( this ); + bd->computeLBD( sl ); /* resize output matrix */ if( !returnFloatDescr ) @@ -657,9 +498,9 @@ void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& k descriptors = cv::Mat( keylines.size(), NUM_OF_BANDS * 8, CV_32FC1 ); /* fill output matrix with descriptors */ - for ( size_t k = 0; k < sl.size(); k++ ) + for ( int k = 0; k < (int)sl.size(); k++ ) { - for ( size_t lineC = 0; lineC < sl[k].size(); lineC++ ) + for ( int lineC = 0; lineC < (int)sl[k].size(); lineC++ ) { /* get original index of keypoint */ int lineOctave = ( sl[k][lineC] ).octaveCount; @@ -667,7 +508,6 @@ void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& k if( !returnFloatDescr ) { - /* get a pointer to correspondent row in output matrix */ uchar* pointerToRow = descriptors.ptr( originalIndex ); @@ -677,22 +517,21 @@ void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& k /* fill current row with binary descriptor */ for ( int comb = 0; comb < 32; comb++ ) { - *pointerToRow = bn->binaryConversion( &desVec[8 * combinations[comb][0]], &desVec[8 * combinations[comb][1]] ); - + *pointerToRow = bd->binaryConversion( &desVec[8 * combinations[comb][0]], &desVec[8 * combinations[comb][1]] ); pointerToRow++; } } else { - + std::cout << "Descrittori float" <( originalIndex ); /* get LBD data */ std::vector desVec = sl[k][lineC].descriptor; - for ( size_t count = 0; count < desVec.size(); count++ ) + for ( int count = 0; count < (int)desVec.size(); count++ ) { *pointerToRow = desVec[count]; pointerToRow++; @@ -704,337 +543,7 @@ void BinaryDescriptor::computeImpl( const Mat& imageSrc, std::vector& k } -/* gather lines in groups. Each group contains the same line, detected in different octaves */ -int BinaryDescriptor::OctaveKeyLines( ScaleLines &keyLines ) -{ - - /* final number of extracted lines */ - unsigned int numOfFinalLine = 0; - std::vector prec, w_idth, nfa; - - for ( size_t scaleCounter = 0; scaleCounter < octaveImages.size(); scaleCounter++ ) - { - /* get current scaled image */ - cv::Mat currentScaledImage = octaveImages[scaleCounter]; - - /* create an LSD detector and store a pointer to it */ - cv::Ptr ls = cv::createLineSegmentDetector( cv::LSD_REFINE_ADV, 0.8, 0.6, 2.0, 22.5 ); - - /* prepare a vector to host extracted segments */ - std::vector lines_std; - - /* use detector to extract segments */ - ls->detect( currentScaledImage, lines_std, w_idth, prec/*, nfa*/); - - /* store lines extracted from current image */ - extractedLines.push_back( lines_std ); - - /* update lines counter */ - numOfFinalLine += lines_std.size(); - - } - - /* prepare a vector to store octave information associated to extracted lines */ - std::vector octaveLines( numOfFinalLine ); - - /* set lines' counter to 0 for reuse */ - numOfFinalLine = 0; - - /* counter to give a unique ID to lines in LineVecs */ - unsigned int lineIDInScaleLineVec = 0; - - /* floats to compute lines' lengths */ - float dx, dy; - - /* loop over lines extracted from scale 0 (original image) */ - for ( unsigned int lineCurId = 0; lineCurId < extractedLines[0].size(); lineCurId++ ) - { - /* set octave from which it was extracted */ - octaveLines[numOfFinalLine].octaveCount = 0; - /* set ID within its octave */ - octaveLines[numOfFinalLine].lineIDInOctave = lineCurId; - /* set a unique ID among all lines extracted in all octaves */ - octaveLines[numOfFinalLine].lineIDInScaleLineVec = lineIDInScaleLineVec; - - /* compute absolute value of difference between X coordinates of line's extreme points */ - dx = fabs( ( extractedLines[0][lineCurId] )[0] - ( extractedLines[0][lineCurId] )[2] ); - /* compute absolute value of difference between Y coordinates of line's extreme points */ - dy = fabs( ( extractedLines[0][lineCurId] )[1] - ( extractedLines[0][lineCurId] )[3] ); - /* compute line's length */ - octaveLines[numOfFinalLine].lineLength = sqrt( dx * dx + dy * dy ); - - /* update counters */ - numOfFinalLine++; - lineIDInScaleLineVec++; - - } - - /* create and fill an array to store scale factors */ - float *scale = new float[params.numOfOctave_]; - scale[0] = 1; - for ( unsigned int octaveCount = 1; octaveCount < (unsigned int) params.numOfOctave_; octaveCount++ ) - { - scale[octaveCount] = params.reductionRatio * scale[octaveCount - 1]; - } - - /* some variables' declarations */ - float rho1, rho2, tempValue; - float direction, near, length; - unsigned int octaveID, lineIDInOctave; - - /*more than one octave image, organize lines in scale space. - *lines corresponding to the same line in octave images should have the same index in the ScaleLineVec */ - if( params.numOfOctave_ > 1 ) - { - /* some other variables' declarations */ - float twoPI = 2 * M_PI; - unsigned int closeLineID; - float endPointDis, minEndPointDis, minLocalDis, maxLocalDis; - float lp0, lp1, lp2, lp3, np0, np1, np2, np3; - - /* loop over list of octaves */ - for ( unsigned int octaveCount = 1; octaveCount < (unsigned int) params.numOfOctave_; octaveCount++ ) - { - /*for each line in current octave image, find their corresponding lines - in the octaveLines, - give them the same value of lineIDInScaleLineVec*/ - - /* loop over list of lines extracted from current octave */ - for ( unsigned int lineCurId = 0; lineCurId < extractedLines[octaveCount].size(); lineCurId++ ) - { - /* get (scaled) known term from equation of current line */ - cv::Vec3i line_equation; - getLineParameters( extractedLines[octaveCount][lineCurId], line_equation ); - rho1 = scale[octaveCount] * fabs( line_equation[2] ); - - /*nearThreshold depends on the distance of the image coordinate origin to current line. - *so nearThreshold = rho1 * nearThresholdRatio, where nearThresholdRatio = 1-cos(10*pi/180) = 0.0152*/ - tempValue = rho1 * 0.0152; - float nearThreshold = ( tempValue > 6 ) ? ( tempValue ) : 6; - nearThreshold = ( nearThreshold < 12 ) ? nearThreshold : 12; - - /* compute scaled lenght of current line */ - dx = fabs( ( extractedLines[octaveCount][lineCurId] )[0] - ( extractedLines[octaveCount][lineCurId][2] ) ); //x1-x2 - dy = fabs( ( extractedLines[octaveCount][lineCurId] )[1] - ( extractedLines[octaveCount][lineCurId][3] ) ); //y1-y2 - length = scale[octaveCount] * sqrt( dx * dx + dy * dy ); - - minEndPointDis = 12; - /* loop over the octave representations of all lines */ - for ( unsigned int lineNextId = 0; lineNextId < numOfFinalLine; lineNextId++ ) - { - /* if a line from same octave is encountered, - a comparison with it shouldn't be considered */ - octaveID = octaveLines[lineNextId].octaveCount; - if( octaveID == octaveCount ) - break; - - /* take ID (in octave) of line to be compared */ - lineIDInOctave = octaveLines[lineNextId].lineIDInOctave; - - /* compute difference between lines' directions, to check - whether they are parallel */ - cv::Vec3i line_equation_to_compare; - getLineParameters( extractedLines[octaveID][lineIDInOctave], line_equation_to_compare ); - - direction = fabs( getLineDirection( line_equation ) - getLineDirection( line_equation_to_compare ) ); - - /* the angle between two lines are larger than 10degrees - (i.e. 10*pi/180=0.1745), they are not close to parallel */ - if( direction > 0.1745 && ( twoPI - direction > 0.1745 ) ) - continue; - - /*now check whether current line and next line are near to each other. - Get known term from equation to be compared */ - rho2 = scale[octaveID] * fabs( line_equation_to_compare[2] ); - - /* compute difference between known terms */ - near = fabs( rho1 - rho2 ); - - /* two lines are not close in the image */ - if( near > nearThreshold ) - continue; - - /* get the extreme points of the two lines */ - lp0 = scale[octaveCount] * ( extractedLines[octaveCount][lineCurId] )[0]; - lp1 = scale[octaveCount] * ( extractedLines[octaveCount][lineCurId] )[1]; - lp2 = scale[octaveCount] * ( extractedLines[octaveCount][lineCurId] )[2]; - lp3 = scale[octaveCount] * ( extractedLines[octaveCount][lineCurId] )[3]; - np0 = scale[octaveID] * ( extractedLines[octaveID][lineIDInOctave] )[0]; - np1 = scale[octaveID] * ( extractedLines[octaveID][lineIDInOctave] )[1]; - np2 = scale[octaveID] * ( extractedLines[octaveID][lineIDInOctave] )[2]; - np3 = scale[octaveID] * ( extractedLines[octaveID][lineIDInOctave] )[3]; - - /* get the distance between the two leftmost extremes of lines - L1(0,1)<->L2(0,1) */ - dx = lp0 - np0; - dy = lp1 - np1; - endPointDis = sqrt( dx * dx + dy * dy ); - - /* set momentaneously min and max distance between lines to - the one between left extremes */ - minLocalDis = endPointDis; - maxLocalDis = endPointDis; - - /* compute distance between right extremes - L1(2,3)<->L2(2,3) */ - dx = lp2 - np2; - dy = lp3 - np3; - endPointDis = sqrt( dx * dx + dy * dy ); - - /* update (if necessary) min and max distance between lines */ - minLocalDis = ( endPointDis < minLocalDis ) ? endPointDis : minLocalDis; - maxLocalDis = ( endPointDis > maxLocalDis ) ? endPointDis : maxLocalDis; - - /* compute distance between left extreme of current line and - right extreme of line to be compared - L1(0,1)<->L2(2,3) */ - dx = lp0 - np2; - dy = lp1 - np3; - endPointDis = sqrt( dx * dx + dy * dy ); - - /* update (if necessary) min and max distance between lines */ - minLocalDis = ( endPointDis < minLocalDis ) ? endPointDis : minLocalDis; - maxLocalDis = ( endPointDis > maxLocalDis ) ? endPointDis : maxLocalDis; - - /* compute distance between right extreme of current line and - left extreme of line to be compared - L1(2,3)<->L2(0,1) */ - dx = lp2 - np0; - dy = lp3 - np1; - endPointDis = sqrt( dx * dx + dy * dy ); - - /* update (if necessary) min and max distance between lines */ - minLocalDis = ( endPointDis < minLocalDis ) ? endPointDis : minLocalDis; - maxLocalDis = ( endPointDis > maxLocalDis ) ? endPointDis : maxLocalDis; - - /* check whether conditions for considering line to be compared - wremoveInvalidPointsorth to be inserted in the same LineVec are satisfied */ - if( ( maxLocalDis < 0.8 * ( length + octaveLines[lineNextId].lineLength ) ) && ( minLocalDis < minEndPointDis ) ) - { - /* keep the closest line */ - minEndPointDis = minLocalDis; - closeLineID = lineNextId; - } - - } - - /* add current line into octaveLines */ - if( minEndPointDis < 12 ) - octaveLines[numOfFinalLine].lineIDInScaleLineVec = octaveLines[closeLineID].lineIDInScaleLineVec; - else - { - octaveLines[numOfFinalLine].lineIDInScaleLineVec = lineIDInScaleLineVec; - lineIDInScaleLineVec++; - } - - octaveLines[numOfFinalLine].octaveCount = octaveCount; - octaveLines[numOfFinalLine].lineIDInOctave = lineCurId; - octaveLines[numOfFinalLine].lineLength = length; - numOfFinalLine++; - } - } - } - - /* Reorganize the detected lines into keyLines */ - keyLines.clear(); - keyLines.resize( lineIDInScaleLineVec ); - unsigned int tempID; - float s1, e1, s2, e2; - bool shouldChange; - OctaveSingleLine singleLine; - for ( unsigned int lineID = 0; lineID < numOfFinalLine; lineID++ ) - { - lineIDInOctave = octaveLines[lineID].lineIDInOctave; - octaveID = octaveLines[lineID].octaveCount; - - cv::Vec3i tempParams; - getLineParameters( extractedLines[octaveID][lineIDInOctave], tempParams ); - - singleLine.octaveCount = octaveID; - singleLine.lineLength = octaveLines[lineID].lineLength; - -// decide the start point and end point - shouldChange = false; - - s1 = ( extractedLines[octaveID][lineIDInOctave] )[0]; //sx - s2 = ( extractedLines[octaveID][lineIDInOctave] )[1]; //sy - e1 = ( extractedLines[octaveID][lineIDInOctave] )[2]; //ex - e2 = ( extractedLines[octaveID][lineIDInOctave] )[3]; //ey - - dx = e1 - s1; //ex-sx - dy = e2 - s2; //ey-sy - - if( direction >= -0.75 * M_PI && direction < -0.25 * M_PI ) - { - if( dy > 0 ) - shouldChange = true; - } - - if( direction >= -0.25 * M_PI && direction < 0.25 * M_PI ) - if( dx < 0 ) - { - shouldChange = true; - } - - if( direction >= 0.25 * M_PI && direction < 0.75 * M_PI ) - if( dy < 0 ) - { - shouldChange = true; - } - - if( ( direction >= 0.75 * M_PI && direction < M_PI ) || ( direction >= -M_PI && direction < -0.75 * M_PI ) ) - { - if( dx > 0 ) - shouldChange = true; - } - - tempValue = scale[octaveID]; - - if( shouldChange ) - { - singleLine.sPointInOctaveX = e1; - singleLine.sPointInOctaveY = e2; - singleLine.ePointInOctaveX = s1; - singleLine.ePointInOctaveY = s2; - singleLine.startPointX = tempValue * e1; - singleLine.startPointY = tempValue * e2; - singleLine.endPointX = tempValue * s1; - singleLine.endPointY = tempValue * s2; - } - - else - { - singleLine.sPointInOctaveX = s1; - singleLine.sPointInOctaveY = s2; - singleLine.ePointInOctaveX = e1; - singleLine.ePointInOctaveY = e2; - singleLine.startPointX = tempValue * s1; - singleLine.startPointY = tempValue * s2; - singleLine.endPointX = tempValue * e1; - singleLine.endPointY = tempValue * e2; - } - - singleLine.direction = atan2( ( singleLine.endPointY - singleLine.startPointY ), ( singleLine.endPointX - singleLine.startPointX ) ); - - tempID = octaveLines[lineID].lineIDInScaleLineVec; - - /* compute number of pixels covered by line */ - LineIterator li( octaveImages[octaveID], Point( singleLine.startPointX, singleLine.startPointY ), - Point( singleLine.endPointX, singleLine.endPointY ) ); - - singleLine.numOfPixels = li.count; - - /* store line */ - keyLines[tempID].push_back( singleLine ); - - } - - delete[] scale; - return 1; - -} - -int BinaryDescriptor::OctaveKeyLines_EDL( cv::Mat& image, ScaleLines &keyLines ) +int BinaryDescriptor::OctaveKeyLines( cv::Mat& image, ScaleLines &keyLines ) { /* final number of extracted lines */ @@ -1056,6 +565,7 @@ int BinaryDescriptor::OctaveKeyLines_EDL( cv::Mat& image, ScaleLines &keyLines ) float increaseSigma = sqrt( curSigma2 - preSigma2 ); cv::GaussianBlur( image, blur, cv::Size( params.ksize_, params.ksize_ ), increaseSigma ); images_sizes[octaveCount] = blur.size(); + /* for current octave, extract lines */ if( ( edLineVec_[octaveCount]->EDline( blur, true ) ) != true ) { @@ -1074,9 +584,6 @@ int BinaryDescriptor::OctaveKeyLines_EDL( cv::Mat& image, ScaleLines &keyLines ) } /* end of loop over number of octaves */ - /*lines which correspond to the same line in the octave images will be stored - in the same element of ScaleLines.*/ - /* prepare a vector to store octave information associated to extracted lines */ std::vector octaveLines( numOfFinalLine ); @@ -1370,21 +877,18 @@ int BinaryDescriptor::OctaveKeyLines_EDL( cv::Mat& image, ScaleLines &keyLines ) keyLines[tempID].push_back( singleLine ); } - //////////////////////////////////// - delete[] scale; return 1; } -/* compute LBD descriptors */ -int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) +int BinaryDescriptor::computeLBD( ScaleLines &keyLines ) { //the default length of the band is the line length. short numOfFinalLine = keyLines.size(); float *dL = new float[2]; //line direction cos(dir), sin(dir) float *dO = new float[2]; //the clockwise orthogonal vector of line direction. short heightOfLSP = params.widthOfBand_ * NUM_OF_BANDS; //the height of line support region; - short descriptor_size = NUM_OF_BANDS * 8; //each band, we compute the m( pgdL, ngdL, pgdO, ngdO) and std( pgdL, ngdL, pgdO, ngdO); + short descriptorSize = NUM_OF_BANDS * 8; //each band, we compute the m( pgdL, ngdL, pgdO, ngdO) and std( pgdL, ngdL, pgdO, ngdO); float pgdLRowSum; //the summation of {g_dL |g_dL>0 } for each row of the region; float ngdLRowSum; //the summation of {g_dL |g_dL<0 } for each row of the region; float pgdL2RowSum; //the summation of {g_dL^2 |g_dL>0 } for each row of the region; @@ -1433,26 +937,14 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) pSingleLine = & ( keyLines[lineIDInScaleVec][lineIDInSameLine] ); octaveCount = pSingleLine->octaveCount; - /* retrieve associated dxImg and dyImg*/ - if( flags == 1 ) - { - pdxImg = edLineVec_[octaveCount]->dxImg_.ptr(); - pdyImg = edLineVec_[octaveCount]->dyImg_.ptr(); - } + /* retrieve associated dxImg and dyImg */ + pdxImg = edLineVec_[octaveCount]->dxImg_.ptr(); + pdyImg = edLineVec_[octaveCount]->dyImg_.ptr(); - else - { - pdxImg = dxImg_vector[octaveCount].ptr(); - pdyImg = dyImg_vector[octaveCount].ptr(); - } - - /* get image size to work on from real one - realWidth = edLineVec_[octaveCount]->imageWidth; - imageWidth = realWidth -1; - imageHeight = edLineVec_[octaveCount]->imageHeight-1; */ - realWidth = images_sizes[octaveCount].width; + /* get image size to work on from real one */ + realWidth = edLineVec_[octaveCount]->imageWidth; imageWidth = realWidth - 1; - imageHeight = images_sizes[octaveCount].height - 1; + imageHeight = edLineVec_[octaveCount]->imageHeight - 1; /* initialize memory areas */ memset( pgdLBandSum, 0, numOfBitsBand ); @@ -1518,22 +1010,18 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) { pgdLRowSum += gDL; } - else { ngdLRowSum -= gDL; } - if( gDO > 0 ) { pgdORowSum += gDO; } - else { ngdORowSum -= gDO; } - sCorX += dL[0]; sCorY += dL[1]; /* gDLMat[hID][wID] = gDL; */ @@ -1552,7 +1040,7 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) /* compute {g_dL |g_dL>0 }, {g_dL |g_dL<0 }, {g_dO |g_dO>0 }, {g_dO |g_dO<0 } of each band in the line support region - first, current row belongs to current band */ + first, current row belong to current band */ bandID = hID / params.widthOfBand_; coefInGaussion = gaussCoefL_[hID % params.widthOfBand_ + params.widthOfBand_]; pgdLBandSum[bandID] += coefInGaussion * pgdLRowSum; @@ -1580,7 +1068,6 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) pgdO2BandSum[bandID] += coefInGaussion * coefInGaussion * pgdO2RowSum; ngdO2BandSum[bandID] += coefInGaussion * coefInGaussion * ngdO2RowSum; } - bandID = bandID + 2; if( bandID < NUM_OF_BANDS ) {/*the band below the current band */ @@ -1599,7 +1086,7 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) return 0; */ /* construct line descriptor */ - pSingleLine->descriptor.resize( descriptor_size ); + pSingleLine->descriptor.resize( descriptorSize ); desVec = pSingleLine->descriptor.data(); short desID; @@ -1675,7 +1162,7 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) * vector no larger than this threshold. In Z.Wang's work, a value of 0.4 is found * empirically to be a proper threshold.*/ desVec = pSingleLine->descriptor.data(); - for ( short i = 0; i < descriptor_size; i++ ) + for ( short i = 0; i < descriptorSize; i++ ) { if( desVec[i] > 0.4 ) { @@ -1685,19 +1172,30 @@ int BinaryDescriptor::computeLBD( ScaleLines &keyLines, int flags ) //re-normalize desVec; temp = 0; - for ( short i = 0; i < descriptor_size; i++ ) + for ( short i = 0; i < descriptorSize; i++ ) { temp += desVec[i] * desVec[i]; } temp = 1 / sqrt( temp ); - for ( short i = 0; i < descriptor_size; i++ ) + for ( short i = 0; i < descriptorSize; i++ ) { desVec[i] = desVec[i] * temp; } }/* end for(short lineIDInSameLine = 0; lineIDInSameLine( 0 ); + for ( int g = 0; g < 32; g++ ) + { + /* get LBD data */ + float* desVec = keyLines[lineIDInScaleVec][0].descriptor.data(); + *pointerToRow = desVec[g]; + pointerToRow++; + + } + }/* end for(short lineIDInScaleVec = 0; lineIDInScaleVec0 } for each row of the region; - float ngdLRowSum;//the summation of {g_dL |g_dL<0 } for each row of the region; - float pgdL2RowSum;//the summation of {g_dL^2 |g_dL>0 } for each row of the region; - float ngdL2RowSum;//the summation of {g_dL^2 |g_dL<0 } for each row of the region; - float pgdORowSum;//the summation of {g_dO |g_dO>0 } for each row of the region; - float ngdORowSum;//the summation of {g_dO |g_dO<0 } for each row of the region; - float pgdO2RowSum;//the summation of {g_dO^2 |g_dO>0 } for each row of the region; - float ngdO2RowSum;//the summation of {g_dO^2 |g_dO<0 } for each row of the region; - - float *pgdLBandSum = new float[NUM_OF_BANDS];//the summation of {g_dL |g_dL>0 } for each band of the region; - float *ngdLBandSum = new float[NUM_OF_BANDS];//the summation of {g_dL |g_dL<0 } for each band of the region; - float *pgdL2BandSum = new float[NUM_OF_BANDS];//the summation of {g_dL^2 |g_dL>0 } for each band of the region; - float *ngdL2BandSum = new float[NUM_OF_BANDS];//the summation of {g_dL^2 |g_dL<0 } for each band of the region; - float *pgdOBandSum = new float[NUM_OF_BANDS];//the summation of {g_dO |g_dO>0 } for each band of the region; - float *ngdOBandSum = new float[NUM_OF_BANDS];//the summation of {g_dO |g_dO<0 } for each band of the region; - float *pgdO2BandSum = new float[NUM_OF_BANDS];//the summation of {g_dO^2 |g_dO>0 } for each band of the region; - float *ngdO2BandSum = new float[NUM_OF_BANDS];//the summation of {g_dO^2 |g_dO<0 } for each band of the region; - - short numOfBitsBand = NUM_OF_BANDS*sizeof(float); - short lengthOfLSP; //the length of line support region, varies with lines - short halfHeight = (heightOfLSP-1)/2; - short halfWidth; - short bandID; - float coefInGaussion; - float lineMiddlePointX, lineMiddlePointY; - float sCorX, sCorY,sCorX0, sCorY0; - short tempCor, xCor, yCor;//pixel coordinates in image plane - short dx, dy; - float gDL;//store the gradient projection of pixels in support region along dL vector - float gDO;//store the gradient projection of pixels in support region along dO vector - short imageWidth, imageHeight, realWidth; - short *pdxImg, *pdyImg; - float *desVec; - - short sameLineSize; - short octaveCount; - OctaveSingleLine *pSingleLine; - /* loop over list of LineVec */ - for(short lineIDInScaleVec = 0; lineIDInScaleVecoctaveCount; - - /* retrieve associated dxImg and dyImg */ - pdxImg = edLineVec_[octaveCount]->dxImg_.ptr(); - pdyImg = edLineVec_[octaveCount]->dyImg_.ptr(); - - - /* get image size to work on from real one */ - realWidth = edLineVec_[octaveCount]->imageWidth; - imageWidth = realWidth -1; - imageHeight = edLineVec_[octaveCount]->imageHeight-1; - - - /* initialize memory areas */ - memset(pgdLBandSum, 0, numOfBitsBand); - memset(ngdLBandSum, 0, numOfBitsBand); - memset(pgdL2BandSum, 0, numOfBitsBand); - memset(ngdL2BandSum, 0, numOfBitsBand); - memset(pgdOBandSum, 0, numOfBitsBand); - memset(ngdOBandSum, 0, numOfBitsBand); - memset(pgdO2BandSum, 0, numOfBitsBand); - memset(ngdO2BandSum, 0, numOfBitsBand); - - /* get length of line and its half */ - lengthOfLSP = keyLines[lineIDInScaleVec][lineIDInSameLine].numOfPixels; - halfWidth = (lengthOfLSP-1)/2; - - /* get middlepoint of line */ - lineMiddlePointX = 0.5 * (pSingleLine->sPointInOctaveX + pSingleLine->ePointInOctaveX); - lineMiddlePointY = 0.5 * (pSingleLine->sPointInOctaveY + pSingleLine->ePointInOctaveY); - - /*1.rotate the local coordinate system to the line direction (direction is the angle - between positive line direction and positive X axis) - *2.compute the gradient projection of pixels in line support region*/ - - /* get the vector representing original image reference system after rotation to aligh with - line's direction */ - dL[0] = cos(pSingleLine->direction); - dL[1] = sin(pSingleLine->direction); - - /* set the clockwise orthogonal vector of line direction */ - dO[0] = -dL[1]; - dO[1] = dL[0]; - - /* get rotated reference frame */ - sCorX0= -dL[0]*halfWidth + dL[1]*halfHeight + lineMiddlePointX;//hID =0; wID = 0; - sCorY0= -dL[1]*halfWidth - dL[0]*halfHeight + lineMiddlePointY; - - - /* BIAS::Matrix gDLMat(heightOfLSP,lengthOfLSP) */ - for(short hID = 0; hID imageWidth)?imageWidth:tempCor; - tempCor = round(sCorY); - yCor = (tempCor<0)?0:(tempCor>imageHeight)?imageHeight:tempCor; - - /* To achieve rotation invariance, each simple gradient is rotated aligned with - * the line direction and clockwise orthogonal direction.*/ - dx = pdxImg[yCor*realWidth+xCor]; - dy = pdyImg[yCor*realWidth+xCor]; - gDL = dx * dL[0] + dy * dL[1]; - gDO = dx * dO[0] + dy * dO[1]; - if(gDL>0){ - pgdLRowSum += gDL; - }else{ - ngdLRowSum -= gDL; - } - if(gDO>0){ - pgdORowSum += gDO; - }else{ - ngdORowSum -= gDO; - } - sCorX +=dL[0]; - sCorY +=dL[1]; - /* gDLMat[hID][wID] = gDL; */ - } - sCorX0 -=dL[1]; - sCorY0 +=dL[0]; - coefInGaussion = gaussCoefG_[hID]; - pgdLRowSum = coefInGaussion * pgdLRowSum; - ngdLRowSum = coefInGaussion * ngdLRowSum; - pgdL2RowSum = pgdLRowSum * pgdLRowSum; - ngdL2RowSum = ngdLRowSum * ngdLRowSum; - pgdORowSum = coefInGaussion * pgdORowSum; - ngdORowSum = coefInGaussion * ngdORowSum; - pgdO2RowSum = pgdORowSum * pgdORowSum; - ngdO2RowSum = ngdORowSum * ngdORowSum; - - /* compute {g_dL |g_dL>0 }, {g_dL |g_dL<0 }, - {g_dO |g_dO>0 }, {g_dO |g_dO<0 } of each band in the line support region - first, current row belong to current band */ - bandID = hID/params.widthOfBand_; - coefInGaussion = gaussCoefL_[hID%params.widthOfBand_+params.widthOfBand_]; - pgdLBandSum[bandID] += coefInGaussion * pgdLRowSum; - ngdLBandSum[bandID] += coefInGaussion * ngdLRowSum; - pgdL2BandSum[bandID] += coefInGaussion * coefInGaussion * pgdL2RowSum; - ngdL2BandSum[bandID] += coefInGaussion * coefInGaussion * ngdL2RowSum; - pgdOBandSum[bandID] += coefInGaussion * pgdORowSum; - ngdOBandSum[bandID] += coefInGaussion * ngdORowSum; - pgdO2BandSum[bandID] += coefInGaussion * coefInGaussion * pgdO2RowSum; - ngdO2BandSum[bandID] += coefInGaussion * coefInGaussion * ngdO2RowSum; - - /* In order to reduce boundary effect along the line gradient direction, - * a row's gradient will contribute not only to its current band, but also - * to its nearest upper and down band with gaussCoefL_.*/ - bandID--; - if(bandID>=0){/* the band above the current band */ - coefInGaussion = gaussCoefL_[hID%params.widthOfBand_ + 2*params.widthOfBand_]; - pgdLBandSum[bandID] += coefInGaussion * pgdLRowSum; - ngdLBandSum[bandID] += coefInGaussion * ngdLRowSum; - pgdL2BandSum[bandID] += coefInGaussion * coefInGaussion * pgdL2RowSum; - ngdL2BandSum[bandID] += coefInGaussion * coefInGaussion * ngdL2RowSum; - pgdOBandSum[bandID] += coefInGaussion * pgdORowSum; - ngdOBandSum[bandID] += coefInGaussion * ngdORowSum; - pgdO2BandSum[bandID] += coefInGaussion * coefInGaussion * pgdO2RowSum; - ngdO2BandSum[bandID] += coefInGaussion * coefInGaussion * ngdO2RowSum; - } - bandID = bandID+2; - if(bandIDdescriptor.resize(descriptorSize); - desVec = pSingleLine->descriptor.data(); - - short desID; - - /*Note that the first and last bands only have (lengthOfLSP * widthOfBand_ * 2.0) pixels - * which are counted. */ - float invN2 = 1.0/(params.widthOfBand_ * 2.0); - float invN3 = 1.0/(params.widthOfBand_ * 3.0); - float invN, temp; - for(bandID = 0; bandIDdescriptor.data(); - - int base = 0; - for(short i=0; idescriptor.data(); - base = 0; - for(short i=0; idescriptor.data(); - for(short i=0; i0.4){ - desVec[i]=0.4; - } - } - - //re-normalize desVec; - temp = 0; - for(short i=0; i(0); - for(int g = 0; g<32; g++) - { - /* get LBD data */ - float* desVec = keyLines[lineIDInScaleVec][0].descriptor.data(); - *pointerToRow = desVec[g]; - pointerToRow++; - - } - - }/* end for(short lineIDInScaleVec = 0; - lineIDInScaleVecsecond].at( counter ) != 0 ) { @@ -212,7 +212,7 @@ void BinaryDescriptorMatcher::match( const Mat& queryDescriptors, const Mat& tra /* compose matches */ for ( int counter = 0; counter < queryDescriptors.rows; counter++ ) { - /* create a DMatch object if required by mask of if there is + /* create a DMatch object if required by mask or if there is no mask at all */ if( mask.empty() || ( !mask.empty() && mask.at( counter ) != 0 ) ) { diff --git a/modules/line_descriptor/src/ed_line_detector.cpp b/modules/line_descriptor/src/ed_line_detector.cpp index e9b936cf2..b377b4478 100644 --- a/modules/line_descriptor/src/ed_line_detector.cpp +++ b/modules/line_descriptor/src/ed_line_detector.cpp @@ -5,38 +5,37 @@ copy or use the software. - License Agreement - For Open Source Computer Vision Library + License Agreement + For Open Source Computer Vision Library -Copyright (C) 2011-2012, Lilian Zhang, all rights reserved. -Copyright (C) 2013, Manuele Tamburrano, Stefano Fabri, all rights reserved. -Third party copyrights are property of their respective owners. + Copyright (C) 2011-2012, Lilian Zhang, all rights reserved. + Copyright (C) 2013, Manuele Tamburrano, Stefano Fabri, all rights reserved. + Third party copyrights are property of their respective owners. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + 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 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. + * 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. - * The name of the copyright holders may not 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 the Intel Corporation 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. -*/ + * The name of the copyright holders may not 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 the Intel Corporation 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 "precomp.hpp" @@ -55,963 +54,1139 @@ the use of this software, even if advised of the possibility of such damage. using namespace std; EDLineDetector::EDLineDetector() { - // cout<<"Call EDLineDetector constructor function"<(2,2); - ATV = cv::Mat_(1,2); - tempMatLineFit = cv::Mat_(2,2); - tempVecLineFit = cv::Mat_(1,2); - fitMatT = cv::Mat_(2,minLineLen_); - fitVec = cv::Mat_(1,minLineLen_); - for(int i=0; i( 2, 2 ); + ATV = cv::Mat_( 1, 2 ); + tempMatLineFit = cv::Mat_( 2, 2 ); + tempVecLineFit = cv::Mat_( 1, 2 ); + fitMatT = cv::Mat_( 2, minLineLen_ ); + fitVec = cv::Mat_( 1, minLineLen_ ); + for ( int i = 0; i < minLineLen_; i++ ) + { + fitMatT[1][i] = 1; + } + dxImg_.create( 1, 1, CV_16SC1 ); + dyImg_.create( 1, 1, CV_16SC1 ); + gImgWO_.create( 1, 1, CV_8SC1 ); + pFirstPartEdgeX_ = NULL; + pFirstPartEdgeY_ = NULL; + pFirstPartEdgeS_ = NULL; + pSecondPartEdgeX_ = NULL; + pSecondPartEdgeY_ = NULL; + pSecondPartEdgeS_ = NULL; + pAnchorX_ = NULL; + pAnchorY_ = NULL; } -EDLineDetector::~EDLineDetector(){ - if(pFirstPartEdgeX_!=NULL){ - delete [] pFirstPartEdgeX_; - delete [] pFirstPartEdgeY_; - delete [] pSecondPartEdgeX_; - delete [] pSecondPartEdgeY_; - delete [] pAnchorX_; - delete [] pAnchorY_; - } - if(pFirstPartEdgeS_ != NULL){ - delete [] pFirstPartEdgeS_; - delete [] pSecondPartEdgeS_; - } -} - - -int EDLineDetector::EdgeDrawing(cv::Mat &image, EdgeChains &edgeChains, bool smoothed ) +EDLineDetector::~EDLineDetector() { - imageWidth = image.cols; - imageHeight= image.rows; - unsigned int pixelNum = imageWidth*imageHeight; + if( pFirstPartEdgeX_ != NULL ) + { + delete[] pFirstPartEdgeX_; + delete[] pFirstPartEdgeY_; + delete[] pSecondPartEdgeX_; + delete[] pSecondPartEdgeY_; + delete[] pAnchorX_; + delete[] pAnchorY_; + } + if( pFirstPartEdgeS_ != NULL ) + { + delete[] pFirstPartEdgeS_; + delete[] pSecondPartEdgeS_; + } +} - #ifdef DEBUGEdgeDrawing - cv::imshow("prima blur", image); - cv::waitKey(); - #endif - if(!smoothed){//input image hasn't been smoothed. - std::cout << "Dentro smoothed " << std::endl; - cv::Mat InImage = image.clone(); - cv::GaussianBlur(InImage, image, cv::Size(ksize_,ksize_), sigma_); - } - - #ifdef DEBUGEdgeDrawing - cv::imshow("dopo blur", image); - cv::waitKey(); - #endif - - //Is this useful? Doesn't seem to have references - //else{ -// unsigned char *pImage = image.GetImageData(); -// memcpy(cvImage->data.ptr, pImage, pixelNum*sizeof(unsigned char)); -// } +int EDLineDetector::EdgeDrawing( cv::Mat &image, EdgeChains &edgeChains, bool smoothed ) +{ + imageWidth = image.cols; + imageHeight = image.rows; + unsigned int pixelNum = imageWidth * imageHeight; - unsigned int edgePixelArraySize = pixelNum/5; - unsigned int maxNumOfEdge = edgePixelArraySize/20; - std::cout<<"imageHeight: "<(); - short *pdyImg = dyImg_.ptr(); - short *pgImg = gImg_.ptr(); - unsigned char *pdirImg = dirImg_.ptr(); - - //extract the anchors in the gradient image, store into a vector - memset(pAnchorX_, 0, edgePixelArraySize*sizeof(unsigned int));//initialization - memset(pAnchorY_, 0, edgePixelArraySize*sizeof(unsigned int)); - unsigned int anchorsSize = 0; - int indexInArray; - unsigned char gValue1, gValue2, gValue3; - for(unsigned int w=1; w=pgImg[indexInArray-imageWidth]+anchorThreshold_ - &&pgImg[indexInArray]>=pgImg[indexInArray+imageWidth]+anchorThreshold_){// (w,h) is accepted as an anchor - pAnchorX_[anchorsSize] = w; - pAnchorY_[anchorsSize++] = h; - } - }else{// if(pdirImg[indexInArray]==Vertical){//it is vertical edge, should be compared with left and right - //gValue2 = pgImg[indexInArray]; - if(pgImg[indexInArray]>=pgImg[indexInArray-1]+anchorThreshold_ - &&pgImg[indexInArray]>=pgImg[indexInArray+1]+anchorThreshold_){// (w,h) is accepted as an anchor - pAnchorX_[anchorsSize] = w; - pAnchorY_[anchorsSize++] = h; - } - } - } - } - if(anchorsSize>edgePixelArraySize){ - cout<<"anchor size is larger than its maximal size. anchorsSize="<0&&!pEdgeImg[indexInArray]){ - pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel - pFirstPartEdgeX_[offsetPFirst] = x; - pFirstPartEdgeY_[offsetPFirst++] = y; - shouldGoDirection = 0;//unknown - if(pdirImg[indexInArray]==Horizontal){//should go left or right - if(lastDirection == UpDir ||lastDirection == DownDir){//change the pixel direction now - if(x>lastX){//should go right - shouldGoDirection = RightDir; - }else{//should go left - shouldGoDirection = LeftDir; - } - } - lastX = x; - lastY = y; - if(lastDirection == RightDir||shouldGoDirection == RightDir){//go right - if(x==imageWidth-1||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the right and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray-imageWidth+1]; - gValue2 = pgImg[indexInArray+1]; - gValue3 = pgImg[indexInArray+imageWidth+1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-right - x = x+1; - y = y+1; - }else{//straight-right - x = x+1; - } - lastDirection = RightDir; - } else if(lastDirection == LeftDir || shouldGoDirection==LeftDir){//go left - if(x==0||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the left and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray-imageWidth-1]; - gValue2 = pgImg[indexInArray-1]; - gValue3 = pgImg[indexInArray+imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-left - x = x-1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-left - x = x-1; - } - lastDirection = LeftDir; - } - }else{//should go up or down. - if(lastDirection == RightDir || lastDirection == LeftDir){//change the pixel direction now - if(y>lastY){//should go down - shouldGoDirection = DownDir; - }else{//should go up - shouldGoDirection = UpDir; - } - } - lastX = x; - lastY = y; - if(lastDirection==DownDir || shouldGoDirection == DownDir){//go down - if(x==0||x==imageWidth-1||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the down and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray+imageWidth+1]; - gValue2 = pgImg[indexInArray+imageWidth]; - gValue3 = pgImg[indexInArray+imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//down-right - x = x+1; - y = y+1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-down - y = y+1; - } - lastDirection = DownDir; - }else if(lastDirection==UpDir || shouldGoDirection == UpDir){//go up - if(x==0||x==imageWidth-1||y==0){//reach the image border - break; - } - // Look at 3 neighbors to the up and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray-imageWidth+1]; - gValue2 = pgImg[indexInArray-imageWidth]; - gValue3 = pgImg[indexInArray-imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//up-left - x = x-1; - y = y-1; - }else{//straight-up - y = y-1; - } - lastDirection = UpDir; - } - } - indexInArray = y*imageWidth+x; - }//end while go right - //then go left, pixel direction may be different during linking. - x = pAnchorX_[i]; - y = pAnchorY_[i]; - indexInArray = y*imageWidth+x; - pEdgeImg[indexInArray] = 0;//mark the anchor point be a non-edge pixel and - lastDirection = LeftDir; - pSecondPartEdgeS_[offsetPS] = offsetPSecond; - while(pgImg[indexInArray]>0&&!pEdgeImg[indexInArray]){ - pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel - pSecondPartEdgeX_[offsetPSecond] = x; - pSecondPartEdgeY_[offsetPSecond++] = y; - shouldGoDirection = 0;//unknown - if(pdirImg[indexInArray]==Horizontal){//should go left or right - if(lastDirection == UpDir ||lastDirection == DownDir){//change the pixel direction now - if(x>lastX){//should go right - shouldGoDirection = RightDir; - }else{//should go left - shouldGoDirection = LeftDir; - } - } - lastX = x; - lastY = y; - if(lastDirection == RightDir||shouldGoDirection == RightDir){//go right - if(x==imageWidth-1||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the right and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray-imageWidth+1]; - gValue2 = pgImg[indexInArray+1]; - gValue3 = pgImg[indexInArray+imageWidth+1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-right - x = x+1; - y = y+1; - }else{//straight-right - x = x+1; - } - lastDirection = RightDir; - }else if(lastDirection == LeftDir || shouldGoDirection==LeftDir){//go left - if(x==0||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the left and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray-imageWidth-1]; - gValue2 = pgImg[indexInArray-1]; - gValue3 = pgImg[indexInArray+imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-left - x = x-1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-left - x = x-1; - } - lastDirection = LeftDir; - } - }else{//should go up or down. - if(lastDirection == RightDir || lastDirection == LeftDir){//change the pixel direction now - if(y>lastY){//should go down - shouldGoDirection = DownDir; - }else{//should go up - shouldGoDirection = UpDir; - } - } - lastX = x; - lastY = y; - if(lastDirection==DownDir || shouldGoDirection == DownDir){//go down - if(x==0||x==imageWidth-1||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the down and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray+imageWidth+1]; - gValue2 = pgImg[indexInArray+imageWidth]; - gValue3 = pgImg[indexInArray+imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//down-right - x = x+1; - y = y+1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-down - y = y+1; - } - lastDirection = DownDir; - }else if(lastDirection==UpDir || shouldGoDirection == UpDir){//go up - if(x==0||x==imageWidth-1||y==0){//reach the image border - break; - } - // Look at 3 neighbors to the up and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth+1]; - gValue2 = pgImg[indexInArray- imageWidth]; - gValue3 = pgImg[indexInArray- imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//up-left - x = x-1; - y = y-1; - }else{//straight-up - y = y-1; - } - lastDirection = UpDir; - } - } - indexInArray = y*imageWidth+x; - }//end while go left - //end anchor is Horizontal - }else{//the direction of this pixel is vertical, go up and down - //fist go down, pixel direction may be different during linking. - lastDirection = DownDir; - while(pgImg[indexInArray]>0&&!pEdgeImg[indexInArray]){ - pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel - pFirstPartEdgeX_[offsetPFirst] = x; - pFirstPartEdgeY_[offsetPFirst++] = y; - shouldGoDirection = 0;//unknown - if(pdirImg[indexInArray]==Horizontal){//should go left or right - if(lastDirection == UpDir ||lastDirection == DownDir){//change the pixel direction now - if(x>lastX){//should go right - shouldGoDirection = RightDir; - }else{//should go left - shouldGoDirection = LeftDir; - } - } - lastX = x; - lastY = y; - if(lastDirection == RightDir||shouldGoDirection == RightDir){//go right - if(x==imageWidth-1||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the right and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth+1]; - gValue2 = pgImg[indexInArray+1]; - gValue3 = pgImg[indexInArray+ imageWidth+1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-right - x = x+1; - y = y+1; - }else{//straight-right - x = x+1; - } - lastDirection = RightDir; - }else if(lastDirection == LeftDir || shouldGoDirection==LeftDir){//go left - if(x==0||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the left and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth-1]; - gValue2 = pgImg[indexInArray-1]; - gValue3 = pgImg[indexInArray+ imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-left - x = x-1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-left - x = x-1; - } - lastDirection = LeftDir; - } - }else{//should go up or down. - if(lastDirection == RightDir || lastDirection == LeftDir){//change the pixel direction now - if(y>lastY){//should go down - shouldGoDirection = DownDir; - }else{//should go up - shouldGoDirection = UpDir; - } - } - lastX = x; - lastY = y; - if(lastDirection==DownDir || shouldGoDirection == DownDir){//go down - if(x==0||x==imageWidth-1||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the down and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray+ imageWidth+1]; - gValue2 = pgImg[indexInArray+ imageWidth]; - gValue3 = pgImg[indexInArray+ imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//down-right - x = x+1; - y = y+1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-down - y = y+1; - } - lastDirection = DownDir; - }else if(lastDirection==UpDir || shouldGoDirection == UpDir){//go up - if(x==0||x==imageWidth-1||y==0){//reach the image border - break; - } - // Look at 3 neighbors to the up and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth+1]; - gValue2 = pgImg[indexInArray- imageWidth]; - gValue3 = pgImg[indexInArray- imageWidth-1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//up-left - x = x-1; - y = y-1; - }else{//straight-up - y = y-1; - } - lastDirection = UpDir; - } - } - indexInArray = y*imageWidth+x; - }//end while go down - //then go up, pixel direction may be different during linking. - lastDirection = UpDir; - x = pAnchorX_[i]; - y = pAnchorY_[i]; - indexInArray = y*imageWidth+x; - pEdgeImg[indexInArray] = 0;//mark the anchor point be a non-edge pixel and - pSecondPartEdgeS_[offsetPS] = offsetPSecond; - while(pgImg[indexInArray]>0&&!pEdgeImg[indexInArray]){ - pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel - pSecondPartEdgeX_[offsetPSecond] = x; - pSecondPartEdgeY_[offsetPSecond++] = y; - shouldGoDirection = 0;//unknown - if(pdirImg[indexInArray]==Horizontal){//should go left or right - if(lastDirection == UpDir ||lastDirection == DownDir){//change the pixel direction now - if(x>lastX){//should go right - shouldGoDirection = RightDir; - }else{//should go left - shouldGoDirection = LeftDir; - } - } - lastX = x; - lastY = y; - if(lastDirection == RightDir||shouldGoDirection == RightDir){//go right - if(x==imageWidth-1||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the right and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth+1]; - gValue2 = pgImg[indexInArray+1]; - gValue3 = pgImg[indexInArray+ imageWidth +1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-right - x = x+1; - y = y+1; - }else{//straight-right - x = x+1; - } - lastDirection = RightDir; - }else if(lastDirection == LeftDir || shouldGoDirection==LeftDir){//go left - if(x==0||y==0||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the left and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth -1]; - gValue2 = pgImg[indexInArray-1]; - gValue3 = pgImg[indexInArray+ imageWidth -1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-left - x = x-1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-left - x = x-1; - } - lastDirection = LeftDir; - } - }else{//should go up or down. - if(lastDirection == RightDir || lastDirection == LeftDir){//change the pixel direction now - if(y>lastY){//should go down - shouldGoDirection = DownDir; - }else{//should go up - shouldGoDirection = UpDir; - } - } - lastX = x; - lastY = y; - if(lastDirection==DownDir || shouldGoDirection == DownDir){//go down - if(x==0||x==imageWidth-1||y==imageHeight-1){//reach the image border - break; - } - // Look at 3 neighbors to the down and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray+ imageWidth +1]; - gValue2 = pgImg[indexInArray+ imageWidth]; - gValue3 = pgImg[indexInArray+ imageWidth -1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//down-right - x = x+1; - y = y+1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//down-left - x = x-1; - y = y+1; - }else{//straight-down - y = y+1; - } - lastDirection = DownDir; - }else if(lastDirection==UpDir || shouldGoDirection == UpDir){//go up - if(x==0||x==imageWidth-1||y==0){//reach the image border - break; - } - // Look at 3 neighbors to the up and pick the one with the max. gradient value - gValue1 = pgImg[indexInArray- imageWidth +1]; - gValue2 = pgImg[indexInArray- imageWidth]; - gValue3 = pgImg[indexInArray- imageWidth -1]; - if(gValue1>=gValue2&&gValue1>=gValue3){//up-right - x = x+1; - y = y-1; - }else if(gValue3>=gValue2&&gValue3>=gValue1){//up-left - x = x-1; - y = y-1; - }else{//straight-up - y = y-1; - } - lastDirection = UpDir; - } - } - indexInArray = y*imageWidth+x; - }//end while go up - }//end anchor is Vertical - //only keep the edge chains whose length is larger than the minLineLen_; - edgeLenFirst = offsetPFirst - pFirstPartEdgeS_[offsetPS]; - edgeLenSecond = offsetPSecond - pSecondPartEdgeS_[offsetPS]; - if(edgeLenFirst+edgeLenSecondmaxNumOfEdge){ - cout<<"Edge drawing Error: The total number of edges is larger than MaxNumOfEdge, " - "numofedge = "<= indexInArray; tempID--){//add first part edge - pxCors[indexInCors] = pFirstPartEdgeX_[tempID]; - pyCors[indexInCors++] = pFirstPartEdgeY_[tempID]; - } - indexInArray = pSecondPartEdgeS_[edgeId]; - offsetPSecond= pSecondPartEdgeS_[edgeId+1]; - for(tempID = indexInArray+1; tempID(); + unsigned char *pdirImg = dirImg_.ptr(); + + //extract the anchors in the gradient image, store into a vector + memset( pAnchorX_, 0, edgePixelArraySize * sizeof(unsigned int) ); //initialization + memset( pAnchorY_, 0, edgePixelArraySize * sizeof(unsigned int) ); + unsigned int anchorsSize = 0; + int indexInArray; + unsigned char gValue1, gValue2, gValue3; + for ( unsigned int w = 1; w < imageWidth - 1; w = w + scanIntervals_ ) + { + for ( unsigned int h = 1; h < imageHeight - 1; h = h + scanIntervals_ ) + { + indexInArray = h * imageWidth + w; + //gValue1 = pdirImg[indexInArray]; + if( pdirImg[indexInArray] == Horizontal ) + { //if the direction of pixel is horizontal, then compare with up and down + //gValue2 = pgImg[indexInArray]; + if( pgImg[indexInArray] >= pgImg[indexInArray - imageWidth] + anchorThreshold_ + && pgImg[indexInArray] >= pgImg[indexInArray + imageWidth] + anchorThreshold_ ) + { // (w,h) is accepted as an anchor + pAnchorX_[anchorsSize] = w; + pAnchorY_[anchorsSize++] = h; + } + } + else + { // if(pdirImg[indexInArray]==Vertical){//it is vertical edge, should be compared with left and right + //gValue2 = pgImg[indexInArray]; + if( pgImg[indexInArray] >= pgImg[indexInArray - 1] + anchorThreshold_ && pgImg[indexInArray] >= pgImg[indexInArray + 1] + anchorThreshold_ ) + { // (w,h) is accepted as an anchor + pAnchorX_[anchorsSize] = w; + pAnchorY_[anchorsSize++] = h; + } + } + } + } + if( anchorsSize > edgePixelArraySize ) + { + cout << "anchor size is larger than its maximal size. anchorsSize=" << anchorsSize << ", maximal size = " << edgePixelArraySize << endl; + return -1; + } +#ifdef DEBUGEdgeDrawing + cout<<"Anchor point detection, anchors.size="< 0 && !pEdgeImg[indexInArray] ) + { + pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel + pFirstPartEdgeX_[offsetPFirst] = x; + pFirstPartEdgeY_[offsetPFirst++] = y; + shouldGoDirection = 0; //unknown + if( pdirImg[indexInArray] == Horizontal ) + { //should go left or right + if( lastDirection == UpDir || lastDirection == DownDir ) + { //change the pixel direction now + if( x > lastX ) + { //should go right + shouldGoDirection = RightDir; + } + else + { //should go left + shouldGoDirection = LeftDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == RightDir || shouldGoDirection == RightDir ) + { //go right + if( x == imageWidth - 1 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the right and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray + 1]; + gValue3 = pgImg[indexInArray + imageWidth + 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-right + x = x + 1; + y = y + 1; + } + else + { //straight-right + x = x + 1; + } + lastDirection = RightDir; + } + else if( lastDirection == LeftDir || shouldGoDirection == LeftDir ) + { //go left + if( x == 0 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the left and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth - 1]; + gValue2 = pgImg[indexInArray - 1]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-left + x = x - 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-left + x = x - 1; + } + lastDirection = LeftDir; + } + } + else + { //should go up or down. + if( lastDirection == RightDir || lastDirection == LeftDir ) + { //change the pixel direction now + if( y > lastY ) + { //should go down + shouldGoDirection = DownDir; + } + else + { //should go up + shouldGoDirection = UpDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == DownDir || shouldGoDirection == DownDir ) + { //go down + if( x == 0 || x == imageWidth - 1 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the down and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray + imageWidth + 1]; + gValue2 = pgImg[indexInArray + imageWidth]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //down-right + x = x + 1; + y = y + 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-down + y = y + 1; + } + lastDirection = DownDir; + } + else if( lastDirection == UpDir || shouldGoDirection == UpDir ) + { //go up + if( x == 0 || x == imageWidth - 1 || y == 0 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the up and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray - imageWidth]; + gValue3 = pgImg[indexInArray - imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //up-left + x = x - 1; + y = y - 1; + } + else + { //straight-up + y = y - 1; + } + lastDirection = UpDir; + } + } + indexInArray = y * imageWidth + x; + } //end while go right + //then go left, pixel direction may be different during linking. + x = pAnchorX_[i]; + y = pAnchorY_[i]; + indexInArray = y * imageWidth + x; + pEdgeImg[indexInArray] = 0; //mark the anchor point be a non-edge pixel and + lastDirection = LeftDir; + pSecondPartEdgeS_[offsetPS] = offsetPSecond; + while ( pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray] ) + { + pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel + pSecondPartEdgeX_[offsetPSecond] = x; + pSecondPartEdgeY_[offsetPSecond++] = y; + shouldGoDirection = 0; //unknown + if( pdirImg[indexInArray] == Horizontal ) + { //should go left or right + if( lastDirection == UpDir || lastDirection == DownDir ) + { //change the pixel direction now + if( x > lastX ) + { //should go right + shouldGoDirection = RightDir; + } + else + { //should go left + shouldGoDirection = LeftDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == RightDir || shouldGoDirection == RightDir ) + { //go right + if( x == imageWidth - 1 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the right and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray + 1]; + gValue3 = pgImg[indexInArray + imageWidth + 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-right + x = x + 1; + y = y + 1; + } + else + { //straight-right + x = x + 1; + } + lastDirection = RightDir; + } + else if( lastDirection == LeftDir || shouldGoDirection == LeftDir ) + { //go left + if( x == 0 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the left and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth - 1]; + gValue2 = pgImg[indexInArray - 1]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-left + x = x - 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-left + x = x - 1; + } + lastDirection = LeftDir; + } + } + else + { //should go up or down. + if( lastDirection == RightDir || lastDirection == LeftDir ) + { //change the pixel direction now + if( y > lastY ) + { //should go down + shouldGoDirection = DownDir; + } + else + { //should go up + shouldGoDirection = UpDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == DownDir || shouldGoDirection == DownDir ) + { //go down + if( x == 0 || x == imageWidth - 1 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the down and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray + imageWidth + 1]; + gValue2 = pgImg[indexInArray + imageWidth]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //down-right + x = x + 1; + y = y + 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-down + y = y + 1; + } + lastDirection = DownDir; + } + else if( lastDirection == UpDir || shouldGoDirection == UpDir ) + { //go up + if( x == 0 || x == imageWidth - 1 || y == 0 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the up and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray - imageWidth]; + gValue3 = pgImg[indexInArray - imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //up-left + x = x - 1; + y = y - 1; + } + else + { //straight-up + y = y - 1; + } + lastDirection = UpDir; + } + } + indexInArray = y * imageWidth + x; + } //end while go left + //end anchor is Horizontal + } + else + { //the direction of this pixel is vertical, go up and down + //fist go down, pixel direction may be different during linking. + lastDirection = DownDir; + while ( pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray] ) + { + pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel + pFirstPartEdgeX_[offsetPFirst] = x; + pFirstPartEdgeY_[offsetPFirst++] = y; + shouldGoDirection = 0; //unknown + if( pdirImg[indexInArray] == Horizontal ) + { //should go left or right + if( lastDirection == UpDir || lastDirection == DownDir ) + { //change the pixel direction now + if( x > lastX ) + { //should go right + shouldGoDirection = RightDir; + } + else + { //should go left + shouldGoDirection = LeftDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == RightDir || shouldGoDirection == RightDir ) + { //go right + if( x == imageWidth - 1 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the right and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray + 1]; + gValue3 = pgImg[indexInArray + imageWidth + 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-right + x = x + 1; + y = y + 1; + } + else + { //straight-right + x = x + 1; + } + lastDirection = RightDir; + } + else if( lastDirection == LeftDir || shouldGoDirection == LeftDir ) + { //go left + if( x == 0 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the left and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth - 1]; + gValue2 = pgImg[indexInArray - 1]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-left + x = x - 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-left + x = x - 1; + } + lastDirection = LeftDir; + } + } + else + { //should go up or down. + if( lastDirection == RightDir || lastDirection == LeftDir ) + { //change the pixel direction now + if( y > lastY ) + { //should go down + shouldGoDirection = DownDir; + } + else + { //should go up + shouldGoDirection = UpDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == DownDir || shouldGoDirection == DownDir ) + { //go down + if( x == 0 || x == imageWidth - 1 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the down and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray + imageWidth + 1]; + gValue2 = pgImg[indexInArray + imageWidth]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //down-right + x = x + 1; + y = y + 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-down + y = y + 1; + } + lastDirection = DownDir; + } + else if( lastDirection == UpDir || shouldGoDirection == UpDir ) + { //go up + if( x == 0 || x == imageWidth - 1 || y == 0 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the up and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray - imageWidth]; + gValue3 = pgImg[indexInArray - imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //up-left + x = x - 1; + y = y - 1; + } + else + { //straight-up + y = y - 1; + } + lastDirection = UpDir; + } + } + indexInArray = y * imageWidth + x; + } //end while go down + //then go up, pixel direction may be different during linking. + lastDirection = UpDir; + x = pAnchorX_[i]; + y = pAnchorY_[i]; + indexInArray = y * imageWidth + x; + pEdgeImg[indexInArray] = 0; //mark the anchor point be a non-edge pixel and + pSecondPartEdgeS_[offsetPS] = offsetPSecond; + while ( pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray] ) + { + pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel + pSecondPartEdgeX_[offsetPSecond] = x; + pSecondPartEdgeY_[offsetPSecond++] = y; + shouldGoDirection = 0; //unknown + if( pdirImg[indexInArray] == Horizontal ) + { //should go left or right + if( lastDirection == UpDir || lastDirection == DownDir ) + { //change the pixel direction now + if( x > lastX ) + { //should go right + shouldGoDirection = RightDir; + } + else + { //should go left + shouldGoDirection = LeftDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == RightDir || shouldGoDirection == RightDir ) + { //go right + if( x == imageWidth - 1 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the right and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray + 1]; + gValue3 = pgImg[indexInArray + imageWidth + 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-right + x = x + 1; + y = y + 1; + } + else + { //straight-right + x = x + 1; + } + lastDirection = RightDir; + } + else if( lastDirection == LeftDir || shouldGoDirection == LeftDir ) + { //go left + if( x == 0 || y == 0 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the left and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth - 1]; + gValue2 = pgImg[indexInArray - 1]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-left + x = x - 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-left + x = x - 1; + } + lastDirection = LeftDir; + } + } + else + { //should go up or down. + if( lastDirection == RightDir || lastDirection == LeftDir ) + { //change the pixel direction now + if( y > lastY ) + { //should go down + shouldGoDirection = DownDir; + } + else + { //should go up + shouldGoDirection = UpDir; + } + } + lastX = x; + lastY = y; + if( lastDirection == DownDir || shouldGoDirection == DownDir ) + { //go down + if( x == 0 || x == imageWidth - 1 || y == imageHeight - 1 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the down and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray + imageWidth + 1]; + gValue2 = pgImg[indexInArray + imageWidth]; + gValue3 = pgImg[indexInArray + imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //down-right + x = x + 1; + y = y + 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //down-left + x = x - 1; + y = y + 1; + } + else + { //straight-down + y = y + 1; + } + lastDirection = DownDir; + } + else if( lastDirection == UpDir || shouldGoDirection == UpDir ) + { //go up + if( x == 0 || x == imageWidth - 1 || y == 0 ) + { //reach the image border + break; + } + // Look at 3 neighbors to the up and pick the one with the max. gradient value + gValue1 = pgImg[indexInArray - imageWidth + 1]; + gValue2 = pgImg[indexInArray - imageWidth]; + gValue3 = pgImg[indexInArray - imageWidth - 1]; + if( gValue1 >= gValue2 && gValue1 >= gValue3 ) + { //up-right + x = x + 1; + y = y - 1; + } + else if( gValue3 >= gValue2 && gValue3 >= gValue1 ) + { //up-left + x = x - 1; + y = y - 1; + } + else + { //straight-up + y = y - 1; + } + lastDirection = UpDir; + } + } + indexInArray = y * imageWidth + x; + } //end while go up + } //end anchor is Vertical + //only keep the edge chains whose length is larger than the minLineLen_; + edgeLenFirst = offsetPFirst - pFirstPartEdgeS_[offsetPS]; + edgeLenSecond = offsetPSecond - pSecondPartEdgeS_[offsetPS]; + if( edgeLenFirst + edgeLenSecond < minLineLen_ + 1 ) + { //short edge, drop it + offsetPFirst = pFirstPartEdgeS_[offsetPS]; + offsetPSecond = pSecondPartEdgeS_[offsetPS]; + } + else + { + offsetPS++; + } + } + //store the last index + pFirstPartEdgeS_[offsetPS] = offsetPFirst; + pSecondPartEdgeS_[offsetPS] = offsetPSecond; + if( offsetPS > maxNumOfEdge ) + { + cout << "Edge drawing Error: The total number of edges is larger than MaxNumOfEdge, " + "numofedge = " + << offsetPS << ", MaxNumOfEdge=" << maxNumOfEdge << endl; + return -1; + } + if( offsetPFirst > edgePixelArraySize || offsetPSecond > edgePixelArraySize ) + { + cout << "Edge drawing Error: The total number of edge pixels is larger than MaxNumOfEdgePixels, " + "numofedgePixel1 = " + << offsetPFirst << ", numofedgePixel2 = " << offsetPSecond << ", MaxNumOfEdgePixel=" << edgePixelArraySize << endl; + return -1; + } + + /*now all the edge information are stored in pFirstPartEdgeX_, pFirstPartEdgeY_, + *pFirstPartEdgeS_, pSecondPartEdgeX_, pSecondPartEdgeY_, pSecondPartEdgeS_; + *we should reorganize them into edgeChains for easily using. */ + int tempID; + edgeChains.xCors.resize( offsetPFirst + offsetPSecond ); + edgeChains.yCors.resize( offsetPFirst + offsetPSecond ); + edgeChains.sId.resize( offsetPS + 1 ); + unsigned int *pxCors = edgeChains.xCors.data(); + unsigned int *pyCors = edgeChains.yCors.data(); + unsigned int *psId = edgeChains.sId.data(); + offsetPFirst = 0; + offsetPSecond = 0; + unsigned int indexInCors = 0; + unsigned int numOfEdges = 0; + for ( unsigned int edgeId = 0; edgeId < offsetPS; edgeId++ ) + { + //step1, put the first and second parts edge coordinates together from edge start to edge end + psId[numOfEdges++] = indexInCors; + indexInArray = pFirstPartEdgeS_[edgeId]; + offsetPFirst = pFirstPartEdgeS_[edgeId + 1]; + for ( tempID = offsetPFirst - 1; tempID >= indexInArray; tempID-- ) + { //add first part edge + pxCors[indexInCors] = pFirstPartEdgeX_[tempID]; + pyCors[indexInCors++] = pFirstPartEdgeY_[tempID]; + } + indexInArray = pSecondPartEdgeS_[edgeId]; + offsetPSecond = pSecondPartEdgeS_[edgeId + 1]; + for ( tempID = indexInArray + 1; tempID < (int)offsetPSecond; tempID++ ) + { //add second part edge + pxCors[indexInCors] = pSecondPartEdgeX_[tempID]; + pyCors[indexInCors++] = pSecondPartEdgeY_[tempID]; + } + } + psId[numOfEdges] = indexInCors; //the end index of the last edge + edgeChains.numOfEdges = numOfEdges; + + return 1; } - -int EDLineDetector::EDline(cv::Mat &image, LineChains &lines, bool smoothed) +int EDLineDetector::EDline( cv::Mat &image, LineChains &lines, bool smoothed ) { - //first, call EdgeDrawing function to extract edges - EdgeChains edges; - if((EdgeDrawing(image, edges, smoothed))!=true){ - cout<<"Line Detection not finished"< lineEquation(2, 0); - //lineEquation.reserve(2); - lineEquations_.clear(); - lineEndpoints_.clear(); - lineDirection_.clear(); - unsigned char *pdirImg = dirImg_.data; - unsigned int numOfLines = 0; - unsigned int offsetInEdgeArrayS, offsetInEdgeArrayE, newOffsetS;//start index and end index - unsigned int offsetInLineArray=0; - float direction;//line direction + //detect lines + unsigned int linePixelID = edges.sId[edges.numOfEdges]; + lines.xCors.resize( linePixelID ); + lines.yCors.resize( linePixelID ); + lines.sId.resize( 5 * edges.numOfEdges ); + unsigned int *pEdgeXCors = edges.xCors.data(); + unsigned int *pEdgeYCors = edges.yCors.data(); + unsigned int *pEdgeSID = edges.sId.data(); + unsigned int *pLineXCors = lines.xCors.data(); + unsigned int *pLineYCors = lines.yCors.data(); + unsigned int *pLineSID = lines.sId.data(); + logNT_ = 2.0 * ( log10( (double) imageWidth ) + log10( (double) imageHeight ) ); + double lineFitErr; //the line fit error; + std::vector lineEquation( 2, 0 ); + lineEquations_.clear(); + lineEndpoints_.clear(); + lineDirection_.clear(); + unsigned char *pdirImg = dirImg_.data; + unsigned int numOfLines = 0; + unsigned int offsetInEdgeArrayS, offsetInEdgeArrayE, newOffsetS; //start index and end index + unsigned int offsetInLineArray = 0; + float direction; //line direction - for(unsigned int edgeID=0; edgeID offsetInEdgeArrayS+minLineLen_){//extract line segments from an edge, may find more than one segments - //find an initial line segment - while(offsetInEdgeArrayE > offsetInEdgeArrayS+minLineLen_){ - lineFitErr = LeastSquaresLineFit_(pEdgeXCors,pEdgeYCors, - offsetInEdgeArrayS,lineEquation); - if(lineFitErr<=lineFitErrThreshold_) break;//ok, an initial line segment detected - offsetInEdgeArrayS +=SkipEdgePoint; //skip the first two pixel in the chain and try with the remaining pixels - } - if(lineFitErr>lineFitErrThreshold_) break; //no line is detected - //An initial line segment is detected. Try to extend this line segment - pLineSID[numOfLines] = offsetInLineArray; - double coef1;//for a line ax+by+c=0, coef1 = 1/sqrt(a^2+b^2); - double pointToLineDis;//for a line ax+by+c=0 and a point(xi, yi), pointToLineDis = coef1*|a*xi+b*yi+c| - bool bExtended=true; - bool bFirstTry = true; - int numOfOutlier;//to against noise, we accept a few outlier of a line. - int tryTimes = 0; - if(pdirImg[pEdgeYCors[offsetInEdgeArrayS]*imageWidth + pEdgeXCors[offsetInEdgeArrayS]]==Horizontal){//y=ax+b, i.e. ax-y+b=0 - while(bExtended){ - tryTimes++; - if(bFirstTry){ - bFirstTry = false; - for(int i=0; i offsetInEdgeArrayS){ - pointToLineDis = fabs(lineEquation[0]*pEdgeXCors[offsetInEdgeArrayS] - - pEdgeYCors[offsetInEdgeArrayS] + lineEquation[1])*coef1; - pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; - pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; - if(pointToLineDis>lineFitErrThreshold_){ - numOfOutlier++; - if(numOfOutlier>3) break; - }else{//we count number of connective outliers. - numOfOutlier=0; - } - } - //pop back the last few outliers from lines and return them to edge chain - offsetInLineArray -=numOfOutlier; - offsetInEdgeArrayS -=numOfOutlier; - if(offsetInLineArray - newOffsetS>0&&tryTimes lineEqu(3,0); - //lineEqu.reserve(3); - lineEqu[0] = lineEquation[0]*coef1; - lineEqu[1] = -1*coef1; - lineEqu[2] = lineEquation[1]*coef1; - if(LineValidation_(pLineXCors,pLineYCors,pLineSID[numOfLines],offsetInLineArray,lineEqu,direction)){//check the line - //store the line equation coefficients - lineEquations_.push_back(lineEqu); - /*At last, compute the line endpoints and store them. - *we project the first and last pixels in the pixelChain onto the best fit line - *to get the line endpoints. - *xp= (w2^2*x0-w1*w2*y0-w3*w1)/(w1^2+w2^2) - *yp= (w1^2*y0-w1*w2*x0-w3*w2)/(w1^2+w2^2) */ - std::vector lineEndP(4, 0);//line endpoints - //lineEndP.resize(4); - double a1 = lineEqu[1]*lineEqu[1]; - double a2 = lineEqu[0]*lineEqu[0]; - double a3 = lineEqu[0]*lineEqu[1]; - double a4 = lineEqu[2]*lineEqu[0]; - double a5 = lineEqu[2]*lineEqu[1]; - unsigned int Px = pLineXCors[pLineSID[numOfLines] ];//first pixel - unsigned int Py = pLineYCors[pLineSID[numOfLines] ]; - lineEndP[0] = a1*Px-a3*Py-a4;//x - lineEndP[1] = a2*Py-a3*Px-a5;//y - Px = pLineXCors[offsetInLineArray -1 ];//last pixel - Py = pLineYCors[offsetInLineArray -1 ]; - lineEndP[2] = a1*Px-a3*Py-a4;//x - lineEndP[3] = a2*Py-a3*Px-a5;//y - lineEndpoints_.push_back(lineEndP); - lineDirection_.push_back(direction); - numOfLines++; - }else{ - offsetInLineArray = pLineSID[numOfLines];// line was not accepted, the offset is set back - } - }else{//x=ay+b, i.e. x-ay-b=0 - while(bExtended){ - tryTimes++; - if(bFirstTry){ - bFirstTry = false; - for(int i=0; i offsetInEdgeArrayS){ - pointToLineDis = fabs(pEdgeXCors[offsetInEdgeArrayS] - - lineEquation[0]*pEdgeYCors[offsetInEdgeArrayS] - lineEquation[1])*coef1; - pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; - pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; - if(pointToLineDis>lineFitErrThreshold_){ - numOfOutlier++; - if(numOfOutlier>3) break; - }else{//we count number of connective outliers. - numOfOutlier=0; - } - } - //pop back the last few outliers from lines and return them to edge chain - offsetInLineArray -= numOfOutlier; - offsetInEdgeArrayS -= numOfOutlier; - if(offsetInLineArray - newOffsetS>0&&tryTimes lineEqu(3,0); - //lineEqu.reserve(3); - lineEqu[0] = 1*coef1; - lineEqu[1] = -lineEquation[0]*coef1; - lineEqu[2] = -lineEquation[1]*coef1; + for ( unsigned int edgeID = 0; edgeID < edges.numOfEdges; edgeID++ ) + { + offsetInEdgeArrayS = pEdgeSID[edgeID]; + offsetInEdgeArrayE = pEdgeSID[edgeID + 1]; + while ( offsetInEdgeArrayE > offsetInEdgeArrayS + minLineLen_ ) + { //extract line segments from an edge, may find more than one segments + //find an initial line segment + while ( offsetInEdgeArrayE > offsetInEdgeArrayS + minLineLen_ ) + { + lineFitErr = LeastSquaresLineFit_( pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS, lineEquation ); + if( lineFitErr <= lineFitErrThreshold_ ) + break; //ok, an initial line segment detected + offsetInEdgeArrayS += SkipEdgePoint; //skip the first two pixel in the chain and try with the remaining pixels + } + if( lineFitErr > lineFitErrThreshold_ ) + break; //no line is detected + //An initial line segment is detected. Try to extend this line segment + pLineSID[numOfLines] = offsetInLineArray; + double coef1; //for a line ax+by+c=0, coef1 = 1/sqrt(a^2+b^2); + double pointToLineDis; //for a line ax+by+c=0 and a point(xi, yi), pointToLineDis = coef1*|a*xi+b*yi+c| + bool bExtended = true; + bool bFirstTry = true; + int numOfOutlier; //to against noise, we accept a few outlier of a line. + int tryTimes = 0; + if( pdirImg[pEdgeYCors[offsetInEdgeArrayS] * imageWidth + pEdgeXCors[offsetInEdgeArrayS]] == Horizontal ) + { //y=ax+b, i.e. ax-y+b=0 + while ( bExtended ) + { + tryTimes++; + if( bFirstTry ) + { + bFirstTry = false; + for ( int i = 0; i < minLineLen_; i++ ) + { //First add the initial line segment to the line array + pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; + pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; + } + } + else + { //after each try, line is extended, line equation should be re-estimated + //adjust the line equation + lineFitErr = LeastSquaresLineFit_( pLineXCors, pLineYCors, pLineSID[numOfLines], newOffsetS, offsetInLineArray, lineEquation ); + } + coef1 = 1 / sqrt( lineEquation[0] * lineEquation[0] + 1 ); + numOfOutlier = 0; + newOffsetS = offsetInLineArray; + while ( offsetInEdgeArrayE > offsetInEdgeArrayS ) + { + pointToLineDis = fabs( lineEquation[0] * pEdgeXCors[offsetInEdgeArrayS] - pEdgeYCors[offsetInEdgeArrayS] + lineEquation[1] ) * coef1; + pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; + pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; + if( pointToLineDis > lineFitErrThreshold_ ) + { + numOfOutlier++; + if( numOfOutlier > 3 ) + break; + } + else + { //we count number of connective outliers. + numOfOutlier = 0; + } + } + //pop back the last few outliers from lines and return them to edge chain + offsetInLineArray -= numOfOutlier; + offsetInEdgeArrayS -= numOfOutlier; + if( offsetInLineArray - newOffsetS > 0 && tryTimes < TryTime ) + { //some new pixels are added to the line + } + else + { + bExtended = false; //no new pixels are added. + } + } + //the line equation coefficients,for line w1x+w2y+w3 =0, we normalize it to make w1^2+w2^2 = 1. + std::vector lineEqu( 3, 0 ); + lineEqu[0] = lineEquation[0] * coef1; + lineEqu[1] = -1 * coef1; + lineEqu[2] = lineEquation[1] * coef1; + if( LineValidation_( pLineXCors, pLineYCors, pLineSID[numOfLines], offsetInLineArray, lineEqu, direction ) ) + { //check the line + //store the line equation coefficients + lineEquations_.push_back( lineEqu ); + /*At last, compute the line endpoints and store them. + *we project the first and last pixels in the pixelChain onto the best fit line + *to get the line endpoints. + *xp= (w2^2*x0-w1*w2*y0-w3*w1)/(w1^2+w2^2) + *yp= (w1^2*y0-w1*w2*x0-w3*w2)/(w1^2+w2^2) */ + std::vector lineEndP( 4, 0 ); //line endpoints + double a1 = lineEqu[1] * lineEqu[1]; + double a2 = lineEqu[0] * lineEqu[0]; + double a3 = lineEqu[0] * lineEqu[1]; + double a4 = lineEqu[2] * lineEqu[0]; + double a5 = lineEqu[2] * lineEqu[1]; + unsigned int Px = pLineXCors[pLineSID[numOfLines]]; //first pixel + unsigned int Py = pLineYCors[pLineSID[numOfLines]]; + lineEndP[0] = a1 * Px - a3 * Py - a4; //x + lineEndP[1] = a2 * Py - a3 * Px - a5; //y + Px = pLineXCors[offsetInLineArray - 1]; //last pixel + Py = pLineYCors[offsetInLineArray - 1]; + lineEndP[2] = a1 * Px - a3 * Py - a4; //x + lineEndP[3] = a2 * Py - a3 * Px - a5; //y + lineEndpoints_.push_back( lineEndP ); + lineDirection_.push_back( direction ); + numOfLines++; + } + else + { + offsetInLineArray = pLineSID[numOfLines]; // line was not accepted, the offset is set back + } + } + else + { //x=ay+b, i.e. x-ay-b=0 + while ( bExtended ) + { + tryTimes++; + if( bFirstTry ) + { + bFirstTry = false; + for ( int i = 0; i < minLineLen_; i++ ) + { //First add the initial line segment to the line array + pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; + pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; + } + } + else + { //after each try, line is extended, line equation should be re-estimated + //adjust the line equation + lineFitErr = LeastSquaresLineFit_( pLineXCors, pLineYCors, pLineSID[numOfLines], newOffsetS, offsetInLineArray, lineEquation ); + } + coef1 = 1 / sqrt( 1 + lineEquation[0] * lineEquation[0] ); + numOfOutlier = 0; + newOffsetS = offsetInLineArray; + while ( offsetInEdgeArrayE > offsetInEdgeArrayS ) + { + pointToLineDis = fabs( pEdgeXCors[offsetInEdgeArrayS] - lineEquation[0] * pEdgeYCors[offsetInEdgeArrayS] - lineEquation[1] ) * coef1; + pLineXCors[offsetInLineArray] = pEdgeXCors[offsetInEdgeArrayS]; + pLineYCors[offsetInLineArray++] = pEdgeYCors[offsetInEdgeArrayS++]; + if( pointToLineDis > lineFitErrThreshold_ ) + { + numOfOutlier++; + if( numOfOutlier > 3 ) + break; + } + else + { //we count number of connective outliers. + numOfOutlier = 0; + } + } + //pop back the last few outliers from lines and return them to edge chain + offsetInLineArray -= numOfOutlier; + offsetInEdgeArrayS -= numOfOutlier; + if( offsetInLineArray - newOffsetS > 0 && tryTimes < TryTime ) + { //some new pixels are added to the line + } + else + { + bExtended = false; //no new pixels are added. + } + } + //the line equation coefficients,for line w1x+w2y+w3 =0, we normalize it to make w1^2+w2^2 = 1. + std::vector lineEqu( 3, 0 ); + lineEqu[0] = 1 * coef1; + lineEqu[1] = -lineEquation[0] * coef1; + lineEqu[2] = -lineEquation[1] * coef1; - if(LineValidation_(pLineXCors,pLineYCors,pLineSID[numOfLines],offsetInLineArray,lineEqu,direction)){//check the line - //store the line equation coefficients - lineEquations_.push_back(lineEqu); - /*At last, compute the line endpoints and store them. - *we project the first and last pixels in the pixelChain onto the best fit line - *to get the line endpoints. - *xp= (w2^2*x0-w1*w2*y0-w3*w1)/(w1^2+w2^2) - *yp= (w1^2*y0-w1*w2*x0-w3*w2)/(w1^2+w2^2) */ - std::vector lineEndP(4,0);//line endpoints - //lineEndP.reserve(4); - double a1 = lineEqu[1]*lineEqu[1]; - double a2 = lineEqu[0]*lineEqu[0]; - double a3 = lineEqu[0]*lineEqu[1]; - double a4 = lineEqu[2]*lineEqu[0]; - double a5 = lineEqu[2]*lineEqu[1]; - unsigned int Px = pLineXCors[pLineSID[numOfLines] ];//first pixel - unsigned int Py = pLineYCors[pLineSID[numOfLines] ]; - lineEndP[0] = a1*Px-a3*Py-a4;//x - lineEndP[1] = a2*Py-a3*Px-a5;//y - Px = pLineXCors[offsetInLineArray -1 ];//last pixel - Py = pLineYCors[offsetInLineArray -1 ]; - lineEndP[2] = a1*Px-a3*Py-a4;//x - lineEndP[3] = a2*Py-a3*Px-a5;//y - lineEndpoints_.push_back(lineEndP); - lineDirection_.push_back(direction); - numOfLines++; - }else{ - offsetInLineArray = pLineSID[numOfLines];// line was not accepted, the offset is set back - } - } - //Extract line segments from the remaining pixel; Current chain has been shortened already. - } - }//end for(unsigned int edgeID=0; edgeID lineEndP( 4, 0 ); //line endpoints + double a1 = lineEqu[1] * lineEqu[1]; + double a2 = lineEqu[0] * lineEqu[0]; + double a3 = lineEqu[0] * lineEqu[1]; + double a4 = lineEqu[2] * lineEqu[0]; + double a5 = lineEqu[2] * lineEqu[1]; + unsigned int Px = pLineXCors[pLineSID[numOfLines]]; //first pixel + unsigned int Py = pLineYCors[pLineSID[numOfLines]]; + lineEndP[0] = a1 * Px - a3 * Py - a4; //x + lineEndP[1] = a2 * Py - a3 * Px - a5; //y + Px = pLineXCors[offsetInLineArray - 1]; //last pixel + Py = pLineYCors[offsetInLineArray - 1]; + lineEndP[2] = a1 * Px - a3 * Py - a4; //x + lineEndP[3] = a2 * Py - a3 * Px - a5; //y + lineEndpoints_.push_back( lineEndP ); + lineDirection_.push_back( direction ); + numOfLines++; + } + else + { + offsetInLineArray = pLineSID[numOfLines]; // line was not accepted, the offset is set back + } + } + //Extract line segments from the remaining pixel; Current chain has been shortened already. + } + } //end for(unsigned int edgeID=0; edgeID &lineEquation ) +{ -double EDLineDetector::LeastSquaresLineFit_( unsigned int *xCors, unsigned int *yCors, - unsigned int offsetS, std::vector &lineEquation) -{ -// if(lineEquation.size()!=2){ -// //std::cout<<"SHOULD NOT BE != 2"<();//fitMatT = [x0, x1, ... xn; 1,1,...,1]; - for(int i=0; i(); - coef = 1.0/(double(pATA[0])*double(pATA[3]) - double(pATA[1])*double(pATA[2])); - // lineEquation = svd.Invert(ATA) * matT * vec; - lineEquation[0] = coef *( double(pATA[3])*double(ATV[0][0])-double(pATA[1])*double(ATV[0][1])); - lineEquation[1] = coef *( double(pATA[0])*double(ATV[0][1])-double(pATA[2])*double(ATV[0][0])); - /*compute line fit error */ - for(int i=0; i();//fitMatT = [y0, y1, ... yn; 1,1,...,1]; - for(int i=0; i(); - coef = 1.0/(double(pATA[0])*double(pATA[3]) - double(pATA[1])*double(pATA[2])); - // lineEquation = svd.Invert(ATA) * matT * vec; - lineEquation[0] = coef *( double(pATA[3])*double(ATV[0][0])-double(pATA[1])*double(ATV[0][1])); - lineEquation[1] = coef *( double(pATA[0])*double(ATV[0][1])-double(pATA[2])*double(ATV[0][0])); - /*compute line fit error */ - for(int i=0; i(); //fitMatT = [x0, x1, ... xn; 1,1,...,1]; + for ( int i = 0; i < minLineLen_; i++ ) + { + //*(pMatT+minLineLen_) = 1; //the value are not changed; + * ( pMatT++ ) = xCors[offsetS]; + fitVec[0][i] = yCors[offsetS++]; + } + ATA = fitMatT * fitMatT.t(); + ATV = fitMatT * fitVec.t(); + /* [a,b]^T = Inv(mat^T * mat) * mat^T * vec */ + pATA = ATA.ptr(); + coef = 1.0 / ( double( pATA[0] ) * double( pATA[3] ) - double( pATA[1] ) * double( pATA[2] ) ); + // lineEquation = svd.Invert(ATA) * matT * vec; + lineEquation[0] = coef * ( double( pATA[3] ) * double( ATV[0][0] ) - double( pATA[1] ) * double( ATV[0][1] ) ); + lineEquation[1] = coef * ( double( pATA[0] ) * double( ATV[0][1] ) - double( pATA[2] ) * double( ATV[0][0] ) ); + /*compute line fit error */ + for ( int i = 0; i < minLineLen_; i++ ) + { + //coef = double(yCors[offset]) - double(xCors[offset++]) * lineEquation[0] - lineEquation[1]; + coef = double( yCors[offset] ) - double( xCors[offset] ) * lineEquation[0] - lineEquation[1]; + offset++; + fitError += coef * coef; + } + return sqrt( fitError ); + } + /*If the first pixel in this chain is vertical, + *then we try to find a vertical line, x=ay+b;*/ + if( pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Vertical ) + { + /*Build the system,and solve it using least square regression: mat * [a,b]^T = vec + * [y0,1] [x0] + * [y1,1] [a] [x1] + * . [b] = . + * [yn,1] [xn]*/ + pMatT = fitMatT.ptr(); //fitMatT = [y0, y1, ... yn; 1,1,...,1]; + for ( int i = 0; i < minLineLen_; i++ ) + { + //*(pMatT+minLineLen_) = 1;//the value are not changed; + * ( pMatT++ ) = yCors[offsetS]; + fitVec[0][i] = xCors[offsetS++]; + } + ATA = fitMatT * ( fitMatT.t() ); + ATV = fitMatT * fitVec.t(); + /* [a,b]^T = Inv(mat^T * mat) * mat^T * vec */ + pATA = ATA.ptr(); + coef = 1.0 / ( double( pATA[0] ) * double( pATA[3] ) - double( pATA[1] ) * double( pATA[2] ) ); + // lineEquation = svd.Invert(ATA) * matT * vec; + lineEquation[0] = coef * ( double( pATA[3] ) * double( ATV[0][0] ) - double( pATA[1] ) * double( ATV[0][1] ) ); + lineEquation[1] = coef * ( double( pATA[0] ) * double( ATV[0][1] ) - double( pATA[2] ) * double( ATV[0][0] ) ); + /*compute line fit error */ + for ( int i = 0; i < minLineLen_; i++ ) + { + //coef = double(xCors[offset]) - double(yCors[offset++]) * lineEquation[0] - lineEquation[1]; + coef = double( xCors[offset] ) - double( yCors[offset] ) * lineEquation[0] - lineEquation[1]; + offset++; + fitError += coef * coef; + } + return sqrt( fitError ); + } + return 0; } -double EDLineDetector::LeastSquaresLineFit_( unsigned int *xCors, unsigned int *yCors, - unsigned int offsetS, unsigned int newOffsetS, - unsigned int offsetE, std::vector &lineEquation) +double EDLineDetector::LeastSquaresLineFit_( unsigned int *xCors, unsigned int *yCors, unsigned int offsetS, unsigned int newOffsetS, + unsigned int offsetE, std::vector &lineEquation ) { - int length = offsetE - offsetS; - int newLength = offsetE - newOffsetS; - if(length<=0||newLength<=0){ - cout<<"EDLineDetector::LeastSquaresLineFit_ Error:" - " the expected line index is wrong...offsetE = " - < matT(2,newLength); - cv::Mat_ vec(newLength,1); - float * pMatT; - float * pATA; - // double fitError = 0; - double coef; - unsigned char *pdirImg = dirImg_.data; - /*If the first pixel in this chain is horizontal, - *then we try to find a horizontal line, y=ax+b;*/ - if(pdirImg[yCors[offsetS]*imageWidth + xCors[offsetS] ]==Horizontal){ - /*Build the new system,and solve it using least square regression: mat * [a,b]^T = vec - * [x0',1] [y0'] - * [x1',1] [a] [y1'] - * . [b] = . - * [xn',1] [yn']*/ - pMatT = matT.ptr();//matT = [x0', x1', ... xn'; 1,1,...,1] - for(int i=0; i(); - coef = 1.0/(double(pATA[0])*double(pATA[3]) - double(pATA[1])*double(pATA[2])); - lineEquation[0] = coef *( double(pATA[3])*double(ATV[0][0])-double(pATA[1])*double(ATV[0][1])); - lineEquation[1] = coef *( double(pATA[0])*double(ATV[0][1])-double(pATA[2])*double(ATV[0][0])); - /*compute line fit error */ - // for(int i=0; i();//matT = [y0', y1', ... yn'; 1,1,...,1] - for(int i=0; i matT( 2, newLength ); + cv::Mat_ vec( newLength, 1 ); + float * pMatT; + float * pATA; + double coef; + unsigned char *pdirImg = dirImg_.data; + /*If the first pixel in this chain is horizontal, + *then we try to find a horizontal line, y=ax+b;*/ + if( pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Horizontal ) + { + /*Build the new system,and solve it using least square regression: mat * [a,b]^T = vec + * [x0',1] [y0'] + * [x1',1] [a] [y1'] + * . [b] = . + * [xn',1] [yn']*/ + pMatT = matT.ptr(); //matT = [x0', x1', ... xn'; 1,1,...,1] + for ( int i = 0; i < newLength; i++ ) + { + * ( pMatT + newLength ) = 1; + * ( pMatT++ ) = xCors[newOffsetS]; + vec[0][i] = yCors[newOffsetS++]; + } + /* [a,b]^T = Inv(ATA + mat^T * mat) * (ATV + mat^T * vec) */ + tempMatLineFit = matT * matT.t(); + tempVecLineFit = matT * vec; + ATA = ATA + tempMatLineFit; + ATV = ATV + tempVecLineFit; + pATA = ATA.ptr(); + coef = 1.0 / ( double( pATA[0] ) * double( pATA[3] ) - double( pATA[1] ) * double( pATA[2] ) ); + lineEquation[0] = coef * ( double( pATA[3] ) * double( ATV[0][0] ) - double( pATA[1] ) * double( ATV[0][1] ) ); + lineEquation[1] = coef * ( double( pATA[0] ) * double( ATV[0][1] ) - double( pATA[2] ) * double( ATV[0][0] ) ); + + return 0; + } + /*If the first pixel in this chain is vertical, + *then we try to find a vertical line, x=ay+b;*/ + if( pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Vertical ) + { + /*Build the system,and solve it using least square regression: mat * [a,b]^T = vec + * [y0',1] [x0'] + * [y1',1] [a] [x1'] + * . [b] = . + * [yn',1] [xn']*/ + pMatT = matT.ptr(); //matT = [y0', y1', ... yn'; 1,1,...,1] + for ( int i = 0; i < newLength; i++ ) + { + * ( pMatT + newLength ) = 1; + * ( pMatT++ ) = yCors[newOffsetS]; + vec[0][i] = xCors[newOffsetS++]; + } + /* [a,b]^T = Inv(ATA + mat^T * mat) * (ATV + mat^T * vec) */ // matT.MultiplyWithTransposeOf(matT, tempMatLineFit); - tempMatLineFit = matT*matT.t(); - tempVecLineFit = matT * vec; - ATA = ATA + tempMatLineFit; - ATV = ATV + tempVecLineFit; + tempMatLineFit = matT * matT.t(); + tempVecLineFit = matT * vec; + ATA = ATA + tempMatLineFit; + ATV = ATV + tempVecLineFit; // pATA = ATA.GetData(); - pATA = ATA.ptr(); - coef = 1.0/(double(pATA[0])*double(pATA[3]) - double(pATA[1])*double(pATA[2])); - lineEquation[0] = coef *( double(pATA[3])*double(ATV[0][0])-double(pATA[1])*double(ATV[0][1])); - lineEquation[1] = coef *( double(pATA[0])*double(ATV[0][1])-double(pATA[2])*double(ATV[0][0])); - /*compute line fit error */ - // for(int i=0; i(); + coef = 1.0 / ( double( pATA[0] ) * double( pATA[3] ) - double( pATA[1] ) * double( pATA[2] ) ); + lineEquation[0] = coef * ( double( pATA[3] ) * double( ATV[0][0] ) - double( pATA[1] ) * double( ATV[0][1] ) ); + lineEquation[1] = coef * ( double( pATA[0] ) * double( ATV[0][1] ) - double( pATA[2] ) * double( ATV[0][0] ) ); + + } + return 0; } -bool EDLineDetector::LineValidation_( unsigned int *xCors, unsigned int *yCors, - unsigned int offsetS, unsigned int offsetE, - std::vector &lineEquation, float &direction) +bool EDLineDetector::LineValidation_( unsigned int *xCors, unsigned int *yCors, unsigned int offsetS, unsigned int offsetE, + std::vector &lineEquation, float &direction ) { - if(bValidate_){ - int n = offsetE - offsetS; - /*first compute the direction of line, make sure that the dark side always be the - *left side of a line.*/ - int meanGradientX=0, meanGradientY=0; - short *pdxImg = dxImg_.ptr(); - short *pdyImg = dyImg_.ptr(); - double dx, dy; - std::vector pointDirection; - int index; - for(int i=0; i0&&meanGradientY>=0){//first quadrant, and positive direction of X axis. - direction = atan2(-dy,dx);//line direction is in fourth quadrant - } - if(meanGradientX<=0&&meanGradientY>0){//second quadrant, and positive direction of Y axis. - direction = atan2(dy,dx);//line direction is in first quadrant - } - if(meanGradientX<0&&meanGradientY<=0){//third quadrant, and negative direction of X axis. - direction = atan2(dy,-dx);//line direction is in second quadrant - } - if(meanGradientX>=0&&meanGradientY<0){//fourth quadrant, and negative direction of Y axis. - direction = atan2(-dy,-dx);//line direction is in third quadrant - } - /*then check whether the line is on the border of the image. We don't keep the border line.*/ - if(fabs(direction)<0.15||M_PI-fabs(direction)<0.15){//Horizontal line - if(fabs(lineEquation[2])<10||fabs(imageHeight - fabs(lineEquation[2]))<10){//upper border or lower border - return false; - } - } - if(fabs(fabs(direction)-M_PI*0.5)<0.15){//Vertical line - if(fabs(lineEquation[2])<10||fabs(imageWidth - fabs(lineEquation[2]))<10){//left border or right border - return false; - } - } - //count the aligned points on the line which have the same direction as the line. - double disDirection; - int k = 0; - for(int i=0; i(); + short *pdyImg = dyImg_.ptr(); + double dx, dy; + std::vector pointDirection; + int index; + for ( int i = 0; i < n; i++ ) + { + index = yCors[offsetS] * imageWidth + xCors[offsetS]; + offsetS++; + meanGradientX += pdxImg[index]; + meanGradientY += pdyImg[index]; + dx = (double) pdxImg[index]; + dy = (double) pdyImg[index]; + pointDirection.push_back( atan2( -dx, dy ) ); + } + dx = fabs( lineEquation[1] ); + dy = fabs( lineEquation[0] ); + if( meanGradientX == 0 && meanGradientY == 0 ) + { //not possible, if happens, it must be a wrong line, + return false; + } + if( meanGradientX > 0 && meanGradientY >= 0 ) + { //first quadrant, and positive direction of X axis. + direction = atan2( -dy, dx ); //line direction is in fourth quadrant + } + if( meanGradientX <= 0 && meanGradientY > 0 ) + { //second quadrant, and positive direction of Y axis. + direction = atan2( dy, dx ); //line direction is in first quadrant + } + if( meanGradientX < 0 && meanGradientY <= 0 ) + { //third quadrant, and negative direction of X axis. + direction = atan2( dy, -dx ); //line direction is in second quadrant + } + if( meanGradientX >= 0 && meanGradientY < 0 ) + { //fourth quadrant, and negative direction of Y axis. + direction = atan2( -dy, -dx ); //line direction is in third quadrant + } + /*then check whether the line is on the border of the image. We don't keep the border line.*/ + if( fabs( direction ) < 0.15 || M_PI - fabs( direction ) < 0.15 ) + { //Horizontal line + if( fabs( lineEquation[2] ) < 10 || fabs( imageHeight - fabs( lineEquation[2] ) ) < 10 ) + { //upper border or lower border + return false; + } + } + if( fabs( fabs( direction ) - M_PI * 0.5 ) < 0.15 ) + { //Vertical line + if( fabs( lineEquation[2] ) < 10 || fabs( imageWidth - fabs( lineEquation[2] ) ) < 10 ) + { //left border or right border + return false; + } + } + //count the aligned points on the line which have the same direction as the line. + double disDirection; + int k = 0; + for ( int i = 0; i < n; i++ ) + { + disDirection = fabs( direction - pointDirection[i] ); + if( fabs( 2 * M_PI - disDirection ) < 0.392699 || disDirection < 0.392699 ) + { //same direction, pi/8 = 0.392699081698724 + k++; + } + } + //now compute NFA(Number of False Alarms) + double ret = nfa( n, k, 0.125, logNT_ ); - return (ret>0); //0 corresponds to 1 mean false alarm - }else{ - return true; - } + return ( ret > 0 ); //0 corresponds to 1 mean false alarm + } + else + { + return true; + } } -int EDLineDetector::EDline(cv::Mat &image, bool smoothed) +int EDLineDetector::EDline( cv::Mat &image, bool smoothed ) { - if((EDline(image, lines_, smoothed)) != true){ - return -1; - } - lineSalience_.clear(); - lineSalience_.resize(lines_.numOfLines); - unsigned char *pgImg = gImgWO_.ptr(); - unsigned int indexInLineArray; - unsigned int *pXCor = lines_.xCors.data(); - unsigned int *pYCor = lines_.yCors.data(); - unsigned int *pSID = lines_.sId.data(); - for(unsigned int i=0; i #include - - #include "opencv2/line_descriptor.hpp" #endif