本节书摘来自异步社区出版社《数字图像处理与机器视觉——Visual C++与Matlab实现》一书中的第1章,第1.1节,作者: 张铮 , 王艳平 , 薛桂香,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.1 Matlab操作简介
数字图像处理与机器视觉——Visual C++与Matlab实现
本节将介绍一些Matlab中与图像处理密切相关的数据结构及基本操作,如基本文件操作、变量使用、程序流程控制、打开和关闭图像以及图像格式转换和存储方式等。这些都是后续学习图像处理算法的基础。
1.1.1 Matlab软件环境
1.软件界面
如图1.1所示是运行于32-bit Windows操作系统上的Matlab R2008a截图。软件主界面由3个子窗口组成,左上为当前工作目录的文件列表(可以通过上方的Current Directory组合框更改当前目录)和当前工作区的变量(可以通过上方的标签切换),左下为当前和最近会话的命令历史记录,右侧的主窗口则是命令输入和结果输出区,“>>”为提示符。
2.Matlab命令与程序
可以在>>提示符后面输入简单的算式(如“53-2”)或带有函数的算式(如“sin(pi/2)sqrt(3)/2”)并回车,系统会提示计算结果,这就是Matlab最基本的计算功能。
这样的输入形式实际上是Matlab命令。如果在每行命令的结尾输入半角分号,则命令窗口不会立即显示命令执行的结果,而会将结果保存在工作区中。例如:
>> res = sin(pi/2)*sqrt(3)/2; % 将计算结果保存至变量res当中
此时,变量res已经存在于工作区中,但是命令窗口不会显示它的值。
另外,也可以在文件菜单下执行“New”→“M-Files”命令来创建一个新的Matlab文件,在文件中输入命令(以半角分号结尾),即可得到一个Matlab程序。在Matlab程序中,使用“%”表示注释,其用法和C/C++中的“//”注释符类似。
3.跨行语句
Matlab允许在同一行中输入多条语句,之间用分号隔开。同时,Matlab还允许将同一条语句分割在多行中书写以方便阅读较长的语句,方法是在行末使用三个半角圆点。例如:
>> z = 2 .* x +exp( x .^ 2 + y .^ 2 - sqrt(1 - log(x) - log (y) ) )...
- y .* sqrt(t) - x .* sqrt(t);
1.1.2 文件操作
默认情况下,Matlab可以自动搜索到当前目录(Current Directory)和其路径变量path中所含目录下面的文件,且直接在命令窗口中键入这些文件名即可运行。如果需要直接运行其他目录下的文件,则需使用addpath和genpath等命令向路径列表中添加路径。
1.addpath函数
该函数用于向path变量中加入指定的目录路径,其原型如下:
addpath('dir','dir2','dir3' ...'-flag')
该函数可以接受任意数目的参数。
参数说明:
dir、dir2、dir3等为要加入的目录路径,这些变量必须是绝对路径。
flag参数可以用来指定函数的行为,它是可选参数,其取值的含义如表1.1所示。
可以在使用addpath函数前后查看path变量的内容,以确定添加成功。
2.genpath函数
该函数用于生成包含指定目录下所有子目录的路径变量,其原型如下:
p = genpath('directory');
参数说明:
参数directory为指定的目录。
返回值:
函数返回包含指定目录本身和其全部子目录的数据。返回值也可以直接提供给addpath,从而直接添加一个目录及其全部子目录到当前路径列表中。通过这样的方式可以方便地调用我们自己的程序工具箱,例如使用下面的命令将目录“F:doctor researchMatlab WorkFaceRec”添加到系统当前路径列表后,就可以直接调用人脸识别工具箱FaceRec中的任何函数了。
>> addpath(genpath('F:\doctor research\Matlab Work\FaceRec')) %注意这里要使用绝对路径
也可以在运行M文件时使用完整的文件路径,从而避免同名文件的冲突问题,或是从资源管理器中将M文件拖动到Matlab的命令窗口中直接运行。
3.打开与编辑M文件
如果需要编辑某个M文件,可以使用open命令和edit命令,它们的调用形式如下:
open filename
edit filename
参数filename为需要打开的文件名。edit命令只能编辑M文件,而open命令可以使用windows默认操作打开一系列其他类型的文件。
1.1.3 在线帮助的使用
在Matlab中,有3种方法获取软件的在线帮助。
1.help命令
help命令可以用于查看Matlab系统或M文件中内置的在线帮助信息。命令格式为:
help command-name
command-name为需要查看在线帮助的命令或函数的名称。例如,想要查看doc命令的使用方法,可在命令提示符下直接输入help doc,如图1.2所示。
2.doc命令
doc命令可以用于查看命令或函数的HTML帮助,这种帮助信息可以在帮助浏览器窗口中打开。其调用格式如下:
doc function-name
doc命令可提供比help命令更多的信息,还可能包含图片或视频等多媒体例子,对图像处理工具箱中的函数更是如此。
3.lookfor命令
如果忘记命令或函数的完整拼写,可以使用lookfor命令查找当前目录和自动搜索列表下所有名字中含有所查内容的函数或命令。其调用格式如下:
lookfor keyword
keyword为指定要查找的关键字。此命令可以给出一个包含指定字符串的函数列表,其中的函数名称为超链接,点击即可查看该函数的在线帮助,如图1.3所示。
在后面的章节中,如果忘记了曾经提到的命令的含义,建议首先通过在线帮助寻求相关信息,增强自学能力。
1.1.4 变量的使用
变量可以保存中间结果和输出数值等信息,Matlab中变量的命名规则和C/C++等常见的编程语言很类似,也区分大小。另外,Matlab中的变量无需先行定义,但使用前一定要赋值。
1.变量的赋值
可以通过赋值语句来给变量赋值。赋值操作使用等号“=”,例如“a=5”是给a(注意不是A)变量赋值5,如果未定义变量a,Matlab会自动定义。在Matlab中,变量定义时不需要显式地指明类型,Matlab会根据等号右边的值自动确定变量的类型。默认情况下,数字的存储类型为double型或double型数组,字符的存储类型为char型,而字符串的存储类型则为char型数组。
对字符串赋值时,需要用半角单引号“'”括起来(注意不是双引号,也不是任何的全角字符),例如msg='Hello world'。
2.内部变量
Matlab有某些内部变量名和保留字,如表1.2所示。变量命名时不要与它们重名。
表1.2 Matlab内部变量列表
3.查看工作区中的变量
使用who和whos命令可以查看所有当前工作区中变量的情况。使用clear或clear all命令可以清除工作区中所有的变量定义,也可以在clear后面加上变量名清除特定的变量定义。另外,clear命令可以用来清屏,所以这两个命令常常用在M文件的开头用来构造一个干净的工作区。
>> a = 1; %定义一个变量a
>> v = [1 2 3]; %定义一个向量v
>> whos %查看当前工作区中的变量
Name Size Bytes Class
a 1x1 8 double array
v 1x3 24 double array
Grand total is 4 elements using 32 bytes
>> clear all %清空工作区
>> whos %再次查看,工作区中已无变量
>>
4.数据类型及其转换
Matlab中的数据类型列表如表1.3所示。
默认情况下,Matlab将变量存储为双精度浮点数(double),而Matlab中的很多函数也只接收这种类型的数据。然而,图像处理操作中经常使用到uint8等类型的数据,这就需要执行数据类型的强制转换操作。这种操作很简单,调用格式统一为:
Destination_Var = type_name(Source_Var)
其中,type_name即数据的存储类型,Destination_Var和Source_Var分别为目标变量和原始变量。
5.读取与保存工作区中的变量
save命令可以将当前工作区的变量以二进制的方式保存到扩展名为MAT的文件中;load命令可以读出这样的文件。它们的调用格式为:
save filename arg1 arg2 arg3, …
load filename arg1 arg2 arg3, …
filename参数用于指定保存或读取变量所使用的文件名。如果不指定文件名,默认使用的文件是matlab.MAT。
arg1、arg2、arg3等参数为需要从文件中存储或读出的变量名。这两个命令分别可以存储或读取一个或一组变量。
例如,下面的命令将price、age和number 3个变量保存到文件MyData.mat中:
>> save('MyData.mat', 'price', 'age', 'number')
也可以不指定变量名,从而将当前工作区中所有的变量一起储存到mat文件或将文件中保存的所有变量一起读入工作区,这个批量保存和读取功能在运行非常耗时的程序时显得十分有用——由于Matlab执行效率并不高(和Visual C++相比),所以对于一个计算量很大的程序而言,运行几个小时并不稀奇。这时,可以根据需要在希望中断程序时保存程序的所有上下文变量,以备之后随时从中断点开始执行。
1.1.5 矩阵的使用
1.矩阵的定义
在Matlab中定义矩阵很简单。可以使用半角分号分隔行与行,使用半角逗号(或者空格)分隔列与列来直接定义矩阵,比如下面的命令就定义了一个3行3列的二维矩阵A:
A=[1, 2, 3; 4, 5, 6; 7, 8, 9]
A =
1 2 3
4 5 6
7 8 9
还有另一种方式可以生成行向量,如v=[2:1:10]表示生成从2到10的间隔为1的向量(一维矩阵),即:
v=
2 3 4 5 6 7 8 9 10
如果间隔为1,也可以忽略中间的参数,直接输入I=[2:10]即可。
2.生成特殊矩阵
除直接定义外,还可以通过函数生成特定的矩阵,比如eye(n)生成N 阶单位阵,zeros(n)生成N阶每个元素均为0的方阵,magic(n)生成N阶幻方阵等。常见的用于生成矩阵的函数列表如表1.4所示。
3.获得矩阵大小和维度
size函数可以获得指定数组某一维的大小,可以用来查看图像的高度和宽度以及动态图像的帧数等。其调用方法为:
size(A,dim)
参数说明:
参数A为需要查看大小的数组;
参数dim为指定的要查看的维数,这是一个可选参数,若不指定此参数,则返回值为一个包含数组从第一维到最后一维大小的数组。
例如,对于一个3行5列的矩阵B,有size(B, 1)=3,size(B, 2) = 5,size(B) = [3 5]。
函数ndims可以查看数组的维数。调用方式为:
ndims(A)
其中参数A为需要查看维数的数组。
4.访问矩阵元素
访问矩阵的一个元素的方式是在矩阵名字的后面注明行列序号,例如访问A的第三行第二列元素就是A(3,2)。提取矩阵的一整行元素,如要提出A的第二行可使用A(2,:),如果是第二列则是A(:,2);而A(:)表示将矩阵按列存储得到一个长列向量。示例如下:
>> A=[1, 2, 3; 4, 5, 6; 7, 8, 9] ; %定义矩阵A
>> A(1, :) %提取第1行
ans =
1 2 3
>> A(:, 3) %提取第3列
ans =
3
6
9
>> A(:)'
ans =
1 4 7 2 5 8 3 6 9
icon-info注意:
Matlab中的矩阵下标是从1开始的,对图像矩阵也是一样,所以一个m×n的矩阵实际的下标范围为[1:m]和[1:n]。
对于矩阵A,提取矩阵元素或子块的方法如表1.5所示。
5.进行矩阵运算
可以像对数字操作一样对矩阵进行操作,常见算术运算符的使用方法如表1.6所示。
矩阵运算的求值顺序和一般的数学求值顺序相同:表达式从左向右执行,幂运算的优先级最高,乘除次之,最后是加减。如果有括号,则括号的优先级最高。
对于图像矩阵,还有一系列Matlab函数可以进行专门针对图像的像素级操作。如图像叠加——imadd,图像相减——imsubtract等。
1.1.6 细胞数组(Cell Array)和结构体(Structure)
1.细胞数组
在处理函数返回值和示波器部件输出时,常常会遇到不同维度的返回值同时被一个函数返回的情况。同时,通常用户也希望函数的输入参数尽可能少。Matlab提供了允许这样做的方式。
细胞数组是Matlab特有的一种数据结构,它的各个元素可以是不同的数据类型。细胞数组可采用下标访问。
例如,一个细胞数组可以采用如下方式定义:
Cell = {'Harry', 15, [1 0; 15 2]};
也可以通过{}加上索引来直接定义细胞数组的某个元素,如下所示:
%定义细胞数组的另一种方式
>>Cell {1}= 'Harry';
>>Cell{2}= 15;
>>Cell{3}= [1 0; 15 2];
注意,使用花括号{}而不是方括号[]来定义细胞数组。对细胞数组的访问方式也很简单,同样使用花括号{}来给定索引值。
%访问细胞数组
>> Cell{1}
ans =
Harry
>> Cell{2}
ans =
15
>> Cell{3}
ans =
1 0
15 2
而使用圆括号形式的索引可以得到变量的描述,如:
>> Cell(3)
ans =
[2x2 double]
需要注意的是,细胞数组中存储的是建立该对象时所使用的其他对象(矩阵或字符串、数字等)的拷贝而不是引用或指针,即使其他对象的值被改变,细胞数组中的值也不变。
2.结构体
结构体是另一种形式的聚合类型,它与C/C++中的结构体很相似,拥有多个不同类型的字段,通过圆点运算符“.”引用内部字段,字段必须具有独特的名字以便区分。访问结构体的方式与定义的方式相同。上面的例子如果用结构体表示,则为:
% 定义结构体
Struct.Name = 'Harry';
Struct.Age = 15;
Struct.SalaryMatrix = [1 0; 15 2];
>>
>> Struct %显示结构体的内容
Struct =
Name: 'Harry'
Age: 15
SalaryMatrix: [2x2 double]
>>
% 访问结构体的内部字段
>>name = Struct.Name;
访问结构体内容时,使用相同的语法即可,例如Struct.Name的值仍然是Harry。
这两种复合类型在保存用户输入和使用Simulink仿真输出时尤为常用。
1.1.7 关系运算与逻辑运算
关系运算符的运算结果是布尔量(0或1),具体说明如表1.7所示。
Matlab同样支持逻辑运算,常见的逻辑运算符如表1.8所示。
1.1.8 常用图像处理数学函数
Matlab最为强大的功能是依靠函数实现的,这些函数可能是Matlab内置的,也可能是由M文件提供的。常见的有sin、cos、tan、log、log2这样的数值函数和trace这样的矩阵函数,还有逻辑函数等。逻辑函数和矩阵函数在图像处理中应用较多,表1.9为其中较常用的一部分函数。关于这些函数更详细的用法描述,可以通过help或open命令获得。
调用Matlab函数的方法为:函数名后使用一对圆括号括住提供给函数的参数,如sin(t)。如果函数有返回值,但调用者没有指定接收返回值的变量,系统会使用默认的ans变量存储返回值。如果函数返回多个值,则ans中只保留第一个返回值,对于这种情况应显式地使用向量来接收返回值,如:
[V D] = eigs(A) ; %计算矩阵A的特征值和特征向量,返回值中V为特征向量,D为特征值
使用函数还应当注意以下几点:
(1)函数只能出现在等式的右边。
(2)每个函数依原型不同,对自变量的个数和类型有一定的要求,如sin和sind函数。
(3)函数允许按照规则嵌套,比如sin(acos(0.5))。
1.1.9 Matlab程序流程控制
Matlab提供了程序流程分支控制的语句,它们的用法几乎和C/C++中的用法完全一致,如表1.10所示。
1.简要示例
后文中将多次使用这些语句,因而在此给出几个简单的例子。可以通过菜单“File”→“New”→“M-Files”新建一个M文件,在出现的窗口中粘贴这些代码,然后运行。在非注释行处按下F12键可以设置断点,按下F5键可以运行程序。
【例1.1】 if语句和for循环及其嵌套。
arg=input('Input argument:'); % 提示输入arg变量
total = 0; detail = 0;
% if语句开始
if(arg==1)
% 外层for语句开始
for i=1:1:5
total = total + 1;
% 内层for语句开始
for j=1:0.1:2
detail = detail + total;
% 内层for语句结束
end
% 外层for语句结束
End
% if语句的另一分支
elseif (arg==2)
total = 0;
detail = total;
% if语句的其他所有分支
else
error('Invalid arguments!');
% if语句结束
end
detail % 显示detail变量
请注意本例中分号的使用。
【例1.2】 与例1.1类似的功能,使用switch分支和while循环。
arg=input('Input argument:');
total = 0; detail = 0;
% switch语句开始
switch arg
% 分支1
case 1
i=1;
% 外层while语句开始
while (i<=5)
total = total + 1;
i = i + 1;
j = 1;
% 内层while语句开始
while (j<=2);
detail = detail + total;
j = j + 0.1;
% 内层while语句结束
end
% 外层while语句结束
end
% 分支2
case 2
total = 0;
detail = total;
% 分支其他
case others
error('Invalid arguments');
% switch语句结束
end
detail
总结以上两个例子,可以发现,在分支较多时更适合使用switch,而for和while用于循环控制,这一点与C/C++是完全相同的。但是,相对于C/C++,Matlab有一个突出的优点,即可以自动生成元素之间具有特定间隔的矩阵,从而避免使用某些循环,这里仅仅使用二维的情况举例。
【例1.3】 产生一幅亮度按对角线方向的余弦规律变化的灰度图,比较一维方法和二维方法所需的时间。
A = rand(3000, 3000);
f = zeros(3000, 3000);
u0 = 100; v0 = 100;
tic; % 开始计时
% 一维方法
% 外层for循环开始
for r=1:3000
u0x=u0*(r-1);
% 内层for循环开始
for c=1:3000
v0y=v0*(c-1);
f(r,c) = A(r,c) * cos(u0x+v0y);
% 内层for循环结束
End
% 外层for循环结束
end
t1=toc % 停止计时并记录时间到t1
tic; % 重新开始计时
% 二维方法
r = 0:3000-1;
c = 0:3000-1;
[C, R] = meshgrid(c, r);
% meshgrid是生成网格坐标的函数,实际就是生成需要的二维像素点的坐标拟合表示
% 建议读者在这里中断,观察一下C和R矩阵的内容。
g = A .* cos(u0 .* R + v0 .* C);
%系统将自动执行“循环”操作,实质是对R和C中每个数据按照指定公式操作
t2 = toc % 停止计时并将计时值保存到t2
运行后可以发现,t2远小于t1。因此,在用Matlab对数字图像按像素进行操作时,应尽可能地避免使用笨拙的多层嵌套循环。
2.meshgrid函数
例1.3中用到的meshgrid函数用于根据给定的横纵坐标点生成坐标网格,以便计算二元函数的取值,在绘制三维曲面时常常会用到它。其调用方式如下:
[X,Y] = meshgrid(x, y)
参数说明:
x为输入的横坐标。
y为输入的纵坐标。
返回值:
x和y为输出采样点的横坐标矩阵和纵坐标矩阵,x阵和y阵的元素分别为对应位置点的横坐标和纵坐标。
下面以绘制二维高斯函数曲面为例说明meshgrid的用法。
中心在原点的二维高斯函数表达式为:
下面的程序分别为u和v赋值[-10:0.1:10],令σ=3,使用meshgrid函数生成网格,并计算函数值,(这里使用的是.^和./,而不是^和/,因为计算的对象是矩阵中的元素),然后再使用mesh函数将其显示到绘图窗口中。生成的图像如图1.4所示。
图1.4 高斯函数曲面
u = [-10:0.1:10];
v = [-10:0.1:10];
[U,V] = meshgrid(u,v);
H = exp(-(U.^2 + V.^2)./2/3^2);
mesh(u, v, H);
% mesh函数是绘制三维曲面的函数,第一个和第二个参数分别为_x_轴和_y_轴的坐标点序列,第三个参数为在由坐标点序列确定的每一个方格点上的函数值。
优化小技巧:提前分配矩阵内存。
此技巧与动态内存的使用有关。在C/C++里,使用大块动态内存往往意味着堆操作,而当分配的动态内存零散无序时,会产生大量内存碎片,进而导致内存分配和回收效率降低。所以,可以事先分配一块足够大的空间(当然,不是过大)以尽量减少内存碎片的产生。事实上,Matlab中分配动态内存远没有C/C++那样麻烦,只需要类似如下一条语句即可:
memo = zeros(1024, 128);
这条语句本来用于构造一个元素全部为零的矩阵,但同时也很自然地分配了一块足够大的空间。
1.1.10 M文件编写
M文件和C/C++中c/cpp文件类似,就是存储Matlab代码并可以执行的文件。Matlab的源代码文件可以直接执行而不需编译(也可以通过编译使代码运行得更快)。很多情况下,M文件用于封装一个功能函数从而提供某些特定功能。一般来说,M文件以文本格式存储,执行顺序从第1行开始向下运行,以遇到终止语句结束,用户可以在M文件中定义函数和过程。
M文件可以使用任何文本编辑器编写,但最常使用的还是Matlab自带的M文件编辑器。如果编写函数,则最好将它放在Matlab搜索路径列表中的某个目录下,并与系统自带的M文件分开,以便管理。
对位于当前工作目录中的M文件,可以直接在命令行输入其文件名来运行它。作为函数时,M文件也可以接受参数。
稍后将提供一个Matlab自带M文件的例子,并给予简单分析。
1.1.11 Matlab函数编写
1.函数语法
Matlab函数通常在M文件中定义,一个文件可以定义多个函数。一个Matlab函数通常包含以下组成部分。
(1)函数定义行。
function [outputs] = name(inputs)
Matlab允许返回多个参数(outputs),如果只返回一个参数,可以省略方括号。需要注意的是,输入参数是使用圆括号括起来的。例如,如果要定义一个用于平滑图像的函数,可以将定义行书写如下:
function [imgOut, retCode] = smooth(imgIn, args)
某些函数可能没有输出参数,那么就需要在省略方括号及其中内容的同时,省略等号。于是无返回值的函数就需要定义为:
function smooth(imgIn, args)
Matlab允许区分函数名的前63个字母,多出的字母将被忽略。函数的命名规则与C/C++类似,必须以字母开头,可以包含字母、数字和下划线,但不能包含空格。
函数可以在其他的M函数中被调用,也可以在命令行直接调用。调用函数的方法很简单,只需要写出函数定义中除了function之外的部分即可。例如:
[a,b] = smooth(I,arg);
(2)“H1”行。
“H1”行是M文件中的第一个注释行(即以百分号开始的行),它必须紧跟着函数定义行,中间不能有空行,这一行的百分号前也不能有空白字符或缩进。这一行的内容将在使用help命令时显示在第一行,而lookfor命令用于查找H1行中的指定关键词,并在结果的右侧列显示H1行。一个典型的H1行的例子为:
% SMOOTH Perform smooth operation on specified image with certain arguments.
这样,smooth函数在lookfor命令查找时显示如下:
SMOOTH SMOOTH Perform smooth operation on specified image with certain arguments.
(3)帮助文本。
帮助文本的位置和约定同H1行类似,只能紧跟H1行,中间不能有任何空行或者缩进。同样,帮助文本的本质就是注释行,因而需要以%开头。
Matlab通过判断是否紧跟函数定义来判断一个注释行究竟是H1行、帮助文本还是普通注释。因此可以在加入一个或多个空行后加入普通注释,使用HELP命令将不会显示普通注释的内容。
(4)函数体和备注。
这些部分的编写方式和普通的Matlab程序类似,如果有返回值,应在函数体中为输出变量赋值。
2.一个M文件的例子片段——imfinfo.m
(可以在命令行输入edit imfinfo查看完整源文件)
function info = imfinfo(filename, format) 函数定义
%IMFINFO Information about graphics file. H1行
% INFO = IMFINFO(FILENAME,FMT) returns a structure whose 帮助文本
% fields contain information about an image in a graphics
% file. FILENAME is a string that specifies the name of the
% graphics file, and FMT is a string that specifies the format
% of the file. The file must be in the current directory or in
% a directory on the Matlab path. If IMFINFO cannot find a
% file named FILENAME, it looks for a file named FILENAME.FMT.
% Copyright 1984-2007 The MathWorks, Inc.
% $Revision: 1.1.6.12 $ $Date: 2007/11/09 20:33:41 $
error(nargchk(1, 2, nargin, 'struct')); 函数体和注释
…(函数体)
% Delete temporary file from Internet download.
if (isUrl)
deleteDownload(filename);
end
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。