边缘检测

学习

Posted by simplex on June 7, 2021

1. 通过分析说明边缘宽度与模板尺寸间的关系。

模板尺寸越大,边缘越宽;线宽 = 模板大小 - 1

对于真实边缘位置周围的像素值一般都有阶跃(包括阶跃和屋顶状),所以对于真实边缘处两个像素点,如果在模板内,则就有边缘强度,即有宽度。问题就变成了模板的长度下,有几种连续2像素的放法。所以线宽 = 模板大小 - 1。

2. 图像平滑模板的各系数之和1且无负数,边缘检测算子的模板有什么特点?边缘锐化算子的模板有 什么特点?

边缘检测算子:各系数之和为0,且有负数

锐化算子:各系数之和为1,有负数

3. 梯度算子中 \(\sqrt{\Delta_x^2 + \Delta_y^2}\) 的最大值是多少?如何使用查找表来替代开方运算?图像中绝大多数像素 的梯度值是非常小的,如何根据这个特点来设计局部查表法?

\[360.624 \approx360\]

设计LUT[360*360],每一个下标就是\({\Delta_x^2 + \Delta_y^2}\)的值,元素的值就是开方的值

LUT开比较小,比如1024,如果\({\Delta_x^2 + \Delta_y^2} \ge1024\)则使用sqrt函数

关于sqrt的问题,在网上看到一种快速实现的方法,然后复制先来与自带的方法做了比较

float fSqrtByCarmack(float number)
{
	int i;
	float x2, y;
	const float threehalfs = 1.5F;

	x2 = number * 0.5F;
	y = number;
	i = *(int *)&y;            
	i = 0x5f375a86 - (i >> 1); 
	y = *(float *)&i;
	y = y * (threehalfs - (x2 * y * y));   
	
	return 1.0f / y;
}

void sqrt_cmp()
{
	float a;
	time_t start;
	int cishu = 1000000;

	cin >> a >> cishu;
	start = clock();
	for (int i = 0; i < cishu; i++)
	{
		fSqrtByCarmack(a);
	}
	cout << "fSqrtByCarmack: " << clock() - start << endl;
	start = clock();
	for (int i = 0; i < cishu; i++)
	{
		sqrtf(a);
	}
    start = clock();
	cout << "sqrtf: " << clock() - start << endl;	
	
/*
平台配置x86 debug
结果:
fSqrtByCarmack: 33
sqrtf: 27
结论:
自带的更快,原因可能跟硬件有关
*/

}

4. Prewitt,Robinson,Kirsch算子计算得到的边缘强度值,哪个更大?哪个更小?

单从系数上看:

Prewitt < Robinson < Kirsch

在特定条件下有差别

5. 用C/C++编程实现H0401Gry.bmp中的米粒边缘检测,一定要采用一阶微分算子和二阶微分算子沈俊算子结合的方法。

梯度+沈俊 sobel + 沈俊 laplacian+梯度
H0401js_gd H0401_sobel H0401la_gd10

6. 用C/C++编程实现H0402Gry.bmp,H0403Gry.bmp,H0404Gry.bmp中的文本定位,一定要使用变 分辨率、边缘强度、积分图。

H0402Gry.bmp H0403Gry.bmp H0404Gry.bmp
H0402_det H0403_det H0404_det
下面使用canny算子 上面使用sobel+shenjun  
H0402_det_canny H0404_det_canny H0404_det_canny
sobel算子    
H0402_det_sb H0403_det_sb H0404_det_sb
sobel处理后的图像(4倍缩小之后) canny shenjun+sobel
sobel_num canny_num temp
  • 观察到管子的反光很强,可能会造成梯度非常大,边缘强度非常大,削弱检测能力;
  • 处理的图像,看起来shenjun+sobel的图效果非常好,那根大钢管都快没有了,反观canny还有那么大一根。不过最终结果做下来,canny的效果会好一点,一个像素一个像素地观察shenjun+sobel的图片可以发现,shenjun+sobel的那根大钢管里面,其实还是有很多离散点的,所以看起来感觉不错,其实效果不好。如果像canny一样,加一步去掉孤立的弱梯度点,可能效果会更上一层楼, 不过这个的弊端就是,原本shenjun+sobel的边缘就很窄,再加上这一步可能会导致边缘大规模丢失。
  • 同时实验发现,原先使用四块区域来做检测,三块区域也能达到相同的效果,即选取检测区域以及其左右两边,好处是,可以不用积分图,直接列积分就可以完成,节约了时间和空间,不过鲁棒性有一定的下降。

7. 学习并编程实现Canny算子,并用该算子检测H0401Gry.bmp中的米粒边缘。

