Canny edge detector: Difference between revisions

m
m (→‎{{header|Wren}}: Minor tidy)
 
(18 intermediate revisions by 11 users not shown)
Line 1:
{{task|Image processing}}
{{task|Image processing}}'''Task:''' Write a program that performs so-called [[wp:Canny edge detector|canny edge detection]] on an image.
 
;Task:
A possible algorithm consists of the following steps:
Write a program that performs so-called [[wp:Canny edge detector|canny edge detection]] on an image.
 
# '''Noise reduction.''' May be performed by [[wp:Gaussian blur|Gaussian filter]].
# Compute '''intensity gradient''' (matrices <math>G_x</math> and <math>G_y</math>) and its '''magnitude''' <math>G</math>.<br>&nbsp;&nbsp;&nbsp;<math>G=\sqrt{G_x^2+G_y^2}</math><br> May be performed by [[image convolution|convolution of an image]] with [[wp:Sobel operator|Sobel operators]].
# '''Non-maximum suppression.''' For each pixel compute the orientation of intensity gradient vector: <math>\theta = {\rm atan2}\left(G_y, \, G_x\right)</math>. Transform angle <math>\theta</math> to one of four directions: 0, 45, 90, 135 degrees. Compute new array <math>N</math>: if<br>&nbsp;&nbsp;&nbsp;<math>G\left(p_a\right)<G\left(p\right)<G\left(p_b\right)</math><br>where <math>p</math> is the current pixel, <math>p_a</math> and <math>p_b</math> are the two neighbour pixels in the direction of gradient, then <math>N(p) = G(p)</math>, otherwise <math>N(p) = 0</math>. Nonzero pixels in resulting array correspond to local maxima of <math>G</math> in direction <math>\theta(p)</math>.
# '''Tracing edges with hysteresis.''' At this stage two thresholds for the values of <math>G</math> are introduced: <math>T_{min}</math> and <math>T_{max}</math>. Starting from pixels with <math>N(p) \geqslant T_{max}</math> find all paths of pixels with <math>N(p) \geqslant T_{min}</math> and put them to the resulting image.
 
 
A possible algorithm consists of the following steps:
 
# '''Noise reduction.''' &nbsp; May be performed by [[wp:Gaussian blur|Gaussian filter]]. <br> &nbsp;
# Compute '''intensity gradient''' &nbsp; (matrices <math>G_x</math> and <math>G_y</math>) &nbsp; and its '''magnitude''' &nbsp; <math>G</math>:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <math>G=\sqrt{G_x^2+G_y^2}</math><br>May be performed by [[image convolution|convolution of an image]] with [[wp:Sobel operator|Sobel operators]]. <br> &nbsp;
# '''Non-maximum suppression.''' &nbsp; <br>For each pixel compute the orientation of intensity gradient vector: &nbsp; <math>\theta = {\rm atan2}\left(G_y, \, G_x\right)</math>. &nbsp; &nbsp; <br>Transform &nbsp; angle <math>\theta</math> &nbsp; to one of four directions: &nbsp; 0,&nbsp;45,&nbsp;90,&nbsp;135&nbsp;degrees. &nbsp; &nbsp; <br>Compute new array &nbsp; <math>N</math>: &nbsp; &nbsp; if &nbsp; &nbsp; &nbsp; &nbsp; <math>G\left(p_a\right)<G\left(p\right)<G\left(p_b\right)</math><br>where &nbsp; <math>p</math> &nbsp; is the current pixel, &nbsp; <math>p_a</math> &nbsp; and &nbsp; <math>p_b</math> &nbsp; are the two neighbour pixels in the direction of gradient, &nbsp; <br>then &nbsp; &nbsp; <math>N(p) = G(p)</math>, &nbsp; &nbsp; &nbsp; otherwise &nbsp; <math>N(p) = 0</math>. &nbsp; <br>Nonzero pixels in resulting array correspond to local maxima of &nbsp; <math>G</math> &nbsp; in direction &nbsp; <math>\theta(p)</math>. <br> &nbsp;
# '''Tracing edges with hysteresis.''' &nbsp; <br>At this stage two thresholds for the values of &nbsp; <math>G</math> &nbsp; are introduced: &nbsp; <math>T_{min}</math> &nbsp; and &nbsp; <math>T_{max}</math>. &nbsp; <br>Starting from pixels with &nbsp; <math>N(p) \geqslant T_{max}</math>, &nbsp; <br>find all paths of pixels with &nbsp; <math>N(p) \geqslant T_{min}</math> &nbsp; and put them to the resulting image.
<br><br>
=={{header|C}}==
The following program reads an 8 bits per pixel grayscale [[wp:BMP file format|BMP]] file and saves the result to `out.bmp'. Compile with `-lm'.
<langsyntaxhighlight lang="c">#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
Line 462 ⟶ 467:
free((pixel_t*)out_bitmap_data);
return 0;
}</langsyntaxhighlight>
 
=={{header|D}}==
{{trans|C}}
This version retains some of the style of the original C version. This code is faster than the C version, even with the DMD compiler. This version loads and saves PGM images, using the module of the Grayscale image Task.
<langsyntaxhighlight lang="d">import core.stdc.stdio, std.math, std.typecons, std.string, std.conv,
std.algorithm, std.ascii, std.array, bitmap, grayscale_image;
 
Line 666 ⟶ 670:
printf("Image size: %d x %d\n", imIn.nx, imIn.ny);
imIn.cannyEdgeDetection(45, 50, 1.0f).savePGM("lena_canny.pgm");
}</langsyntaxhighlight>
 
 
 
 
=={{header|Go}}==
{{libheader|Imger}}
Line 678:
 
Note that on Linux the extension of the example image file name needs to be changed from .PNG to .png in order for the library used to recognize it.
<langsyntaxhighlight lang="go">package main
 
