MATLAB实现图像的平移缩放和旋转要点
数字图像处理课程设计
题目图像的几何变换重建
系 (部) 信息工程系
班级
姓名
学号
指导教师
2013 年 12 月 16 日至 12 月 27 日共 2 周
2013年 12 月 27 日
数字图像处理课程设计任务书
课程设计成绩评定表
目录
1 引言 (4)
1.1课程设计的目的.......................... 错误!未定义书签。
1.2课程设计的任务.......................... 错误!未定义书签。
1.3课程设计的要求.......................... 错误!未定义书签。
1.4开发工具................................ 错误!未定义书签。2设计内容 (4)
2.1设计内容 (4)
2.2 系统框图 (4)
3 设计方案 (5)
3.1功能模块的划分 (5)
3.2算法描述 (5)
3.3实现主要功能的原理和方法 (8)
3.3.1最近邻域插值法 (8)
3.3.2双线性插值法 (8)
4功能模块的具体实现 (10)
4.1 模块功能 (10)
4.2流程图 (11)
4.3程序清单及各模块的实现效果图 (11)
4.4 系统仿真与调试 (21)
5 总结与体会 (22)
参考文献 (22)
附录 (23)
1 引言
2设计内容
2.1设计内容
我选取的是图像的几何变换,设计内容如下,
(1)能够读取和存储图像。
(2)实现图像的平移、缩放、旋转几何变换。
(3)分别采用最近邻插值和双线性插值技术进行图像重建。
(4)编写代码实现上述功能。
图2-1系统框图
本次课设所做的图像几何变换包括平移变换、缩放变换和旋转变换。缩放变换和旋转变换均用双线性插值变换和最近邻插值变换两种方法来做,对图像进行处理后再存储。
3 设计方案
3.1功能模块的划分
本次设计整体分为四大模块:原图、平移、缩放和旋转。其中缩放和旋转又分别有双线性插值和最近邻插值两模块。
3.2算法描述
图像比例缩放是指将给定的图像在x 轴方向按比例缩放fx 倍,在y 轴按比例缩放fy 倍,从而获得一幅新的图像。如果fx=fy ,即在x 轴方向和y 轴方向缩放的比率相同,称这样的比例缩放为图像的全比例缩放。如果fx ≠fy ,图像的比例缩放会改变原始图象的像素间的相对位置,产生几何畸变。
设原图像中的点P 0(x 0,y 0)比例缩放后,在新图像中的对应点为P(x,y),则比例缩放前后两点P 0(x 0,y 0)、P(x,y)之间的关系用矩阵形式可以表示为
?????
???????????????=??????????1100
0000
100y x fy fx y x (3-1)其逆运算为
?????
?????????????
????????
?=??????????110
010001100y x fy fx y x (3-2)即
???
?
??
?==fy y y fx x x 00
(3-3) 比例缩放所产生的图像中的像素可能在原图像中找不到相应得像素点,这样就必须进行插值处理。下面首先讨论图像的比例缩小。最简单的比例缩小时当fx=fy=0.5时,图像被缩到一半大小,此时缩小后图像中的(0,0)像素对应于原图像中的(0,0)像素;(0,1)像素对应于原图像中的(0,2)像素;(1,0)像素对应于原图像中的(2,0)像素,以此类推。图像缩小之后,因为承载的数据量小了,所以画布可相应缩小。此时,只需在原图像基础上,每行隔一个像素取一点,每隔一行进行操作,即取原图的偶奇数行和偶奇数列构成新的图像。如果图像按任意比例缩小,
则需要计算选择的行和列。
如果M×N 大小的原图像F(x,y)缩小为kM×kN 大小的新图像I(x,y)时,则
I(x,y)=F(int(c×x),int(c×y)) (3-4)
其中,c=1/k 。由此公式可以构造出新图像。
当fx ≠fy 时,图像不按比例缩小,这种操作因为在x 方向和y 方向的缩小比例不同,一定会带来图像的几何畸变。图像不按比例缩小的方法是:如果M×N 大小的旧图F(x,y)缩小为k 1M×k 2N 大小的新图像I(x,y)时,则
I(x,y)=F(int(c 1×x),int(c 2×y)) (3-5)
其中c 1=1/k 1,c 2=1/k 2,由此公式可以构造出新图像。
图像的缩小操作中,是在现有的信息里如何挑选所需要的有用信息。而在图像的放大操作中,则需要对尺寸放大后所多出来的空格填入适当的像素值,这是信息的估计问题,所以较图像的缩小要难一些。当fx=fy=2时,图像被按全比例放大二倍,放大后图像中的(0,0)像素对应于原图中的(0,0)像素;(0,1)像素对应于原图中的(0,0.5)像素,该像素不存在,可以近似为(0,0)也可以近似为(0,1);(0,2)像素对应于原图像中的(0,1)像素;(1,0)像素对应于原图中的(0.5,0),它的像素值近似于(0,0)或(1,0)像素;(2,0)像素对应于原图中的(1,0)像素,依此类推。其实这是将原图像每行中的像素重复取值一遍,然后每行重复一次。
按比例将原图像放大k 倍时,如果按照最近邻域法则需要将一个像素值添在新图像的k×k 的子块中。显然,如果放大倍数太大,按照这种方法处理会出现马赛克效应。当fy fx ≠)0,(>fy fx 时,图像在x 方向和y 方向不按比例放大,此时这种操作由于x 方向和y 方向的放大倍数不同,一定会带来图像的几何畸变。放大的方法是将原图像的一个像素添到新图像的一个21k k ?的子块中去。为了提高几何变换后的图像质量,常采用线性插值法。该方法的原理是,当求出的分数地址与像素点不一致时,求出周围四个像素点的距离比,根据该比率,由四个邻域的像素灰度值进行线性插值。
旋转。一般图像的旋转是以图像的中心为原点,旋转一定的角度,也就是将图像上的所有像素都旋转一个相同的角度。旋转后图像的的大小一般会改变,即可以把转出显示区域的图像截去,或者扩大图像范围来显示所有的图像。图像的旋转变换也可以用矩阵变换来表示。设点),(000y x P 逆时针旋转θ角后的对应点为
),(y x P 。那么,旋转前后点),(000y x P 、),(y x P 的坐标分别是:
??
?==α
α
cos cos 00r y r x (3-6) ?
?
?+=+=+=-=-=+=θθθαθαθαθ
θθαθαθαcos sin sin cos cos sin )sin(sin cos sin sin cos cos )cos(0000y x r r r y y x r r r x (3-7)
写成矩阵表达式为
????
?
???????????????-=??????????110
0cos sin 0sin cos 100y x y x θ
θθθ (3-8)其逆运算为
????
?
???????????????-=??????????110
0cos sin 0sin cos 100y x y x θ
θθθ
(3-9) 利用上述方法进行图像旋转时需要注意如下两点:
(1)图像旋转之前,为了避免信息的丢失,一定要有坐标平移。
(2)图像旋转之后,会出现许多空洞点。对这些空洞点必须进行填充处理,否则画面效果不好,一般也称这种操作为插值处理。
以上所讨论的旋转是绕坐标轴原点(0,0)进行的。如果图像旋转是绕一个指定点(a,b)旋转,则先要将坐标系平移到该点,再进行旋转,然后将旋转后的图象平移回原来的坐标原点,这实际上是图像的复合变换。如将一幅图像绕点(a,b)逆时针旋转θ度,首先将原点平移到(a,b),即
??
??
?
?????--=1001001b a A (3-10) 然后旋转
????
??????-=00
0cos sin 0sin cos θ
θ
θθB (3-11) 然后再平移回来
??
??
?
?????=1001001b a C (3-12) 综上所述,变换矩阵为A B C T ??=。
3.3实现主要功能的原理和方法
3.3.1最近邻域插值法
最近邻域插值法:它输出的像素灰度值就等于距离它映射到的位置最近的输入像素的灰度值,取离I(i/b,j/a,k)最近的整数点的灰度值为点I(i/b,j/a,k)的灰度值。取最近整数点用四舍五入。在许多情况下,最近邻插值的结果可以令人接受。
最邻近点插值取插值点的4个邻点中距离最近的邻点灰度值作为该点的灰度值。设插值点(i,j)到周边4个邻点f k(i,j)(k =1,2,3,4)的距离为d k(k =1,2,3,4),则:g(i,j)=f k(i,j),d l=min{d1,d2,d3,d4},l=1,2,3,4。
最近邻点插值的一个隐含的假设条件是任一网格点p(x,y)的属性值都使用距它最的位置点的属性值,用每一个网格节点的最邻点值作为待的节点值[3]。当数据已经是均匀间隔分布,要先将数据转换为SURFER的网格文件,可以应用最近邻点插值法;或者在一个文件中,数据紧密完整,只有少数点没有取值,可用最近邻点插值法来填充无值的数据点。有时需要排除网格文件中的无值数据的区域,在搜索椭圆(SearchEllipse)设置一个值,对无数据区域赋予该网格文件里的空白值。设置的搜索半径的大小要小于该网格文件数据值之间的距离,所有的无数据网格节点都被赋予空白值。在使用最近邻点插值网格化法,将一个规则间隔的XYZ数据转换为一个网格文件时,可设置网格间隔和XYZ数据的数据点之间的间距相等。最近邻点插值网格化法没有选项,它是均质且无变化的,对均匀间隔的数据进行插值很有用,同时,它对填充无值数据的区域很有效。
3.3.2双线性插值法
双线性插值法:它是利用了需要处理的原始图像像素点周围的四个像素点的相关陛,通过双线眭算法计算得出的。对于一个目的坐标,通过向后映射法得到其在原始图像的对应的浮点坐标(i+u,j+v),其中i,j均为非负整数,u,v为[0,l]区间的浮点数,则这个像素的值f(i+u,j+v)可由原图像中坐标为(I,j)、(i+l,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:
f(i+u,j+v)=(1-u)×(1-v)×f(,j)+(1-u)×v×f(i,j+1)+u×(1-v)×f(i+l,j)+u×v×f(i+,j+1),其中f(i,j)表示源图像(i,j)处的的像素值,以此类推,这就是双线性插值法。
如图3-1所示,已知(0,0)、(0,1)、(1,0)、(1,1)四点的的灰度,可以由相邻像素的灰度值f(0,0)和f(1,0)在X方向上线性插值求出(x,0)的灰度f(x,0),由另外两个相邻像素f(0,1)和f(1,1)在X方向上线性插值可求出(x,1)的灰度f(x,1),最后由f(x,0),f(x,1)在Y方向上进行线性插值就可以得到(x,y)的灰度f(x,y)。
在同一行内根据待插值像素点与其前后的原图像像素点的位置距离进行加权线性插值,即离原图像像素点越近的待插值像素点,原图像像素的加权系数就
越大;行间根据待插值行与其上下的原图像行间的距离进行加权线性插值,即离原图像行越近的待插值行,原图像行的加权系数就越大。
假设原始图像大小为size=n m ?,其中m 与n 分别是原始图像的行数与列<数。若图像的缩放因子是t(t>0),则目标图像的大小size=m t n t ???。对于目标图像的某个像素点P(x,y)通过P*1/t 可得到对应的原始图像坐标P ’( x1,y1),其中x1=x/t ,y1=y/t ,由于x1,y1都不是整数所以并不存在这样的点,这样可以找出与它相邻的四个点的灰度f1、f2、f3、f4,使用双线性插值算法就可以得到这个像素点P ’(x1,y1)的灰度,也就是像素点P(x,y)的灰度。
一个完整的双线性插值算法可描述如下:
(1)通过原始图像和比例因子得到新图像的大小,并创建新图像。 (2)由新图像的某个像素(x,y)映射到原始图像(x ’,y ’)处。
(3)对x ’,y ’取整得到(xx,yy)并得到(xx,yy)、(xx+1,yy)、(xx,yy+1)和(xx+1,yy+1)的值。
(4)利用双线性插值得到像素点(x,y)的值并写回新图像。 (5)重复步骤(2)直到新图像的所有像素写完。
图3-1双线性插值原理
4功能模块的具体实现
4.1 模块功能
本课程设计通过菜单和按钮的方式来对图片进行各种几何变换,操作简单,处理结果一目了然。系统具备良好的图形用户界面,通过按钮或菜单进行操作控制,能让用户方便地选择不同的图像文件进行处理,并显示图像处理的结果。
利用菜单按钮及简介的版块设计实现各功能的处理结果与操作方式。介绍图片处理按钮菜单的功能。主界面设计如下:
图4-1主界面
4.2流程图
图4-2流程图
4.3程序清单及各模块的实现效果图
读入原图代码如下,
img=imread('lena.bmp');
subplot(2,3,1);imshow(img);
title('原始图像');
imread函数读入图像并通过imshow函数显示出来。
运行结果如下,
图4-3原始图像
图像的平移代码如下,
delta_x=str2double(inputdlg('请输入向右平移量', 'INPUT scale factor', 1, {'-60'})); %负数为向左
delta_y=str2double(inputdlg('请输入向下平移量', 'INPUT scale factor', 1, {'-89'})); %负数为向上
img=imread('lena.bmp'); %这里v为原图像的高度,u为原图像的宽度
[v u]=size(img); %这里y为变换后图像高度,x为变换后图像宽度imgn=zeros(v+abs(delta_y),u+abs(delta_x));
rot=[1 0 0;0 1 0;delta_y delta_x 1];
inv_rot=inv(rot);
pix1=[1 1 1]*rot; %pix1(1),pix1(2)分别为变换后图像的左上角的y,x
pix4=[v u 1]*rot; %pix4(1),pix4(2)分别为变换后图像的右下角的y,x
%%向右下方移动
if delta_x>=0 && delta_y>=0
for y=pix1(1):pix4(1)
for x=pix1(2):pix4(2)
pix=[y x 1]*inv_rot;
if pix(1)>=0.5 && pix(2)>=0.5 && pix(1)<=v && pix(2)<=u
imgn(y,x)=img(round(pix(1)),round(pix(2)));
end
end
end
end
%%向左下方移动
if delta_x<0 && delta_y>=0
for y=pix1(1):pix4(1)
for x=pix1(2):pix4(2)
pix=[y x 1]*inv_rot;
if pix(1)>=0.5 && pix(2)>=0.5 && pix(1)<=v && pix(2)<=u
imgn(y,x-delta_x)=img(round(pix(1)),round(pix(2)));
end
end
end
end
%%向左上方移动
if delta_x<0 && delta_y<0
for y=pix1(1):pix4(1)
for x=pix1(2):pix4(2)
pix=[y x 1]*inv_rot;
if pix(1)>=0.5 && pix(2)>=0.5 && pix(1)<=v && pix(2)<=u
imgn(y-delta_y,x-delta_x)=img(round(pix(1)),round(pix(2)));
end
end
end
end
%%向右上方移动
if delta_x>=0 && delta_y<0
for y=pix1(1):pix4(1)
for x=pix1(2):pix4(2)
pix=[y x 1]*inv_rot;
if pix(1)>=0.5 && pix(2)>=0.5 && pix(1)<=v && pix(2)<=u imgn(y-delta_y,x)=img(round(pix(1)),round(pix(2)));
end
end
end
end
subplot(2,3,4);
imshow(uint8(imgn));imwrite(uint8(imgn),'平移.bmp','bmp') ;
title('平移');
通过imread函数读取图像并编译运行之后首先出现对话框,输入数据点击确认,默认为向左偏移量为60 向上偏移量为89。再通过imwrite函数对图像进行存储。
图4-5运行平移出现的对话框
图4-6平移后的图像
图像的缩放变换。设计的缩放变换有双线性插值缩放和最近邻差值缩放两种。两种代码分别如下,
双线性插值缩放代码如下,
I=imread('lena.bmp'); %读入原图像
[rows,cols]=size(I);
K1 =str2double(inputdlg('请输入列缩放倍数', 'INPUT scale factor', 1, {'0.5'}));%行默认变为原来的0.5倍
K2 =str2double(inputdlg('请输入行缩放倍数', 'INPUT scale factor', 1, {'1.5'}));%列默认变为原来的1.5倍
width = K1 * rows;
height = K2 * cols;
Out = uint8(zeros(width,height)); %创建输出图像矩阵
widthScale = rows/width;
heightScale = cols/height;
for x = 6:width - 6 % 6是为了防止矩阵超出边界溢出for y = 6:height - 6
oldX = x * widthScale; % oldX,oldY为原坐标,x,y为新坐标
oldY = y * heightScale;
if (oldX/double(uint16(oldX)) == 1.0) & (oldY/double(uint16(oldY)) == 1.0) Out(x,y) = I(int16(oldX),int16(oldY));%若oldX,oldY为整数,直接赋值else
a = double(uint16(oldX));
b = double(uint16(oldY));
x11 = double(I(a,b)); % x11 赋值为I(a,b)
x12 = double(I(a,b+1)); % x12 赋值为I(a,b+1)
x21 = double(I(a+1,b)); % x21 赋值为I(a+1,b)
x22 = double(I(a+1,b+1)); % x22 赋值为I(a+1,b+1)
Out(x,y) = uint8( (b+1-oldY) * ((oldX-a)*x21 + (a+1-oldX)*x11) + (oldY-b) * ((oldX-a)*x22 +(a+1-oldX) * x12) ); % 用双线性插值计算公式计算end
end
end
subplot(2,3,5);
imshow(Out);
title('双线性插值缩放');imwrite(uint8(imgn),'双线性插值缩放.bmp','bmp') ; 运行程序之后,和平移一样也是会先后出现两个对话框,输入合适的数据,点击确认即出现结果。下图是行缩放0.5倍,列缩放0.5倍。
图4-7运行双线性插值缩放出现的对话框
图4-8双线性插值缩放后的图像
最近邻差值缩放代码如下,
I=imread('lena.bmp');%读入图像
[rows,cols]=size(I);
K1 = str2double(inputdlg('请输入列缩放倍数', 'INPUT scale factor', 1, {'0.5'}));%列默认变为原来的0.6倍
K2 = str2double(inputdlg('请输入行缩放倍数', 'INPUT scale factor', 1, {'1.5'}));%行默认变为原来的0.4倍
width = K1 * rows;
height = K2 * cols;
im2 = uint8(zeros(width,height)); %定义输出图像矩阵
widthScale = rows/width;
heightScale = cols/height;
for x = 6:width - 6 %为防止矩阵溢出而选择的参数6
for y = 6:height - 6
oldX = x * widthScale; %oldX,oldY为原坐标,x,y为新坐标
oldY = y * heightScale;
if(oldX/double(uint16(oldX)) == 1.0) & (oldY/double(uint16(oldY)) == 1.0) im2(x,y) = I(int16(oldX),int16(oldY));
else
a = double(round(oldX));
b = double(round(oldY)); %若不是整数四舍五入后把临近值赋过去
im2(x,y) = I(a,b);
end
end
end
subplot(2,3,6);
imshow(im2);
title('最近邻缩放');
imwrite(Out,'最近邻缩放.bmp','bmp');
运行程序之后,和上面一样也是会先后出现两个对话框,输入合适的数据,点击确认即出现结果。下图是选用默认值行缩放0.5倍,列缩放0.5倍。
图4-9运行最近邻插值缩放出现的对话框
图4-10最近邻插值缩放后的图像
旋转变换。设计的旋转变换有双线性插值旋转和最近邻插值旋转两种。
双线性插值旋转代码如下,
jiaodu=str2double(inputdlg('请输入旋转角度', 'INPUT scale factor', 1, {'60'})); %要旋转的角度,旋转方向为顺时针
img=imread('lena.bmp'); %这里v为原图像的高度,u为原图像的宽度
[h w]=size(img);
theta=jiaodu/180*pi;
rot=[cos(theta) -sin(theta) 0;sin(theta) cos(theta) 0;0 0 1];
pix1=[1 1 1]*rot; %变换后图像左上点的坐标
pix2=[1 w 1]*rot; %变换后图像右上点的坐标
pix3=[h 1 1]*rot; %变换后图像左下点的坐标
pix4=[h w 1]*rot; %变换后图像右下点的坐标
height=round(max([abs(pix1(1)-pix4(1))+0.5
abs(pix2(1)-pix3(1))+0.5])); %变换后图像的高度
width=round(max([abs(pix1(2)-pix4(2))+0.5abs(pix2(2)-pix3(2))+0.5]));
%变换后图像的宽度
imgn=zeros(height,width);
delta_y=abs(min([pix1(1) pix2(1) pix3(1) pix4(1)])); %取得y方向的负轴超出的偏移量
delta_x=abs(min([pix1(2) pix2(2) pix3(2) pix4(2)])); %取得x方向的负轴超出的偏移量
for i=1-delta_y:height-delta_y
for j=1-delta_x:width-delta_x
pix=[i j 1]/rot; %用变换后图像的点的坐标去寻找原图像点的坐标,
float_Y=pix(1)-floor(pix(1));
float_X=pix(2)-floor(pix(2));
if pix(1)>=1 && pix(2)>=1 && pix(1) <= h && pix(2) <= w
pix_up_left=[floor(pix(1)) floor(pix(2))]; %四个相邻的点
pix_up_right=[floor(pix(1)) ceil(pix(2))];
pix_down_left=[ceil(pix(1)) floor(pix(2))];
pix_down_right=[ceil(pix(1)) ceil(pix(2))];
value_up_left=(1-float_X)*(1-float_Y);%计算临近四个点的权重
value_up_right=float_X*(1-float_Y);
value_down_left=(1-float_X)*float_Y;
value_down_right=float_X*float_Y;
imgn(i+delta_y,j+delta_x)=value_up_left*img(pix_up_left(1),pix_up_left(2))+... value_up_right*img(pix_up_right(1),pix_up_right(2))+...
value_down_left*img(pix_down_left(1),pix_down_left(2))+...
value_down_right*img(pix_down_right(1),pix_down_right(2));
end
end
end
subplot(2,3,2);
imshow(uint8(imgn));
title('双线性插值旋转');imwrite(uint8(imgn),'双线性插值旋转.bmp','bmp') ;
运行程序之后,先后出现一个对话框,输入合适的数据,点击确认即出现结果。下图是选用默认值顺时针旋转60度。
图4-11运行双线性插值旋转出现的对话框
图4-12双线性插值旋转后的图像
最近邻插值旋转代码如下,
H=1; %索引pix中第一个元素,即高度
W=2; %索引pix中第二个元素,即宽度
jiaodu=str2double(inputdlg('请输入旋转角度', 'INPUT scale factor', 1, {'60'})); %要旋转的角度,旋转方向为顺时针img=imread('lena.bmp'); %这里v为原图像的高度,u为原图像的宽度[v u]=size(img);
theta=jiaodu/180*pi;
rot=[cos(theta) -sin(theta) 0;sin(theta) cos(theta) 0;0 0 1];
inv_rot=inv(rot);
pix1=[1 1 1]*rot; %变换后图像左上点的坐标
pix2=[1 u 1]*rot; %变换后图像右上点的坐标
pix3=[v 1 1]*rot; %变换后图像左下点的坐标
pix4=[v u 1]*rot; %变换后图像右下点的坐标
height=round(max([abs(pix1(H)-pix4(H))+0.5abs(pix2(H)-pix3(H))+0.5]));
%变换后图像的高度
width=round(max([abs(pix1(W)-pix4(W))+0.5abs(pix2(W)-pix3(W))+0.5]));
%变换后图像的宽度
imgn=zeros(height,width);