MATLAB计算机视觉实战
上QQ阅读APP看书,第一时间看更新

1.2 MATLAB编程基础

1.2.1 变量命名规则及其类型

1.变量的命名规则

MATLAB中变量的命名规则是:必须以字母开头,后面的字符可以由字母、数字和下画线混合组成。尽管变量名可以是任意长的,但是MATLAB仅取前N个字符,忽略后面的字符,因此要保证变量名的前N个字符有唯一确定性。N的值可以用函数namelengthmax查出来,在MATLAB 8.0中,N=63。

经验分享:在MATLAB中,变量区分大小写,因此变量A与变量a是两个不同的变量。用函数isvarname可以确定一个变量名是否为合法的。

2.变量类型

MATLAB中有三种基本的变量类型:局部变量、全局变量和静态变量。

通常,每个函数体内都有自己定义的变量,不能从其他函数和MATLAB工作空间访问这些变量,这些变量就是局部变量。

如果要使某个变量在几个函数中和MATLAB工作空间都能使用,可以把它定义为全局变量。在一个函数中改变了全局变量的值,会影响到每一个使用全局变量的函数。全局变量用关键字global声明。全局变量最好在函数体的开始声明,并且全局变量名尽量大写,并能够反映它本身的含义。如果需要在几个函数中和MATLAB工作空间内都能访问一个全局变量,则必须在每个函数中和MATLAB工作空间内都声明该变量为全局的。

经验分享:在实际编程中,应尽量避免使用全局变量。因为全局变量的值一旦在一个地方被改变,那么在其他包括该变量的函数中都将改变,这样有可能出现不可预见的情况。例如,在一个程序中声明了一个全局变量,而这个变量恰巧在另外一个程序中也被声明为全局变量,这样当两个程序运行时,一个程序中的变量值可能会覆盖另一个程序中的变量值。在编程中出现这种错误是极难发现的。

静态变量只能在M函数中用关键字persistent声明,只有声明了静态变量的函数才允许使用它。只要函数存在,MATLAB就不清除静态变量,因此静态变量的值可以从一个函数传递到另一个函数。使用静态变量必须先声明,最好把静态变量的声明放在程序的开始。例如,要把变量SUM_X声明为静态变量,可以用下面的形式声明:

persistent SUM_X

使用clear functionname、clear all语句或者编辑M函数,都会清除函数中所声明的静态变量。可以用mlock防止函数被清除,从而保证M函数中所声明的静态变量不被清除。

使用变量名时,注意不要使用关键字。MATLAB中的关键字是break、case、catch、classdef、continue、else、elseif、end、for、function、global、if、otherwise、parfor、persistent、return、switch、try、while,用函数iskeyword可以查看所有关键字。

1.2.2 基本程序结构

1.顺序结构

MATLAB语言的赋值语句格式是

variables_list=expression

等号左边的变量名列表为MATLAB语句的返回值。等号右边的表达式可以是数值计算式,也可以是函数调用等。

赋值语句的等号右端可以用分号结束,也可以用逗号结束或者直接回车。用逗号结束或直接回车,运行后变量名列表中所赋的值都会显示出来。用分号结束,运行后变量名列表中所赋的值不会显示出来。不同形式的赋值语句的运行效果如图1.2.1所示。

图1.2.1 不同语句的运行效果

经验分享:分号和逗号是语句的分隔符。在一行代码中可以有多个语句,语句之间用逗号或分号分隔。MATLAB中有些函数调用的返回值有多个,这时就需要把变量名列表用方括号[]括起来,方括号中的各个变量之间用逗号分隔。如果左边的变量名列表和等号省略,则MATLAB会自动把表达式的值赋给默认变量anx。

顺序结构是最简单的一种程序控制结构。用户只要按自己的要求将命令按顺序逐条编写即可。

例1.2.1 输入x,y的值,并将它们的值互换后输出。

分析:从键盘输入数据,可以使用input函数进行,该函数的调用格式为

A=input(提示信息,选项);

其中,提示信息为一个字符串,用于提示用户输入什么样的数据。

MATLAB提供的命令行窗口输出函数主要有disp函数,其调用格式为

disp(输出项)

其中,输出项既可以为字符串,也可以为矩阵。

程序如下:

2.条件转移语句

条件转移语句控制程序运行过程中执行哪一块程序代码。一种条件转移语句是if语句,根据判断条件为“真”或“假”选择代码块的执行;另一种条件转移语句是switch语句,根据条件表达式的值,选择执行哪一块代码。

1)if语句

if语句先计算一个由逻辑运算符<,<=,>,>=,==,~=等连接的逻辑表达式的值,根据逻辑表达式值的“真”或“假”来决定执行哪一部分代码。

(1)“if,end”形式。

条件转移语句最简单的形式是“if,end”形式,其调用格式是

如果逻辑表达式的值为“真”(逻辑值为1),则MATLAB执行if和end间的所有语句,然后再执行end后面的语句。如果逻辑表达式的值为“假”(逻辑0),则MATLAB跳过if和end之间的语句,直接执行end后面的语句。

例如:

这一程序段判断变量a是否为偶数,如果a是偶数,则先显示“a是偶数”,再把a除以2后赋值给变量b。其中,rem是求余函数。

判断语句的逻辑表达式也可以是非数值形式的,例如逻辑表达式是矩阵形式。

(2)“if,else,end”形式。

“if,else,end”形式的条件转移语句的调用格式是

判断逻辑表达式的值,如果为“真”,则执行语句块statements1,再跳到end后面执行语句;否则执行语句块statements2,再执行end后面的语句。

例如:

这一程序段判断变量a是否为偶数,如果a是偶数,则先显示“a是偶数”,再把a除以2后赋值给变量b;否则显示“a是奇数”,再把a的值赋给变量b。

例1.2.2 计算分段函数的值。

程序如下:

(3)“if,elseif,end”形式。

“if,elseif,end”形式的条件转移语句的调用格式是

当表达式logical_expression1为“真”时,执行语句statements1,然后跳到语句end后面执行;当表达式logical_expression1为“假”而表达式logical_expression2为“真”时,执行语句statements2,然后跳到语句end后面执行。其中elseif语句可以有一个也可以有多个。

经验分享:elseif是一个整体,不要在else和if之间添加空格。

例如:

这一程序段计算函数true的值。

(4)“if,elseif,else,end”形式。

“if,elseif,else,end”形式是if条件转移语句最完全的形式,其调用格式是

如果条件表达式logical_expression1的值为“真”,则执行语句块statements1,然后跳到end后面执行。如果条件表达式logical_expression1的值为“假”,而条件表达式logical_expression2的值为“真”,则执行语句块statements2,然后跳到end后面执行……如果上面所有条件表达式的值都为“假”,则执行语句statements,再到end后面继续执行。

经验分享:elseif语句可能有多个。

例如:

该程序段给出已知数x的符号。

经验分享:if条件语句中,if和end必须成对出现,就像是一对括号。在编程时,如果遗忘了end,MATLAB会在程序的后面找end与if配对,这样在程序运行时报告在某一位置出错时,可能错误出在更前面。在程序调试时,这种错误极难修改。在编程时,在if语句中可以再嵌入if语句。

例1.2.3 输入一个字符,若为大写字母,则输出其对应的小写字母;若为小写字母,则输出其对应的大写字母;若为数字字符,则输出其对应的数值;若为其他字符,则原样输出。

程序如下:

2)switch语句

用if形式的条件转移语句,如果检查的重数过多,会使得程序非常混乱。这时可以用switch形式的条件转移语句,其调用格式是

表达式expression计算出的是一个标量或是一个字符串。如果expression的值是value1,则运行语句块statements1,再跳到end后面执行;如果expression的值是value2,则运行语句块statements2,再跳到end后面执行;依次类推。如果各种情况都不满足,则执行语句块statements,再执行end后面的语句。

经验分享:value是表达式expression可能计算出的值,也可以是单元数组形式。语句块中也可以包含switch语句。case语句可以有多个,但otherwise语句只能有一个。如果有多个value值满足条件,则只执行第一个。

例如:

这段程序把用字母形式表示的成绩,转换成文字形式。

switch语句可以是如下形式:

经验分享:switch与end必须配对,两者就像括号一样把程序段括在一起。switch语句中可以没有otherwise语句。

例1.2.4 某商场对顾客所购买的商品实行打折销售,标准如下(商品价格用price表示):

输入所售商品的价格,求其实际销售价格。

程序如下:

3.循环语句

用循环语句来重复执行一段代码。在MATLAB中循环语句有for循环和while循环两种。当循环次数已知时,用for循环。while循环是通过检查一个控制条件来决定是否进行循环。用continue和break语句可以更灵活地退出循环。

1)for循环

在循环次数已知的情况下,使用for循环,其调用格式是

默认情况下,增量increment是1,可以指定任何数值为增量,包括负数。当增量为正数时,index从start开始增加,直到超过end时停止循环。当增量为负数时,直到index小于end值时循环停止。

经验分享:循环语句可以嵌套,构成多重循环。

例如:

这一程序段运行后构成的矩阵A是一个三阶的单位阵。

经验分享:循环变量也可以是矩阵形式或多维数组形式,还可以是字符串形式。在循环体内对循环变量重新赋值是不会终止循环的。

例如:

这一程序段虽然在运行过程中把变量k赋值为3,但循环照样执行,运行的结果是输出了三次k=3。

经验分享:for与end必须配对,两者就像是一对括号把需要重复循环的语句括在其中。在选择循环变量时,尽量不要用i、j,以免与复数单位相混。

例1.2.5 一个三位整数各位数字的立方和等于该数本身,则称该数为水仙花数。输出全部水仙花数。

程序如下:

2)while循环

while是通过检测控制条件是否成立来决定循环是否进行,也就是说,当循环次数不能确定的时候用while循环,其调用格式是

通常循环控制条件(expression)是由逻辑运算符==,<,>,<=,>=,~=连接起来的表达式。如果表达式的运算结果为逻辑1,则执行循环体;如果表达式的运算结果为逻辑0,则退出循环。

经验分享:循环控制条件(expression)通常的运算结果是标量,但也可以是矩阵,此时要求运算结果矩阵的所有元素为“真”。

例如:

这一程序段可以计算从1开始多少个自然数之和超过100。

经验分享:while与end必须配对,两者就像是一对括号把需要重复循环的语句括在其中。在循环体内(statements)必须改变循环控制条件,否则可能使程序进入死循环,无法正常退出。

3)continue语句

在进行for循环或while循环时,用continue语句可以跳过循环体中未执行的语句进入下一次循环。

例如:

这一程序段计算出文件magic.m共有多少行代码(不包括其中的空行和注释行)。

4)break语句

在进行for循环或while循环时,用break语句可以跳出循环,执行循环体后面的代码。如果是多重循环,则只是退出内层循环,进入外层循环的下一次循环。

例如:

这一程序段从文件fft.m读取内容,直到读到一个空行,退出。

例1.2.6 求[100,200]区间第一个能被21整除的整数。

代码如下:

4.错误处理语句

错误处理语句用来处理程序运行过程中出现的错误。其调用格式是

用try语句检测程序段statement1中是否有错误。如果在程序段statement1中出现错误,则MATLAB跳到catch语句块中的statement2执行。在statement2也应该有处理错误的方法。

经验分享:try、catch、end必须配对。

1.2.3 M文件

M文件有两种类型:脚本式M文件和M函数。

脚本式M文件实际上就是为了实现某一目的而编写的命令集,以便于对程序的代码进行维护和管理,也有利于程序代码的重复使用。

M文件是在M文件编辑器窗口中编写的。在MATLAB的界面上单击“新建脚本”按钮true,就可以打开M文件编辑器窗口,也可以通过选择“新建”→“脚本”新建并打开M文件编辑器窗口,还可以通过在命令行窗口中输入edit指令打开M文件编辑窗口,打开后的M文件编辑窗口如图1.2.2所示。

图1.2.2 M文件编辑器窗口

在M文件编辑器窗口中编写M文件,就像在“记事本”或“写字板”等一般的文本编辑器中编写文件一样。甚至是在“记事本”或“写字板”中用MATLAB命令编写的文件,只要用.m为扩展名保存,也是M文件。

M文件编写完成后,单击M文件编辑器窗口中的保存按钮true,就可以打开保存文件对话框,在其中选择保存文件的路径和文件名,就可以保存文件。

1.M脚本文件

脚本是最简单的M文件,脚本文件是一个包含有一系列MATLAB语句的命令集合。在命令行窗口中输入文件名就可以运行脚本文件中的所有命令。

脚本文件使用工作空间窗口中的变量数据,运行脚本文件所产生的变量数据也存放在工作空间窗口中。

例1.2.7 M脚本文件。

在M文件编辑器窗口中输入以下内容:

单击M文件编辑器窗口中的保存按钮true,以ex1.m为文件名保存在当前工作目录下。

在命令行窗口中输入:

运行后可以在命令行窗口中看到变量A的图标true,继续在命令行窗口中输入:

运行后显示效果如图1.2.3所示。

图1.2.3 例1.2.7的运行结果

这一脚本文件创建了一个3阶单位矩阵。

经验分享:脚本文件没有输入参数和输出参数。为了使程序清晰,可以在脚本文件中加入注释。

2.M函数

M函数是具有某一功能的M文件,它有输入参数和输出参数。每一个函数都占有部分内存,叫作函数工作空间。函数的工作空间与MATLAB的工作空间独立,各个函数的工作空间也相互独立。函数中的变量都是局部变量,一个函数只能访问自己工作空间中的变量,不能访问其他函数或MATLAB工作空间中的变量。

可以通过选择“新建”→“函数”新建并打开M函数编辑器窗口,如图1.2.4所示。

图1.2.4 M函数编辑器窗口

一个M函数由5部分构成:函数定义行、H1行、帮助文本、函数体、注释。下面以例子说明M函数的各部分。

例1.2.8 编写M函数。

在M文件编辑器窗口中输入以下内容,如图1.2.5所示。

图1.2.5 例1.2.8所编写的M函数

输入完毕后,单击“保存”按钮true,把文件保存在当前工作目录下,文件名为stat.m。

1)函数定义行

函数M文件的第一行用关键字function开头,把M文件定义为一个函数,并指定了函数名和函数的输入和输出参数。

在例1.2.8中语句function[mean,stdev]=stat(x)就是函数的定义行。函数定义行中定义了函数名是stat,函数有一个输入参数x、两个输出参数mean和stdev。

函数定义行的一般形式是

function[out1,out2,…]=funname(in1,in2,…)

定义函数funname和输入参数in1,in2,…及输出参数out1,out2,…。

函数名像变量名一样,以字母开头,后面的字符可以由字母、数字和下画线混合组成,用函数isvarname可以检查一个函数名是否合法。函数名的字符的个数可以是任意个,但是MATLAB只区分前面的63个字符。不同的操作系统对函数名中字符确认的个数可能不一样,用函数namelengthmax可以检查本机中MATLAB确认的函数名中字符的个数。

函数的输入参数可以没有,可以有1个,也可以有多个。函数的多个输入参数之间用逗号分隔,所有的输入参数用小括号(圆括号)括起来。如果没有输入参数,则输入参数部分可以省略。

函数的输出参数可以没有,可以有1个,也可以有多个。多个输出参数之间用逗号分隔,所有的输出参数用中括号(方括号)括起来。如果没有输出参数,则输出参数部分可以省略。

经验分享:通常把函数保存为M文件,文件名就是函数名。如果文件名与函数名不一致,则进行函数调用时,将忽略函数名,用文件名进行调用。因此为了不致引起混淆,保存函数时,应把函数名作为文件名。

例1.2.8中的M函数保存的文件名为stat.m。

2)H1行

函数的第二行,也就是紧跟在函数定义行后面,以符号%开头的那第一行,叫作H1行。用帮助命令lookfor进行查找时,就是搜索函数的H1行。通常都在H1行中对函数的功能进行简单的说明。

经验分享:在MATLAB中,以百分号%开头的语句都是注释语句,在运行过程中,注释语句不会被执行。

在例1.2.8中的H1行是

3)帮助文本

在函数的H1行后面,连续的以符号%开头的那些语句叫作帮助文本。帮助文本用来详细介绍函数的功能和用法。通常包括对函数输入参数和输出参数的要求,以及函数的用法,有时可以在帮助文本中加入实例,以帮助使用者了解函数的具体用法。

经验分享:在命令行窗口中输入help命令,就可以查到函数的H1行和函数帮助文件。

在例1.2.8中函数stat的帮助文本是

在帮助文本中还给出了一个调用实例。

4)函数体

函数体是函数的主体部分,函数体包括进行运算和赋值操作的所有MATLAB程序代码。

函数体中可以有流程控制、输入/输出、计算、赋值、注释,可以包括函数调用和对脚本的调用,还可以有空行。

在例1.2.8中,函数体是

函数体的第1行是个空行,以把函数体部分与帮助文本分开。

5)注释

注释是以百分号%开头的,注释可以在函数的任何位置,也可以在一行代码的最后添加注释。

经验分享:MATLAB在执行M文件时,把每一行中%后面的内容全部作为注释,不进行编译。

在例1.2.8中,语句

中,“%计算向量长度”就是注释,说明语句“n=length(x);”的作用。

6)函数的参数传递

函数调用的过程实际上就是参数传递的过程。

在命令行窗口中输入:

运行后如图1.2.6所示。

图1.2.6 例1.2.8中函数调用的过程

在函数的调用过程中,先把变量a的值传递给函数的输入参数x,通过调用函数stat计算出向量的均值赋值给函数的输出参数mean,方差赋值给函数的输出参数stdev,然后再把mean的值赋给变量m,把stdev的值赋给变量s。

由于各个函数都有各自的函数工作空间,它们与MATLAB的工作空间分开。函数内变量与MATLAB工作空间之间唯一的联系就是函数的输入和输出参数。如果函数任一输入参数值发生变化,其变化仅在函数体出现,不影响MATLAB工作空间的变量。

经验分享:在MATLAB工作空间定义的变量不会延伸到函数的工作空间;在函数内定义的变量也不会延伸到MATLAB的工作空间中。

例1.2.9 编写函数文件求半径为r的圆的面积和周长。

代码如下:

7)子函数

函数文件可以包含一个以上的函数,文件中的第一个函数是主函数,其后所有的函数都是子函数,子函数只能被同一文件中的主函数和其他子函数访问。函数文件名和主函数名相同。下面通过一个例子来说明。

例1.2.10 编写子函数。

在M文件编辑器窗口中输入以下内容,如图1.2.7所示。

图1.2.7 例1.2.10输入的代码

代码如下:

输入完毕后,单击保存按钮true,把文件保存在当前工作目录下,文件名为stats.m。

这一程序中主函数是stats,子函数是avg。在进行计算时,主函数stats把变量x、n作为参数传递给子函数avg。子函数计算出平均值再作为参数传递给主函数中的变量mean。主函数对子函数的调用和同一文件内的子函数之间的互相调用都是通过参数传递实现的。

8)递归调用

在函数f的执行过程中,调用了函数f本身,这就是函数的直接递归调用。在函数f的执行过程中,调用了函数g,而在函数g的执行过程中,又调用了函数f,这就是函数的间接递归调用。直接递归调用和间接递归调用统称为函数的递归调用。

经验分享:函数在进行递归调用时,必须确保程序会终止,否则MATLAB会陷入死循环。

例1.2.11 函数的递归调用。

编写函数计算自然数n的阶乘。

代码如下:

在这一函数中,输入参数是1时,输出参数为1,确保程序终止。在输入参数为大于1的自然数时进行递归调用。

经验分享:通常函数递归调用时,使用内存大,程序运行速度慢,尽可能不要用。

许多的递归程序可以用递推的形式来完成,如例1.2.12所示。

例1.2.12 利用函数的递推形式,编写计算自然数n的阶乘递推函数。

代码如下:

经验分享:在MATLAB中计算阶乘的函数是factorial,也可以用prod(1:n)计算阶乘。

3.M脚本文件与M函数的对比

读者朋友可通过以下例子对M脚本文件和M函数有一个清晰的了解与认识。

例1.2.13 分别建立命令文件和函数文件,将华氏温度(℉)转换为摄氏温度(℃)。

1)采用M脚本文件形式

首先,建立命令文件并以文件名f2c.m存盘。

然后,在MATLAB的命令行窗口中输入f2c,将会执行该命令文件,执行情况为

2)采用M函数形式

首先,建立函数文件f2c.m。

然后,在MATLAB的命令行窗口调用该函数文件。

输出情况为

1.2.4 函数句柄与匿名函数

变量不仅可以用来表示数值(如1,0.2,-5),表示字符串(如't'),也可以用来表示函数。

将函数句柄赋值给变量要用到@符号:

语法:变量名=@函数名

此处的函数名可以是当前MATLAB中使用的任意函数,例如mysin=@sin,此后mysin就和sin使用方法一样,mysin(pi)和sin(pi)的含义相同,其运行效果如图1.2.8所示。

图1.2.8 函数句柄的运行效果

匿名函数是函数句柄的一种高级用法,这样产生的函数句柄变量不指向特定的函数,而是一个函数表达式,其语法如下:

变量名=(输入参数列表)运算表达式

举个例子,定义f(x)=x^2,可以写为f=@(x)(x.^2)。其中,@(x)(x.^2)就是匿名函数,第一个括号里面是自变量,第二个括号里面是表达式,@是函数指针。f=@(x)(x.^2)表示将匿名函数@(x)(x.^2)赋值给f,于是f就表示该函数,该匿名函数的运行效果如图1.2.9所示。

图1.2.9 匿名函数的运行效果

1.2.5 MATLAB编程技巧

(1)尽量避免使用循环。

循环语句及循环体经常被认为是MATLAB编程的瓶颈问题。改进这种状况的方法为:尽量用向量化的运算来代替循环操作。

下面通过例子来演示如何将一般的循环结构转换成向量化的语句。

例1.2.14 考虑下面无穷级数求和问题:

如果只求出其中前有限项,例如100000项之和,则可以采用下面的常规语句进行计算:

如果采用向量化的方法,则可以大大提高运行效率,采用向量法进行编程的MATLAB代码如下:

经验分享:使用tic和toc命令可以计算程序的运行时间。

(2)在必须使用多重循环的情况下,如果两个循环执行的次数不同,则建议在循环的外环执行循环次数少的,内环执行循环次数多的。这样也可以显著提高速度。

例1.2.15 生成一个5×10000的Hilbert长方矩阵,该矩阵的定义是其第i行第j列元素为H{ij}=1/i+j-1)。

先进行行循环的程序:

后进行行循环的程序:

(3)对大型矩阵进行预先定维。

给大型矩阵动态地定维是件很费时间的事。建议在定义大型矩阵时,首先用MATLAB的内在函数,如zeros()或ones()对其先进行定维,然后再进行赋值处理,这样会显著减少所需的时间。

(4)优先考虑内在函数。

矩阵运算应该尽量采用MATLAB的内在函数,因为内在函数是由更底层的编程语言C构造的,其执行速度明显快于使用循环的矩阵运算。

(5)应用Mex技术。

虽然采用了很多措施,但执行速度仍然很慢,如果耗时的循环是不可避免的,就应该考虑用其他语言,如C或Fortran语言。按照Mex技术要求的格式编写相应部分的程序,然后通过编译链接,形成在MATLAB可以直接调用的动态链接库(DLL)文件,这样可以显著加快运算速度。

(6)使用节(cells)加快M文件的调试。

如果M文件代码较多,里面有若干功能模块,则可使用MATLAB中的节(cells)功能加快M文件的调试。

可采用如下方法对M文件中的程序进行节的划分:在某一程序模块前面添加“%%+空格”,并在空格之后添加该程序模块的功能注解,以形成节的名称,如图1.2.10所示。

图1.2.10 对M文件的代码进行节划分

经验分享:对于if语句,必须将完整的控流语句“if,else,end”放在同一节中。

在对M文件的代码进行节划分之后,选择“编辑器”→“转至”,再选择节的名称,如图1.2.11所示,便可直接跳转到相应的程序段。

图1.2.11 跳转到相应的程序段

选择“编辑器”→“运行并前进”,可实现逐节运行程序,如图1.2.12所示。

图1.2.12 逐节运行程序

将鼠标指向已划分好的某一节,选择“编辑器”→“运行节”,如图1.2.13所示,可仅运行该节的代码。

图1.2.13 仅运行该节的代码