Skip to content

Instantly share code, notes, and snippets.

@egonSchiele
Created December 28, 2010 02:42
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save egonSchiele/756833 to your computer and use it in GitHub Desktop.
Save egonSchiele/756833 to your computer and use it in GitHub Desktop.
Adding automatic thresholding to cvCanny in OpenCV
// new
/*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.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, 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 Intel Corporation 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"
CV_IMPL void cvCanny( const void* srcarr, void* dstarr,
double low_thresh, double high_thresh,
int aperture_size )
{
cv::Ptr<CvMat> dx, dy;
cv::AutoBuffer<char> buffer;
std::vector<uchar*> stack;
uchar **stack_top = 0, **stack_bottom = 0;
double percent = low_thresh;
CvMat srcstub, *src = cvGetMat( srcarr, &srcstub );
CvMat dststub, *dst = cvGetMat( dstarr, &dststub );
CvSize size;
int flags = aperture_size;
int low, high;
uchar* map;
ptrdiff_t mapstep;
int maxsize;
int i, j;
CvMat mag_row;
if( CV_MAT_TYPE( src->type ) != CV_8UC1 ||
CV_MAT_TYPE( dst->type ) != CV_8UC1 )
CV_Error( CV_StsUnsupportedFormat, "" );
if( !CV_ARE_SIZES_EQ( src, dst ))
CV_Error( CV_StsUnmatchedSizes, "" );
if( low_thresh > high_thresh )
{
double t;
CV_SWAP( low_thresh, high_thresh, t );
}
aperture_size &= INT_MAX;
if( (aperture_size & 1) == 0 || aperture_size < 3 || aperture_size > 7 )
CV_Error( CV_StsBadFlag, "" );
size = cvGetMatSize( src );
// convolve with sobel operator to get derivative approximations
dx = cvCreateMat( size.height, size.width, CV_16SC1 );
dy = cvCreateMat( size.height, size.width, CV_16SC1 );
cvSobel( src, dx, 1, 0, aperture_size );
cvSobel( src, dy, 0, 1, aperture_size );
if( flags & CV_CANNY_L2_GRADIENT )
{
Cv32suf ul, uh;
ul.f = (float)low_thresh;
uh.f = (float)high_thresh;
low = ul.i;
high = uh.i;
}
else
{
low = cvFloor( low_thresh );
high = cvFloor( high_thresh );
}
// buffer structure will be: top half for 2d mag array,
// bottom half for map of edges (either 0, 1, or 2...see below)
buffer.allocate( (size.width+2)*(size.height+2) + (size.width + 2)*(size.height+2)*sizeof(int) );
// mag is a pointer to the magnitude array
int *mag = (int*)(char*)buffer;
// map is a pointer to the edges array
map = (uchar*)(mag + (size.width+2)*(size.height+2));
mapstep = size.width + 2;
maxsize = MAX( 1 << 10, size.width*size.height/10 );
stack.resize( maxsize );
stack_top = stack_bottom = &stack[0];
memset( mag, 0, (size.width + 2) * (size.height + 2) * sizeof(int) );
memset( map, 1, mapstep );
memset( map + mapstep*(size.height + 1), 1, mapstep );
/* sector numbers
(Top-Left Origin)
1 2 3
* * *
* * *
0*******0
* * *
* * *
3 2 1
*/
#define CANNY_PUSH(d) *(d) = (uchar)2, *stack_top++ = (d)
#define CANNY_POP(d) (d) = *--stack_top
mag_row = cvMat( 1, size.width, CV_32F );
// we actually want to start from (1,1), because there's a 1-cell border
// around the whole image for padding.
mag = mag + size.width + 2 + 1;
// calculate magnitude and angle of gradient, perform non-maxima supression.
// fill the map with one of the following values:
// 0 - the pixel might belong to an edge
// 1 - the pixel can not belong to an edge
// 2 - the pixel does belong to an edge
for( i = 0; i <= size.height; i++ )
{
// here we move one column over, b/c the first column is padding.
int *_mag = mag + (size.width + 2) * i;
float* _magf = (float*)_mag;
const short* _dx = (short*)(dx->data.ptr + dx->step*i);
const short* _dy = (short*)(dy->data.ptr + dy->step*i);
int x, y;
if( i < size.height ) {
_mag[-1] = _mag[size.width] = 0;
if( !(flags & CV_CANNY_L2_GRADIENT) ) {
for( j = 0; j < size.width; j++ ) {
_mag[j] = abs(_dx[j]) + abs(_dy[j]);
}
}
else {
for( j = 0; j < size.width; j++ ) {
x = _dx[j]; y = _dy[j];
_magf[j] = (float)std::sqrt((double)x*x + (double)y*y);
}
}
}
else
memset( _mag-1, 0, (size.width + 2)*sizeof(int) );
}
// Choose better thresholds
int max = 0;
for (i = 0; i < size.height; i++) {
int *_mag = mag + (size.width + 2) * i;
for( j = 0; j < size.width; j++ ) {
if (_mag[j] > max) {
max = _mag[j];
}
}
}
// step 2: Get the histogram of the data.
#define NUM_BINS 64
// might want to make this max - min / NUM_BINS after you have normalized.
int bin_size = max / NUM_BINS;
if (bin_size < 1) bin_size = 1;
int bins[NUM_BINS] = { 0 };
for (i = 0; i < size.height; i++) {
int *_mag = mag + (size.width + 2) * i;
for( j = 0; j < size.width; j++ ) {
bins[_mag[j] / bin_size]++;
}
}
// step 3: get the high threshold
double percent_of_pixels_not_edges = 0.8;
double threshold_ratio = 0.4;
int total = 0;
high = 0;
// size.height should be here too, but right now we're going row-by-row
while (total < size.height * size.width * percent_of_pixels_not_edges) {
total+= bins[high];
high++;
}
high *= bin_size;
low = threshold_ratio * high;
cout << "high: " << high << endl;
cout << "low: " << low << endl;
int adit = 10;
// non-maxima suppression
for( i = 1; i <= size.height; i++ )
{
int *_mag = mag + (size.width + 2) * i;
if( (stack_top - stack_bottom) + size.width > maxsize )
{
int sz = (int)(stack_top - stack_bottom);
maxsize = MAX( maxsize * 3/2, maxsize + 8 );
stack.resize(maxsize);
stack_bottom = &stack[0];
stack_top = stack_bottom + sz;
}
const short* _dx = (short*)(dx->data.ptr + dx->step*(i-1));
const short* _dy = (short*)(dy->data.ptr + dy->step*(i-1));
int prev_flag = 0;
int x, y;
uchar* _map;
ptrdiff_t magstep1, magstep2;
_map = map + mapstep*i + 1;
_map[-1] = _map[size.width] = 1;
if (i % 3 == 1) {
magstep1 = size.width + 2;
magstep2 = -(size.width + 2);
} else if (i % 3 == 2) {
magstep1 = -2 * (size.width + 2);
magstep2 = -(size.width + 2);
} else {
magstep1 = size.width + 2;
magstep2 = 2 * (size.width + 2);
}
for( j = 0; j < size.width; j++ )
{
#define CANNY_SHIFT 15
// i.e. tan(pi/8) * (1 << CANNY_SHIFT etc...)
#define TG22 (int)(0.4142135623730950488016887242097*(1<<CANNY_SHIFT) + 0.5)
x = _dx[j];
y = _dy[j];
int s = x ^ y;
int m = _mag[j];
x = abs(x);
y = abs(y);
if( m > low )
{
int tg22x = x * TG22;
int tg67x = tg22x + ((x + x) << CANNY_SHIFT);
y <<= CANNY_SHIFT;
if( y < tg22x )
{
if( m > _mag[j-1] && m >= _mag[j+1] )
{
if( m > high && !prev_flag && _map[j-mapstep] != 2 )
{
CANNY_PUSH( _map + j );
prev_flag = 1;
}
else {
_map[j] = (uchar)0;
}
continue;
}
}
else if( y > tg67x )
{
if( m > _mag[j-magstep2] && m >= _mag[j+magstep1] )
{
if( m > high && !prev_flag && _map[j-mapstep] != 2 )
{
CANNY_PUSH( _map + j );
prev_flag = 1;
}
else {
_map[j] = (uchar)0;
}
continue;
}
}
else
{
s = s < 0 ? -1 : 1;
if( m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s] )
{
if( m > high && !prev_flag && _map[j-mapstep] != 2 )
{
CANNY_PUSH( _map + j );
prev_flag = 1;
}
else {
_map[j] = (uchar)0;
}
continue;
}
}
}
prev_flag = 0;
_map[j] = (uchar)1;
}
}
// now track the edges (hysteresis thresholding)
while( stack_top > stack_bottom )
{
uchar* m;
if( (stack_top - stack_bottom) + 8 > maxsize )
{
int sz = (int)(stack_top - stack_bottom);
maxsize = MAX( maxsize * 3/2, maxsize + 8 );
stack.resize(maxsize);
stack_bottom = &stack[0];
stack_top = stack_bottom + sz;
}
CANNY_POP(m);
if( !m[-1] )
CANNY_PUSH( m - 1 );
if( !m[1] )
CANNY_PUSH( m + 1 );
if( !m[-mapstep-1] )
CANNY_PUSH( m - mapstep - 1 );
if( !m[-mapstep] )
CANNY_PUSH( m - mapstep );
if( !m[-mapstep+1] )
CANNY_PUSH( m - mapstep + 1 );
if( !m[mapstep-1] )
CANNY_PUSH( m + mapstep - 1 );
if( !m[mapstep] )
CANNY_PUSH( m + mapstep );
if( !m[mapstep+1] )
CANNY_PUSH( m + mapstep + 1 );
}
// the final pass, form the final image
for( i = 0; i < size.height; i++ )
{
const uchar* _map = map + mapstep*(i+1) + 1;
uchar* _dst = dst->data.ptr + dst->step*i;
for( j = 0; j < size.width; j++ )
_dst[j] = (uchar)-(_map[j] >> 1);
}
adit = 20;
}
void cv::Canny( const Mat& image, Mat& edges,
double threshold1, double threshold2,
int apertureSize, bool L2gradient )
{
Mat src = image;
edges.create(src.size(), CV_8U);
CvMat _src = src, _dst = edges;
cvCanny( &_src, &_dst, threshold1, threshold2,
apertureSize + (L2gradient ? CV_CANNY_L2_GRADIENT : 0));
}
/* End of file. */
@ricardocunha
Copy link

Hi, do you have an example using this function?

@egonSchiele
Copy link
Author

Hmm, if I recall correctly, the usage is:

edges = cv.CreateMat(image.rows, image.cols, image.type)
cv.Canny(image, edges, 0, 0)

And cv.Canny will automatically determine the high and low threshold.

@csharma2
Copy link

how do I implement this autodetection file (canny.cpp) and make it detect the edges automatically?

Thanks

@egonSchiele
Copy link
Author

csharma2,
You need to download openCV and compile it. Before compiling, replace canny.cpp with this file. Then check out the openCV examples to learn how to detect edges: http://opencv.willowgarage.com/wiki/Welcome/Introduction

@csharma2
Copy link

Silly question but with the OpenCV 2.1.0, I cant seem to find the canny.cpp file anywhere in any folders! is there a particular folder it is should be located in (or do i need to download a different version of openCV)

@egonSchiele
Copy link
Author

Hmm, things could've changed...this fix is pretty old. Sadly the only version I know that definitely works is this one: https://github.com/egonSchiele/OpenCV

@2shou
Copy link

2shou commented Jun 25, 2013

could you provide a newer implement with opencv 2.4.5?
i got some problems in compiling with ios
thanks in advance

@daviddoria
Copy link

I don't suppose anyone has implemented this as a free function that doesn't need to be compiled into OpenCV?

@Silencode
Copy link

Is this the original canny detector?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment