1透视投影变换概述
透视投影变换(Perspective Transformation)是对图像做视平面(Viewing Plane)的变换,通俗来讲可以用下面一组图来表示
图1 图中画了一只小鸟^v^
在图中有一幅画,画中是一只小鸟,小蓝人在画的左边,离画较远;小红人站在画的右边,离画较近。他们眼中的图分别是
图2 小蓝人眼中的画(左) 小红人眼中的画(右)
如果小蓝人想知道小红人看到的画是什么样的,那么他有两种方法,一种是他走到小红人站的位置,也就是说在小红人所处的视觉坐标下观察;另个一种方法就是将小蓝人看到的图像所处的视平面变换到小红人所处的视平面下。对于墙壁上的画来说,这两种方法的结果是一样的。如果换做是雕像那种三维的物体,结果就不一样了,但在精度要求不高的情况下,也是可以近似的^v^。
2透视投影变换原理
具体详细的原理的介绍,相关书籍中写的很详细,这里简单介绍下:
透视变换将(x,y)点映射为(u,v)点,如下所示:
u=(ax+by+c)/(dx+ey+f)
v=(gx+hy+1)/(dx+ey+f)
在物理上我们将2D图像嵌入到3D空间中,在包含2D图像的平面上应用透视变换,然后应用透视图回顾一下仿射变换的形式:
u=ax+by+c
v=dx+ey+f
u的等式中的参数a,b,c不出现在v的等式中,但是对于透视变换,参数d,e,f出现在两个方程中,u,v的分母相同,dx + ey + f是将3D透视变换应用于包含2D图像的平面之后的点的深度。在物理上我们增加了深度的维度。
假设我们有四个控制点p1到p4分别映射到矩形的四个顶点q1到q4(也可以不是矩形,任意四边形都是可以的)。p1到p4为原图中期望矫正成为矩形的四边形的顶点。这可以以矩阵形式写成
Ax=B
其中x是未知数的向量
x=(a,b,c,d,e,f,g,h)^T
且A是由下式给出 (文章使用word拟的草稿,公式不能粘贴,只能截图了)
B为
B=(q1.x q1.y q2.x q2.y q3.x q3.y q4.x q4.y)^T
解以上方程可以得到参数a,b,c,d,e,f,g,h。
利用变换阵
对图像进行变换即可。
3透视投影变换的实现
直接上代码
先是MATLAB版的,用于验证算法。后面还有C语言版的,比较实用。
MATLAB版:
img= imread('20160303.bmp');%读取图像,灰度
%img= rgb2gray(img);%如果是彩色的,要转成灰度!!!
[row col] = size(img);%输入图像的尺寸
%按照[行,列],而不是[x,y]
% 0----------------------->col
% |
% |
% |
% |
% |
% V
% row
p1=[21 57];%这四个点是根据本程序中使用的图像事先取好的,可以是图像中的任意的部分。顺序为左上,右上,左下,右下
p2=[21 108];
p3=[53 39];
p4=[53 120];
%得到p1,p2,p3,p4这个小矩形所对应的变换后的矩形的宽
w=round(sqrt((p1(1)-p2(1))^2+(p1(2)-p2(2))^2));
%矩形的高
h=round(sqrt((p1(1)-p3(1))^2+(p1(2)-p3(2))^2));
%这里是新的顶点,由于取的是矩形 ,使用以下的方式计算,如果取其他形状,就要修改这里
q1=p1;
q2=[p1(1) p1(2)+w];
q3=[p1(1)+h p1(2)];
q4=[p1(1)+h p1(2)+w];
%代入公式
B=[q1(1) q1(2) q2(1) q2(2) q3(1) q3(2) q4(1) q4(2)]';
A=[p1(1) p1(2) 1 0 0 0 -q1(1)*p1(1) -q1(1)*p1(2);
0 0 0 p1(1) p1(2) 1 -q1(2)*p1(1) -q1(2)*p1(2);
p2(1) p2(2) 1 0 0 0 -q2(1)*p2(1) -q2(1)*p2(2);
0 0 0 p2(1) p2(2) 1 -q2(2)*p2(1) -q2(2)*p2(2);
p3(1) p3(2) 1 0 0 0 -q3(1)*p3(1) -q3(1)*p3(2);
0 0 0 p3(1) p3(2) 1 -q3(2)*p3(1) -q3(2)*p3(2);
p4(1) p4(2) 1 0 0 0 -q4(1)*p4(1) -q4(1)*p4(2);
0 0 0 p4(1) p4(2) 1 -q4(2)*p4(1) -q4(2)*p4(2)];
%得到变换系数
tt=inv(A)*B;
%得到变换矩阵
perTra=[tt(1) tt(2) tt(3);
tt(4) tt(5) tt(6);
tt(7) tt(8) 1 ]
%首先要确定图像的边界,MATLAB中的数字是从1开始的,左上角的点应该是(1,1)而不是(0,0)
edge=perTra*[1 row 1 row;
1 1 col col;
1 1 1 1 ];
leftUp=[edge(1,1)/edge(3,1) edge(2,1)/edge(3,1)]
leftDown=[edge(1,2)/edge(3,2) edge(2,2)/edge(3,2)]
rightUp=[edge(1,3)/edge(3,3) edge(2,3)/edge(3,3)]
rightDown=[edge(1,4)/edge(3,4) edge(2,4)/edge(3,4)]
%变换后的图像的宽和高
height=round(max([leftUp(1) leftDown(1) rightUp(1) rightDown(1)])-min([leftUp(1) leftDown(1) rightUp(1) rightDown(1)]))
width=round(max([leftUp(2) leftDown(2) rightUp(2) rightDown(2)])-min([leftUp(2) leftDown(2) rightUp(2) rightDown(2)]))
imgOut=zeros(height,width);%定义新图像
upEdge=round(abs(min([leftUp(1) leftDown(1) rightUp(1) rightDown(1)])));
leftEdge=round(abs(min([leftUp(2) leftDown(2) rightUp(2) rightDown(2)])));
%变换后的图像与原图像可能会有很大的差异,从变换后的图像中,找所对应的原始图像的坐标的点
%这样做可以防止变换后的图像中出现空缺或多重的映射
invPerTra=inv(perTra);
for i = 1-upEdge:height-upEdge
for j = 1-leftEdge:width-leftEdge
originalPix=invPerTra*[i,j,1]';
originalPix(1)=originalPix(1)/originalPix(3);
originalPix(2)=originalPix(2)/originalPix(3);
if originalPix(1)>=1 && originalPix(2)>=1 && originalPix(1)<=row && originalPix(2)<=col
imgOut(i+upEdge,j+leftEdge)=img(round(originalPix(1)),round(originalPix(2))); %这里偷懒啦!使用最简单的最邻近插值
end
end
end
figure;
imshow(uint8(imgOut));
C语言版(为了保证移植性,没有使用函数库):
void PerspectiveTransformation(VERTEX LU,VERTEX RU,VERTEX LD,VERTEX RD)
{
int i,j,k,l,m;
int flag;
int wid,hig;
double width,high;
int dx,dy;
double P1[3][1]={1,1,1};
double PE[3][1]={0};
// double B[8][1];
// double A[8][8];
VERTEX LU1,LD1,RU1,RD1;//定义四个顶点
VERTEXINT LUn,LDn,RUn,RDn;//变换后的四个顶点
///乱给的数/
LU.x=46;
LU.y=24;
RU.x=470;
RU.y=48;
LD.x=15;
LD.y=278;
RD.x=470;
RD.y=318;
width=(sqrt((LU.x-RU.x)*(LU.x-RU.x)+(LU.y-RU.y)*(LU.y-RU.y))+0.5);//新矩形宽
high=(sqrt((LU.x-LD.x)*(LU.x-LD.x)+(LU.y-LD.y)*(LU.y-LD.y))+0.5);//新矩形高
printf("width:%f\n",width);
printf("high:%f\n",high);
变换后的顶点,方程右边的数/
LU1.x=LU.x;
LU1.y=LU.y;
RU1.x=LU.x+width;
RU1.y=LU.y;
LD1.x=LU.x;
LD1.y=LU.y+high;
RD1.x=LU.x+width;
RD1.y=LU.y+high;
{
double B[8][1]={LU1.x,LU1.y, RU1.x, RU1.y, LD1.x, LD1.y, RD1.x ,RD1.y};// %变换后的四个顶点,方程右边的值
double A[8][8]={
{LU.x , LU.y, 1 , 0 , 0 , 0, -LU1.x*LU.x, -LU1.x*LU.y},
{ 0 , 0 , 0 , LU.x, LU.y, 1, -LU1.y*LU.x, -LU1.y*LU.y},
{RU.x , RU.y, 1 , 0 , 0 , 0, -RU1.x*RU.x, -RU1.x*RU.y},
{ 0 , 0 , 0 , RU.x, RU.y, 1, -RU1.y*RU.x, -RU1.y*RU.y},
{LD.x , LD.y, 1 , 0 , 0 , 0, -LD1.x*LD.x, -LD1.x*LD.y},
{ 0 , 0 , 0 , LD.x, LD.y, 1, -LD1.y*LD.x, -LD1.y*LD.y},
{RD.x , RD.y, 1 , 0 , 0 , 0, -RD1.x*RD.x, -RD1.x*RD.y},
{ 0 , 0 , 0 , RD.x, RD.y, 1, -RD1.y*RD.x, -RD1.y*RD.y}};
/
inv_c(A);//求逆运算,可以参考我的另一篇boke
mulMatrix4B(invA,B,SIZE,SIZE,1,FA);
}
{
double pers[3][3]={{FA[3][0],FA[4][0],FA[5][0]},{FA[0][0],FA[1][0],FA[2][0]},{FA[6][0],FA[7][0],1}};
mulMatrix4P(pers,P1,3,3,1,PE); //复数运算
for(i=0;i<3;++i)
{
PE[i][0]/=(FA[6][0]+FA[7][0]+1);
//printf("LU:%0.31f\n\n",PE[i][0]);
}
LUn.x=(int)(PE[1][0]+0.5);
LUn.y=(int)(PE[0][0]+0.5);
printf("LUn.x:%d\nLUn.y:%d\n",LUn.x,LUn.y);
for(i=0;i<3;++i)
{
PE[i][0]=0;
}
P1[1][0]=IMGCOLSp;
mulMatrix4P(pers,P1,3,3,1,PE);
for(i=0;i<3;++i)
{
PE[i][0]/=(FA[6][0]+FA[7][0]*IMGCOLSp+1);
//printf("RU:%0.31f\n\n",PE[i][0]);
}
RUn.x=(int)(PE[1][0]+0.5);
RUn.y=(int)(PE[0][0]+0.5);
printf("RUn.x:%d\nRUn.y:%d\n",RUn.x,RUn.y);
for(i=0;i<3;++i)
{
PE[i][0]=0;
}
P1[0][0]=IMGROWSp;
P1[1][0]=1;
mulMatrix4P(pers,P1,3,3,1,PE);
for(i=0;i<3;++i)
{
PE[i][0]/=(FA[6][0]*IMGROWSp+FA[7][0]+1);
//printf("LD:%0.31f\n\n",PE[i][0]);
}
LDn.x=(int)(PE[1][0]+0.5);
LDn.y=(int)(PE[0][0]+0.5);
printf("LD.x:%d\nLD.y:%d\n",LDn.x,LDn.y);
for(i=0;i<3;++i)
{
PE[i][0]=0;
}
P1[1][0]=IMGCOLSp;
mulMatrix4P(pers,P1,3,3,1,PE);
for(i=0;i<3;++i)
{
PE[i][0]/=(FA[6][0]*IMGROWSp+FA[7][0]*IMGCOLSp+1);
}
RDn.x=(int)(PE[1][0]+0.5);
RDn.y=(int)(PE[0][0]+0.5);
printf("RD.x:%d\nRD.y:%d\n",RDn.x,RDn.y);
inv_cp(pers);
}
if(LUn.x>LDn.x)
{
k=LUn.x;
l=LDn.x;
}
else
{
k=LDn.x;
l=LUn.x;
}
if(k.x)
{
k=RUn.x;
}
if(k.x)
{
k=RDn.x;
}
if(l>RUn.x)
{
l=RUn.x;
}
if(l>RDn.x)
{
l=RDn.x;
}
//printf("big:%d\nsmall:%d\n",k,l);
wid=k-l;
dx=l>0?l:(-l);
//printf("wid:%d\n",wid);
if(LUn.y>LDn.y)
{
k=LUn.y;
l=LDn.y;
}
else
{
k=LDn.y;
l=LUn.y;
}
if(k.y)
{
k=RUn.y;
}
if(k.y)
{
k=RDn.y;
}
if(l>RUn.y)
{
l=RUn.y;
}
if(l>RDn.y)
{
l=RDn.y;
}
//printf("big:%d\nsmall:%d\n",k,l);
hig=k-l;
dy=l>0?l:(-l);
printf("hig:%d\n",hig);
printf("wid:%d\n",wid);
for(i=0;i
{
for(j=0;j
{
P1[0][0]=i;
P1[1][0]=j;
P1[2][0]=1;
mulMatrix4P(persOut,P1,3,3,1,PE);
{
double C[2][2]={FA[6][0]*PE[0][0]-1,FA[7][0]*PE[0][0],FA[6][0]*PE[1][0],FA[7][0]*PE[1][0]-1};
double CC[2][1]={-PE[0][0],-PE[1][0]};
inv_c2(C);
mulMatrix42(out2,CC,2,2,1,P2);
//printf("%f\n",C[1][1]);
}
// printf("%f\n",P2[0][0]);
if(P2[0][0]>=0.5 && P2[1][0]>=0.5 && P2[0][0]<=IMGROWSp && P2[1][0]<=IMGCOLSp)
{
imageOut[i][j]=BMP2.grayMatrix[(int)P2[0][0]][(int)P2[1][0]]; //最邻近插值
}
// {
// printf("%f\n",P2[0][0]);
// printf("%f\n",P2[1][0]);
// }
if(m0 ][0])
{
m=P2[0][0];
}
}
}
/
printf("max:%d\n",m);
bmpOutput8("yy.bmp",hig,wid);//保存为BMP文件
}