From 4a59fa652c0e77b1529cb67cbf27559b831668fd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Feb 2018 18:33:20 +0000 Subject: [PATCH] make vips_sobel() cut out of vips_canny() --- ChangeLog | 1 + configure.ac | 6 +- libvips/convolution/Makefile.am | 1 + libvips/convolution/canny.c | 92 ++++++++--------- libvips/convolution/convolution.c | 2 + libvips/convolution/sobel.c | 156 +++++++++++++++++++++++++++++ libvips/create/gaussmat.c | 5 + libvips/include/vips/convolution.h | 2 + 8 files changed, 210 insertions(+), 55 deletions(-) create mode 100644 libvips/convolution/sobel.c diff --git a/ChangeLog b/ChangeLog index e549754d..3d910b2a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ - hough_line scales width to 0 - 180, not 0 - 360 - hough_line is 4x faster - hough_circle is 2x faster +- add vips_sobel() and vips_canny() edge detectors 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/configure.ac b/configure.ac index f5cae2e1..cb71bb7e 100644 --- a/configure.ac +++ b/configure.ac @@ -1280,8 +1280,8 @@ if test x"$found_introspection" = xyes -a "$VIPS_LIBDIR/girepository-1.0" != "$I ;; # ignore for homebrew *) AC_MSG_RESULT([dnl -Vips-8.0.typelib will install to $VIPS_LIBDIR/girepository-1.0, but your -system repository seems to be $INTROSPECTION_TYPELIBDIR. +Vips-8.0.typelib will be installed to $VIPS_LIBDIR/girepository-1.0, but +your system repository seems to be $INTROSPECTION_TYPELIBDIR. You may need to add this directory to your typelib path, for example: export GI_TYPELIB_PATH="$VIPS_LIBDIR/girepository-1.0" @@ -1313,7 +1313,7 @@ overrides are installed in the correct location. ]) elif test x"$VIPS_PYEXECDIR/gi/overrides" != x"$syspygipath"; then AC_MSG_RESULT([dnl -The vips Python overrides file will install to +The vips Python overrides file will be installed to $VIPS_PYEXECDIR/gi/overrides/Vips.py, but your system gi overrides seem to be $syspygipath. You may need to copy this file, for example: diff --git a/libvips/convolution/Makefile.am b/libvips/convolution/Makefile.am index bf1ca89f..4a29b604 100644 --- a/libvips/convolution/Makefile.am +++ b/libvips/convolution/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libconvolution.la libconvolution_la_SOURCES = \ canny.c \ + sobel.c \ convolution.c \ pconvolution.h \ correlation.c \ diff --git a/libvips/convolution/canny.c b/libvips/convolution/canny.c index 71d7a90b..e63cafa6 100644 --- a/libvips/convolution/canny.c +++ b/libvips/convolution/canny.c @@ -35,6 +35,17 @@ #define DEBUG */ +/* TODO + * - verify that our interpolating max edge works + * - does it actually help much? + * - can skip the sqrt() + * - support other image types + * - swap atan2 for a LUT with perhaps +/- 2 or 4 bits + * - check sobel speed with separated and non-sep masks + * - add autothreshold with otsu's method + * - leave blob analysis to a separate pass + */ + #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -53,6 +64,7 @@ typedef struct _VipsCanny { VipsImage *out; double sigma; + gboolean interpolate; double low; double high; @@ -65,50 +77,7 @@ typedef VipsOperationClass VipsCannyClass; G_DEFINE_TYPE( VipsCanny, vips_canny, VIPS_TYPE_OPERATION ); -static int -vips_canny_gradient_sobel( VipsImage *in, VipsImage **Gx, VipsImage **Gy ) -{ - VipsImage *scope; - VipsImage **t; - - scope = vips_image_new(); - t = (VipsImage **) vips_object_local_array( (VipsObject *) scope, 20 ); - - /* Separated Sobel gives Gx / Gy. - */ - t[1] = vips_image_new_matrixv( 1, 3, 1.0, 2.0, 1.0 ); - t[2] = vips_image_new_matrixv( 3, 1, 1.0, 0.0, -1.0 ); - vips_image_set_double( t[2], "offset", 128.0 ); - if( vips_conv( in, &t[3], t[1], - "precision", VIPS_PRECISION_INTEGER, - NULL ) || - vips_conv( t[3], Gx, t[2], - "precision", VIPS_PRECISION_INTEGER, - NULL ) ) { - g_object_unref( scope ); - return( -1 ); - } - - t[5] = vips_image_new_matrixv( 3, 1, 1.0, 2.0, 1.0 ); - t[6] = vips_image_new_matrixv( 1, 3, 1.0, 0.0, -1.0 ); - vips_image_set_double( t[6], "offset", 128.0 ); - if( vips_conv( in, &t[7], t[5], - "precision", VIPS_PRECISION_INTEGER, - NULL ) || - vips_conv( t[7], Gy, t[6], - "precision", VIPS_PRECISION_INTEGER, - NULL ) ) { - g_object_unref( scope ); - return( -1 ); - } - - g_object_unref( scope ); - - return( 0 ); -} - -/* Simple -1/+1 difference. The sobel version above does an edge - * detect as well. +/* Simple 2x2 -1/+1 difference. */ static int vips_canny_gradient_simple( VipsImage *in, VipsImage **Gx, VipsImage **Gy ) @@ -173,10 +142,14 @@ vips_canny_polar_generate( VipsRegion *or, int y = p2[band] - 128; int a = VIPS_DEG( atan2( x, y ) ) + 360; - /* Faster than hypot() for int args. Scale down - * or we'll clip on very hard edges. + /* We should calculate + * 0.5 * sqrt( x * x + y * y ) + * ie. length of hypot, scaled down to avoid + * clipping. We are only interested in relative + * magnitude, so we can skip the sqrt and just + * shift down 9 bits. */ - q[0] = 0.5 * sqrt( x * x + y * y ); + q[0] = (x * x + y * y + 256) >> 9; q[1] = 256 * a / 360; q += 2; @@ -428,6 +401,8 @@ vips_canny_thresh_generate( VipsRegion *or, VipsCanny *canny = (VipsCanny *) b; VipsRect *r = &or->valid; int sz = r->width * in->im->Bands; + VipsPel low = canny->low; + VipsPel high = canny->high; int x, y; @@ -444,9 +419,9 @@ vips_canny_thresh_generate( VipsRegion *or, int v; v = p[x]; - if( v <= canny->low ) + if( v <= low ) v = 0; - else if( v <= canny->high ) + else if( v <= high ) v = 128; else v = 255; @@ -509,10 +484,16 @@ vips_canny_build( VipsObject *object ) */ if( vips_embed( in, &t[10], 1, 1, in->Xsize + 2, in->Ysize + 2, "extend", VIPS_EXTEND_COPY, - NULL ) || - vips_canny_nonmax( t[10], &t[11] ) ) - //vips_canny_nonmaxi( t[10], &t[11] ) ) + NULL ) ) return( -1 ); + if( canny->interpolate ) { + if( vips_canny_nonmaxi( t[10], &t[11] ) ) + return( -1 ); + } + else { + if( vips_canny_nonmax( t[10], &t[11] ) ) + return( -1 ); + } in = t[11]; /* Double threshold. @@ -578,6 +559,13 @@ vips_canny_class_init( VipsCannyClass *class ) G_STRUCT_OFFSET( VipsCanny, high ), -INFINITY, INFINITY, 7.0 ); + VIPS_ARG_BOOL( class, "interpolate", 13, + _( "Interpolate" ), + _( "Interpolate gradient angles" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsCanny, interpolate ), + FALSE ); + } static void diff --git a/libvips/convolution/convolution.c b/libvips/convolution/convolution.c index 6a215c4a..bb195984 100644 --- a/libvips/convolution/convolution.c +++ b/libvips/convolution/convolution.c @@ -168,6 +168,7 @@ vips_convolution_operation_init( void ) extern int vips_spcor_get_type( void ); extern int vips_sharpen_get_type( void ); extern int vips_gaussblur_get_type( void ); + extern int vips_sobel_get_type( void ); extern int vips_canny_get_type( void ); vips_conv_get_type(); @@ -182,4 +183,5 @@ vips_convolution_operation_init( void ) vips_sharpen_get_type(); vips_gaussblur_get_type(); vips_canny_get_type(); + vips_sobel_get_type(); } diff --git a/libvips/convolution/sobel.c b/libvips/convolution/sobel.c new file mode 100644 index 00000000..6f825e29 --- /dev/null +++ b/libvips/convolution/sobel.c @@ -0,0 +1,156 @@ +/* Sobel edge detector + * + * 2/2/18 + * - from vips_sobel() + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include + +/* TODO + * - check sobel speed with separated and non-sep masks + * - add an 8-bit sobel path, with offset 128 and code for abs() + abs() + */ + +typedef struct _VipsSobel { + VipsOperation parent_instance; + + VipsImage *in; + VipsImage *out; + +} VipsSobel; + +typedef VipsOperationClass VipsSobelClass; + +G_DEFINE_TYPE( VipsSobel, vips_sobel, VIPS_TYPE_OPERATION ); + +static int +vips_sobel_build( VipsObject *object ) +{ + VipsSobel *sobel = (VipsSobel *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 20 ); + + t[1] = vips_image_new_matrixv( 1, 3, 1.0, 2.0, 1.0 ); + t[2] = vips_image_new_matrixv( 3, 1, 1.0, 0.0, -1.0 ); + if( vips_conv( sobel->in, &t[3], t[1], NULL ) || + vips_conv( t[3], &t[4], t[2], NULL ) ) + return( -1 ); + + t[5] = vips_image_new_matrixv( 3, 1, 1.0, 2.0, 1.0 ); + t[6] = vips_image_new_matrixv( 1, 3, 1.0, 0.0, -1.0 ); + if( vips_conv( sobel->in, &t[7], t[5], NULL ) || + vips_conv( t[7], &t[8], t[6], NULL ) ) + return( -1 ); + + if( vips_abs( t[4], &t[9], NULL ) || + vips_abs( t[8], &t[10], NULL ) || + vips_add( t[9], t[10], &t[11], NULL ) || + vips_cast( t[11], &t[12], sobel->in->BandFmt, NULL ) ) + return( -1 ); + + g_object_set( object, "out", vips_image_new(), NULL ); + + if( vips_image_write( t[12], sobel->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_sobel_class_init( VipsSobelClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "sobel"; + object_class->description = _( "Sobel edge detector" ); + object_class->build = vips_sobel_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsSobel, in ) ); + + VIPS_ARG_IMAGE( class, "out", 2, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsSobel, out ) ); + +} + +static void +vips_sobel_init( VipsSobel *sobel ) +{ +} + +/** + * vips_sobel: (method) + * @in: input image + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Simple Sobel edge detector. + * + * See also: vips_canny(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_sobel( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "sobel", ap, in, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index 4c954fcd..fa67c305 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -157,6 +157,11 @@ vips_gaussmat_build( VipsObject *object ) } } + /* Make sure we can't make sum == 0: it'd certainly cause /0 later. + */ + if( sum == 0 ) + sum = 1; + vips_image_set_double( create->out, "scale", sum ); vips_image_set_double( create->out, "offset", 0.0 ); diff --git a/libvips/include/vips/convolution.h b/libvips/include/vips/convolution.h index 8cbfbac4..45ba2eb7 100644 --- a/libvips/include/vips/convolution.h +++ b/libvips/include/vips/convolution.h @@ -70,6 +70,8 @@ int vips_spcor( VipsImage *in, VipsImage *ref, VipsImage **out, ... ) int vips_fastcor( VipsImage *in, VipsImage *ref, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_sobel( VipsImage *in, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_canny( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel));