# canny edge detection algorithm

Is there a G’MIC filter which makes use of the canny edge detection algorithm?
If not, is there someone (among G’mic coders, I mean) thinking at it? or willing to think at it?
In the past years it happened to me to use the plug-in canny.exe from inside a Gimp filter, and the results were IMHO very good.

Not yet. I thought about it, but may not know how. I have a `Contours > Edge` or `afre_edge` filter that you may want to try. It does not have the cleanup steps that canny uses, but I think it performs better than the first part of the canny algorithm. It has been a long time since I updated my filters. Please let me know if it is broken.

It’s not broken and I’ve used it often.
I asked because I remember the “clean” and well defined contours of “canny” algorithm. So, should you be willing to re-think about it, it would be great (just a hope, but Santa Claus is coming…).
Diego

Reference material:

``````https://github.com/FienSoP/canny_edge_detector/blob/master/canny_edge_detector.py
``````

Simple for direct implementation if @Reptorian or @garagecoder have the time or interest. Basically, my filter is missing L38 and onward, i.e., non-max suppression, threshold and hysteresis.

Thanks!
Hope that one of the two has time and desire to create a Gmic CANNY filter.

@afre Do you have any idea what they mean by “non-maximum suppression” in the wikipedia article? I got this far:

``````#@cli rep_canny_edge:
#@cli : Apply canny edge filter onto images.
#@cli : Author : Reptorian.
rep_canny_edge:
(1,0,-1;\
2,0,-2;\
1,0,-1) => Gx

(1,2,1;\
0,0,0;\
-1,-2,-1) => Gy

store[-2,-1] kernels

foreach {
if d>1 continue fi
if s==2||s>3 s c,-{s-1} fi
to_gray[0]
+blur[0] 5
\$kernels
+convolve[-3] [\$Gx]
+convolve[-4] [\$Gy]
rm[\$Gx,\$Gy]
+atan2[-1] [-2]
append[-3,-2] c norm[-2]
remove[-3]
}
``````

Basically checking neighboring pixels along the gradient angle at the current pixel. If the current pixel is greater or equal to them, keep it. Otherwise set zero.

1 Like

Do you mean qualitatively? …since I already gave you a GitHub example you could translate. This is what the book on my bookcase says:

PS - Ideally, we would like to see continuous contours, which 1-pixel edges cannot describe accurately.

Also just to help with the last couple bits…

Double threshold = quantization. It’s just two thresholds -“weak” and “strong” gradients. It’s either unwanted, a low gradient or a high gradient, e.g.:
`gradient_norm n 0,1 +gt 0.15 gt.. 0.05 sub.. .`

Hysteresis = connectivity. For the weak pixels, is there a strong pixel in the 3x3 neighborhood.
`+dilate[strong] 3 mul[weak] [-1]`

Edit: just to mention, connectivity is supposed to propagate. If you found a connected weak pixel, it’s now considered strong. One way to do that is with a dynamic fill like: `fill ">..."`

Well, I decided to skip a couple of steps. Here’s my Canny Edge filter result:

