一、简单介绍
Hough变换是图像处理中从图像中识别几何形状的基本方法之一。Hough变换的基本原理在于利用点与线的对偶性,将原始图像空间的给定的曲线通过曲线表达形式变为参数空间的一个点。这样就把原始图像中给定曲线的检测问题转化为寻找参数空间中的峰值问题。也即把检测整体特性转化为检测局部特性。比如直线、椭圆、圆、弧线等。
二、Hough变换的基本思想
设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用y=k*x+b来表示,其中k和b是参数,分别是斜率和截距。过某一点(x0,y0)的所有直线的参数都会满足方程y0=kx0+b。即点(x0,y0)确定了一族直线。方程y0=kx0+b在参数k--b平面上是一条直线,(你也可以是方程b=-x0*k+y0对应的直线)。这样,图像x--y平面上的一个前景像素点就对应到参数平面上的一条直线。我们举个例子说明解决前面那个问题的原理。设图像上的直线是y=x,我们先取上面的三个点:A(0,0), B(1,1), C(22)。可以求出,过A点的直线的参数要满足方程b=0,过B点的直线的参数要满足方程1=k+b,过C点的直线的参数要满足方程2=2k+b,这三个方程就对应着参数平面上的三条直线,而这三条直线会相交于一点(k=1,b=0)。 同理,原图像上直线y=x上的其它点(如(3,3),(4,4)等) 对应参数平面上的直线也会通过点(k=1,b=0)。这个性质就为我们解决问题提供了方法,就是把图像平面上的点对应到参数平面上的线,最后通过统计特性来解决问题。假如图像平面上有两条直线,那么最终在参数平面上就会看到两个峰值点,依此类推。
简而言之,Hough变换思想为:在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。
在实际应用中,y=k*x+b形式的直线方程没有办法表示x=c形式的直线(这时候,直线的斜率为无穷大)。所以实际应用中,是采用参数方程p=x*cos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p---theta平面上的一条曲线上,其它的还是一样。
三、Hough变换推广
1、已知半径的圆
其实Hough变换可以检测任意的已知表达形式的曲线,关键是看其参数空间的选择,参数空间的选择可以根据它的表达形式而定。比如圆的表达形式为,所以当检测某一半径的圆的时候,可以选择与原图像空间同样的空间作为参数空间。那么圆图像空间中的一个圆对应了参数空间中的一个点,参数空间中的一个点对应了图像空间中的一个圆,圆图像空间中在同一个圆上的点,它们的参数相同即a,b相同,那么它们在参数空间中的对应的圆就会过同一个点(a,b),所以,将原图像空间中的所有点变换到参数空间后,根据参数空间中点的聚集程度就可以判断出图像空间中有没有近似于圆的图形。如果有的话,这个参数就是圆的参数。
2、未知半径的圆
对于圆的半径未知的情况下,可以看作是有三个参数的圆的检测,中心和半径。这个时候原理仍然相同,只是参数空间的维数升高,计算量增大。图像空间中的任意一个点都对应了参数空间中的一簇圆曲线。,其实是一个圆锥型。参数空间中的任意一个点对应了图像空间中的一个圆。
3、椭圆
椭圆有5个自由参数,所以它的参数空间是5维的,因此他的计算量非常大,所以提出了许多的改进算法。
四、总结
图像空间中的在同一个圆,直线,椭圆上的点,每一个点都对应了参数空间中的一个图形,在图像空间中这些点都满足它们的方程这一个条件,所以这些点,每个投影后得到的图像都会经过这个参数空间中的点。也就是在参数空间中它们会相交于一点。所以,当参数空间中的这个相交点的越大的话,那么说明元图像空间中满足这个参数的图形越饱满。越象我们要检测的东西。
Hough变换能够查找任意的曲线,只要你给定它的方程。Hough变换在检验已知形状的目标方面具有受曲线间断影响小和不受图形旋转的影响的优点,即使目标有稍许缺损或污染也能被正确识别。
1.hough检测直线
typedef struct MAXVALUE{
int iDist;
int iAngle;
int iMax;
}MAXVALUE;
/*
* 检测直线
*/
void TraceBeeline(int ImageWidth,int ImageHeight,LPBYTE lpSrc,LPBYTE lpDest,int len)
{
// #define pi 3.1415927
int iMaxAngle = 90;
int iAngleNumber = 0;
//最大值
MAXVALUE MaxValue1;
memset(&MaxValue1,0,sizeof(MAXVALUE));
//循环变量
int i;
int j;
int off = 0;
int iMaxDistance = 0,iDistance = 0;
iMaxDistance = (int)sqrt(ImageWidth * ImageWidth + ImageHeight * ImageHeight);
int *lpTrans = new int[iMaxDistance * iMaxAngle];
memset(lpTrans,0,iMaxDistance * iMaxAngle * sizeof(int));
//去掉孤立点
for(j=1,off=0;j<ImageHeight-1;j++)
for(i=1;i<ImageWidth-1;i++,off)
{
if(1==lpSrc[off])
{
if(lpSrc[off-1]==0&&lpSrc[off+1]==0&&lpSrc[off-ImageWidth]==0&&lpSrc[off+ImageWidth]==0 /
lpSrc[off-ImageWidth-1]==0&&lpSrc[off-ImageWidth+1]==0&&lpSrc[off+ImageWidth-1]==0&&lpSrc[off+ImageWidth+1]==0)
lpSrc[off]=0;
}
}
//////////////////////////////
for(j=0;j<ImageHeight;j++)
for(i=0;i<ImageWidth;i++)
{
off = j * ImageWidth + i;
if(1==lpSrc[off])
{
for(iAngleNumber=0;iAngleNumber<iMaxAngle;iAngleNumber++)
{
iDistance = (int)fabs(i * cos(iAngleNumber * 2 * PI/180.0) + j * sin(iAngleNumber * 2 * PI/180.0));
if(iDistance>=0&&iDistance<iMaxDistance)
lpTrans[iDistance * iMaxAngle + iAngleNumber]++;
}
}
}
//找到最大值
for(i = 0;i < iMaxDistance;i++)
for(iAngleNumber=0;iAngleNumber<iMaxAngle;iAngleNumber++)
{
if(lpTrans[i * iMaxAngle + iAngleNumber]>MaxValue1.iMax)
{
MaxValue1.iMax = (int)lpTrans[i * iMaxAngle + iAngleNumber];
MaxValue1.iAngle = iAngleNumber;
MaxValue1.iDist = i;
}
}
//
for(j=0;j<ImageHeight;j++)
for(i=0;i<ImageWidth;i++)
{
off = j * ImageWidth + i;
iDistance = (int)fabs(i * cos(iAngleNumber * 2 * PI/180.0) + j * sin(iAngleNumber * 2 * PI/180.0));
if(iDistance == MaxValue1.iMax)
lpDest[off]=1;
}
if(lpTrans)
{
delete lpTrans;
lpTrans = NULL;
}
}
2.hough检测圆
根据园的方程(x-a)×(x-a)+(y-b)×(y-b)=R×R,将参数空间增加到a,b,R三维空间.
常用的hough变换计算量太大,然而随机hough变换虽然计算量相对小点,但是准确率没有前者高.现在使用
一种改进的方法:提取边缘图像,然后边缘跟踪,在边缘曲线上进行hough变换检测圆.
double x=0.0,y=0.0,x0=0.0,y0=0.0,cc=0.0,ss=0.0,temp1,temp2;
int aa=0,bb=0,rr=0,saveaa=0,savebb=0,saverr=0,savexx0=0,saveyy0=0,saveQ=0;
int pos = ptNumber/3;
int i = 0,off = 0,k = 0,iBaseFlag = 100,jj = 0;
int tt = 0,m=0,n=0;
int firstBase = 5;
int secendBase = (2*firstBase+1);
int aaBase = (R.right+R.left)/2 - firstBase,bbBase = (R.bottom+R.top)/2 -firstBase;
LONG minrr = 0;
minrr = (R.right - R.left)>(R.bottom - R.top)?(R.bottom - R.top):(R.right - R.left);
int memsize = (int)(minrr+2*firstBase) * secendBase *secendBase;
BYTE * lpabr = new BYTE[memsize];
memset(lpabr,0,sizeof(BYTE)*memsize);
int maxcount = -1;
/* 局部hough变换检测圆曲线段的一部分*/
for(k=0;k<ptNumber;k+=pos)
{
for(aa=(int)(R.left+R.right)/2 - firstBase;aa<=(int)(R.left+R.right)/2 + firstBase;aa++)
{
if(aa<0)
continue;
if(aa>ImageWidth)
break;
for(bb=(int)(R.top+R.bottom)/2 -firstBase;bb<=(int)(R.top+R.bottom)/2 + firstBase;bb++)
{
if(bb<0)
continue;
if(bb>ImageHeight)
break;
temp1 = (pt[k].x-aa)*(pt[k].x-aa) + (pt[k].y-bb)*(pt[k].y-bb);
rr = (int)sqrt(temp1);
if(rr>=10&&rr<=(int)(minrr/2) + firstBase)
lpabr[rr*secendBase*secendBase+(aa-aaBase)*secendBase+(bb-bbBase)]++;
}
}
}
for(rr=10;rr<=(int)(minrr/2) + firstBase;rr++)
for(aa=(int)(R.left+R.right)/2 - firstBase;aa<=(int)(R.left+R.right)/2 + firstBase;aa++)
{
if(aa<0)
continue;
if(aa>ImageWidth)
break;
for(bb=(int)(R.top+R.bottom)/2 - firstBase;bb<=(int)(R.top+R.bottom)/2 + firstBase;bb++)
{
if(bb<0)
continue;
if(bb>ImageHeight)
break;
if(maxcount<lpabr[rr*secendBase*secendBase+(aa-aaBase)*secendBase+(bb-bbBase)])
{
maxcount = lpabr[rr*secendBase*secendBase+(aa-aaBase)*secendBase+(bb-bbBase)];
saverr = rr;
saveaa = aa;
savebb = bb;
}
}
}
if(maxcount>=4)
{
//记录保存中心,半经
saverr = rr;
saveaa = aa;
savebb = bb;
}
3.hough变换检测椭圆的程序
一个MATLAB程序,有些地方你自己修改以下就可以了。
[row col]=size(fedge);
minofa=a;
maxofa=round(row/2);
minofy0=round(col/2)-30;
maxofy0=round(col/2)+30;
minofb=round(col/2)-60;
maxofb=round(col/2);
maxofx=round(row/2);
scalor=4;
H=zeros(floor((maxofa-minofa)/scalor)+1,floor((maxofa-minofa)/scalor)+1,...
floor((maxofy0-minofy0)/scalor)+1,floor((maxofb-minofb)/scalor)+1);
for x=1:maxofx
for y=1:col
temp=fedge(x,y);
if temp==255
for a=minofa:scalor:maxofa
for x0=a:scalor:maxofa
for b=minofb:scalor:maxofb
for y0=minofy0:scalor:maxofy0
temp=((y-y0)/b)^2+((x-x0)/a)^2;
if abs(temp-1)<=0.01
xtemp=floor((x0-minofa)/scalor)+1;
atemp=floor((a-minofa)/scalor)+1;
ytemp=floor((y0-minofy0)/scalor)+1;
btemp=floor((b-minofb)/scalor)+1;
H(xtemp,atemp,ytemp,btemp)=H(xtemp,atemp,ytemp,btemp)+1;
end
end
end
end
end
end
end
end
maxofH=max(max(max(max(H))));
for i=1:floor((maxofa-minofa)/scalor)+1
for j=1:floor((maxofa-minofa)/scalor)+1
for m=1:floor((maxofy0-minofy0)/scalor)+1
for n=1:floor((maxofb-minofb)/scalor)+1
temp=H(i,j,m,n);
if temp==maxofH
xtemp=i;
atemp=j;
ytemp=m;
btemp=n;
break;
end
end
end
end
end
x0=(xtemp-1)*scalor+minofa;
a=(atemp-1)*scalor+minofa;
y0=(ytemp-1)*scalor+minofy0;
b=(btemp-1)*scalor+minofb;
figure;
imshow(fedge,[]);
hold on;
plot(y0,x0,'r+');
t=0:pi/180:2*pi;
plot(y0+b*sin(t),x0+a*cos(t),'r-');
hold off;
URL查看 http://www.qqread.com/itlife/u415111021.html进入讨论组讨论。
void CImageCompressView::DoHough()
{
int temp=0;
for(int i=0;i<theApp.ROW-1;i++)
{
for(int j=0;j<theApp.COL-1;j++)///取每个象素
{
temp=theApp.iImageDataTemp[i][j];
int value[9];///存入当前3*3窗口内的象素
GetPixelValueEx(value,i,j);
long t=0,t1=0;
for(int k=0;k<4;k++)
{
for(int m=0;m<8;m++)///对3*3模板卷积
{
t1+= Hough[k][m]*value[m];
}
if(t1>t)
t=t1;
}
if(t>m_iValve)
theApp.iImageDataTemp[i][j]=255;
else
theApp.iImageDataTemp[i][j]=0;
}
}
ShowImage(1);
}
void CImageCompressView::GetPixelValueEx(int *value, int i, int j)
{
value[0]=GetPixelValue(i-1,j-1);
value[1]=GetPixelValue(i,j-1);
value[2]=GetPixelValue(i+1,j-1);
value[3]=GetPixelValue(i-1,j);
value[4]=GetPixelValue(i,j);
value[5]=GetPixelValue(i+1,j);
value[6]=GetPixelValue(i-1,j+1);
value[7]=GetPixelValue(i,j+1);
value[8]=GetPixelValue(i+1,j+1);
}
////计算偏移地址
int CImageCompressView::GetPixelValue(int i, int j)
{
int m=i;
int n=j;
if(i<0)
m=-i;
if(i>(theApp.ROW-1))
m=theApp.COL-i+1;
if(j<0)
n=-j;
if(j>(theApp.COL-1))
n=theApp.COL-j+1;
return theApp.iImageData[m][n];
}
其中theApp.iImageDataTemp[i][j]
和theApp.iImageData[i][j]都是当前处理图象的二维数组(全局)
///以下是全局数组
int Hough[4][9] = {
{-1, 0, 1,
-1, 0, 1,
-1, 0, 1},
{-1, -1, 0,
-1, 0, 1,
0, 1, 1},
{-1, -1, -1,
0, 0, 0,
1, 1, 1},
{0, -1, -1,
1, 0, -1,
1, 1, 0}
};