H0401Gry_canny

  • 实验发现,这张图像本身的结构就很好,第一步高斯滤波甚至不需要,加了高斯滤波反而可能会降低效果。
  • 相比于sobel+shenjun,canny的边缘更加粗一些,因为canny有一步,将可能是边缘的弱梯度点当成边缘了。对于本张图来说,还有一些毛刺,这与高斯滤波的好坏有一定的关系,这是canny的缺点。

  • sobel+shenjun的方法,丢失边缘的可能性比canny要高一些,对于图像中一些比较集中地噪声,抑制效果比shenjun+sobel要好,比如说米粒内部,shenjun方法经常会有一条线,而canny没有,因为canny有一步特别将这种线去除了
  • canny的边更加连续,因为高斯模糊,和之后的双阈值法,以及通过滞后进行边缘跟踪,使得边缘的更加好。

  • canny参数很多,调参有一定的讲究,有好有坏

8. 看到模板的结构就能知道模板的效果和性能,是图像处理研究人员的基本素质。若使用下列模板分别 对一幅灰度图像进行卷积,会达到什么样的效果?请在模板的系数之和、系数的正负号等方面进行区 分。注意,(17)到(24)带有绝对值。可以自己编个小程序测试,也可以使用Photoshop验证。

\((1){1 \over{16}}\left[ \begin{matrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{matrix} \right]\)系数和=1,无负数,均值滤波,模糊效果tree1

\((2)\left[ \begin{matrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{matrix} \right]\) 系数和>1,无负数,模糊,并且使图像变亮tree2

\((3){1 \over 6}\left[ \begin{matrix} 1 & 0 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 0 \end{matrix} \right]\) 系数和>1,无负数, 均值滤波,模糊,更倾向于对右上边角的模糊tree3

\((4){1 \over 6}\left[ \begin{matrix} 1 & 1 & 0 \\ 1 & 1 & 0 \\ 1 & 1 & 0 \end{matrix} \right]\) 系数和=1,无负数,均值滤波,模糊,更倾向于对右侧边界的模糊tree4

\((5)\left[ \begin{matrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{matrix} \right]\) 系数和=0,有负数,Laplacian边缘检测算子,边缘检测tree5

\((6)\left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 8 & -1 \\ -1 & -1 & -1 \end{matrix} \right]\) 系数和=0,有负数, Laplacian边缘检测算子的负数,边缘检测tree6

\((7)\left[ \begin{matrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 0 & 0 \end{matrix} \right]\) 系数和=1,有负数, 边缘锐化,但会变暗tree7

\((8)\left[ \begin{matrix} 0 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 0 \end{matrix} \right]\) 系数和=0,有负数, Laplacian边缘检测算子tree8

\((9)\left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 9 & -1 \\ -1 & -1 & -1 \end{matrix} \right]\) 系数和=1,有负数, Laplacian锐化算子tree9

\((10)\left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 8 & -1 \\ -1 & -1 & 0 \end{matrix} \right]\) 系数和=1,有负数, 锐化算子, 但是对于左上角这种边角不敏感tree10

\((11)\left[ \begin{matrix} 0 & -1 & 0 \\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{matrix} \right]\) 系数和=1,有负数, Laplacian锐化算子tree11

\((12)\left[ \begin{matrix} 0 & 1 & 0 \\ 1 & -5 & 1 \\ 0 & 1 & 0 \end{matrix} \right]\)系数和=-1,有负数, 锐化,不过图像会变得非常暗tree12

\((13)\left[ \begin{matrix} -1 & -1 & -1 \\ -1 & 5 & 0 \\ 0 & -1 & 0 \end{matrix} \right]\) 系数和=0,有负数, 边缘检测算子,左侧边缘敏感程度低tree13

\((14)\left[ \begin{matrix} -1 & -1 & 0 \\ -1 & 6 & -1 \\ -1 & -1 & 0 \end{matrix} \right]\) 系数和=0,有负数, 边缘检测算子,左侧边缘敏感程度低tree14

\((15)\left[ \begin{matrix} 0 & 0 & 0 \\ -1 & 3 & -1 \\ 0 & 0 & 0 \end{matrix} \right]\) 系数和=1,有负数, 锐化算子, 行内进行锐化tree15

\((16)\left[ \begin{matrix} 0 & 0 & 0 \\ -1 & 2 & 0 \\ 0 & 0 & 0 \end{matrix} \right]\) 系数和=1,有负数, 锐化算子,只与左侧一个像素进行tree16

\((17)\left[ \begin{matrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{matrix} \right]\) 系数和=0,有负数, 梯度算子,计算检测横向的边缘tree17

\((18)\left[ \begin{matrix} 1 & 1 & 1 \\ 0 &0 & 0 \\ -1 & -1 & -1 \end{matrix} \right]\)系数和=0,有负数, 梯度算子,计算检测横向的边缘tree18

\((19)\left[ \begin{matrix} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -1 & -2 \end{matrix} \right]\) 系数和=0,有负数, 边缘算子,计算检测横向的边缘tree19

\((20)\left[ \begin{matrix} 1 & 2 & 0 \\ 0 & 0 & 0 \\ -1 & -1 & -1 \end{matrix} \right]\) 系数和=0,有负数, 检测右下角拐角tree20

\((21)\left[ \begin{matrix} 0 & 0 & -1 \\ 0 & 0 & 0 \\ 1 & 0 & 0 \end{matrix} \right]\) 系数和=0,有负数, 边缘检测算子,检测左上到右下的斜线tree21

\((22)\left[ \begin{matrix} 1 & 1 & -1 \\ 0 & 0 & 0 \\ 0 & -1 & 0 \end{matrix} \right]\) 系数和=0,有负数, 边缘检测算子tree22

\((23)\left[ \begin{matrix} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{matrix} \right]\) 系数和=0,有负数, sobel算子tree23

\((24)\left[ \begin{matrix} 1 & 0 & -1 \\ 2 & 0 & -2 \\ 1 & 0 & -1 \end{matrix} \right]\) 系数和=0,有负数, sobel算子tree24

代码

出现的问题

  • 之前用了类编程,一开始的时候,也花了很多时间构思这个类,最后再开始写,不过后面随着作业的增加,这个类变得极度臃肿,被迫添加一些方法,逐渐脱离自己的最初的构想。

解决

  • 痛定思痛,在前一次作业的基础上,脱离了将核心方法与调用,IO,内存申请,操作解耦,之后的核心方法都写在另外的文件里。开了一个新的文件BMPCore.h/.cpp,来写核心方法,原本的bmpConverter来调用这些方法,以及完成IO,内存申请等的操作。

现在的调用堆栈

main.cpp调用homework.h中特定作业的题目的函数
homework.cpp调用bmpConverter.h的类来完成任务
bmpConverter.cpp调用BMPCore.h的方法来完成相应功能
    优点:结构更加明确,相较之前,bug变少了(可能是最近写代码状态不错),调试时间明显缩减
    缺点:修改参数会变得非常繁琐,因为文件变多了

链接

BMPCore.cpp bmpConverter.cpp

// main.cpp
int main()
{
	//hw4_grad1_grad2_shenjun();
	//hw4_num_dect();
	//test();
	//hw4_grad1_grad2_shenjun();
	return 0;
}
// homework.cpp


void hw4_grad1_grad2_shenjun()
{
	bmpConverter bmpCvt("./pic/H0401Gry.bmp");  
	bmpCvt.AEadgeDectGrad_SJ2d1c(0.5);
	bmpCvt.Img2Bmp("./pic/H0401js_gd.bmp");
	bmpConverter bmpCvt2("./pic/H0401Gry.bmp");  
	bmpCvt2.AEadgeDectLaplacain_Gd2d1c(0.5);
	bmpCvt2.Img2Bmp("./pic/H0401la_gd.bmp");
}

void test()
{
	bmpConverter bmpCvt("./pic/treeGry.bmp");  
	bmpCvt.test();
	//bmpCvt.Img2Bmp("./pic/tree.bmp");
}

void hw4_num_dect()
{
	// 缩小
	// 得到边缘强度
	// 积分图
	// 得到最大强度区域
	// 放大
	bmpConverter bmpCvt("./pic/H0402Gry.bmp");  
	bmpCvt.num_dect();
	bmpCvt.Img2Bmp("./pic/H0402_det_sb.bmp");
	bmpConverter bmpCvt1("./pic/H0403Gry.bmp");  
	bmpCvt1.num_dect();
	bmpCvt1.Img2Bmp("./pic/H0403_det_sb.bmp");
	bmpConverter bmpCvt2("./pic/H0404Gry.bmp");  
	bmpCvt2.num_dect();
	bmpCvt2.Img2Bmp("./pic/H0404_det_sb.bmp");

}

void hw4_canny()
{
	bmpConverter bmpCvt("./pic/H0401Gry.bmp");  
	bmpCvt.AEadgeCanny2d1c(0.4, 6, 125, 75);
	bmpCvt.Img2Bmp("./pic/H0401_canny.bmp");
}

bmpConverter.cpp
//bmpConverter.cpp

void getEdg(BYTE * pSrc, int width, int height, BYTE threshold)
{
	BYTE * pCur, *pEnd;
	for (pCur = pSrc, pEnd = pSrc + width * height; pCur < pEnd; pCur++)
	{
		*pCur = *pCur > threshold ? 255 : 0;
	}
}
// 边缘检测,一阶梯度+沈俊算子
Img bmpConverter::AEadgeDectGrad_SJ2d1c(double a0, bool inplace)
{
	if (!pImg)return Img();
	BYTE *pDstImg, *pTmpImg;
	BYTE threshold;
	int i;

	threshold = 10;
	pDstImg = new BYTE[width * height];
	pTmpImg = new BYTE[width * height];
	a0 = (max)(0.01, (min)(a0, 0.99));

	AEDOpShenJun4_8bit(pImg, pTmpImg, width, height, a0, pDstImg);
	AEDOpGrad1_8bit(pImg, width, height, pTmpImg);
	getEdg(pTmpImg, width, height, threshold);

	for (i = 0; i < width * height; i++)
	{
		pDstImg[i] = pDstImg[i] & pTmpImg[i];
	}

	delete pTmpImg;
	if (inplace)
	{
		delete pImg;
		pImg = pDstImg;
		return Img();
	}
	else
	{
		return Img(pDstImg, width, height);
	}

	return Img();
}

Img bmpConverter::AEadgeDectLaplacain_Gd2d1c(double a0, BYTE threshold, bool inplace)
{
	if (!pImg)return Img();
	BYTE *pDstImg, *pTmpImg;
	int i;

	pDstImg = new BYTE[width * height];
	pTmpImg = new BYTE[width * height];
	a0 = (min)(0.01, (max)(a0, 0.99));

	AEDOpShenJun4_8bit(pImg, pTmpImg, width, height, a0, pDstImg);
	AEDOpGrad1_8bit(pImg, width, height, pTmpImg);
	getEdg(pTmpImg, width, height, threshold);

	for (i = 0; i < width * height; i++)
	{
		pDstImg[i] = pDstImg[i] & pTmpImg[i];
	}

	delete pTmpImg;
	if (inplace)
	{
		delete pImg;
		pImg = pDstImg;
		return Img();
	}
	else
	{
		return Img(pDstImg, width, height);
	}

	return Img();
}


Img bmpConverter::AEadgeDectsobel_ShenJun2d1c(double a0, BYTE threshold, bool inplace)
{
	if (!pImg)return Img();
	BYTE *pDstImg, *pTmpImg;
	int i;

	pDstImg = new BYTE[width * height];
	pTmpImg = new BYTE[width * height];
	a0 = (max)(0.01, (min)(a0, 0.99));

	AEDOpShenJun4_8bit(pImg, pTmpImg, width, height, a0, pDstImg);
	AEDOpSobel_8bit(pImg, width, height, pTmpImg);

	getEdg(pTmpImg, width, height, threshold);
	for (i = 0; i < width * height; i++)
	{
		pDstImg[i] = pDstImg[i] & pTmpImg[i];
	}


	delete []pTmpImg;
	if (inplace)
	{
		delete pImg;
		pImg = pDstImg;
		return Img();
	}
	else
	{
		return Img(pDstImg, width, height);
	}
}
// 缩小到1/16,使用均值,已知宽高
void bmpConverter::shrink16(BYTE *pSrcImg, int width, int height, BYTE *pDstImg)
{
	int sum;
	BYTE *pCur, *pDes;
	int x, y;

	for (y = 0, pCur = pSrcImg, pDes = pDstImg; y < height; y += 4)
	{
		for (x = 0; x < width; x += 4)
		{
			// 求和
			//sum = 0, y = 0;
			//for (y = 0; y < 4 * width; y += width)
			//{
			//	sum += pCur[y];
			//	sum += pCur[y + 1];
			//	sum += pCur[y + 2];
			//	sum += pCur[y + 3];
			//}
			//*pDes++ = sum / 16;
			*pDes++ = *pCur;
			pCur += 4;
		}
		pCur += 3 * width;
	}

}

void getMaxAreaCalGraph(int *pSum, int m, int n, int width, int height, int &mx, int &my)
{
	int *pY1, *pY2, *pY3;
	int halfx, halfy, w;
	int y, x1, x2;
	int curSum, befSum, nxtSum, maxSum, c, tempSum;
	int rowSum[1024];

	// 初始化
	m = m | 1; 
	n = n | 1; 
	halfx = m / 2; 
	halfy = n / 2; 
	w = width + 1;
	c = (1 << 23) / (m * n); 
	mx = 0, my = 0;
	maxSum = -1e9;
	for (x1 = width - m; x1 <= width + m; x1++)rowSum[x1] = 0;

	// 最大值区域求取
	//for (y = 2 * n, pY3 = pSum, pY1 = pY3 + w * n, pY2 = pY1 + w * n;	y < height;	y++, pY1 += w, pY2 += w)
	for (y = 2 * n, pY3 = pSum, pY1 = pY3 + w * n, pY2 = pY1 + w * n;	y < height;	y++, pY1 += w, pY2 += w, pY3 += w)
	{
		befSum = 0, curSum = 0;
		for (x1 = 0, x2 = m; x2 <= width; x1++, x2++)
		{
			rowSum[x1] = pY1[x1] - pY1[x2] - pY2[x1] + pY2[x2];
		}
		for (x1 = m; x1 <= width; x1++)
		{
			//tempSum = rowSum[x1] - rowSum[x1 - m] - rowSum[x1 + m];
			tempSum = rowSum[x1] - rowSum[x1 - m] - rowSum[x1 + m] -   (pY3[x1] - pY3[x1 + w] - pY1[x1] + pY1[x1 + w]);
			if (tempSum > maxSum)
			{
				maxSum = tempSum;
				mx = x1, my = y;

			}
		}
	}
	return;
}

void drawSquare(BYTE *pSrcImg, int width, int height, int mx, int my, int w, int h)
{
	BYTE *pCur, *pY, *pX;

	pCur = pSrcImg;
	// 上横线
	for (pCur = pSrcImg + width * my + mx, pX = pCur + w; pCur < pX; ) *pCur++ = 255;
	//下横线
	for (pCur = pSrcImg + width * (my + h) + mx, pX = pCur + w; pCur < pX; ) *pCur++ = 255;
	// 左竖线
	for (pCur = pSrcImg + width * my + mx, pY = pCur + h * width; pCur < pY; pCur += width) *pCur = 255;
	// 右竖线
	for (pCur = pSrcImg + width * (my)+ mx + w, pY = pCur + h * width; pCur <= pY; pCur += width) *pCur = 255;
}
void bmpConverter::num_dect()
{
// 缩小
// 得到边缘强度
// 积分图
// 得到最大强度区域
// 放大

	BYTE *pDstImg, *pTmpImg, *pSrcImg, *pOrgImg;
	int *pCalGraph;
	int winSize, mx, my;
	BYTE threshold;
	double a0;

	// 初始化
	a0 = 0.1;
	threshold = 80;
	winSize = 25;
	pCalGraph = new int[(width / 4 + (winSize | 1)) * (height / 4 + (winSize | 1))];
	pSrcImg = new BYTE[width * height / 16];
	//BYTE * pSbImg = new BYTE[width * height / 16];
	pOrgImg = pImg;

	// 缩小图片
	shrink16(pImg, width, height, pSrcImg);
	width /= 4, height /= 4;
	pImg = pSrcImg;
	// 得到边缘数目
	AEadgeDectsobel_ShenJun2d1c(a0, threshold);
	//AEadgeCanny2d1c(1, 5, 60, 80);
	//AEDOpSobel_8bit(pImg, width, height, pSbImg);
	//delete[] pSrcImg;
	//pImg = pSbImg;
	Img2Bmp("./pic/temp.bmp");
	// 获得边缘数目的积分图
	AgetCalGraph(pImg, width, height, 1, 1, pCalGraph);
	// 原图像100x100的区域
	// 4x4缩小之后,25x25
	// 寻找计算图中值最大区域
	getMaxAreaCalGraph(pCalGraph, winSize, winSize, width, height, mx, my);
	//drawSquare(pImg, width, height, mx, my - winSize, winSize, winSize);
	mx *= 4, my = my * 4, width = width * 4, height = height * 4, winSize = winSize * 4;
	pSrcImg = pImg;
	pImg = pOrgImg;
	// 画出框框
	drawSquare(pImg, width, height, mx, my - winSize, winSize, winSize);
	delete[] pCalGraph;
	delete[] pSrcImg;
}

// canny算子边缘检测
Img bmpConverter::AEadgeCanny2d1c(double sigma, int Fsize, int uBound, int lBound, bool inplace)
{
	BYTE *pDstImg, *pTmpImg;
	int *pFilter, *dx, *dy;

	// 初始化
	sigma = 0.4;
	Fsize = 5;
	uBound = 125;
	lBound = 75;
	pDstImg = new BYTE[width * height];
	dx = new int[width * height];
	dy = new int[width * height];
	pFilter = new int[width * height];
	pTmpImg = new BYTE[width * height];

	AEDOpCanny_8bit(
		pImg,
		pTmpImg,
		dx, dy,
		width, height,
		pFilter, sigma, Fsize,
		uBound, lBound,
		pDstImg
	);

	// 结束
	delete[] pTmpImg;
	delete[] pFilter;
	delete[] dx;
	delete[] dy;
	if (inplace)
	{
		delete[] pImg;
		pImg = pDstImg;
		return Img();
	}
	else
	{
		return Img(pDstImg, width, height);
	}
}
void bmpConverter::test()
{

// 缩小
// 得到边缘强度
// 积分图
// 得到最大强度区域
// 放大

	BYTE *pDstImg, *pTmpImg, *pSrcImg, *pOrgImg, *pTansImg;
	int *dx, *dy;
	int Fsize, uBound, lBound;
	BYTE threshold;
	double sigma;

	pDstImg = new BYTE[height * width];
	double pFilter[]{
		1 , 1 , 1 ,
		1 , -8 , 1 ,
		1 , 1 , 1 , };
	for (int i = 0; i < 9; i++)pFilter[i] *= -1;
	AConv2d3_8bit(pImg, width, height, pFilter, pDstImg);
	pImg = pDstImg;
	Img2Bmp("./pic/tree6.bmp");

}

BMPCore.cpp  

// 邻域运算,边缘检测梯度算子4邻域8bit图像
void AEDOpGrad1_8bit(BYTE *pSrc, int width, int height, BYTE *pDst)
{
	BYTE *pCur, *pDes;
	int dx, dy;
	int x, y, i, m;
	BYTE LUT[1024];  // 局部查表

	for (i = 0; i < 1024; i++)LUT[i] = (BYTE)sqrt(double(i));

	for (y = 0, pCur = pSrc, pDes = pDst; y < height - 1; y++)
	{
		for (x = 0; x < width - 1; x++, pCur++)
		{
			dx = *pCur - pCur[1];
			dy = *pCur - pCur[width];
			m = dx * dx + dy * dy;
			*(pDes++) = m < 1024 ? LUT[m] : (min)(255, (int)(sqrt((double) m)));
		}
		*(pDes++) = 0;  // 尾列不做,边缘强度赋0
		pCur++;
	}
	memset(pDes, 0, width);  // 尾行不做,边缘强度赋0
	return;
}
// 邻域运算,边缘检测索贝尔算子4邻域8bit图像
void AEDOpSobel_8bit(BYTE *pGryImg, int width, int height, BYTE *pSbImg)
{
	BYTE *pGry, *pSb;
	int dx, dy;
	int x, y;

	memset(pSbImg, 0, width); //首行不做,边缘强度赋0
	for (y = 1, pGry = pGryImg + width, pSb = pSbImg + width; y < height - 1; y++)
	{
		*(pSb++) = 0;  //首列不做,边缘强度赋0
		pGry++;
		for (x = 1; x < width - 1; x++, pGry++)
		{
			//求dx
			dx = *(pGry - 1 - width) + (*(pGry - 1) * 2) + *(pGry - 1 + width);
			dx -= *(pGry + 1 - width) + (*(pGry + 1) * 2) + *(pGry + 1 + width);
			//求dy
			dy = *(pGry - width - 1) + (*(pGry - width) * 2) + *(pGry - width + 1);
			dy -= *(pGry + width - 1) + (*(pGry + width) * 2) + *(pGry + width + 1);
			//结果
			*(pSb++) = min(255, abs(dx) + abs(dy));
		}
		*(pSb++) = 0; //尾列不做,边缘强度赋0
		pGry++;
	}
	memset(pSb, 0, width); //尾行不做,边缘强度赋0
	return;
}


// 邻域运算,边缘检测Laplacian算子4邻域8bit图像
void AEDOpLaplacian4_8bit(BYTE *pSrc, BYTE *pTmp, int width, int height, BYTE *pDst)
{
	BYTE *pCur, *pDes;
	int grad;
	int x, y;

	memset(pTmp, 0, width);  // 首行不做,边缘强度赋0
	for (y = 1, pCur = pSrc + width, pDes = pTmp + width; y < height - 1; y++)
	{
		*(pDes++) = 0;  // 首列不做,边缘强度赋0
		pCur++;
		for (x = 1; x < width - 1; x++, pCur++)
		{
			// grad更新
			grad = -((int)*pCur) * 4;
			grad += pCur[-width];
			grad += pCur[-1];
			grad += pCur[+1];
			grad += pCur[+width];
			*(pDes++) = grad > 0;
		}
		*(pDes++) = 0; //尾列不做,边缘强度赋0
		pCur++;
	}
	memset(pDes, 0, width); //尾行不做,边缘强度赋0

	pDes = pDst + width, pCur = pTmp + width;
	memset(pDst, 0, width);
	for (y = 1; y < height - 1; y++)
	{
		*pDes++ = 0, pCur++;
		for (x = 1; x < width - 1; x++, pCur++)
		{
			if (*pCur && (!pCur[-1] || !pCur[1] || !pCur[-width] || !pCur[width]))*pDes++ = 255;
			else *pDes++ = 0;
		}
		*pDes++ = 0, pCur++;
	}
	memset(pDes, 0, width);
	return;
}


// 邻域运算,边缘检测沈俊算子4邻域8bit图像
void AEDOpShenJun4_8bit(
	BYTE *pSrcImg, //原始灰度图像 
	BYTE *pTmpImg, //辅助图像
	int width, int height,
	double a0,
	BYTE *pDstImg
)
{
	BYTE * pCur, * pDes, * pEnd;
	int LUT[511], *A;
	int x, y, i, pre;
	// 初始化
	A = LUT + 255;
	for (A[0] = 0, i = 1; i < 256; i++)  // 向上取整
	{
		A[i] = a0 * i + 0.5;	
		A[-i] = -a0 * i - 0.5;  
	}
	// 差分
	// 横向
	for (y = 0, pCur = pSrcImg, pDes = pTmpImg; y < height; y++)
	{
		pre = *pDes++ = *pCur++;
		// 从左到右
		for (x = 1; x < width; x++)
		{
			pre = *pDes++ = pre + A[*pCur++ - pre];
		}
		// 从右到左
		pDes--;
		for (x = 1; x < width; x++)
		{
			pDes--;
			pre = *pDes = pre + A[*pDes - pre];
		}
		pDes += width;
	}
	// 纵向
	for (x = 0, pDes=pTmpImg; x < width; x++)
	{
		// 从上到下
		pDes = pTmpImg + x, pre = *pDes;
		for (y = 1; y < height; y++)
		{
			pDes += width;
			pre = *pDes = pre + A[*pDes - pre];
		}
		// 从下到上
		for (y = 1; y < height; y++)
		{
			pDes -= width;
			pre = *pDes = pre + A[*pDes - pre];
		}
	}


	// 求二阶梯度的符号
	for (pCur = pTmpImg, pEnd = pTmpImg + width * height, pDes = pSrcImg; pCur<pEnd; pDes++)
	{
		*(pCur++) = (*pCur <= *pDes);
	}

	// 求边缘
	pDes = pDstImg + width, pCur = pTmpImg + width;
	memset(pDstImg, 0, width);
	for (y = 1; y < height - 1; y++)
	{
		*pDes++ = 0, pCur++;
		for (x = 1; x < width - 1; x++, pCur++)
		{
			if (!*pCur && (pCur[-1] ||pCur[1] || pCur[-width] || pCur[width]))*pDes++ = 255;
			else *pDes++ = 0;
		}
		*pDes++ = 0, pCur++;
	}
#ifdef DEBUG
	//printf("求边缘ok\n");
#endif // DEBUG
	memset(pDes, 0, width);
#ifdef DEBUG
	//printf("return ok\n");
#endif // DEBUG

	return;
}

void AGuassFilter1d_8bit(BYTE * pSrc, int width, int height, int * pFilter, int m, BYTE * pDes)
{
	int offset = 17;
	int halfm, y, x, f, sum = 0;
	BYTE  *pCur, *pRes, *pY;

	halfm = m / 2;

	for (y = 0, pY = pSrc, pRes = pDes; y < height; y++, pY += width)
	{
		// 处理左侧
		for (x = 0, sum = 0; x < halfm; x++, sum = 0)
		{
			for (f = halfm - x, pCur = pY; f < m; f++)
			{
				sum += pFilter[f] * *pCur++;
			}
			sum >>= offset;
			*pRes++ = sum;
		}
		// 处理中间
		for (x = halfm, sum = 0; x < width - halfm; x++, sum = 0)
		{
			for (f = 0, pCur = pY + x - halfm; f < m; f++)
			{
				sum += pFilter[f] * *pCur++;
			}
			sum >>= offset;
			*pRes++ = sum;
		}
		// 处理右侧
		for (x = width - halfm, sum = 0; x < width; x++, sum = 0)
		{
			for (f = 0, pCur = pY + x - halfm; pCur < pY + width; f++)
			{
				sum += pFilter[f] * *pCur++;
			}
			sum >>= offset;
			*pRes++ = sum;
		}
	}
}

void Transpose(BYTE * pSrc, int w, int h, BYTE * pDes)
{
	BYTE * pX, *pCur;
	int x, y;

	pCur = pDes;

	for (x = 0; x < w; x++)
	{
		for (y = 0, pX = pSrc + x; y < h; y++, pX += w)
		{
			*pCur++ = *pX;
		}
	}

	return;
}

void getGuassFilter_8bit(double dev, int m, int * pFilter, int offset)
{
	int halfm;
	double sum;
	double dF[1024];

	halfm = m / 2;
	sum = dF[halfm] = 1;

	for (int i = 1; i <= halfm; i++)
	{
		dF[halfm + i] = dF[halfm - i] = exp(-i * i / 2.0 / dev / dev);
		sum += dF[halfm + i] + dF[halfm + i];
	}
	for (int i = 0; i <= halfm; i++)
	{
		pFilter[halfm + i] = pFilter[halfm - i] = (1 << offset) * dF[halfm + i] / sum;
	}
}

void AEDOpSobe4Canny(BYTE *pGryImg, int width, int height, int *pDX, int *pDY, BYTE *pSbImg)
{
	BYTE *pGry, *pSb;
	int *pDx, *pDy;
	int dx, dy;
	int x, y;

	pDx = pDX;
	pDy = pDY;
	memset(pSbImg, 0, width); //首行不做,边缘强度赋0
	for (y = 1, pGry = pGryImg + width, pSb = pSbImg + width; y < height - 1; y++)
	{
		*(pSb++) = 0;  //首列不做,边缘强度赋0
		*(pDx++) = 0;
		*(pDy++) = 0;
		pGry++;
		for (x = 1; x < width - 1; x++, pGry++)
		{
			//求dx
			dx = *(pGry - 1 - width) + (*(pGry - 1) * 2) + *(pGry - 1 + width);
			dx -= *(pGry + 1 - width) + (*(pGry + 1) * 2) + *(pGry + 1 + width);
			//求dy
			dy = *(pGry - width - 1) + (*(pGry - width) * 2) + *(pGry - width + 1);
			dy -= *(pGry + width - 1) + (*(pGry + width) * 2) + *(pGry + width + 1);
			//结果
			*(pSb++) = min(255, abs(dx) + abs(dy));
			*(pDx++) = dx;
			*(pDy++) = dy;

		}
		*(pSb++) = 0; //尾列不做,边缘强度赋0
		*(pDx++) = 0;
		*(pDy++) = 0;
		pGry++;
	}
	memset(pSb, 0, width); //尾行不做,边缘强度赋0
	return;
}

// 对梯度幅值进行非极大值抑制
void GradMagThresholding(BYTE *pSrcImg, int width, int height, int *pDx, int *pDy)
{
	BYTE *pCur;
	int *pDX, *pDY;
	BYTE Cur;
	int DX, DY;
	int x, y;
	BYTE btan, binvtan;
	// 不做边缘
	for (y = 1, pCur = pSrcImg + width, pDX = pDx + width, pDY = pDy + width; y < height - 1; y++)
	{
		pCur++, pDX++, pDY++; 
		for (x = 1; x < width - 1; x++, pCur++, pDX++, pDY++)
		{
			Cur = *pCur, DX = *pDX, DY = *pDY;
			// 取周围的8个点作比较
			if (!Cur)continue; // 模为0,则继续
			if (!DX)  // 纵向梯度
			{
				if (Cur < pCur[-width] || Cur < pCur[width])*pCur = 0;
			}
			else if (!DY) // 纵向梯度
			{
				if (Cur < pCur[-1] || Cur < pCur[1])*pCur = 0;
			}
			else 
			{
				btan = DY / DX;
				if (!btan)  // abs(tan)小于1
				{
					binvtan = DX / DY;
					if (binvtan > 2 || binvtan < -2)
					{
						if (Cur < pCur[-1] || Cur < pCur[1])*pCur = 0;
					}
					else if (binvtan > 0)
					{
						if (Cur < pCur[-width - 1] || Cur < pCur[width + 1])*pCur = 0;
					}
					else
					{
						if (Cur < pCur[-width + 1] || Cur < pCur[width - 1])*pCur = 0;
					}
				}
				else
				{
					if (btan >= 2 || btan < -2)
					{
						if (Cur < pCur[-width] || Cur < pCur[width])*pCur = 0;
					}
					else if (btan > 0)
					{
						if (Cur < pCur[-width - 1] || Cur < pCur[width + 1])*pCur = 0;
					}
					else
					{
						if (Cur < pCur[-width + 1] || Cur < pCur[width - 1])*pCur = 0;
					}
				}
			}
		}
		pCur++, pDX++, pDY++;
	}
}


void DoubThre_EdgTrack(BYTE *pSrcImg, int width, int height, int uBound, int lBound)
{
	BYTE *pCur;
	int x, y;

	for (y = 1, pCur = pSrcImg + width; y < height - 1; y++)  // 不处理边缘
	{
		pCur++;
		for (x = 1; x < width - 1; x++)
		{
			BYTE &Cur = *pCur++;
			if (Cur > uBound)Cur = 255;  // 该点是强连接点
			else if (Cur < lBound)Cur = 0;  // 该点无连接
			else  // 该点时弱连接点
			{
				// 如果周围有强连接点,则弱->强
				if (pCur[-width - 1] == 255 || pCur[-width] == 255 || pCur[-width + 1] == 255 ||
					pCur[-1] == 255 || pCur[1] > uBound ||
					pCur[width - 1] > uBound || pCur[width] > uBound || pCur[width + 1] > uBound)
				{
					Cur = 255;
				}
				else Cur = 0;  // 否则弱->无
			}
		}
		pCur++;
	}
}

void AEDOpCanny_8bit(
	BYTE *pSrcImg,							// 原图像
	BYTE * pTmpImg,		
	int *dx, int *dy,						// sobel算子处理得到的梯度
	int width, int height,					// 图像宽高
	int *pFilter, double sigma, int Fsize,	// 高斯滤波器的参数
	int uBound, int lBound,					// 用双阈值算法检测和连接边缘
	BYTE * pDstImg							// 目标图像
)
{
	// 高斯平滑
	// sobel算子处理
	// 对梯度幅值进行非极大值抑制
	// 用双阈值算法检测和连接边缘


	// 初始化
	Fsize |= 1;
	getGuassFilter_8bit(sigma, Fsize, pFilter);

	// 施展高斯平滑
	AGuassFilter1d_8bit(pSrcImg, width, height, pFilter, Fsize, pDstImg);
	Transpose(pDstImg, width, height, pTmpImg);
	AGuassFilter1d_8bit(pTmpImg, height, width, pFilter, Fsize, pDstImg);
	Transpose(pDstImg, height, width, pTmpImg);
	
	// sobel算子处理
	AEDOpSobe4Canny(pTmpImg, width, height, dx, dy, pDstImg);

	// 对梯度幅值进行非极大值抑制
	GradMagThresholding(pDstImg, width, height, dx, dy);

	// 用双阈值算法检测和连接边缘
	DoubThre_EdgTrack(pDstImg, width, height, uBound, lBound);
}

void AConv2d3_8bit(BYTE * pSrcImg, int width, int height, double * pFilter, BYTE * pDstImg)
{
	BYTE *pCur, *pDes;
	int x, y, i, j;
	double *pF, sum;

	pCur = pSrcImg;
	pDes = pDstImg;
	pF = pFilter;

	for (x = 0; x < width; x++)*pDes++ = *pCur++;  // 边缘处理
	for (y = 1; y < height - 1; y++)
	{
		*pDes++ = *pCur++;
		for (x = 1; x < width - 1; x++, pCur++)
		{
			sum = pCur[-1 - width] * pF[0] + pCur[-width] * pF[1] + pCur[1 - width] * pF[2] +
				pCur[-1] * pF[3] + pCur[0] * pF[4] + pCur[1] * pF[5] +
				pCur[-1 + width] * pF[6] + pCur[width] * pF[7] + pCur[width + 1] * pF[8];
			//cout << pCur[-1 - width] * pF[0] << endl;
			//cout << sum << endl;
			*pDes++ = max(0, min(255, (sum)));
		}
		*pDes++ = *pCur++;

	}
	for (x = 0; x < width; x++)*pDes++ = *pCur++;  // 边缘处理

}