Canny边缘检测算子是澳洲计算机科学家约翰·坎尼(John F. Canny)于1986年开发出来的一个多级边缘检测算法。
算法步骤
1. 降噪
任何边缘检测算法都不可能在未经处理的原始数据上很好地处理,所以第一步是对原始数据与高斯平滑模板作卷积,得到的图像与原始图像相比有些轻微的模糊(blurred)。这样,单独的一个像素噪声在经过高斯平滑的图像上变得几乎没有影响。
2. 寻找图像中的亮度梯度
图像中的边缘可能会指向不同的方向,所以Canny算法使用4个mask检测水平、垂直以及对角线方向的边缘。原始图像与每个mask所作的卷积都存储起来。对于每个点我们都标识在这个点上的最大值以及生成的边缘的方向。这样我们就从原始图像生成了图像中每个点亮度梯度图以及亮度梯度的方向。
3. 在图像中跟踪边缘
较高的亮度梯度比较有可能是边缘,但是没有一个确切的值来限定多大的亮度梯度是边缘多大又不是,所以Canny使用了滞后阈值。
滞后阈值需要两个阈值——高阈值与低阈值。假设图像中的重要边缘都是连续的曲线,这样我们就可以跟踪给定曲线中模糊的部分,并且避免将没有组成曲线的噪声像素当成边缘。所以我们从一个较大的阈值开始,这将标识出我们比较确信的真实边缘,使用前面导出的方向信息,我们从这些真正的边缘开始在图像中跟踪整个的边缘。在跟踪的时候,我们使用一个较小的阈值,这样就可以跟踪曲线的模糊部分直到我们回到起点。
一旦这个过程完成,我们就得到了一个二值图像,每点表示是否是一个边缘点。
一个获得亚像素精度边缘的改进实现是在梯度方向检测二阶方向导数的过零点
它在梯度方向的三阶方向导数满足符号条件
其中Lx,Ly,··· ,Lyyy表示用高斯核平滑原始图像得到的尺度空间表示{\displaystyle L}L计算得到的偏导数。用这种方法得到的边缘片断是连续曲线,这样就不需要另外的边缘跟踪改进。滞后阈值也可以用于亚像素边缘检测。
代码实现
C
该程序读取每像素8bit的灰度BMP文件,并将结果保存到`out.bmp’。用-lm编译。
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#define MAX_BRIGHTNESS 255
// C99 doesn't define M_PI (GNU-C99 does)
#define M_PI 3.14159265358979323846264338327
/*
* Loading part taken from
* http://www.vbforums.com/showthread.php?t=261522
* BMP info:
* http://en.wikipedia.org/wiki/BMP_file_format
*
* Note: the magic number has been removed from the bmpfile_header_t
* structure since it causes alignment problems
* bmpfile_magic_t should be written/read first
* followed by the
* bmpfile_header_t
* [this avoids compiler-specific alignment pragmas etc.]
*/
typedef struct {
uint8_t magic[2];
} bmpfile_magic_t;
typedef struct {
uint32_t filesz;
uint16_t creator1;
uint16_t creator2;
uint32_t bmp_offset;
} bmpfile_header_t;
typedef struct {
uint32_t header_sz;
int32_t width;
int32_t height;
uint16_t nplanes;
uint16_t bitspp;
uint32_t compress_type;
uint32_t bmp_bytesz;
int32_t hres;
int32_t vres;
uint32_t ncolors;
uint32_t nimpcolors;
} bitmap_info_header_t;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t nothing;
} rgb_t;
// Use short int instead `unsigned char' so that we can
// store negative values.
typedef short int pixel_t;
pixel_t *load_bmp(const char *filename,
bitmap_info_header_t *bitmapInfoHeader)
{
FILE *filePtr = fopen(filename, "rb");
if (filePtr == NULL) {
perror("fopen()");
return NULL;
}
bmpfile_magic_t mag;
if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
fclose(filePtr);
return NULL;
}
// verify that this is a bmp file by check bitmap id
// warning: dereferencing type-punned pointer will break
// strict-aliasing rules [-Wstrict-aliasing]
if (*((uint16_t*)mag.magic) != 0x4D42) {
fprintf(stderr, "Not a BMP file: magic=%c%c\n",
mag.magic[0], mag.magic[1]);
fclose(filePtr);
return NULL;
}
bmpfile_header_t bitmapFileHeader; // our bitmap file header
// read the bitmap file header
if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t),
1, filePtr) != 1) {
fclose(filePtr);
return NULL;
}
// read the bitmap info header
if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t),
1, filePtr) != 1) {
fclose(filePtr);
return NULL;
}
if (bitmapInfoHeader->compress_type != 0)
fprintf(stderr, "Warning, compression is not supported.\n");
// move file point to the beginning of bitmap data
if (fseek(filePtr, bitmapFileHeader.bmp_offset, SEEK_SET)) {
fclose(filePtr);
return NULL;
}
// allocate enough memory for the bitmap image data
pixel_t *bitmapImage = malloc(bitmapInfoHeader->bmp_bytesz *
sizeof(pixel_t));
// verify memory allocation
if (bitmapImage == NULL) {
fclose(filePtr);
return NULL;
}
// read in the bitmap image data
size_t pad, count=0;
unsigned char c;
pad = 4*ceil(bitmapInfoHeader->bitspp*bitmapInfoHeader->width/32.) - bitmapInfoHeader->width;
for(size_t i=0; i<bitmapInfoHeader->height; i++){
for(size_t j=0; j<bitmapInfoHeader->width; j++){
if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
fclose(filePtr);
return NULL;
}
bitmapImage[count++] = (pixel_t) c;
}
fseek(filePtr, pad, SEEK_CUR);
}
// If we were using unsigned char as pixel_t, then:
// fread(bitmapImage, 1, bitmapInfoHeader->bmp_bytesz, filePtr);
// close file and return bitmap image data
fclose(filePtr);
return bitmapImage;
}
// Return: true on error.
bool save_bmp(const char *filename, const bitmap_info_header_t *bmp_ih,
const pixel_t *data)
{
FILE* filePtr = fopen(filename, "wb");
if (filePtr == NULL)
return true;
bmpfile_magic_t mag = {
{
0x42, 0x4d}};
if (fwrite(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
fclose(filePtr);
return true;
}
const uint32_t offset = sizeof(bmpfile_magic_t) +
sizeof(bmpfile_header_t) +
sizeof(bitmap_info_header_t) +
((1U << bmp_ih->bitspp) * 4);
const bmpfile_header_t bmp_fh = {
.filesz = offset + bmp_ih->bmp_bytesz,
.creator1 = 0,
.creator2 = 0,
.bmp_offset = offset
};
if (fwrite(&bmp_fh, sizeof(bmpfile_header_t), 1, filePtr) != 1) {
fclose(filePtr);
return true;
}
if (fwrite(bmp_ih, sizeof(bitmap_info_header_t), 1, filePtr) != 1) {
fclose(filePtr);
return true;
}
// Palette
for (size_t i = 0; i < (1U << bmp_ih->bitspp); i