这个是主函数
#define PI 3.1415926
#define EDGE_VALUE 235
#define NON_EDGE_VALUE 16
typedef unsigned char uint8;
enum {HOR = 0, VER, POS45, NEG45};
const int row_table[4] = {0, 1, 1, 1};
const int col_table[4] = {1, 0, 1, -1};
void CannyEdge(const uint8* img, int width, int height, int pitch,
int low_thr, int high_thr, uint8 *edge)
{
uint8 *lpf_img = new uint8[pitch * height];
int *grad = new int[pitch * height];
int *max_grad = new int[pitch * height];
GaussianFilter(img, width, height, pitch, lpf_img);
CalcGrad(lpf_img, width, height, pitch, grad);
SuppressNonMax(grad, width, height, pitch, max_grad);
GetEdge(max_grad, width, height, pitch, low_thr, high_thr, edge);
delete [] lpf_img;
delete [] grad;
delete [] max_grad;
}
对图像做高斯低通滤波,这里用一个7tap的滤波器,滤波器尺寸可以根据需要做调整
二维高斯滤波可以分解成两个一维的高斯滤波,先做水平滤波,再做垂直滤波
void GaussianFilter(const uint8* srcp, int width, int height, int pitch, uint8 *dstp)
{
int filter[7] = {1, 6, 19, 27, 19, 6, 1};
int weight_sum = 79;
int *fltp = filter + 3;
uint8 *midp = new uint8[pitch * height];
memcpy(dstp, srcp, pitch * height);
memcpy(midp, srcp, pitch * height);
// hor filter
for(int ver = 0; ver < height; ++ver) {
for(int hor = radius; hor < width - radius; ++hor) {
int sum = 0;
for(int x = -radius; x <= radius; ++x) {
sum += srcp[ver * pitch + hor + x] * fltp[x];
}
midp[ver * pitch + hor] = sum / weight_sum;
}
}
// ver filter
for(int ver = radius; ver < height - radius; ++ver) {
for(int hor = 0; hor < width; ++hor) {
int sum = 0;
for(int y = -radius; y <= radius; ++y) {
sum += midp[(ver + y) * pitch + hor] * fltp[y];
}
dstp[ver * pitch + hor] = sum / weight_sum;
}
}
delete [] midp;
}
计算梯度值和法线方向
void CalcGrad(uint8 *img, int width, int height, int pitch, int *grad)
{
const double tan225 = tan(PI / 8);
const double tan675 = tan(PI * 3 / 8);
memset((void*)grad, 0, pitch * height * sizeof(int));
for(int ver = 0; ver < height - 1; ++ver) {
for(int hor = 0; hor < width - 1; ++hor) {
int offset = ver * pitch + hor;
int diff_x = img[offset + 1] - img[offset];
int diff_y = img[offset + pitch] - img[offset];
int dir;
double tanv = diff_y / (diff_x + 0.0001);
if(tanv < tan225 && tanv >= -tan225) dir = HOR;
else if(tanv < tan675 && tanv >= tan225) dir = POS45;
else if(tanv < -tan675 || tanv >= tan675) dir = VER;
else dir = NEG45;
int gradient = (int)sqrt(double(diff_x * diff_x + diff_y * diff_y) );
grad[offset] = (gradient << 2) + dir; // 低2位放方向,高位放梯度值
}
}
}
非最大值抑制
void SuppressNonMax(int *grad, int width, int height, int pitch, int *max_grad)
{
memset((void*)max_grad, 0, pitch * height * sizeof(int));
for(int ver = 1; ver < height - 1; ++ver) {
for(int hor = 1; hor < width - 1; ++hor) {
int offset = ver * pitch + hor;
int dir = grad[offset] & 0x3;
int row = row_table[dir];
int col = col_table[dir];
int offset1 = (ver + row) * pitch + hor + col;
int offset2 = (ver - row) * pitch + hor - col;
int grad0 = grad[offset] >> 2;
int grad1 = grad[offset1] >> 2;
int grad2 = grad[offset2] >> 2;
if(grad0 >= grad1 && grad0 >= grad2) {
max_grad[offset] = grad0;
}
}
}
}
利用双阈值得到边缘,连接线
阈值可以根据需要进行设置,这里设置low_thr = 7,high_thr = 14
void GetEdge(int *max_grad, int width, int height, int pitch,
int low_thres, int high_thres, uint8 *edge)
{
memset(edge, NON_EDGE_VALUE, pitch * height);
for(int ver = 1; ver < height-1; ++ver) {
for(int hor = 1; hor < width-1; ++hor) {
int offset = ver * pitch + hor;
if(max_grad[offset] >= high_thres) {
edge[offset] = EDGE_VALUE;
}
else if(max_grad[offset] >= low_thres) {
for(int row = -1; row <= 1; ++row) {
for(int col = -1; col <= 1; ++col) {
if(max_grad[(ver + row) * pitch + hor + col] >= high_thres) {
edge[offset] = EDGE_VALUE;
break;
}
}
}
}
}
}
}
左边是原图,右边是得到的边缘