``````#@cli rep_canny_edge:
#@cli : Apply canny edge filter onto images.
#@cli : Author : Reptorian.
rep_canny_edge:
foreach {
if d>1 continue fi
if s==2||s>3 s c,-{s-1} fi
to_gray[0]
+blur[0] 5
fill[0] "
v=i#-1;greater_than_neighbors=0;
if( ( v>j(#-1,-1,0,0,0,0,1)&&v>j(#-1,1,0,0,0,0,1) )
||  ( v>j(#-1,0,-1,0,0,0,1)&&v>j(#-1,0,1,0,0,0,1) ),
greater_than_neighbors=1;
);
if(greater_than_neighbors,i#-1);
"

ge[0] {ia#0/2} rm.
if \$!==2 append c fi
}
``````
1 Like

I’ve also started on it now, I have some ideas to speed things up. Maybe we can get the best of both at the end

1 Like
``````#@cli gcd_canny : sigma,_low_threshold,_high_threshold
#@cli : Locate image edges using canny edge detector.
#@cli : Default values: 'sigma=1', '_low_threshold=0.05' and 'high_threshold=0.15'.
#@cli : \$ image.jpg gcd_canny 1.5
gcd_canny : skip \${1=1},\${2=0.05},\${3=0.15}
e[^-1] "Apply canny edge detection for image\$?, with sigma \$1."
foreach {
low,high:=[\$2*\$3,\$3]
# gaussian
b \$1
# sobel
(1,2,1;0,0,0;-1,-2,-1)
+convolve[0] . transpose.. convolve[0] .. rm..
# polar coord regions
+atan2.. . sqr[0,1] +[0,1] sqrt..
mul. {4/pi} round. 1 mod. 4
# non-maximum suppression
f.. "
A=i#1;
Y=A<1?max(j(0,-1),j(0,1)):A<2?max(j(-1,-1),j(1,1)):A<3?max(j(-1),j(1)):max(j(-1,1),j(1,-1));
i>=Y?i:0
" rm.
# double thresholds
div {iM} +ge \$high ge.. \$low
# hysteresis
f. ">max(crop(x-1,y-1,z,c,3,3,1,1))&&i#0?1:i"
f. "<max(crop(x-1,y-1,z,c,3,3,1,1))&&i#0?1:i" rm..
}
``````
2 Likes

This works. I have extended your code:

``````#@cli rep_gcd_canny: _sigma,_low_threshold,_high_threshold,grayscale_mode,color_only,normalize,cmyk_mode
#@cli : Apply Canny Edge filters onto images.
#@cli : Default values: '_sigma=1','_low_threshold=.05','_high_threshold=.15'
#@cli : Authors: Reptorian, Garagecoder.
rep_gcd_canny:
skip \${1=1},\${2=.05},\${3=.15},\${4=1},\${5=1},\${6=0},\${7=0}

e[^-1] "Apply canny edge detection for images\$?, with sigma \$1."

low,high,s_thres,grayscale_mode,color_only,cmyk_mode={[\$2*\$3,\$3,3+(\$7?1)]},\${4-5},\$7

foreach {
if \$grayscale_mode||\$color_only
if \$color_only
if s==2||s>\$s_thres
s c,-{s-1}
fi
if \$grayscale_mode
ns:=s#0
compose_channels[0] +
/[0] \$ns
fi
else
if s>\$s_thres
s c,-{s-1}
fi
if s#0>2
ns:=s#0
compose_channels[0] +
/[0] \$ns
fi
a c
fi
fi

b[0] \$1

# sobel
(1,2,1;0,0,0;-1,-2,-1)
+convolve[0] . transpose.. convolve[0] .. rm..
+atan2[0] .
sqr[0,-2]
+[0,-2]
sqrt[0]

mul. {4/pi} round. 1 %. 4

# non-maximum suppression
f[0] "
A=i#1;
B=A<1?max(j(0,-1),j(0,1)):A<2?max(j(-1,-1),j(1,1)):A<3?max(j(-1),j(1)):max(j(-1,1),j(1,-1));
i>=B?i:0
"
rm.

# double_threshold
div[0] {iM#0} +ge[0] \$high ge[0] \$low

#hysteresis
f. ">max(crop(x-1,y-1,z,c,3,3,1,1))&&i#0?1:i"
f. "<max(crop(x-1,y-1,z,c,3,3,1,1))&&i#0?1:i"
rv[0,-1] rm.

if \$6 *[0] 0xff fi
if \$!==2 append c fi
}
``````

Not tested on CMYK/CMYKA, but that’s pointless to test because it’s pretty rare.

1 Like

All that’s left then is to make it into a gui filter and push. If you have the time/will @Reptorian , I’d be grateful if you can produce that (I think you’re more skilled with gui side anyway). If not, I can probably do that this week.

Thanks a lot Reptorian and Garagecoder for the hard work done.
Hope to see (and use) soon the G’mic filter available.
A Christmas gift for many people here around, I presume.

I might attempt this one by Wednesday, however, it would be good for you to explore doing it. The naive method is basically just the interface with the command as code. This script is time consuming, so it would have to be done with _persistent and hidden variables for maximum speed possible.

I’ve pushed the naive one. I will only optimise if I get complaints about speed, because it tends to make code harder to read and can break things. For me it runs in about 2 to 3 seconds on standard HD images, which is not a problem unless you’re doing batch processing.

Edit: oops, forgot to say how to find. It’s called “Canny Edge Detection” in testing > garagecoder

Thanks a lot, if you don’t mind, I’ve added it to the stdlb directly (after a few modifications).
This is a classical image processing operator, so it’s good to have it as a new command `canny` in the stdlib.

I’ve tried replacing your Gaussian+Sobel steps by a direct use of the `vanvliet` or `deriche` operators, with order 1 (they have been designed by their authors for that purpose initially), but the results are really less pleasant (but it’s faster, definitely ).

Really cool contribution BTW. Thanks!

1 Like

I would never object to that for any code I wrote, this is good news thanks

2 Likes

Thanks a lot, Garagecoder and David. Excellent news.
I will post comments as soon as used/tested.