import (
Line 701:
log.Fatal("Could not write Canny image to disk")
}
}</langsyntaxhighlight>
 
=={{header|J}}==
<p>In this solution images are represented as 2D arrays of pixels, with first and second axes representing down and right respectively. Each processing step has a specific pixel representation. In the original and Gaussian-filtered images, array elements represent monochromatic intensity values as numbers ranging from 0 (black) to 255 (white). In the intensity gradient image, gradient values are vectors, and are represented as complex numbers, with real and imaginary components representing down and right respectively. </p>
<p>Detected edge and non-edge points are represented as ones and zeros respectively. An edge is a set of connected edge points (points adjacent horizontally, vertically, or diagonally are considered to be connected). In the final image, each edge is represented by assigning its set of points a common unique value. </p>
<langsyntaxhighlight Jlang="j">NB. 2D convolution, filtering, ...
 
convolve =: 4 : 'x apply (($x) partition y)'
Line 795 ⟶ 794:
canny =: step5 @ step4 @ step3 @ step2 @ step1
 
</syntaxhighlight>
</lang>
<p>The above implementation solves the 'inner problem' of Canny Edge Detection in the J language, with no external dependencies. J's Qt IDE provides additional support including interfaces to image file formats, graphic displays, and the user. The following code exercises these features</p>
 
<p>The file 'valve.png' referenced in this code is from one of several Wikipedia articles on edge detection. It can be viewed at [https://upload.wikimedia.org/wikipedia/commons/2/2e/Valve_gaussian_%282%29.PNG[https://upload.wikimedia.org/wikipedia/commons/2/2e/Valve_gaussian_%282%29.PNG]]</p>
<syntaxhighlight lang="j">
<lang J>
require 'gl2'
coclass 'edge'
Line 857 ⟶ 856:
form_close=: exit bind 0
 
run''</langsyntaxhighlight>
=={{header|Java}}==
El código es de Tom Gibara [The code is from Tom Gibara] (http://www.tomgibara.com/)
 
Se implementa utilizando una sola clase Java. [It is implemented using a single Java class.]
<syntaxhighlight lang="java">import java.awt.image.BufferedImage;
import java.util.Arrays;
 
/**
* <p><em>This software has been released into the public domain.
* <strong>Please read the notes in this source file for additional information.
* </strong></em></p>
*
* <p>This class provides a configurable implementation of the Canny edge
* detection algorithm. This classic algorithm has a number of shortcomings,
* but remains an effective tool in many scenarios. <em>This class is designed
* for single threaded use only.</em></p>
*
* <p>Sample usage:</p>
*
* <pre><code>
* //create the detector
* CannyEdgeDetector detector = new CannyEdgeDetector();
* //adjust its parameters as desired
* detector.setLowThreshold(0.5f);
* detector.setHighThreshold(1f);
* //apply it to an image
* detector.setSourceImage(frame);
* detector.process();
* BufferedImage edges = detector.getEdgesImage();
* </code></pre>
*
* <p>For a more complete understanding of this edge detector's parameters
* consult an explanation of the algorithm.</p>
*
* @author Tom Gibara
*
*/
 
public class CannyEdgeDetector {
 
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
 
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
 
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
 
// accessors
/**
* The image that provides the luminance data used by this detector to
* generate edges.
*
* @return the source image, or null
*/
public BufferedImage getSourceImage() {
return sourceImage;
}
/**
* Specifies the image that will provide the luminance data in which edges
* will be detected. A source image must be set before the process method
* is called.
*
* @param image a source of luminance data
*/
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
 
/**
* Obtains an image containing the edges detected during the last call to
* the process method. The buffered image is an opaque image of type
* BufferedImage.TYPE_INT_ARGB in which edge pixels are white and all other
* pixels are black.
*
* @return an image containing the detected edges, or null if the process
* method has not yet been called.
*/
public BufferedImage getEdgesImage() {
return edgesImage;
}
/**
* Sets the edges image. Calling this method will not change the operation
* of the edge detector in any way. It is intended to provide a means by
* which the memory referenced by the detector object may be reduced.
*
* @param edgesImage expected (though not required) to be null
*/
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
 
/**
* The low threshold for hysteresis. The default value is 2.5.
*
* @return the low hysteresis threshold
*/
public float getLowThreshold() {
return lowThreshold;
}
/**
* Sets the low threshold for hysteresis. Suitable values for this parameter
* must be determined experimentally for each application. It is nonsensical
* (though not prohibited) for this value to exceed the high threshold value.
*
* @param threshold a low hysteresis threshold
*/
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
/**
* The high threshold for hysteresis. The default value is 7.5.
*
* @return the high hysteresis threshold
*/
public float getHighThreshold() {
return highThreshold;
}
/**
* Sets the high threshold for hysteresis. Suitable values for this
* parameter must be determined experimentally for each application. It is
* nonsensical (though not prohibited) for this value to be less than the
* low threshold value.
*
* @param threshold a high hysteresis threshold
*/
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
 
/**
* The number of pixels across which the Gaussian kernel is applied.
* The default value is 16.
*
* @return the radius of the convolution operation in pixels
*/
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* This implementation will reduce the radius if the contribution of pixel
* values is deemed negligable, so this is actually a maximum radius.
*
* @param gaussianKernelWidth a radius for the convolution operation in
* pixels, at least 2.
*/
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
 
/**
* The radius of the Gaussian convolution kernel used to smooth the source
* image prior to gradient calculation. The default value is 16.
*
* @return the Gaussian kernel radius in pixels
*/
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
/**
* Sets the radius of the Gaussian convolution kernel used to smooth the
* source image prior to gradient calculation.
*
* @return a Gaussian kernel radius in pixels, must exceed 0.1f.
*/
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
/**
* Whether the luminance data extracted from the source image is normalized
* by linearizing its histogram prior to edge extraction. The default value
* is false.
*
* @return whether the contrast is normalized
*/
public boolean isContrastNormalized() {
return contrastNormalized;
}
/**
* Sets whether the contrast is normalized
* @param contrastNormalized true if the contrast should be normalized,
* false otherwise
*/
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
 
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
//NOTE: The elements of the method below (specifically the technique for
//non-maximal suppression and the technique for gradient computation)
//are derived from an implementation posted in the following forum (with the
//clear intent of others using the code):
// http://forum.java.sun.com/thread.jspa?threadID=546211&start=45&tstart=0
//My code effectively mimics the algorithm exhibited above.
//Since I don't know the providence of the code that was posted it is a
//possibility (though I think a very remote one) that this code violates
//someone's intellectual property rights. If this concerns you feel free to
//contact me for an alternative, though less efficient, implementation.
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
 
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
 
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
 
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
/*
* An explanation of what's happening here, for those who want
* to understand the source: This performs the "non-maximal
* supression" phase of the Canny edge detection in which we
* need to compare the gradient magnitude to that in the
* direction of the gradient; only if the value is a local
* maximum do we consider the point as an edge candidate.
*
* We need to break the comparison into a number of different
* cases depending on the gradient direction so that the
* appropriate values can be used. To avoid computing the
* gradient direction, we use two simple comparisons: first we
* check that the partial derivatives have the same sign (1)
* and then we check which is larger (2). As a consequence, we
* have reduced the problem to one of four identical cases that
* each test the central gradient magnitude against the values at
* two points with 'identical support'; what this means is that
* the geometry required to accurately interpolate the magnitude
* of gradient function at those points has an identical
* geometry (upto right-angled-rotation/reflection).
*
* When comparing the central gradient to the two interpolated
* values, we avoid performing any divisions by multiplying both
* sides of each inequality by the greater of the two partial
* derivatives. The common comparand is stored in a temporary
* variable (3) and reused in the mirror case (4).
*
*/
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
//NOTE: It is quite feasible to replace the implementation of this method
//with one which only loosely approximates the hypot function. I've tested
//simple approximations such as Math.abs(x) + Math.abs(y) and they work fine.
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
//NOTE: this implementation reuses the data array to store both
//luminance data from the image, and edge intensity from the processing.
//This is done for memory efficiency, other implementations may wish
//to separate these functions.
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
 
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
//NOTE: There is currently no mechanism for obtaining the edge data
//in any other format other than an INT_ARGB type BufferedImage.
//This may be easily remedied by providing alternative accessors.
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
}</syntaxhighlight>
=={{header|Julia}}==
{{works with|Julia|0.6}}
<langsyntaxhighlight lang="julia">using Images
 
canny_edges = canny(img, sigma = 1.4, upperThreshold = 0.80, lowerThreshold = 0.20)</langsyntaxhighlight>
=={{header|Mathematica}}/{{header|Wolfram Language}}==
 
<syntaxhighlight lang="mathematica">Export["out.bmp", EdgeDetect[Import[InputString[]]]];</syntaxhighlight>
=={{header|Mathematica}}==
<lang Mathematica>Export["out.bmp", EdgeDetect[Import[InputString[]]]];</lang>
Mathematica uses canny edge detection by default. This seems so cheaty next to all of these giant answers...
=={{header|MATLAB}} / {{header|Octave}}==
There is a function in the image processing toolbox [http://www.mathworks.com/help/images/ref/edge.html edge], that has Canny Edge Detection as one of its options.
<syntaxhighlight lang="matlab">BWImage = edge(GrayscaleImage,'canny');</syntaxhighlight>
=={{header|Nim}}==
{{trans|D}}
{{libheader|nimPNG}}
We use the PNG image present on Wikipedia article as input and produce a PNG grayscale image as result.
 
<syntaxhighlight lang="nim">import lenientops
=={{header|MATLAB}}==
import math
There is a built-in function, [http://www.mathworks.com/help/images/ref/edge.html edge], that has Canny Edge Detection as one of its options.
import nimPNG
<lang MATLAB>BWImage = edge(GrayscaleImage,'canny');</lang>
 
const MaxBrightness = 255
 
type Pixel = int16 # Used instead of byte to be able to store negative values.
 
#---------------------------------------------------------------------------------------------------
 
func convolution*[normalize: static bool](input: seq[Pixel]; output: var seq[Pixel];
kernel: seq[float]; nx, ny, kn: int) =
## Do a convolution.
## If normalize is true, map pixels to range 0...maxBrightness.
 
doAssert kernel.len == kn * kn
doAssert (kn and 1) == 1
doAssert nx > kn and ny > kn
doAssert input.len == output.len
 
let khalf = kn div 2
 
when normalize:
 
var pMin = float.high
var pMax = -float.high
 
for m in khalf..<(nx - khalf):
for n in khalf..<(ny - khalf):
var pixel = 0.0
var c = 0
for j in -khalf..khalf:
for i in -khalf..khalf:
pixel += input[(n - j) * nx + m - i] * kernel[c]
inc c
if pixel < pMin:
pMin = pixel
if pixel > pMax:
pMax = pixel
 
for m in khalf..<(nx - khalf):
for n in khalf..<(ny - khalf):
var pixel = 0.0
var c = 0
for j in -khalf..khalf:
for i in -khalf..khalf:
pixel += input[(n - j) * nx + m - i] * kernel[c]
inc c
when normalize:
pixel = MaxBrightness * (pixel - pMin) / (pMax - pMin)
output[n * nx + m] = Pixel(pixel)
 
#---------------------------------------------------------------------------------------------------
 
func gaussianFilter(input: seq[Pixel]; output: var seq[Pixel]; nx, ny: int; sigma: float) =
## Apply a gaussian filter.
 
doAssert input.len == output.len
 
let n = 2 * (2 * sigma).toInt + 3
let mean = floor(n / 2)
var kernel = newSeq[float](n * n)
 
var c = 0
for i in 0..<n:
for j in 0..<n:
kernel[c] = exp(-0.5 * (((i - mean) / sigma) ^ 2 + ((j - mean) / sigma) ^ 2)) /
(2 * PI * sigma * sigma)
inc c
 
convolution[true](input, output, kernel, nx, ny, n)
 
#---------------------------------------------------------------------------------------------------
 
proc cannyEdgeDetection(input: seq[Pixel];
nx, ny: int;
tmin, tmax: int;
sigma: float): seq[byte] =
 
 
 
## Detect edges.
var output = newSeq[Pixel](input.len)
gaussianFilter(input, output, nx, ny, sigma)
 
const Gx = @[float -1, 0, 1,
-2, 0, 2,
-1, 0, 1]
var afterGx = newSeq[Pixel](input.len)
convolution[false](input, afterGx, Gx, nx, ny, 3)
 
const Gy = @[float 1, 2, 1,
0, 0, 0,
-1, -2, -1]
var afterGy = newSeq[Pixel](input.len)
convolution[false](input, afterGy, Gy, nx, ny, 3)
 
var g = newSeq[Pixel](input.len)
for i in 1..(nx - 2):
for j in 1..(ny - 2):
let c = i + nx * j
g[c] = hypot(afterGx[c].toFloat, afterGy[c].toFloat).Pixel
 
# Non-maximum suppression: straightforward implementation.
var nms = newSeq[Pixel](input.len)
for i in 1..(nx - 2):
for j in 1..(ny - 2):
let
c = i + nx * j
nn = c - nx
ss = c + nx
ww = c + 1
ee = c - 1
nw = nn + 1
ne = nn - 1
sw = ss + 1
se = ss - 1
let aux = arctan2(afterGy[c].toFloat, afterGx[c].toFloat) + PI
let dir = aux mod PI / PI * 8
if (((dir <= 1 or dir > 7) and g[c] > g[ee] and g[c] > g[ww]) or # O°.
((dir > 1 and dir <= 3) and g[c] > g[nw] and g[c] > g[se]) or # 45°.
((dir > 3 and dir <= 5) and g[c] > g[nn] and g[c] > g[ss]) or # 90°.
((dir > 5 and dir <= 7) and g[c] > g[ne] and g[c] > g[sw])): # 135°.
nms[c] = g[c]
else:
nms[c] = 0
 
# Tracing edges with hysteresis. Non-recursive implementation.
var edges = newSeq[int](input.len div 2)
for item in output.mitems: item = 0
var c = 0
for j in 1..(ny - 2):
for i in 1..(nx - 2):
inc c
 
if nms[c] >= tMax and output[c] == 0:
# Trace edges.
output[c] = MaxBrightness
var nedges = 1
edges[0] = c
 
while nedges > 0:
dec nedges
let t = edges[nedges]
let neighbors = [t - nx, # nn.
t + nx, # ss.
t + 1, # ww.
t - 1, # ee.
t - nx + 1, # nw.
t - nx - 1, # ne.
t + nx + 1, # sw.
t + nx - 1] # se.
 
for n in neighbors:
if nms[n] >= tMin and output[n] == 0:
output[n] = MaxBrightness
edges[nedges] = n
inc nedges
 
# Store the result as a sequence of bytes.
result = newSeqOfCap[byte](output.len)
for val in output:
result.add(byte(val))
 
 
#———————————————————————————————————————————————————————————————————————————————————————————————————
 
when isMainModule:
 
const
Input = "Valve.png"
Output = "Valve_edges.png"
 
let pngImage = loadPNG24(seq[byte], Input).get()
 
# Convert to grayscale and store luminances as 16 bits signed integers.
var pixels = newSeq[Pixel](pngImage.width * pngImage.height)
for i in 0..pixels.high:
pixels[i] = Pixel(0.2126 * pngImage.data[3 * i] +
0.7152 * pngImage.data[3 * i + 1] +
0.0722 * pngImage.data[3 * i + 2] + 0.5)
 
# Find edges.
let data = cannyEdgeDetection(pixels, pngImage.width, pngImage.height, 45, 50, 1.0)
 
# Save result as a PNG image.
let status = savePNG(Output, data, LCT_GREY, 8, pngImage.width, pngImage.height)
if status.isOk:
echo "File ", Input, " processed. Result is available in file ", Output
else:
echo "Error: ", status.error</syntaxhighlight>
=={{header|Perl}}==
Used a [https://raw.githubusercontent.com/abend/Image-EdgeDetect/master/lib/Image/EdgeDetect.pm non-CPAN module] by [https://github.com/abend Sasha Kovar]
<syntaxhighlight lang="perl"># 20220120 Perl programming solution
 
use strict;
use warnings;
 
use lib '/home/hkdtam/lib';
use Image::EdgeDetect;
 
my $detector = Image::EdgeDetect->new();
$detector->process('./input.jpg', './output.jpg') or die; # na.cx/i/pHYdUrV.jpg</syntaxhighlight>
Output: [https://drive.google.com/file/d/1ww21DHz-IQTaFKNLC2I2No_fHUehYooG/view (Offsite image file) ]
=={{header|Phix}}==
{{libheader|Phix/pGUI}}
Ported from demo\Arwen32dibdemo\manip.exw (menu entry Manipulate/Filter/Detect Edges, windows-32-bit only) to pGUI.
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\Canny_Edge_Detection.exw
-- =====================================
--</span>
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span> <span style="color: #000080;font-style:italic;">-- imImage, im_width, im_height, im_pixel, IupImageRGB,
-- imFileImageLoadBitmap, and IupImageFromImImage()</span>
<span style="color: #008080;">include</span> <span style="color: #000000;">pGUI</span><span style="color: #0000FF;">.</span><span style="color: #000000;">e</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">TITLE</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"Canny Edge Detection"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">IMGFILE</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"Valve.png"</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">C_E_D</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{{-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">},</span>
<span style="color: #0000FF;">{-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">8</span><span style="color: #0000FF;">,</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">},</span>
<span style="color: #0000FF;">{-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">}}</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">detect_edges</span><span style="color: #0000FF;">(</span><span style="color: #000000;">imImage</span> <span style="color: #000000;">img</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">width</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">im_width</span><span style="color: #0000FF;">(</span><span style="color: #000000;">img</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">height</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">im_height</span><span style="color: #0000FF;">(</span><span style="color: #000000;">img</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">original</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">repeat</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">repeat</span><span style="color: #0000FF;">(</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">width</span><span style="color: #0000FF;">),</span><span style="color: #000000;">height</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">fh</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">C_E_D</span><span style="color: #0000FF;">),</span> <span style="color: #000000;">hh</span><span style="color: #0000FF;">=(</span><span style="color: #000000;">fh</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)/</span><span style="color: #000000;">2</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">fw</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">C_E_D</span><span style="color: #0000FF;">[</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]),</span> <span style="color: #000000;">hw</span><span style="color: #0000FF;">=(</span><span style="color: #000000;">fw</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)/</span><span style="color: #000000;">2</span><span style="color: #0000FF;">,</span>
<span style="color: #000000;">divisor</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">max</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">sum</span><span style="color: #0000FF;">(</span><span style="color: #000000;">C_E_D</span><span style="color: #0000FF;">),</span><span style="color: #000000;">1</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- read original pixels and make them grey,</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">y</span><span style="color: #0000FF;">=</span><span style="color: #000000;">height</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">0</span> <span style="color: #008080;">by</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">x</span><span style="color: #0000FF;">=</span><span style="color: #000000;">0</span> <span style="color: #008080;">to</span> <span style="color: #000000;">width</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">c1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">c2</span><span style="color: #0000FF;">,</span><span style="color: #000000;">c3</span><span style="color: #0000FF;">}</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">im_pixel</span><span style="color: #0000FF;">(</span><span style="color: #000000;">img</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">x</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">y</span><span style="color: #0000FF;">),</span>
<span style="color: #000000;">grey</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">floor</span><span style="color: #0000FF;">((</span><span style="color: #000000;">c1</span><span style="color: #0000FF;">*</span><span style="color: #000000;">114</span><span style="color: #0000FF;">+</span><span style="color: #000000;">c2</span><span style="color: #0000FF;">*</span><span style="color: #000000;">587</span><span style="color: #0000FF;">+</span><span style="color: #000000;">c3</span><span style="color: #0000FF;">*</span><span style="color: #000000;">299</span><span style="color: #0000FF;">)/</span><span style="color: #000000;">1000</span><span style="color: #0000FF;">)</span>
<span style="color: #000000;">original</span><span style="color: #0000FF;">[</span><span style="color: #000000;">height</span><span style="color: #0000FF;">-</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span><span style="color: #000000;">x</span><span style="color: #0000FF;">+</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">grey</span><span style="color: #0000FF;">,</span><span style="color: #000000;">grey</span><span style="color: #0000FF;">,</span><span style="color: #000000;">grey</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #000080;font-style:italic;">-- then apply an edge detection filter</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">new_image</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">original</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">y</span><span style="color: #0000FF;">=</span><span style="color: #000000;">hh</span><span style="color: #0000FF;">+</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">height</span><span style="color: #0000FF;">-</span><span style="color: #000000;">hh</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">x</span><span style="color: #0000FF;">=</span><span style="color: #000000;">hw</span><span style="color: #0000FF;">+</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #000000;">width</span><span style="color: #0000FF;">-</span><span style="color: #000000;">hw</span><span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">sequence</span> <span style="color: #000000;">newrgb</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">,</span><span style="color: #000000;">0</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=-</span><span style="color: #000000;">hh</span> <span style="color: #008080;">to</span> <span style="color: #0000FF;">+</span><span style="color: #000000;">hh</span> <span style="color: #008080;">do</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">j</span><span style="color: #0000FF;">=-</span><span style="color: #000000;">hw</span> <span style="color: #008080;">to</span> <span style="color: #0000FF;">+</span><span style="color: #000000;">hw</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">newrgb</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sq_add</span><span style="color: #0000FF;">(</span><span style="color: #000000;">newrgb</span><span style="color: #0000FF;">,</span><span style="color: #7060A8;">sq_mul</span><span style="color: #0000FF;">(</span><span style="color: #000000;">C_E_D</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">+</span><span style="color: #000000;">hh</span><span style="color: #0000FF;">+</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #000000;">j</span><span style="color: #0000FF;">+</span><span style="color: #000000;">hw</span><span style="color: #0000FF;">+</span><span style="color: #000000;">1</span><span style="color: #0000FF;">],</span><span style="color: #000000;">original</span><span style="color: #0000FF;">[</span><span style="color: #000000;">y</span><span style="color: #0000FF;">+</span><span style="color: #000000;">i</span><span style="color: #0000FF;">,</span><span style="color: #000000;">x</span><span style="color: #0000FF;">+</span><span style="color: #000000;">j</span><span style="color: #0000FF;">]))</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #000000;">new_image</span><span style="color: #0000FF;">[</span><span style="color: #000000;">y</span><span style="color: #0000FF;">,</span><span style="color: #000000;">x</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sq_max</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">sq_min</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">sq_floor_div</span><span style="color: #0000FF;">(</span><span style="color: #000000;">newrgb</span><span style="color: #0000FF;">,</span><span style="color: #000000;">divisor</span><span style="color: #0000FF;">),</span><span style="color: #000000;">255</span><span style="color: #0000FF;">),</span><span style="color: #000000;">0</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #000000;">new_image</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">flatten</span><span style="color: #0000FF;">(</span><span style="color: #000000;">new_image</span><span style="color: #0000FF;">)</span> <span style="color: #000080;font-style:italic;">-- (as needed by IupImageRGB)</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">new_img</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">IupImageRGB</span><span style="color: #0000FF;">(</span><span style="color: #000000;">width</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">height</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">new_image</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">new_img</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #7060A8;">IupOpen</span><span style="color: #0000FF;">()</span>
<span style="color: #000000;">imImage</span> <span style="color: #000000;">im1</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">imFileImageLoadBitmap</span><span style="color: #0000FF;">(</span><span style="color: #000000;">IMGFILE</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">assert</span><span style="color: #0000FF;">(</span><span style="color: #000000;">im1</span><span style="color: #0000FF;">!=</span><span style="color: #004600;">NULL</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"error opening "</span><span style="color: #0000FF;">&</span><span style="color: #000000;">IMGFILE</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">label1</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">(),</span>
<span style="color: #000000;">label2</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupLabel</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupSetAttributeHandle</span><span style="color: #0000FF;">(</span><span style="color: #000000;">label1</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"IMAGE"</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">IupImageFromImImage</span><span style="color: #0000FF;">(</span><span style="color: #000000;">im1</span><span style="color: #0000FF;">))</span>
<span style="color: #7060A8;">IupSetAttributeHandle</span><span style="color: #0000FF;">(</span><span style="color: #000000;">label2</span><span style="color: #0000FF;">,</span> <span style="color: #008000;">"IMAGE"</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">detect_edges</span><span style="color: #0000FF;">(</span><span style="color: #000000;">im1</span><span style="color: #0000FF;">))</span>
<span style="color: #004080;">Ihandle</span> <span style="color: #000000;">dlg</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">IupDialog</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">IupHbox</span><span style="color: #0000FF;">({</span><span style="color: #000000;">label1</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">label2</span><span style="color: #0000FF;">}),</span><span style="color: #008000;">`TITLE="%s"`</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">TITLE</span><span style="color: #0000FF;">})</span>
<span style="color: #7060A8;">IupShow</span><span style="color: #0000FF;">(</span><span style="color: #000000;">dlg</span><span style="color: #0000FF;">)</span>
<span style="color: #7060A8;">IupMainLoop</span><span style="color: #0000FF;">()</span>
<span style="color: #7060A8;">IupClose</span><span style="color: #0000FF;">()</span>
<!--</syntaxhighlight>-->
=={{header|PHP}}==
PHP implementation
<syntaxhighlight lang="php">
// input: r,g,b in range 0..255
function RGBtoHSV($r, $g, $b) {
$r = $r/255.; // convert to range 0..1
$g = $g/255.;
$b = $b/255.;
$cols = array("r" => $r, "g" => $g, "b" => $b);
asort($cols, SORT_NUMERIC);
$min = key(array_slice($cols, 1)); // "r", "g" or "b"
$max = key(array_slice($cols, -1)); // "r", "g" or "b"
 
// hue
if($cols[$min] == $cols[$max]) {
$h = 0;
} else {
if($max == "r") {
$h = 60. * ( 0 + ( ($cols["g"]-$cols["b"]) / ($cols[$max]-$cols[$min]) ) );
} elseif ($max == "g") {
$h = 60. * ( 2 + ( ($cols["b"]-$cols["r"]) / ($cols[$max]-$cols[$min]) ) );
} elseif ($max == "b") {
$h = 60. * ( 4 + ( ($cols["r"]-$cols["g"]) / ($cols[$max]-$cols[$min]) ) );
}
if($h < 0) {
$h += 360;
}
}
 
// saturation
if($cols[$max] == 0) {
$s = 0;
} else {
$s = ( ($cols[$max]-$cols[$min])/$cols[$max] );
$s = $s * 255;
}
 
// lightness
$v = $cols[$max];
$v = $v * 255;
 
return(array($h, $s, $v));
}
 
$filename = "image.png";
$dimensions = getimagesize($filename);
$w = $dimensions[0]; // width
$h = $dimensions[1]; // height
 
$im = imagecreatefrompng($filename);
 
for($hi=0; $hi < $h; $hi++) {
 
for($wi=0; $wi < $w; $wi++) {
$rgb = imagecolorat($im, $wi, $hi);
 
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$hsv = RGBtoHSV($r, $g, $b);
 
// compare pixel below with current pixel
$brgb = imagecolorat($im, $wi, $hi+1);
$br = ($brgb >> 16) & 0xFF;
$bg = ($brgb >> 8) & 0xFF;
$bb = $brgb & 0xFF;
$bhsv = RGBtoHSV($br, $bg, $bb);
 
// if difference in hue > 20, edge is detected
if($hsv[2]-$bhsv[2] > 20) {
imagesetpixel($im, $wi, $hi, imagecolorallocate($im, 255, 0, 0));
}
else {
imagesetpixel($im, $wi, $hi, imagecolorallocate($im, 0, 0, 0));
}
}
}
 
header('Content-Type: image/jpeg');
=={{Header|Phix}}==
imagepng($im);
{{improve|Phix|Port to pGUI for linux and 64 bit compatibility.}}
imagedestroy($im);
The file demo\Arwen32dibdemo\manip.exw in the standard distribution contains a menu entry for this, Manipulate/Filter/Detect Edges, along with 15 or so other effects. It is however windows-32-bit only, and could do with being ported to pGUI.
 
</syntaxhighlight>
=={{Header|Python}}==
=={{header|Python}}==
 
In Python, Canny edge detection would normally be done using [http://scikit-image.org/docs/dev/auto_examples/plot_canny.html scikit-image] or OpenCV-Python. Here is an approach using numpy/scipy:
 
<langsyntaxhighlight lang="python">#!/bin/python
import numpy as np
from scipy.ndimage.filters import convolve, gaussian_filter
Line 966 ⟶ 1,886:
im = imread("test.jpg", mode="L") #Open image, convert to greyscale
finalEdges = CannyEdgeDetector(im)
imshow(finalEdges)</langsyntaxhighlight>
=={{header|Raku}}==
Admittedly laziness always prevails so an off-the-shelf function from ImageMagick is used instead.
 
cannyedge.c
<syntaxhighlight lang="c">#include <stdio.h>
#include <string.h>
#include <magick/MagickCore.h>
 
int CannyEdgeDetector(
const char *infile, const char *outfile,
double radius, double sigma, double lower, double upper ) {
 
ExceptionInfo *exception;
Image *image, *processed_image, *output;
ImageInfo *input_info;
 
exception = AcquireExceptionInfo();
input_info = CloneImageInfo((ImageInfo *) NULL);
(void) strcpy(input_info->filename, infile);
image = ReadImage(input_info, exception);
output = NewImageList();
processed_image = CannyEdgeImage(image,radius,sigma,lower,upper,exception);
(void) AppendImageToList(&output, processed_image);
(void) strcpy(output->filename, outfile);
WriteImage(input_info, output);
// after-party clean up
DestroyImage(image);
output=DestroyImageList(output);
input_info=DestroyImageInfo(input_info);
exception=DestroyExceptionInfo(exception);
MagickCoreTerminus();
 
return 0;
}</syntaxhighlight>
cannyedge.raku
<syntaxhighlight lang="raku" line># 20220103 Raku programming solution
use NativeCall;
sub CannyEdgeDetector(CArray[uint8], CArray[uint8], num64, num64, num64, num64
) returns int32 is native( '/home/hkdtam/LibCannyEdgeDetector.so' ) {*};
 
CannyEdgeDetector( # imagemagick.org/script/command-line-options.php#canny
CArray[uint8].new( 'input.jpg'.encode.list, 0), # pbs.org/wgbh/nova/next/wp-content/uploads/2013/09/fingerprint-1024x575.jpg
CArray[uint8].new( 'output.jpg'.encode.list, 0),
0e0, 2e0, 0.05e0, 0.05e0
)</syntaxhighlight>
{{out}}
<pre>export PKG_CONFIG_PATH=/usr/lib/pkgconfig
gcc -Wall -fPIC -shared -o LibCannyEdgeDetector.so cannyedge.c `pkg-config --cflags --libs MagickCore`
raku -c cannyedge.raku && ./cannyedge.raku</pre>
Output: [https://drive.google.com/file/d/1k9F53egoFP0Sl4uBR85nai8IwC43BkYZ/view (Offsite image file) ]
=={{header|Tcl}}==
{{libheader|crimp}}
<langsyntaxhighlight lang="tcl">package require crimp
package require crimp::pgm
 
Line 986 ⟶ 1,957:
writePGM $outputFile [crimp filter canny sobel [readPGM $inputFile]]
}
cannyFilterFile {*}$argv</langsyntaxhighlight>
=={{header|Wren}}==
{{trans|C}}
{{libheader|DOME}}
{{libheader|Wren-check}}
<syntaxhighlight lang="wren">import "dome" for Window
import "graphics" for Canvas, Color, ImageData
import "math" for Math
import "./check" for Check
 
var MaxBrightness = 255
 
class Canny {
construct new(inFile, outFile) {
Window.title = "Canny edge detection"
var image1 = ImageData.load(inFile)
var w = image1.width
var h = image1.height
Window.resize(w * 2 + 20, h)
Canvas.resize(w * 2 + 20, h)
var image2 = ImageData.create(outFile, w, h)
var pixels = List.filled(w * h, 0)
var ix = 0
// convert image1 to gray scale as a list of pixels
for (y in 0...h) {
for (x in 0...w) {
var c1 = image1.pget(x, y)
var lumin = (0.2126 * c1.r + 0.7152 * c1.g + 0.0722 * c1.b).floor
pixels[ix] = lumin
ix = ix + 1
}
}
 
// find edges
var data = cannyEdgeDetection(pixels, w, h, 45, 50, 1)
 
// write to image2
ix = 0
for (y in 0...h) {
for (x in 0...w) {
var d = data[ix]
var c = Color.rgb(d, d, d)
image2.pset(x, y, c)
ix = ix + 1
}
}
 
// display the two images side by side
image1.draw(0, 0)
image2.draw(w + 20, 0)
 
// save image2 to outFile
image2.saveToFile(outFile)
}
 
init() {}
 
// If normalize is true, map pixels to range 0..MaxBrightness
convolution(input, output, kernel, nx, ny, kn, normalize) {
Check.ok((kn % 2) == 1)
Check.ok(nx > kn && ny > kn)
var khalf = (kn / 2).floor
var min = Num.largest
var max = -min
if (normalize) {
for (m in khalf...nx-khalf) {
for (n in khalf...ny-khalf) {
var pixel = 0
var c = 0
for (j in -khalf..khalf) {
for (i in -khalf..khalf) {
pixel = pixel + input[(n-j)*nx + m - i] * kernel[c]
c = c + 1
}
}
if (pixel < min) min = pixel
if (pixel > max) max = pixel
}
}
}
 
for (m in khalf...nx-khalf) {
for (n in khalf...ny-khalf) {
var pixel = 0
var c = 0
for (j in -khalf..khalf) {
for (i in -khalf..khalf) {
pixel = pixel + input[(n-j)*nx + m - i] * kernel[c]
c = c + 1
}
}
if (normalize) pixel = MaxBrightness * (pixel - min) / (max - min)
output[n * nx + m] = pixel.truncate
}
}
}
 
gaussianFilter(input, output, nx, ny, sigma) {
var n = 2 * (2 * sigma).truncate + 3
var mean = (n / 2).floor
var kernel = List.filled(n * n, 0)
System.print("Gaussian filter: kernel size = %(n), sigma = %(sigma)")
var c = 0
for (i in 0...n) {
for (j in 0...n) {
var t = (-0.5 * (((i - mean) / sigma).pow(2) + ((j - mean) / sigma).pow(2))).exp
kernel[c] = t / (2 * Num.pi * sigma * sigma)
c = c + 1
}
}
convolution(input, output, kernel, nx, ny, n, true)
}
 
// Returns the square root of 'x' squared + 'y' squared.
hypot(x, y) { (x*x + y*y).sqrt }
 
cannyEdgeDetection(input, nx, ny, tmin, tmax, sigma) {
var output = List.filled(input.count, 0)
gaussianFilter(input, output, nx, ny, sigma)
var Gx = [-1, 0, 1, -2, 0, 2, -1, 0, 1]
var afterGx = List.filled(input.count, 0)
convolution(output, afterGx, Gx, nx, ny, 3, false)
var Gy = [1, 2, 1, 0, 0, 0, -1, -2, -1]
var afterGy = List.filled(input.count, 0)
convolution(output, afterGy, Gy, nx, ny, 3, false)
var G = List.filled(input.count, 0)
for (i in 1..nx-2) {
for (j in 1..ny-2) {
var c = i + nx * j
G[c] = hypot(afterGx[c], afterGy[c]).floor
}
}
 
// non-maximum suppression: straightforward implementation
var nms = List.filled(input.count, 0)
for (i in 1..nx-2) {
for (j in 1..ny-2) {
var c = i + nx * j
var nn = c - nx
var ss = c + nx
var ww = c + 1
var ee = c - 1
var nw = nn + 1
var ne = nn - 1
var sw = ss + 1
var se = ss - 1
var temp = Math.atan(afterGy[c], afterGx[c]) + Num.pi
var dir = (temp % Num.pi) / Num.pi * 8
if (((dir <= 1 || dir > 7) && G[c] > G[ee] && G[c] > G[ww]) || // O°
((dir > 1 && dir <= 3) && G[c] > G[nw] && G[c] > G[se]) || // 45°
((dir > 3 && dir <= 5) && G[c] > G[nn] && G[c] > G[ss]) || // 90°
((dir > 5 && dir <= 7) && G[c] > G[ne] && G[c] > G[sw])) { // 135°
nms[c] = G[c]
} else {
nms[c] = 0
}
}
}
 
// tracing edges with hysteresis: non-recursive implementation
var edges = List.filled((input.count/2).floor, 0)
for (i in 0...output.count) output[i] = 0
var c = 1
for (j in 1..ny-2) {
for (i in 1..nx-2) {
if (nms[c] >= tmax && output[c] == 0) {
// trace edges
output[c] = MaxBrightness
var nedges = 1
edges[0] = c
while (true) {
nedges = nedges - 1
var t = edges[nedges]
var nbs = [ // neighbors
t - nx, // nn
t + nx, // ss
t + 1, // ww
t - 1, // ee
t - nx + 1, // nw
t - nx - 1, // ne
t + nx + 1, // sw
t + nx - 1 // se
]
for (n in nbs) {
if (nms[n] >= tmin && output[n] == 0) {
output[n] = MaxBrightness
edges[nedges] = n
nedges = nedges + 1
}
}
if (nedges == 0) break
}
}
c = c + 1
}
}
return output
}
 
update() {}
 
draw(alpha) {}
}
var Game = Canny.new("Valve_original.png", "Valve_monchrome_canny.png")</syntaxhighlight>
 
=={{header|Yabasic}}==
{{trans|Phix}}
<syntaxhighlight lang="yabasic">// Rosetta Code problem: http://rosettacode.org/wiki/Canny_edge_detector
// Adapted from Phix to Yabasic by Galileo, 01/2022
 
import ReadFromPPM2
 
MaxBrightness = 255
 
readPPM("Valve.ppm")
print "Be patient, please ..."
 
width = peek("winwidth")
height = peek("winheight")
dim pixels(width, height), C_E_D(3, 3)
 
data -1, -1, -1, -1, 8, -1, -1, -1, -1
for i = 0 to 2
for j = 0 to 2
read C_E_D(i, j)
next
next
 
// convert image to gray scale
for y = 1 to height
for x = 1 to width
c$ = right$(getbit$(x, y, x, y), 6)
r = dec(left$(c$, 2))
g = dec(mid$(c$, 3, 2))
b = dec(right$(c$, 2))
lumin = floor(0.2126 * r + 0.7152 * g + 0.0722 * b)
pixels(x, y) = lumin
next
next
 
dim new_image(width, height)
 
divisor = 1
 
// apply an edge detection filter
for y = 2 to height-2
for x = 2 to width-2
newrgb = 0
for i = -1 to 1
for j = -1 to 1
newrgb = newrgb + C_E_D(i+1, j+1) * pixels(x+i, y+j)
next
new_image(x, y) = max(min(newrgb / divisor,255),0)
next
next
next
 
// show result
 
for x = 1 to width
for y = 1 to height
c = new_image(x, y)
color c, c, c
dot x, y
next
next</syntaxhighlight>
9,476

edits