数字图像处理总结(五)
Part6 图像分割 image segmentation
主要内容包括一些点、线、边缘的检测算子和检测原理。
点、线、边缘的检测 point line edge detection
基本思想
根据其变化程度,使用梯度或者Laplace算子来进行检测,再进行阈值筛选;
一般而言,一阶导数产生的边缘比较粗糙;二阶导数产生的边缘比较精细,如细线、孤立点和噪声;
孤立点的检测
基本原理
使用Laplace算子进行检测,再进行阈值筛选即可
直线的检测
基本原理
使用方向算子进行检测,再进行阈值筛选即可
边缘的检测
三步:1、平滑;2、检测;3、边缘定位; 梯度与边缘方向垂直
梯度算子
- 先高斯滤波,去除噪声;然后使用梯度算子进行检测,阈值筛选法
- 先高斯滤波,去除噪声;再使用梯度算子检测,zero crossing法
byte[,] zeroCross(float[,] f,float thr) //
{
int w = f.GetLength(0);
int h = f.GetLength(1);
byte[,] g = new byte[w,h];
for(int y=1;y<h-1;y++)
for (int x=1;x<w-1;x++)
{
if (f[x,y]>=0)
{
if (f[x+1,y]*f[x-1,y]<0 && Math.Abs(f[x+1,y]-f[x-1,y])>thr) g[x,y]=255;
if (f[x,y+1]*f[x,y-1]<0 && Math.Abs(f[x,y+1]-f[x,y-1])>thr) g[x,y]=255;
}
}
return g;
}
canny
float[,] filter2D(float[,]f,float[,] mask)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
float[,] g = new float[w,h];
int M = mask.GetLength(0)/2;
int N = mask.GetLength(1)/2;
for(int y=N;y<h-N;y++)
for (int x=M;x<w-M;x++)
{
float r = 0;
for (int m=-M;m<=M;m++)
for (int n=-N;n<=N;n++)
{
r+=f[x+m,y+n]*mask[M+m,N+n];
}
g[x,y] = r;
}
return g;
}
byte S(double v)
{
if (v<0) return 0;
if (v>255) return 255;
return (byte)v;
}
float[,] gblurMask(double r)
{
int M = (int)(r*3);
if (M==0) M=1;
int N = M;
float[,] mask = new float[2*M+1,2*N+1];
// sampling
for (int m=-M;m<=M;m++)
for (int n=-N;n<=N;n++)
mask[M+m,N+n] = (float)Exp(-(m*m+n*n)/(2*r*r));
// normalized
float s = 0;
for (int m=-M;m<=M;m++)
for (int n=-N;n<=N;n++)
s+=mask[M+m,N+n];
for (int m=-M;m<=M;m++)
for (int n=-N;n<=N;n++)
mask[M+m,N+n] = mask[M+m,N+n]/s;
return mask;
}
float[,] sobelX()
{
float[,] mask = new float[3,3];
mask[0,0] =-1; mask[1,0] = 0; mask[2,0] = 1;
mask[0,1] =-2; mask[1,1] = 0; mask[2,1] = 2;
mask[0,2] =-1; mask[1,2] = 0; mask[2,2] = 1;
return mask;
}
float[,] sobelY()
{
float[,] mask = new float[3,3];
mask[0,0] =-1; mask[1,0] = -2; mask[2,0] =-1;
mask[0,1] = 0; mask[1,1] = 0; mask[2,1] = 0;
mask[0,2] = 1; mask[1,2] = 2; mask[2,2] = 1;
return mask;
}
float[,] getMag(float[,]gx,float[,]gy)
{
int w = gx.GetLength(0);
int h = gx.GetLength(1);
float[,] g = new float[w,h];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
g[x,y] = (float)Sqrt(gx[x,y]*gx[x,y]+gy[x,y]*gy[x,y]);
return g;
}
// return value is 0,1,2 or 3
byte[,] getDir(float[,]gx,float[,]gy)
{
int w = gx.GetLength(0);
int h = gx.GetLength(1);
byte[,] g = new byte[w,h];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
double max = Math.Abs(gx[x,y]);
double v = Math.Abs(gx[x,y]+gy[x,y])*0.707f;
if (v>max)
{
g[x,y] = 1;
max = v;
}
v = Math.Abs(gy[x,y]);
if (v>max)
{
g[x,y] = 2;
max = v;
}
v = Math.Abs(gx[x,y]-gy[x,y])*0.707f;
if (v>max)
{
g[x,y] = 3;
max = v;
}
}
return g;
}
float[,] NMS(float[,]mag,byte[,]dir)
{
int w = mag.GetLength(0);
int h = mag.GetLength(1);
float[,] g = new float[w,h];
for (int y=1;y<h-1;y++)
for (int x=1;x<w-1;x++)
{
if (dir[x,y]==0 && mag[x,y]>mag[x-1,y] && mag[x,y]>mag[x+1,y]) g[x,y]=mag[x,y];
if (dir[x,y]==1 && mag[x,y]>mag[x-1,y-1] && mag[x,y]>mag[x+1,y+1]) g[x,y]=mag[x,y];
if (dir[x,y]==2 && mag[x,y]>mag[x,y-1] && mag[x,y]>mag[x,y+1]) g[x,y]=mag[x,y];
if (dir[x,y]==3 && mag[x,y]>mag[x+1,y-1] && mag[x,y]>mag[x-1,y+1]) g[x,y]=mag[x,y];
}
return g;
}
// return 255:strong edge, 128:weak edge, 0:
byte[,] d_thr(float[,]f,float thr1,float thr2)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
byte[,] g = new byte[w,h];
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
if (f[x,y]>thr1) g[x,y] = 255;
else if (f[x,y]>thr2) g[x,y] = 128;
return g;
}
bool connect_to_strong_edge(byte[,]f, int x,int y)
{
for (int j=-1;j<=1;j++)
for (int i=-1;i<=1;i++)
if (f[x+i,y+j]==255) return true;
return false;
}
void Label(byte [,] f, int x,int y)
{
f[x,y] = 255;
if (f[x+1,y]==128) Label(f,x+1,y);
if (f[x-1,y]==128) Label(f,x-1,y);
if (f[x,y+1]==128) Label(f,x,y+1);
if (f[x,y-1]==128) Label(f,x,y-1);
if (f[x-1,y-1]==128) Label(f,x-1,y-1);
if (f[x+1,y-1]==128) Label(f,x+1,y-1);
if (f[x-1,y+1]==128) Label(f,x-1,y+1);
if (f[x+1,y+1]==128) Label(f,x+1,y+1);
}
byte[,] CA(byte[,]f)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
if (f[x,y]==128 && connect_to_strong_edge(f,x,y))
{
Label(f,x,y);
}
byte[,] g = new byte[w,h];
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
if (f[x,y]==255) g[x,y] = 255;
return g;
}
float[,] ToFloat(byte[,]f)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
float[,] g = new float[w,h];
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
g[x,y] = f[x,y];
}
return g;
}
byte[,] ToByte(float[,]f,float off)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
byte[,] g = new byte[w,h];
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
g[x,y] = S(f[x,y]+off);
}
return g;
}
void main()
{
byte[,] f = LoadImg();
ShowImg("f",f);
float[,] g = filter2D(ToFloat(f),gblurMask(2));
ShowImg("r=2",ToByte(g,0));
float[,] gx = filter2D(g,sobelX());
ShowImg("gx",ToByte(gx,128));
float[,] gy = filter2D(g,sobelY());
ShowImg("gy",ToByte(gy,128));
float[,] mag = getMag(gx,gy);
ShowImg("mag",ToByte(mag,0));
byte[,] dir = getDir(gx,gy);
float[,] nms = NMS(mag,dir);
ShowImg("nms",ToByte(nms,0));
byte[,] edge = d_thr(nms,32,16);
ShowImg("edge",edge);
ShowImg("final edge",CA(edge));
}
Part7 表示与描述
霍夫变换 hough transformation
检测VLIine
基本原理:计算每一列(x=x0)下图像像素点的和,记录在p数组里面,之后遍历p,找到最大的列即为所在的垂直线段;
int detectVLine(byte[,]f)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
int[] p = new int[w];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
p[x]+=f[x,y];
int max = 0;
int x0 = 0;
for (int x=0;x<w;x++)
if (p[x]>max)
{
max = p[x];
x0 = x;
}
return x0;
}
void drawVLine(byte[,]f,int x0)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
for (int y=0;y<h;y++)
f[x0,y] = 255;
}
霍夫变换检测直线
原理:
- 将角度细分为360°,构建一个二维数组,然后使用下式求可能的所有,ρθ的数量,放在数组里面:
2.对图像上的每个点,若像素值=255,则遍历所有的θ(n=0-359)计算ρ,将计算得到的ρ取整,ρ对应数组下标的元素++;
int[,] hough(byte[,]f)
{
double pi = 3.1415926;
int w= f.GetLength(0);
int h= f.GetLength(1);
int[,] ps = new int[360,w+h];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
if (f[x,y]==255)
for (int a=0;a<360;a++)
{
int r = (int)(x*Cos(a*pi/180)+y*Sin(a*pi/180));
if (r>=0 && r<w+h)
{
ps[a,r] += 1;
}
}
}
return ps;
}
- 遍历二维数组的每个数,找到最大的ρθ组合,即为检测到的直线。
void get_params(int[,] ps,out int a0,out int r0)
{
int w= ps.GetLength(0);
int h= ps.GetLength(1);
int max = 0;
a0 = 0;
r0 = 0;
for (int r=0;r<h;r++)
for (int a=0;a<w;a++)
{
if (ps[a,r]>max)
{
max = ps[a,r];
a0 = a;
r0 = r;
}
}
}
- 若要查看具体的ps数组图像,需要先找到其最大值,然后归一化到0-255 即可。
byte[,] toByte(int[,]f)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
byte[,] g = new byte[w,h];
int max = 0;
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
if (f[x,y]>max) max = f[x,y];
for(int y=0;y<h;y++)
for (int x=0;x<w;x++)
g[x,y] = (byte)(f[x,y]*255/max);
return g;
}
霍夫变换检测45°直线
这个时候直线式45°,因此与之垂直的过原点的直线的角度为135°,因此,a=135,然后遍历所有的θ,看看在a=135的时候,那个r最大;
int hough(byte[,]f)
{
double pi = 3.1415926;
int w= f.GetLength(0);
int h= f.GetLength(1);
int[] ps = new int[w+h];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
int a = 135; // 指定 a = 135 (即180-45)
if (f[x,y]==255)
int r = (int)(x*Cos(a*pi/180)+y*Sin(a*pi/180));
if (r>=0 && r<w+h)
{
ps[r] += 1;
}
}
}
int max = 0;
int r0 = 0;
for (int r=0;r<w+h;r++)
{
if (ps[r]>max)
{
max = ps[r];
r0 = r;
}
}
return r0;
}
霍夫圆变换
原理:和直线检测类似,都是在某个寻找最有可能的参数空间,直线式ρ,θ;那么圆就是圆心(x0,y0)和半径r;代码如下:
int[,,] hough(byte[,]f,int x0_min,int x0_max,int y0_min,int y0_max,int r_min,int r_max)
{
//double pi = 3.1415926;
int w= f.GetLength(0);
int h= f.GetLength(1);
int[,,] ps = new int[x0_max-x0_min,y0_max-y0_min,r_max-r_min];
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
if (f[x,y]==255)
for (int x0=x0_min;x0<x0_max;x0++)
for (int y0=y0_min;y0<y0_max;y0++)
{
int r = (int)(Sqrt((x-x0)*(x-x0)+(y-y0)*(y-y0)));
if (r>=r_min && r<r_max)
{
ps[x0-x0_min,y0-y0_min,r-r_min] += 1;
}
}
}
return ps;
}
void get_params(int[,,] ps,int x0_min,int y0_min,int r_min, out int x0,out int y0,out int r)
{
int w= ps.GetLength(0);
int h= ps.GetLength(1);
int d= ps.GetLength(2);
int max = 0;
x0 = 0;
y0 = 0;
r = 0;
for (int x=0;x<w;x++)
for (int y=0;y<w;y++)
for (int _r=0;_r<d;_r++)
{
if (ps[x,y,_r]>max)
{
max = ps[x,y,_r];
x0 = x;
y0 = y;
r = _r;
}
}
x0+=x0_min;
y0+=y0_min;
r+=r_min;
}
2维中心矩(质心) 2D central moment
基本原理:计算加权平均值
void GetCenter(byte[,]f,out int x0,out int y0)
{
int w = f.GetLength(0);
int h = f.GetLength(1);
int m00 = 0; // 这里用int不是很严谨,用double比较严谨,int有可能会溢出
int m10 = 0;
int m01 = 0;
for (int y=0;y<h;y++)
for (int x=0;x<w;x++)
{
m00 += f[x,y];
m10 += x*f[x,y];
m01 += y*f[x,y];
}
x0 = m10/m00;
y0 = m01/m00;
}
void main()
{
byte[,] f = LoadImg();
ShowImg("f",f);
int x0,y0;
GetCenter(f,out x0,out y0);
f[x0,y0] = 0;
ShowImg("center",f);
}