数字图像处理总结(一二)
Part1 Introduction
这部分没啥说的。
Part2 DIP基础
这部分主要介绍了成像的过程和数字图像的生成、表示和量化。还有数字图像的数据结构表示(二维数组)
简单图像形成模型
图像形成模型比较简单,主要由两部分组成,一个照射分量(illumination components)[0,∞],一个反射分量(reflectance components),[0,1]
dpi(dot per inch)
例如下图ipad的分辨率为1024*768,长宽是8,6inch,因此,dpi = 768/6 = 128
灰度分辨率
如何获得n-bits (1-8)的images?
两种方式:
- 先右移位(8-n)位,再左移8-n位(只保留高2^n位);
- 先右移位(8-n)位,再*255/(2^n-1)
c--:
byte[,] get_n_bits_img(byte[,] f, int n)
{
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] = (byte)((f[x,y]>>(8-n))<<(8-n));
g[x,y] = (byte)((f[x,y]>>(8-n))*255/((1<<n)));
}
return g;
}
void main()
{
byte[,] f = LoadImg();
ShowImg("f", f);
for (int i=1;i<=8;i++)
ShowImg("n="+i.ToString(), get_n_bits_img(f, i));
}
像素邻域
由这里主要包括以下几种邻域:4-adjacency、8-adjacency、m-adjacency、和D-adjacency。
4邻域
D邻域
8邻域(N4+ND)
m邻域
对于像素值为1的意思为前景,p和q均在前景中,m邻域意思是满足下面其中一个条件:
- 像素q在p的4邻域中;
- q在p的8邻域,但p和q的四邻域没有在前景的交集。
混合连接实质上是在像素间同时存在4-连接和8-连接时,优先采用4-连接,并屏蔽两个和同一像素间存在4-连接的像素之间的8-连接。
例如下面的例子,最右边是m邻域所绘制的连通,中间是8邻域。
c--:
bool Is_4adjacent(int px, int py, int qx, int qy)
{
return Abs(px-qx)+Abs(py-qy)<=1
}
bool Is_8adjacent(int px, int py, int qx, int qy)
{
return Abs(px-qx)+Abs(py-qy) < 2.1
// return Is_4adjacent(px,py,qx,qy)||Is_D_adjacent(px,py,qx,qy)
}
bool Is_D_adjacent(int px, int py, int qx, int qy)
{
return Abs(px-qx)+Abs(py-qy) == 2
}
图像连通域的内外边界
-
内边界: 自身像素点是区域内的;四邻域有区域内的;(inner-border)
-
外边界:自身像素点是区域外的,八邻域有区域内的点;(outer-border)
图像的距离度量
距离函数应首先满足如下的性质:(非负,对偶,三角)
本课程中老师主要讲了三种距离度量:
-
欧式距离(euclidean distance)
-
城市街区块距离(city-block distance)
x和y的差距和
-
棋盘格距离(chessboard distance)
取 x和y上差距最大的
查看街区D4和棋盘D8距离的代码如下:
其中,D4>=De>=D8
byte[,] chessdistance(int x0, int y0, float r)
{
byte[,] g = new byte[(int)(x0*2),(int)(y0*2)];
for (int y=0;y<(int)(y0*2);y++)
for (int x=0;x<(int)(x0*2);x++)
{
double temp = Abs(x-x0);
if (temp<Abs(y-y0))
temp = Abs(y-y0);
if ( temp <= r )
g[x,y] = 255;
}
return g;
}
byte[,] cityblockdistance(int x0, int y0, float r)
{
byte[,] g = new byte[(int)(x0*2),(int)(y0*2)];
for (int y=0;y<(int)(y0*2);y++)
for (int x=0;x<(int)(x0*2);x++)
{
if ( (Abs(x-x0)+Abs(y-y0)) <= r )
g[x,y] = 255;
}
return g;
}
void main()
{
ShowImg("city", cityblockdistance(200, 200, 50));
ShowImg("chess", chessdistance(200, 200, 50));
}
图像中的数学操作(加减乘除)
-
Rand() [0,1] 随机噪声
-
多个噪声图像相加,用于图像的增强,方差减小;add
-
两个图像相减,目标突出;sub
-
两个图像相乘/除,ROI;mul div
-
-
算术运算的用处,归一化到0-255:
-
混合运算
- 像素取反
- 邻域操作(和后面的卷积类似)
图像中的变换
前向和后向变换
这里是(x,y) -> (u,v),后面仿射变换x,y和u,v是反的,注意区分!
图像处理中常用逆向映射,计算机图形学中常用前向映射。
仿射变换
下面的都是逆向变换,前向变换求一个逆就行了: 一般分析的时候先前向变换,然后再后向变换
先定义一下matrix和矩阵乘法函数
struct Matrix
{
public float m11;
public float m21;
public float m31;
public float m12;
public float m22;
public float m32;
}
byte[,] ST(byte[,] f,Matrix T)
{
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++)
{
int u = (int)(x*T.m11+y*T.m21+T.m31);
int v = (int)(x*T.m12+y*T.m22+T.m32);
if (u>=0 && u<w && v>=0 && v<h)
g[u,v] = f[x,y];
}
return g;
}
- 垂直flip vertical
Matrix createFlipV(int h)
{
Matrix M = new Matrix();
M.m11 = 1; M.m12 = 0;
M.m21 = 0; M.m22 = -1;
M.m31 = 0; M.m32 = h-1;
return M;
}
- 水平flip horizontal
{
Matrix M = new Matrix();
M.m11 = -1; M.m12 = 0;
M.m21 = 0; M.m22 = 1;
M.m31 = w-1; M.m32 = 0;
return M;
}
- Scale
Matrix createScale(float sx,float sy)
{
Matrix M = new Matrix();
M.m11 =1/sx; M.m12 = 0;
M.m21 = 0; M.m22 =1/sy;
M.m31 = 0; M.m32 = 0;
return M;
}
- ScaleAt
Matrix createScaleAt(float x0,float y0,float k)
{
Matrix M = new Matrix();
M.m11 =1/k; M.m12 = 0;
M.m21 = 0; M.m22 =1/k;
M.m31 =x0-x0/k; M.m32 = y0-y0/k;
return M;
}
-
旋转
-
平移
Matrix createTranslation(float tx,float ty)
{
Matrix M = new Matrix();
M.m11 = 1; M.m12 = 0;
M.m21 = 0; M.m22 = 1;
M.m31 =-tx; M.m32 =-ty;
return M;
}
- skewX
Matrix createSkewX(float h,float w0)
{
Matrix M = new Matrix();
M.m11 =1; M.m12 = 0;
M.m21 =-w0/(h-1); M.m22 = 1;
M.m31 = 0; M.m32 = 0;
return M;
}
- skewY
Matrix createSkewX(float h,float w0)
{
Matrix M = new Matrix();
M.m11 =1; M.m12 = -h0/(w-1);
M.m21 =0; M.m22 = 1;
M.m31 = 0; M.m32 = 0;
return M;
}
注意多重仿射变换要会(矩阵堆叠)
- 饶图像中心点顺时针旋转90°和逆时针旋转90°(对于这类比较特殊的变换,可以使用多点观察法,求取转化矩阵)
方法(来自某彭姓大佬):
//下面的是前向映射的矩阵 分别是顺时针旋转90°和逆时针旋转90°
Matrix rotate90()
{
Matrix M = new Matrix();
M.m11 =0; M.m12 = 1;
M.m21 = -1; M.m22 =0;
M.m31 =255; M.m32 = 0;
return M;
}
Matrix rotate_f90()
{
Matrix M = new Matrix();
M.m11 =0; M.m12 = -1;
M.m21 =1; M.m22 =0;
M.m31 =0; M.m32 = 255;
return M;
}
-
最近邻插值(注意0.5)
-
双线性变换
struct Matrix
{
public float m11;
public float m21;
public float m31;
public float m12;
public float m22;
public float m32;
}
byte[,] ST(byte[,] f,Matrix T)
{
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++)
{
int u = (int)(x*T.m11+y*T.m21+T.m31);
int v = (int)(x*T.m12+y*T.m22+T.m32);
if (u>=0 && u<w && v>=0 && v<h)
g[x,y] = f[u,v];
}
return g;
}
byte GetValueAt(byte[,]f,float x,float y)
{
int i = (int)x;
int j = (int)y;
double a = x-i;
double b = y-j;
double fxj = f[i,j]*(1-a)+f[i+1,j]*a;
double fxj1 = f[i,j+1]*(1-a)+f[i+1,j+1]*a;
double fxy = fxj*(1-b)+fxj1*b;
/*
float f00 = f[i,j];
float f10 = f[i+1,j];
float f01 = f[i,j+1];
float f11 = f[i+1,j+1];
float ffxy = (f00*(1-a)+f10*a)*(1-b)+(f10*(1-a)+f11*a)*b;
*/
return (byte)fxy;
}
byte[,] ST_BI(byte[,] f,Matrix T)
{
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++)
{
float u = x*T.m11+y*T.m21+T.m31;
float v = x*T.m12+y*T.m22+T.m32;
if (u>=0 && u<w-2 && v>=0 && v<h-2)
g[x,y] = GetValueAt(f,u,v);
}
return g;
}
Matrix createFlipV(int h)
{
Matrix M = new Matrix();
M.m11 = 1; M.m12 = 0;
M.m21 = 0; M.m22 = -1;
M.m31 = 0; M.m32 = h-1;
return M;
}
Matrix createFlipH(int w)
{
Matrix M = new Matrix();
M.m11 = -1; M.m12 = 0;
M.m21 = 0; M.m22 = 1;
M.m31 = w-1; M.m32 = 0;
return M;
}
Matrix createTranslation(float tx,float ty)
{
Matrix M = new Matrix();
M.m11 = 1; M.m12 = 0;
M.m21 = 0; M.m22 = 1;
M.m31 =-tx; M.m32 =-ty;
return M;
}
Matrix createScale(float sx,float sy)
{
Matrix M = new Matrix();
M.m11 =1/sx; M.m12 = 0;
M.m21 = 0; M.m22 =1/sy;
M.m31 = 0; M.m32 = 0;
return M;
}
Matrix createScaleAt(float x0,float y0,float k)
{
Matrix M = new Matrix();
M.m11 =1/k; M.m12 = 0;
M.m21 = 0; M.m22 =1/k;
M.m31 =x0-x0/k; M.m32 = y0-y0/k;
return M;
}
void main()
{
byte[,] f = LoadImg();
ShowImg("f",f);
int w = f.GetLength(0);
int h = f.GetLength(1);
// ShowImg("FlipV",ST(f,createFlipV(h)));
// ShowImg("FlipH",ST(f,createFlipH(w)));
// ShowImg("Translate",ST(f,createTranslation(40,80)));
// ShowImg("Scale 0.5",ST(f,createScale(0.5f,0.5f)));
// ShowImg("Scale 2",ST(f,createScale(2,2)));
// ShowImg("Scale 0.5",ST(f,createScale(0.5f,0.75f)));
ShowImg("Scale At",ST(f,createScaleAt(w/2,h/2,8)));
ShowImg("Scale At",ST_BI(f,createScaleAt(w/2,h/2,8)));
}
- 配准(简单平移)
FM
(0,0) => x00,y00
(w-1,0) => x10,y10
(0,h-1) => x01,y01
(w-1,h-1) => x11,y11
*/
0,0, 512,80, 80,512, 512+80, 512+80, 512,512
// IM
byte[,] img_reg(byte[,]f,float x00,float y00, float x10,float y10, float x01,float y01, float x11,float y11, int w,int h)
{
int iw = f.GetLength(0);
int ih = f.GetLength(1);
byte[,] g = new byte[w,h];
for (int y=0;y<h;y++)
for(int x=0;x<w;x++)
{
float a = (float)x/(w-1);
float b = (float)y/(h-1);
/*
float f00 = f[i,j];
float f10 = f[i+1,j];
float f01 = f[i,j+1];
float f11 = f[i+1,j+1];
float ffxy = (f00*(1-a)+f10*a)*(1-b)+(f10*(1-a)+f11*a)*b;
*/
int u = (int)((x00*(1-a)+x10*a)*(1-b)+(x01*(1-a)+x11*a)*b);
int v = (int)((y00*(1-a)+y10*a)*(1-b)+(y01*(1-a)+y11*a)*b);
// if (x=0,y=0) u = x00, v = y00
// if (x=w-1,y=0) a = 1, b =0 u = x10, v= y10,
if (u>=0 && u<iw && v>=0 && v<ih)
g[x,y] = f[u,v];
else g[x,y] = 255;
}
return g;
}
- 图像某点 Scale (V2更加自然)
byte[,] ZoomAt(byte[,] f,float x0,float y0,float k) // 直接向量计算就好了
{
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++)
{
int u = (int)((x-x0)/k+x0);
int v = (int)((y-y0)/k+y0);
if (u>=0 && u<w && v>=0 && v<h)
g[x,y] = f[u,v];
}
return g;
}
byte[,] localZooming(byte[,] f,float x0,float y0,float R,float k) // 局部放大,在原来的基础上,限制在一个圆里面,但不够自然
{
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++)
{
int u = (int)((x-x0)/k+x0);
int v = (int)((y-y0)/k+y0);
if ((x-x0)*(x-x0)+(y-y0)*(y-y0)<=R*R)
g[x,y] = f[u,v];
else g[x,y] = f[x,y];
/*
if (u>=0 && u<w && v>=0 && v<h)
g[x,y] = f[u,v];
*/
}
return g;
}
byte[,] localZoomingV2(byte[,] f,float x0,float y0,float R,float K)
// 局部放大,在原来的基础上,限制在一个圆里面,
// 然后距离圆越远的,放大倍数越小
// 这样看起来比较自然
{
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++)
{
double d = Sqrt((x-x0)*(x-x0)+(y-y0)*(y-y0));
double t = d/R;
if (t>1) g[x,y] = f[x,y];
else
{
double k = K*(1-t)+t;
int u = (int)((x-x0)/k+x0);
int v = (int)((y-y0)/k+y0);
if (u>=0 && u<w && v>=0 && v<h)
g[x,y] = f[u,v];
}
}
return g;
}
void main()
{
byte[,] f = LoadImg();
ShowImg("f",f);
// ShowImg("Zoom At",localZooming(f,269,262,64,2));
// ShowImg("Zoom At",localZooming(f,269,262,64,0.5f));
ShowImg("Zoom At",localZoomingV2(f,269,262,64,2));
ShowImg("Zoom At",localZoomingV2(f,269,262,64,0.5f));
}