在图像中,角点是二维图像亮度变化剧烈的点或图像边缘曲线上曲率极大值的点,是一个重要的局部特征,它决定了图像中关键区域的形状,体现了图像中重要的特征信息,所以在目标识别、图像匹配、图像重构方面角点具有十分重要的意义。
角点检测的方法多种多样, 但大致上可以分为4类: 基于边缘特征的角点检测、基于灰度图像的角点检测、基于二值图像的角点检测和数学形态学。其中 Harris 角点检测算法是基于灰度图像的角点检测方法。
Harris 角点检测算法
Harris 角点检测算法是由Chris Harris 和MikeStephens 在1988 年提出, 该算法是在Moravec 算法的基础上发展起来的。Moravec 算法是研究图像中一个局部窗口在不同方向进行少量的偏移后, 考察窗口内图像亮度值的平均变化。
需要考虑下面三种情况:
(1)如果窗口内区域图像的亮度值恒定,那么所有不同方向的偏移几乎不发生变化;
(2)如果窗口跨越一条边,那么沿着这条边的偏移几乎不发生变化, 但是与边垂直的偏移会发生很大的变化;
(3)如果窗口包含一个孤立的点或者角点,那么所有不同方向的偏移会发生很大的变化。
在图像(u,v)处将图像窗口平移[x,y]产生灰度变化E(x,y)
其中
所以E就可以表示为:
Harris提出了对E进行变形:
用α,β表示矩阵M的特征值,这样会产生三种情况:A 如果α,β都很小,说明高斯windows中的图像接近平坦。 B 如果一个大一个小,则表示检测到边。 C 如果α,β都很大,那么表示检测到了角点。
定义角点响应函数:
k=0.04~0.06
R取决于M的特征值,对于角点R很大,平坦的区域R很小,边缘的R为负值。
void Harry(BYTE*BBuf,BYTE*GBuf,BYTE*RBuf) { //gausswidth:二维高斯窗口宽度 //sigma:高斯函数的方差 //size:非极大值抑制的邻域宽度 //thresh:最终确定角点所需的阈值 int i,j,m,n,size,thresh,gausswidth; double sigma; //输入四个参数 //CInput2 input; //input.m_gausswidth =5; //input.m_sigma =0.8; //input.m_size =5; //input.m_thresh =5000; //input.DoModal (); gausswidth=5;//input.m_gausswidth ; sigma=0.8;//input.m_sigma ; size=5;//input.m_size ; thresh=5000;//input.m_thresh ; unsigned char *lpSrc;//一个指向源、目的像素的移动指针 //LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL)m_hDIB); int cxDIB = 320;//(int) ::DIBWidth(lpDIB); // 图像宽度 int cyDIB = 240;//(int) ::DIBHeight(lpDIB); // 图像高度 //LPSTR lpDIBBits=::FindDIBBits (lpDIB); long lLineBytes = 320;//WIDTHBYTES(cxDIB * 8); // 计算灰度图像每行的字节数 //创建I、Ix、Ix2、Iy、Iy2、Ixy、cim、mx、corner数组 double *I=new double[cxDIB*cyDIB]; double *Ix=new double[cxDIB*cyDIB]; double *Ix2=new double[cxDIB*cyDIB]; double *Iy=new double[cxDIB*cyDIB]; double *Iy2=new double[cxDIB*cyDIB]; double *Ixy=new double[cxDIB*cyDIB]; double *cim=new double[cxDIB*cyDIB]; double *mx=new double[cxDIB*cyDIB]; bool*corner=new bool[cxDIB*cyDIB]; memset(corner, 0, cxDIB*cyDIB*sizeof(bool)); //定义宏以方便访问元素 #define I(ROW,COL) I[cxDIB*(ROW)+(COL)] #define Ix(ROW,COL) Ix[cxDIB*(ROW)+(COL)] #define Ix2(ROW,COL) Ix2[cxDIB*(ROW)+(COL)] #define Iy(ROW,COL) Iy[cxDIB*(ROW)+(COL)] #define Iy2(ROW,COL) Iy2[cxDIB*(ROW)+(COL)] #define Ixy(ROW,COL) Ixy[cxDIB*(ROW)+(COL)] #define cim(ROW,COL) cim[cxDIB*(ROW)+(COL)] #define mx(ROW,COL) mx[cxDIB*(ROW)+(COL)] #define corner(ROW,COL) corner[cxDIB*(ROW)+(COL)] //将图像灰度值复制到I中,这步很重要!想想为什么? for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) { lpSrc = (unsigned char*)BBuf + lLineBytes * (cyDIB - 1 - i) + j; //将256级灰度图像转化为double型 I(i,j)=double(*lpSrc); } } //-------------------------------------------------------------------------- // 第一步:利用差分算子对图像进行滤波http://www.cnifx.cn/ //-------------------------------------------------------------------------- //定义水平方向差分算子并求Ix double dx[9]={-1,0,1,-1,0,1,-1,0,1}; Ix=mbys(I,cxDIB,cyDIB,&dx[0],3,3); //定义垂直方向差分算子并求Iy double dy[9]={-1,-1,-1,0,0,0,1,1,1}; Iy=mbys(I,cxDIB,cyDIB,dy,3,3); //将中间结果Ix写入到文本文件以便后续分析 FILE *fp; fp=fopen("Ix.txt","w+"); for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) fprintf(fp,"%f ",Ix(i,j)); fprintf(fp,"\n"); } fp=fopen("Iy.txt","w+"); for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) fprintf(fp,"%f ",Iy(i,j)); fprintf(fp,"\n"); } //计算Ix2、Iy2、Ixy for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) { Ix2(i,j)=Ix(i,j)*Ix(i,j); Iy2(i,j)=Iy(i,j)*Iy(i,j); Ixy(i,j)=Ix(i,j)*Iy(i,j); } } //-------------------------------------------------------------------------- // 第二步:对Ix2/Iy2/Ixy进行高斯平滑,以去除噪声http://www.cnifx.cn/ //-------------------------------------------------------------------------- //本例中使用5×5的高斯模板 //计算模板参数 double *g=new double[gausswidth*gausswidth]; for(i=0;i<gausswidth;i++) for(j=0;j<gausswidth;j++) g[i*gausswidth+j]=exp(-((i-int(gausswidth/2))*(i-int(gausswidth/2))+(j-int(gausswidth/2))*(j-int(gausswidth/2)))/(2*sigma)); //归一化:使模板参数之和为1(其实此步可以省略) double total=0; for(i=0;i<gausswidth*gausswidth;i++) total+=g[i]; for(i=0;i<gausswidth;i++) for(j=0;j<gausswidth;j++) g[i*gausswidth+j]/=total; fp=fopen("g.txt","w+"); for(i = 0; i < gausswidth; i++) { for(j = 0; j < gausswidth; j++) fprintf(fp,"%f ",g[i*gausswidth+j]); fprintf(fp,"\n"); } //进行高斯平滑 Ix2=mbys(Ix2,cxDIB,cyDIB,g,gausswidth,gausswidth); Iy2=mbys(Iy2,cxDIB,cyDIB,g,gausswidth,gausswidth); Ixy=mbys(Ixy,cxDIB,cyDIB,g,gausswidth,gausswidth); //-------------------------------------------------------------------------- // 第三步:计算角点量http://www.cnifx.cn/ //-------------------------------------------------------------------------- //计算cim:即cornerness of image,我们把它称做'角点量' for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) { //注意:要在分母中加入一个极小量以防止除数为零溢出 cim(i,j) = (Ix2(i,j)*Iy2(i,j) - Ixy(i,j)*Ixy(i,j))/(Ix2(i,j) + Iy2(i,j) + 0.000001); } } fp=fopen("cim.txt","w+"); for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) fprintf(fp,"%f ",cim(i,j)); fprintf(fp,"\n"); } //-------------------------------------------------------------------------- // 第四步:进行局部非极大值抑制以获得最终角点http://www.cnifx.cn/ //-------------------------------------------------------------------------- //注意进行局部极大值抑制的思路 //const double size=7; double max; //对每个点在邻域内做极大值滤波:即将该点的值设为邻域中最大的那个值(跟中值滤波有点类似) for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) { max=-1000000; if(i>int(size/2) && i<cyDIB-int(size/2) && j>int(size/2) && j<cxDIB-int(size/2)) for(m=0;m<size;m++) { for(n=0;n<size;n++) { if(cim(i+m-int(size/2),j+n-int(size/2))>max) max=cim(i+m-int(size/2),j+n-int(size/2)); } } if(max>0) mx(i,j)=max; else mx(i,j)=0; } } fp=fopen("mx.txt","w+"); for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) fprintf(fp,"%f ",mx(i,j)); fprintf(fp,"\n"); } //最终确定角点 //const double thresh=4500; for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) { if(cim(i,j)==mx(i,j)) //首先取得局部极大值 if(mx(i,j)>thresh) //然后大于这个阈值 corner(i,j)=1; //满足上两个条件,才是角点! } } fp=fopen("corner.txt","w+"); for(i = 0; i < cyDIB; i++) { for(j = 0; j < cxDIB; j++) fprintf(fp,"%d ",corner(i,j)); fprintf(fp,"\n"); } //::GlobalUnlock((HGLOBAL) m_hDIB); // UpdateAllViews(NULL, 0, NULL); } double * mbys(double * im,int imW,int imH,double *tp,int tpW,int tpH) { double * out=new double[imW*imH]; memset(out, 0, imW*imH*sizeof(double)); int i,j,m,n; #define im(ROW,COL) im[imW*(ROW)+(COL)] #define tp(ROW,COL) tp[tpW*(ROW)+(COL)] #define out(ROW,COL) out[imW*(ROW)+(COL)] double a; for(i=0;i<imH;i++) for(j=0;j<imW;j++) { a=0; //去掉靠近边界的行 if(i>int(tpH/2) && i<imH-int(tpH/2) && j>int(tpW/2) && j<imW-int(tpW/2)) for(m=0;m<tpH;m++) for(n=0;n<tpW;n++) { a+=im(i+m-int(tpH/2),j+n-int(tpW/2))*tp(m,n); } out(i,j)=a; } return out; }