├── CMakeLists.txt ├── LICENSE ├── README.md ├── src ├── CannyChain.cpp ├── CannyChain.h ├── MSED.cpp ├── MSED.h ├── Timer.h └── main.cpp └── test.JPG /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # set project's name 2 | PROJECT( MultiScaleEdgeDetection ) 3 | 4 | ############################################################################### 5 | # CMake settings 6 | CMAKE_MINIMUM_REQUIRED(VERSION 2.8.3) 7 | 8 | # OpenCV 9 | FIND_PACKAGE(OpenCV REQUIRED) 10 | 11 | FILE(GLOB_RECURSE HDRS_FILES "src/*.h" "src/*.hpp") 12 | FILE(GLOB_RECURSE SRCS_FILES "src/*.c" "src/*.cpp") 13 | 14 | ADD_EXECUTABLE(${PROJECT_NAME} ${SRCS_FILES} ${HDRS_FILES}) 15 | TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${OpenCV_LIBS}) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, xiaohulugo 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiScaleEdgeDetection 2 | 3 | MSEdge: A Multi-Scale Edge Chain Detector, Xiaohu Lu, JianYao, Li Li, Yahui Liu and Xiaofeng Zhang, CVM2017. 4 | 5 | https://github.com/xiaohulugo/xiaohulugo.github.com/blob/master/papers/Multi_Scale_Edge_Detector_CVM2017.pdf 6 | 7 | Prerequisites: 8 | --- 9 | OpenCV > 2.4.x 10 | 11 | Usage: 12 | --- 13 | 1. build the project with Cmake 14 | 2. run the code 15 | 16 | Performance: 17 | --- 18 | ![image](https://github.com/xiaohulugo/images/blob/master/MultiScaleEdgeDetection.jpg) 19 | 20 | Please cite these two papers if you feel this code useful: 21 | 22 | @InProceedings{Lu2017MSEdge, 23 | author = {Lu, Xiaohu and Yao, Jian and Li, Li and Liu, Yahui and Zhang, Xiaofeng}, 24 | title = {MSEdge: A Multi-Scale Edge Chain Detector}, 25 | booktitle = {The 5th International Conference on Computational Visual Media (CVM)}, 26 | month = {April}, 27 | year = {2017} 28 | } 29 | 30 | @InProceedings{Lu2015CannyLines, 31 | title={CannyLines: A parameter-free line segment detector}, 32 | author={Lu, Xiaohu and Yao, Jian and Li, Kai and Li, Li}, 33 | booktitle={IEEE International Conference on Image Processing (ICIP)}, 34 | pages={507-511}, 35 | year={2015}, 36 | } 37 | 38 | 39 | Feel free to correct my code, if you spotted the mistakes. You are also welcomed to Email me: fangzelu@gmail.com 40 | -------------------------------------------------------------------------------- /src/CannyChain.cpp: -------------------------------------------------------------------------------- 1 | #include "CannyChain.h" 2 | #include 3 | using namespace std; 4 | 5 | #define MIN_FLOAT 0.000101 6 | 7 | CannyChain::CannyChain() 8 | { 9 | 10 | } 11 | 12 | CannyChain::~CannyChain() 13 | { 14 | 15 | } 16 | 17 | void CannyChain::run( cv::Mat &image, double thGradient, std::vector > &CannyChains ) 18 | { 19 | int cols = image.cols; 20 | int rows = image.rows; 21 | cols_1 = cols - 1; 22 | rows_1 = rows - 1; 23 | int imgSize = rows * cols; 24 | 25 | cv::Mat imgNew; 26 | if ( image.channels() == 3 ) 27 | { 28 | cv::cvtColor( image, imgNew, CV_RGB2GRAY ); 29 | } 30 | else 31 | { 32 | imgNew = image; 33 | } 34 | 35 | // canny 36 | cv::Mat edgeMap; 37 | cv::Canny( imgNew, edgeMap, thGradient, 2.0*thGradient, 3, false ); 38 | 39 | // get image information 40 | gradientMap = cv::Mat::zeros( rows, cols, CV_64FC1 ); 41 | 42 | cv::Mat dx(rows, cols, CV_16S, Scalar(0)); 43 | cv::Mat dy(rows, cols, CV_16S, Scalar(0)); 44 | 45 | cv::Sobel( imgNew, dx, CV_16S, 1, 0, 3, 1, 0, cv::BORDER_REPLICATE); 46 | cv::Sobel( imgNew, dy, CV_16S, 0, 1, 3, 1, 0, cv::BORDER_REPLICATE); 47 | 48 | double binStep = 20.0; 49 | int binSize = 2500.0 / binStep + 10; 50 | std::vector > edgePixelBins( binSize ); 51 | 52 | double *ptrG = (double*) gradientMap.data; 53 | short *ptrX = (short*) dx.data; 54 | short *ptrY = (short*) dy.data; 55 | uchar *ptrE = edgeMap.data; 56 | 57 | for ( int y = 0; y< rows; ++y ) 58 | { 59 | for ( int x=0; x( xTemp, xTemp + 8 ); 79 | 80 | int yTemp[8] = { 1, 0, -1, 0, 1, 1, -1, -1 }; 81 | offsetY = std::vector( yTemp, yTemp + 8 ); 82 | 83 | offsetTotal.resize( 8 ); 84 | for ( int i=0; i<8; ++i ) 85 | { 86 | offsetTotal[i] = offsetY[i] * cols + offsetX[i]; 87 | } 88 | 89 | int thMeaningfulLength = int( 2.0 * log( (double) rows * cols ) / log(8.0) + 0.5 ); 90 | 91 | for ( int i = binSize-1; i>=0; --i ) 92 | { 93 | for ( int j=0; j chain; 96 | 97 | int x = edgePixelBins[i][j].x; 98 | int y = edgePixelBins[i][j].y; 99 | int loc = y * cols + x; 100 | double totalGradient = 0.0; 101 | 102 | uchar *ptrECur = edgeMap.data + loc; 103 | if ( *ptrECur == 0 ) 104 | { 105 | continue; 106 | } 107 | 108 | double *ptrGCur = (double*) gradientMap.data + loc; 109 | do 110 | { 111 | chain.push_back( cv::Point( x, y ) ); 112 | *ptrECur = 0; 113 | totalGradient += *ptrGCur; 114 | } while ( next( x, y, &ptrECur, &ptrGCur ) ); 115 | 116 | cv::Point temp; 117 | for ( int m = 0, n = chain.size() - 1; m 2.0 * thMeaningfulLength && totalGradient > thMeaningfulLength * thGradient ) 140 | { 141 | CannyChains.push_back( chain ); 142 | } 143 | 144 | // if ( chain.size() > thMeaningfulLength ) 145 | // { 146 | // CannyChains.push_back( chain ); 147 | // } 148 | } 149 | } 150 | } 151 | 152 | bool CannyChain::next( int &xSeed, int &ySeed, uchar **ptrE, double **ptrGCur ) 153 | { 154 | if ( xSeed < 1 || xSeed >= cols_1 || ySeed < 1 || ySeed >= rows_1 ) 155 | { 156 | return false; 157 | } 158 | 159 | for (int i = 0; i != 8; ++i) 160 | { 161 | if ( *( *ptrE + offsetTotal[i] ) ) 162 | { 163 | xSeed += offsetX[i]; 164 | ySeed += offsetY[i]; 165 | *ptrE += offsetTotal[i]; 166 | *ptrGCur += offsetTotal[i]; 167 | 168 | return true; 169 | } 170 | } 171 | return false; 172 | } 173 | -------------------------------------------------------------------------------- /src/CannyChain.h: -------------------------------------------------------------------------------- 1 | #ifndef _EDGE_CHAIN_ 2 | #define _EDGE_CHAIN_ 3 | #pragma once 4 | 5 | #include "opencv/cv.h" 6 | #include "highgui.h" 7 | 8 | using namespace std; 9 | using namespace cv; 10 | 11 | class CannyChain 12 | { 13 | public: 14 | CannyChain(); 15 | ~CannyChain(); 16 | 17 | void run( cv::Mat &image, double thGradient, std::vector > &CannyChains ); 18 | 19 | bool next( int &xSeed, int &ySeed, uchar **ptrE, double **ptrGCur ); 20 | 21 | private: 22 | std::vector offsetX, offsetY, offsetTotal; 23 | int cols_1, rows_1; 24 | cv::Mat gradientMap; 25 | }; 26 | 27 | #endif // _PLINKAGE_SUPERPIXEL_ 28 | -------------------------------------------------------------------------------- /src/MSED.cpp: -------------------------------------------------------------------------------- 1 | #include "MSED.h" 2 | #include "CannyChain.h" 3 | 4 | #include 5 | #include 6 | 7 | using namespace cv; 8 | using namespace std; 9 | 10 | #define MIN_FLOAT 0.000101 11 | #define INF 1001001000111 12 | 13 | MSED::MSED(void) 14 | { 15 | idxAll = 1; 16 | } 17 | 18 | 19 | MSED::~MSED(void) 20 | { 21 | } 22 | 23 | void MSED::MSEdge( cv::Mat &image, int gaussSize, double thLow, double cth, std::vector > &edges ) 24 | { 25 | rows = image.rows; 26 | cols = image.cols; 27 | rows_1 = rows - 1; 28 | cols_1 = cols - 1; 29 | 30 | OmapAll = cv::Mat( rows, cols, CV_8UC1, cv::Scalar( 0 ) ); 31 | EmapAll = cv::Mat( rows, cols, CV_64FC1, cv::Scalar( 0.0 ) ); 32 | 33 | if ( image.channels() == 3 ) 34 | { 35 | cv::cvtColor(image, image, CV_BGR2GRAY); 36 | } 37 | cv::GaussianBlur( image, image, cv::Size( gaussSize,gaussSize ), -1 ); 38 | 39 | int levels = 4; 40 | for ( int i=0; i > edgeChains; 52 | CannyChain cannyChainer; 53 | cannyChainer.run( imgResized, thLow, edgeChains ); 54 | 55 | // multi scale suppression for edge area 56 | cv::Mat msEmap, msOmap; 57 | MSNonMaximumSuppression( image, imgResized, edgeChains, cth, scale, msEmap, msOmap ); 58 | 59 | // get overlapped edge map 60 | getMSEdgeMap( edgeChains, msEmap, msOmap, scale ); 61 | } 62 | 63 | // edge map thinning 64 | cv::Mat imgMask( rows, cols, CV_8UC1, cv::Scalar( 0 ) ); 65 | double *ptrE = (double*) EmapAll.data; 66 | uchar *ptrM = imgMask.data; 67 | int imgSize = rows * cols; 68 | for ( int i=0; i > &edgeChains, double thContrastDev, int scale, cv::Mat &msEmap, 84 | cv::Mat &msOmap ) 85 | { 86 | double tanPi8 = tan( CV_PI / 8.0 ); 87 | double tan3Pi8 = tan( CV_PI * 3.0 / 8.0 ); 88 | 89 | double ratio = 1.0 / scale; 90 | int colsR = imgResized.cols; 91 | int rowsR = imgResized.rows; 92 | 93 | // the edge map with edge chain index 94 | cv::Mat EMapR( rowsR, colsR, CV_64FC1, cv::Scalar(0) ); 95 | double *ptrER = (double*) EMapR.data; 96 | for ( int i=0; i tan3Pi8 ); 146 | *ptrOR4 = 4; 147 | } 148 | 149 | if ( abs( gx ) > abs( gy ) ) 150 | { 151 | *ptrOR2 = 0; // vertical 152 | } 153 | else 154 | { 155 | *ptrOR2 = 1; // horizontal 156 | } 157 | 158 | } 159 | 160 | ptrXR ++; ptrYR ++; ptrOR4++; ptrOR2++; ptrER++; 161 | } 162 | 163 | if ( scale == 1 ) 164 | { 165 | msOmap = OmapR2; 166 | msEmap = EMapR; 167 | return; 168 | } 169 | dxR.release(); 170 | dyR.release(); 171 | 172 | // get the multi scale gradient map 173 | std::vector > offset( 4, 2 * scale + 1 ); 174 | for ( int j=0; j<=2*scale; ++j ) 175 | { 176 | int p = j - scale; 177 | offset[0][j] = p; 178 | offset[1][j] = p * cols + p; 179 | offset[2][j] = p * cols - p; 180 | offset[3][j] = p * cols; 181 | } 182 | 183 | cv::Mat msGmap = cv::Mat( rows, cols, CV_64FC1, cv::Scalar( 0.0 ) ); 184 | double *ptrG = (double*) msGmap.data; 185 | 186 | msEmap = cv::Mat( rows, cols, CV_64FC1, cv::Scalar( 0.0 ) ); 187 | double *ptrE = (double*) msEmap.data; 188 | ptrER = (double*) EMapR.data; 189 | 190 | cv::Mat msOmap4Dir( rows, cols, CV_8UC1, cv::Scalar( 0 ) ); 191 | cv::Mat msOmap2Dir( rows, cols, CV_8UC1, cv::Scalar( 0 ) ); 192 | uchar *ptrO4 = msOmap4Dir.data; 193 | uchar *ptrO2 = msOmap2Dir.data; 194 | 195 | ptrOR4 = OmapR4.data; 196 | ptrOR2 = OmapR2.data; 197 | 198 | std::vector offsetTemp( 4 ); 199 | for ( int x=scale; x 0.7 ) 214 | { 215 | dirx = 1; 216 | } 217 | 218 | if ( dyR < 0.3 ) 219 | { 220 | diry = -1; 221 | } 222 | if ( dyR > 0.7 ) 223 | { 224 | diry = 1; 225 | } 226 | 227 | int xR = int( x * ratio ); 228 | int yR = int( y * ratio ); 229 | if ( xR < 1 || xR > colsR - 2 || yR < 1 || yR > rowsR - 2 ) 230 | { 231 | continue; 232 | } 233 | 234 | offsetTemp[0] = 0; 235 | offsetTemp[1] = dirx; 236 | offsetTemp[2] = diry * colsR; 237 | offsetTemp[3] = diry * colsR + dirx; 238 | 239 | int locR = yR * colsR + xR; 240 | uchar *ptrTemp = ptrOR4 + locR; 241 | 242 | int idx = -1, offsetIdx = -1; 243 | for ( int j=0; j<4; ++j ) 244 | { 245 | int temp = *( ptrTemp + offsetTemp[j] ); 246 | if ( temp ) 247 | { 248 | idx = temp - 1; 249 | offsetIdx = offsetTemp[j]; 250 | break; 251 | } 252 | } 253 | 254 | // get the image contrast 255 | if ( idx >= 0 ) 256 | { 257 | uchar *ptrI = image.data + loc; 258 | double I0 = 0.0, I1 = 0.0; 259 | for ( int j=0; j > &edgeChains, cv::Mat &msEmap, cv::Mat &msOmap, int scale ) 369 | { 370 | int rows_n = rows - scale; 371 | int cols_n = cols - scale; 372 | 373 | if ( scale == 1 ) // keep all the edge chains in level 0 374 | { 375 | double *ptrEAll = (double*)EmapAll.data; 376 | 377 | for ( int i=0; i overlappedIdx( edgeChains[i].size(), 0 ); 401 | 402 | for ( int j=0; j= cols_n || y0 < scale || y0 >= rows_n ) 407 | { 408 | continue; 409 | } 410 | 411 | int offsetCur = ( y0 - scale ) * cols + x0 - scale; 412 | double *ptrECur = (double*) msEmap.data + offsetCur; 413 | double *ptrEAll = (double*) EmapAll.data + offsetCur; 414 | 415 | int idLowLevel = 0; 416 | bool foundTwo = false; 417 | for ( int y = -scale; y<=scale; ++y ) 418 | { 419 | for ( int x=-scale; x<=scale; ++x ) 420 | { 421 | if ( * ptrECur == idCur && *ptrEAll ) 422 | { 423 | if ( ! idLowLevel ) 424 | { 425 | idLowLevel = *ptrEAll; 426 | } 427 | else if( *ptrEAll != idLowLevel ) 428 | { 429 | foundTwo = true; 430 | break; 431 | } 432 | } 433 | 434 | ptrECur++; ptrEAll++; 435 | } 436 | if ( foundTwo ) 437 | { 438 | break; 439 | } 440 | 441 | ptrECur += cols - 2 * scale - 1; 442 | ptrEAll += cols - 2 * scale - 1; 443 | } 444 | 445 | if ( ! idLowLevel || ( idLowLevel && foundTwo ) ) 446 | { 447 | overlappedIdx[j] = 1; // cover to the EmapAll 448 | } 449 | else 450 | { 451 | overlappedIdx[j] = 0; // suppressed by the low level edges 452 | } 453 | } 454 | 455 | // find the covering intervals of the edge chain 456 | std::vector intervals; 457 | 458 | int length = edgeChains[i].size(); 459 | for ( int m=0; m= 1 ) 480 | { 481 | intervals.push_back( max( m - 1, 0 ) ); 482 | intervals.push_back( min( n + 1, length - 1 ) ); 483 | m = n; 484 | } 485 | } 486 | 487 | // cover the intervals of the current edge chain onto EmapAll 488 | for ( int j=0; j= cols_n || y0 < scale || y0 >= rows_n ) 498 | { 499 | continue; 500 | } 501 | 502 | int offsetCur = ( y0 - scale ) * cols + x0 - scale; 503 | double *ptrECur = (double*) msEmap.data + offsetCur; 504 | uchar *ptrOCur = msOmap.data + offsetCur; 505 | 506 | double *ptrEAll = (double*) EmapAll.data + offsetCur; 507 | uchar *ptrOAll = OmapAll.data +offsetCur; 508 | 509 | for ( int y = -scale; y<=scale; ++y ) 510 | { 511 | for ( int x=-scale; x<=scale; ++x ) 512 | { 513 | if ( * ptrECur == idCur ) 514 | { 515 | *ptrEAll = idxAll; 516 | *ptrOAll = *ptrOCur; 517 | 518 | int locTemp = ( y0 + y ) * cols + x0 + x; 519 | ptrMU[3*locTemp+0] = 0; 520 | ptrMU[3*locTemp+1] = 255; 521 | ptrMU[3*locTemp+2] = 0; 522 | } 523 | 524 | ptrECur++; ptrOCur++; 525 | ptrEAll++; ptrOAll++; 526 | } 527 | ptrECur += cols - 2 * scale - 1; 528 | ptrOCur += cols - 2 * scale - 1; 529 | 530 | ptrEAll += cols - 2 * scale - 1; 531 | ptrOAll += cols - 2 * scale - 1; 532 | } 533 | } 534 | 535 | idxAll ++; 536 | } 537 | } 538 | } 539 | } 540 | 541 | bool MSED::next( cv::Point &pt, uchar **ptrM, int &dir ) 542 | { 543 | if ( pt.x < 1 || pt.x >= cols_1 || pt.y < 1 || pt.y >= rows_1 ) 544 | { 545 | return false; 546 | } 547 | 548 | for ( int i=0; i 0 ); 593 | 594 | cout<<"iteration: "<= 2 && N <= 3 ) & m == 0) 645 | marker.at( i, j ) = 1; 646 | } 647 | } 648 | 649 | img &= ~marker; 650 | } 651 | 652 | 653 | void MSED::makeUpImage( cv::Mat& img ) 654 | { 655 | int xTemp[8] = { -1, 0, 1, 1, 1, 0, -1, -1 }; 656 | offsetX = std::vector( xTemp, xTemp + 8 ); 657 | 658 | int yTemp[8] = { -1, -1, -1, 0, 1, 1, 1, 0 }; 659 | offsetY = std::vector( yTemp, yTemp + 8 ); 660 | 661 | offsetTotal.resize( 8 ); 662 | for ( int i=0; i<8; ++i ) 663 | { 664 | offsetTotal[i] = offsetY[i] * cols + offsetX[i]; 665 | } 666 | 667 | for ( int y=1; y= 5 ) 685 | { 686 | *ptr = 255; 687 | } 688 | } 689 | } 690 | } 691 | } 692 | 693 | 694 | void MSED::edgeTracking( cv::Mat &edgeMap, std::vector > &edgeChains ) 695 | { 696 | int xTemp[8] = { -1, 0, 1, 1, 1, 0, -1, -1 }; 697 | offsetX = std::vector( xTemp, xTemp + 8 ); 698 | 699 | int yTemp[8] = { -1, -1, -1, 0, 1, 1, 1, 0 }; 700 | offsetY = std::vector( yTemp, yTemp + 8 ); 701 | 702 | offsetTotal.resize( 8 ); 703 | for ( int i=0; i<8; ++i ) 704 | { 705 | offsetTotal[i] = offsetY[i] * cols + offsetX[i]; 706 | } 707 | 708 | idxSearch = std::vector >( 8, 5 ); 709 | for ( int i=0; i<8; ++i ) 710 | { 711 | idxSearch[i][0] = ( i + 4 ) % 8; 712 | idxSearch[i][1] = ( i + 3 ) % 8; 713 | idxSearch[i][2] = ( i + 5 ) % 8; 714 | idxSearch[i][3] = ( i + 2 ) % 8; 715 | idxSearch[i][4] = ( i + 6 ) % 8; 716 | } 717 | 718 | // 719 | edgeMap.copyTo( mask ); 720 | 721 | for ( int y=0; y chain; 744 | 745 | cv::Point pt( x, y ); 746 | uchar* ptrM = mask.data + loc; 747 | do 748 | { 749 | chain.push_back( pt ); 750 | *ptrM = 0; 751 | } while ( next( pt, &ptrM, dir1 ) ); 752 | 753 | cv::Point temp; 754 | for ( int m = 0, n = chain.size() - 1; m= 10 ) 775 | { 776 | edgeChains.push_back( chain ); 777 | } 778 | } 779 | } 780 | } 781 | 782 | void MSED::edgeConnection( std::vector > &edgeChains ) 783 | { 784 | cv::Mat maskTemp( rows, cols, CV_64FC1, cv::Scalar( -1 ) ); 785 | double * ptrM = (double*) maskTemp.data; 786 | 787 | for ( int i=0; i mergedIdx( edgeChains.size(), 0 ); 798 | for ( int i=0; i= cols_1 || y < 1 || y >= rows_1 ) 825 | { 826 | continue; 827 | } 828 | 829 | double *ptrTemp = (double*) maskTemp.data + y * cols + x; 830 | for ( int m=0; m<8; ++m ) 831 | { 832 | int idxSearched = *( ptrTemp + offsetTotal[m] ); 833 | 834 | if ( idxSearched >= 0 && idxSearched != i ) 835 | { 836 | if ( mergedIdx[idxSearched] ) 837 | { 838 | continue; 839 | } 840 | 841 | int n = 0; 842 | int xSearched = x + offsetX[m]; 843 | int ySearched = y + offsetY[m]; 844 | for ( n=0; n edgeChains[idxSearched].size() - step ) // merge these two edge chain 853 | { 854 | merged = true; 855 | idxChain = idxSearched; 856 | if ( n < step ) 857 | { 858 | idxPixelStart = n; 859 | idxPixelEnd = edgeChains[idxSearched].size(); 860 | } 861 | else 862 | { 863 | idxPixelStart = n; 864 | idxPixelEnd = 0; 865 | } 866 | break; 867 | } 868 | } 869 | } 870 | 871 | if ( merged ) 872 | { 873 | break; 874 | } 875 | } 876 | 877 | if ( merged ) 878 | { 879 | std::vector mergedChain; 880 | for ( int m=edgeChains[i].size()-1; m>=j; --m ) 881 | { 882 | mergedChain.push_back( edgeChains[i][m] ); 883 | } 884 | 885 | int order = 1; 886 | if ( idxPixelEnd < idxPixelStart ) 887 | { 888 | order = -1; 889 | } 890 | 891 | for ( int m=idxPixelStart; m!=idxPixelEnd; m+= order ) 892 | { 893 | mergedChain.push_back( edgeChains[idxChain][m] ); 894 | } 895 | edgeChains.push_back( mergedChain ); 896 | 897 | for ( int m=0; mt2; --j ) 925 | { 926 | int x = edgeChains[i][j].x; 927 | int y = edgeChains[i][j].y; 928 | if ( x < 1 || x >= cols_1 || y < 1 || y >= rows_1 ) 929 | { 930 | continue; 931 | } 932 | 933 | 934 | double *ptrTemp = (double*) maskTemp.data + y * cols + x; 935 | for ( int m=0; m<8; ++m ) 936 | { 937 | int idxSearched = *( ptrTemp + offsetTotal[m] ); 938 | 939 | if ( idxSearched >= 0 && idxSearched != i ) 940 | { 941 | if ( mergedIdx[idxSearched] ) 942 | { 943 | continue; 944 | } 945 | 946 | int n = 0; 947 | int xSearched = x + offsetX[m]; 948 | int ySearched = y + offsetY[m]; 949 | for ( n=0; n edgeChains[idxSearched].size() - step ) // merge these two edge chain 958 | { 959 | merged = true; 960 | idxChain = idxSearched; 961 | if ( n < step ) 962 | { 963 | idxPixelStart = n; 964 | idxPixelEnd = edgeChains[idxSearched].size(); 965 | } 966 | else 967 | { 968 | idxPixelStart = n; 969 | idxPixelEnd = 0; 970 | } 971 | break; 972 | } 973 | } 974 | } 975 | 976 | if ( merged ) 977 | { 978 | break; 979 | } 980 | } 981 | 982 | if ( merged ) 983 | { 984 | std::vector mergedChain( edgeChains[i].begin(), edgeChains[i].begin() + j ); 985 | 986 | int order = 1; 987 | if ( idxPixelEnd < idxPixelStart ) 988 | { 989 | order = -1; 990 | } 991 | 992 | for ( int m=idxPixelStart; m!=idxPixelEnd; m+= order ) 993 | { 994 | mergedChain.push_back( edgeChains[idxChain][m] ); 995 | } 996 | edgeChains.push_back( mergedChain ); 997 | 998 | for ( int m=j; m > edgeChainsNew; 1025 | for ( int i=0; i 6 | #include 7 | 8 | class MSED 9 | { 10 | public: 11 | MSED(void); 12 | ~MSED(void); 13 | 14 | public: 15 | 16 | void MSEdge( cv::Mat &image, int gaussSize, double thLow, double cth, std::vector > &edges ); 17 | 18 | void MSNonMaximumSuppression( cv::Mat image, cv::Mat imgResized, std::vector > &edgeChains, double thContrastDev, int scale, cv::Mat &msEmap, 19 | cv::Mat &msOmap ); 20 | 21 | void getMSEdgeMap( std::vector > &edgeChains, cv::Mat &msEmap, cv::Mat &msOmap, int scale ); 22 | 23 | void thinningGuoHall( cv::Mat& img ); 24 | 25 | void edgeTracking( cv::Mat &edgeMap, std::vector > &edgeChains ); 26 | 27 | void edgeConnection( std::vector > &edgeChains ); 28 | 29 | private: 30 | 31 | void makeUpImage( cv::Mat& img ); 32 | 33 | void imgThinning( cv::Mat &img, int iter ); 34 | 35 | bool next( cv::Point &pt, uchar **ptrM, int &dir ); 36 | 37 | private: 38 | 39 | int idxAll; 40 | int rows, cols, rows_1, cols_1; 41 | cv::Mat EmapAll, OmapAll; 42 | cv::Mat mask; 43 | std::vector offsetX, offsetY, offsetTotal; 44 | std::vector > idxSearch; 45 | cv::Mat maskMakeUp; 46 | }; 47 | 48 | #endif //_MSED_H_ 49 | -------------------------------------------------------------------------------- /src/Timer.h: -------------------------------------------------------------------------------- 1 | /* --- --- --- 2 | * Copyright (C) 2008--2010 Idiap Research Institute (.....@idiap.ch) 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | // Timers.h: interface for the CTimer class. 28 | // 29 | ////////////////////////////////////////////////////////////////////// 30 | 31 | #if !defined(_TIMER_H_) 32 | #define _TIMER_H_ 33 | 34 | #include // clock 35 | #include "time.h" 36 | #include 37 | 38 | #ifndef CLOCKS_PER_SEC /* define clocks-per-second if needed */ 39 | #define CLOCKS_PER_SEC 1000000 40 | #endif 41 | 42 | class CTimer 43 | { 44 | public: 45 | /* set the starting time */ 46 | void Start() { 47 | startTime = clock(); 48 | startLocalTime = time(0); 49 | }; 50 | 51 | /* stop the time */ 52 | void Stop(bool local_time=false) { 53 | stopTime = clock(); 54 | stopLocalTime = time(0); 55 | 56 | if ( local_time ) 57 | elapsedTime = difftime(stopLocalTime, startLocalTime); 58 | else 59 | elapsedTime = (stopTime - startTime) / (double)(CLOCKS_PER_SEC); 60 | 61 | if ( elapsedTime <= 0 ) 62 | elapsedTime = (stopTime - startTime) / (double)(CLOCKS_PER_SEC); 63 | }; 64 | 65 | /* get the elapsed hours */ 66 | double GetElapsedHours() { return elapsedTime/3600.0; } 67 | 68 | /* get the elapsed minutes */ 69 | double GetElapsedMinutes() { return elapsedTime/60.0; } 70 | 71 | /* get the elapsed seconds */ 72 | double GetElapsedSeconds() { return elapsedTime; } 73 | 74 | /* print the elapsed time */ 75 | void PrintElapsedTimeMsg(char* msg, 76 | bool used_hours = true, 77 | bool used_minutes = true, 78 | bool used_seconds = true) { 79 | if ( !msg ) return; 80 | 81 | if ( used_hours && used_minutes && used_seconds ) { 82 | int n_hours = (int)GetElapsedHours(); 83 | if ( n_hours > 0 ) { 84 | int n_minutes = (int)fmod(GetElapsedMinutes(),60.0); 85 | if ( n_minutes > 0 ) 86 | sprintf(msg, "%d hours %d minutes %2.4f seconds", 87 | n_hours, n_minutes, fmod(GetElapsedSeconds(),60.0) ); 88 | else 89 | sprintf(msg, "%d hours %2.4f seconds", 90 | n_hours, fmod(GetElapsedSeconds(),60.0) ); 91 | } 92 | else { 93 | int n_minutes = (int)fmod(GetElapsedMinutes(),60.0); 94 | if ( n_minutes > 0 ) 95 | sprintf(msg, "%d minutes %2.4f seconds", 96 | n_minutes, fmod(GetElapsedSeconds(),60.0) ); 97 | else 98 | sprintf(msg, "%2.4f seconds", GetElapsedSeconds() ); 99 | } 100 | } 101 | else if ( used_hours && used_minutes ) { 102 | int n_hours = (int)GetElapsedHours(); 103 | if ( n_hours > 0 ) 104 | sprintf(msg, "%d hours %2.4f minutes", 105 | n_hours, fmod(GetElapsedMinutes(),60.0) ); 106 | else 107 | sprintf(msg, "%2.4f minutes", GetElapsedMinutes() ); 108 | } 109 | else if ( used_hours && used_seconds ) { 110 | int n_hours = (int)GetElapsedHours(); 111 | if ( n_hours > 0 ) 112 | sprintf(msg, "%d hours %2.4f minutes", 113 | n_hours, fmod(GetElapsedSeconds(),3600.0) ); 114 | else 115 | sprintf(msg, "%0.4f seconds", GetElapsedSeconds() ); 116 | } 117 | else if ( used_minutes && used_seconds ) { 118 | int n_minutes = (int)GetElapsedMinutes(); 119 | if ( n_minutes > 0 ) 120 | sprintf(msg, "%d minutes %0.4f seconds", 121 | n_minutes, fmod(GetElapsedSeconds(),60.0) ); 122 | else 123 | sprintf(msg, "%0.4f seconds", GetElapsedSeconds() ); 124 | } 125 | else if ( used_hours ) 126 | sprintf(msg, "%.4f hours", GetElapsedHours() ); 127 | else if ( used_minutes ) 128 | sprintf(msg, "%.4f minutes", GetElapsedMinutes() ); 129 | else if ( used_seconds ) 130 | sprintf(msg, "%.4f seconds", GetElapsedSeconds() ); 131 | }; 132 | 133 | 134 | /* print now local global time using strftime 135 | format_args example: "Now is %Y-%m-%d %H:%M:%S" */ 136 | void PrintLocalTime(char* buffer, int length, const char* format_args) { 137 | time_t rawtime; 138 | struct tm * timeinfo; 139 | 140 | time ( &rawtime ); 141 | timeinfo = localtime ( &rawtime ); 142 | 143 | strftime (buffer, length, format_args, timeinfo); 144 | }; 145 | 146 | CTimer() { elapsedTime = 0; }; 147 | virtual ~CTimer() {}; 148 | 149 | private: 150 | time_t startLocalTime; 151 | time_t stopLocalTime; 152 | 153 | /* starting time */ 154 | clock_t startTime; 155 | clock_t stopTime; 156 | public: 157 | double elapsedTime; 158 | }; 159 | 160 | #endif // !defined(_TIMER_H_) 161 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "MSED.h" 7 | #include "Timer.h" 8 | 9 | using namespace cv; 10 | using namespace std; 11 | 12 | 13 | void main() 14 | { 15 | string inputImage = "D:\\test.jpg"; 16 | string outputEdge = "D:\\test_edge.jpg"; 17 | cv::Mat image=imread( inputImage, 0 ); 18 | 19 | // MSEdge 20 | CTimer mtimer; 21 | char msg[1024]; 22 | mtimer.Start(); 23 | 24 | int gaussSize=3; 25 | double thLow = 30.0; 26 | double cth = 5.0; 27 | std::vector > edgeChains; 28 | MSED edgeDetector; 29 | edgeDetector.MSEdge( image, gaussSize, thLow, cth, edgeChains ); 30 | 31 | mtimer.Stop(); 32 | double timeTotal = mtimer.elapsedTime; 33 | cout<<"time: "<=1 && x0 < image.cols -1 && y0 >=1 && y0 < image.rows -1 ) 49 | { 50 | int loc = y0 * image.cols + x0; 51 | ptr[3*loc+0] = B; ptr[3*loc+1] = G; ptr[3*loc+2] = R; 52 | 53 | ptr[3*(loc+1)+0] = B; ptr[3*(loc+1)+1] = G; ptr[3*(loc+1)+2] = R; 54 | ptr[3*(loc-1)+0] = B; ptr[3*(loc-1)+1] = G; ptr[3*(loc-1)+2] = R; 55 | ptr[3*(loc-image.cols)+0] = B; ptr[3*(loc-image.cols)+1] = G; ptr[3*(loc-image.cols)+2] = R; 56 | ptr[3*(loc+image.cols)+0] = B; ptr[3*(loc+image.cols)+1] = G; ptr[3*(loc+image.cols)+2] = R; 57 | } 58 | } 59 | } 60 | imwrite( outputEdge,imgShow ); 61 | } 62 | -------------------------------------------------------------------------------- /test.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaohulugo/MultiScaleEdgeDetection/8b7680538586ab5da466569a10bf10b3742585f3/test.JPG --------------------------------------------------------------------------------