第2章 MATLAB編程基礎
2.1 M-文件
MATLAB中的M-文件能夠是簡單執行一系列MATLAB語句的源文件,也能夠是接收自變量併產生一個或多個輸出的函數。
M-文件由文本編輯器建立,並以filename.m形式的文件名存儲,好比average.m以及filter.m。M-文件的組成部分以下:
2.1.1 函數定義行
函數定義行的形式爲:
function [outputs]=name(inputs)
例如,某個計算兩幅圖像的求和與乘積(兩個不一樣的輸出)的函數應該具備以下形式:
function [s,p]=sumprod(f,g)
其中,f和g是輸入圖像,s是求和圖像,p是乘積圖像。名稱sumprod能夠任意定義,但function老是在左側,注意,輸出參量必須位於方括號內,而輸入參量位於圓括號內。若是函數只有單個輸出參量,可不用方括號而直接列出。若是函數沒有輸出,只須要使用function, 不須要使用括號或等號。函數名必須以字母開頭,後面能夠跟字母、數字、下劃線,但不容許有空格
函數能夠在命令提示符中調用,例如:
[s,p]=sumprod(f,g);
也能夠被用做其餘函數的元素,在這種狀況下,這些函數就成爲子函數。若是輸出只有單個變量,也能夠不寫括號:
y=sum(x)
2.1.2 H1語句
H1語句是第一個文本行,也就是函數定義行後面的單獨註釋行。函數定義行和H1語句之間無空行或空格,H1語句的示例以下:
% SUMPROD Computes the sum and product of two images
當用戶在MATLAB提示符處輸入
>> help function_name
H1語句是最早出現的文本。輸入lookfor keyword就會顯示出全部含有字符串keyword的H1語句。
2.1.3 幫助文本
幫助文本是緊跟在H1語句後面的文本塊,兩者之間無空行。幫助文本用來爲函數提供註釋或在線幫助。當用戶在提示符後鍵入help function_name時,MATLAB會顯示函數定義行和第一個非註釋行(執行語句或空白語句)之間的所有註釋行。但幫助系統會忽略幫助文本塊後面的任何註釋行。
2.1.4 函數主體
函數主體包含了執行計算並給出輸出變量賦值的全部MATLAB代碼。
符號"%"後面的非H1語句或幫助文本的全部行都被認爲是函數註釋行,他們不是幫助文本的一部分。代碼行的末尾也可附加註釋。
M-文件能夠在任何文本編輯器中進行建立和編輯,並以擴展名.m保存到指定目錄下,一般保存在MATLAB搜索路徑中。建立和編輯M-文件的另→種方法是在提示符處使用edit函數。例如,若是文件存在於MATLAB搜索路徑的目錄中或者在當前目錄下,可鍵入:
>> edit sumprod
就會打開文件sumprod.m並進行編輯。若是找不到該文件,MATLAB會爲用戶提供用於建立該文件的選項.
2.2 算子
MATLAB有兩種不一樣類型的算子。矩陣運算由線性代數的規則來定義,而數組運算能夠逐個元素地執行。字符.用來區分數組運算與矩陣運算,如$A*B$表示傳統意義的矩陣乘法,而$A.*B$則表示數組乘法,這種乘法是指兩個大小相同的數組對應元素的乘積。換句話說,假如$C=A.*B$,就有$C(i,j)=A(i,j)*B(i,j)$。當書寫$B=A$這樣的表達式時,MATLAB將作$B=A$的"記錄",但並不將$A$的數據複製到$B$中,除非在後面的程序中,$A$的內容有了變化。這一點很重要,由於使用不一樣的v\變量來「存儲」相同的內容有時候能夠加強代碼的清晰性和可讀性。下表列出了MATLAB的算子:
運算符 |
名稱 |
描述 |
+ |
數組和矩陣加法 |
a+b、A+B 或 a+B |
- |
數組和矩陣減法 |
a-b、A-B、A-a 或 a-A |
.* |
數組乘法 |
Cv=A.*B、C(i,j)=A(i,j)*B(i,j) |
* |
矩陣乘法 |
A*B,即標準矩陣乘 |
./ |
數組右除 |
C=A./B、C(i,j)=A(i,j)/B(i,j) |
.\ |
數組左除 |
C=A.\B、C(i,j)=B(i,j)/A(i,j) |
/ |
矩陣右除 |
A/B是計算A*inv(B) |
\ |
矩陣左除 |
A\B是計算inv(A)*B |
.^ |
數組乘冪 |
若是$C=A.^B$,那麼C(i,j)=A(i,j)^B(i,j) |
^ |
矩陣乘冪 |
查閱幫助 |
.' |
向量和矩陣轉置 |
A.'標準向量和矩陣轉置 |
' |
向量和矩陣複共軛轉置 |
A',標準向量和矩陣複共軛轉置 |
2.3 關係算子
<style> table th:nth-of-type(2) { width: 200px; } </style>express
算子 |
名稱 |
< |
小於 |
<= |
小於等於 |
--- |
-------- |
> |
大於 |
>= |
大於等於 |
== |
等於 |
~= |
不等於 |
2.4 邏輯算子
算子 |
名稱 |
& |
與 |
| |
或 |
~ |
非 |
&& |
標量"與" |
|| |
標量"或" |
##### 算子&和 |
針對數組操做,他們分別針對輸入元素執行"與"和"或"運算;算子&&和 |
2.5 流程控制
語句 |
描述 |
if |
與else和elseif結合使用,執行一組基於指定邏輯條件的語句 |
for |
執行規定次數的一組語句 |
while |
根據規定的邏輯條件,執行不肯定次數的一組語句 |
break |
終止執行for或while循環 |
continue |
將控制傳遞給for或while循環的下一次迭代,跳出循環體中的剩餘部分 |
switch |
與case和otherwise結合使用,根據指定的值或字符串執行不一樣的語句組 |
return |
返回調用函數 |
try...catch |
若是在執行期間檢測到錯誤,就改變流程控制 |
2.6 數組索引
$1\times N$的數組被稱爲行向量,這種向量的元素可使用單一索引值來訪問。例如$v(1)$是向量$v$的第一個元素,$v(2)$是第二個元素:
>> v=[1 3 5 7 9]
v =
1 3 5 7 9
>> v(2)
ans =
3
使用轉置算子(.')可將行向量轉換爲列向量:
>> w=v.'
w =
1
3
5
7
9
爲訪問元素塊,使用冒號。例如,爲訪問向量$v$的前3個元素,可以使用語句:
>> v(1:3)
ans =
1 3 5
相似的,咱們能夠訪問向量$v$的第3個元素到最後一個元素:
>> v(3:end)
ans =
5 7 9
其中,end表示向量中的最後一個元素。還能夠將向量用做索引以進入另外一個向量,例如:
>> v([1 4 5])
ans =
1 7 9
此外,索引並不限於連續的元素,例如:
>> v(1:2:end)
ans =
1 5 9
其中,符號1:2:end表示索引從1開始計數,步長爲2,當計數達到最後一個元素時中止:
>> x=[1 2 3 4 5 6 7 8]
x =
1 2 3 4 5 6 7 8
>> x(1:2:end)
ans =
1 3 5 7
在MATLAB中,矩陣能夠很方便地用一列被方括號括起並用分號隔開地行向量來表示:
>> A=[1 2 3;4 5 6;7 8 9]
A =
1 2 3
4 5 6
7 8 9
從矩陣中選擇元素和從向量中選擇元素是同樣地,但須要兩個索引:一個肯定行的位置,另外一個對應於列。咱們也能夠選擇整行和整列,或使用冒號做爲索引來選擇整個矩陣:
>> A(2,:)
ans =
4 5 6
>> sum(A(:))
ans =
45
>> A(:)
ans =
1
4
7
2
5
8
3
6
9
函數sum計算參量每一列的和,單冒號索引把A轉換爲列向量,將結果傳給sum。
另外一種很是有用的索引形式是邏輯索引,邏輯索引表達式的形式是A(D),其中A是數組,D是與A大小相同的邏輯數組,表達式A(D)提取A中與D中1值對應的全部元素:
>> D=logical([1 0 0;0 0 1;0 0 0])
D =
3×3 logical 數組
1 0 0
0 0 1
0 0 0
>> A(D)
ans =
1
6
>> D=[1 0 0;0 0 1;0 0 0]
D =
1 0 0
0 0 1
0 0 0
>> A(D)
下標索引必須爲正整數類型或邏輯類型。
對圖像處理頗有用的最後一種索引是線性索引。線性索引表達式使用單個下標來編制矩陣或高維數組的索引。對於$M\times N$的矩陣,元素(r,c)能夠用單一的下標r+M(c-1)。這樣一來,A(2,3)就能夠用A(8)來選擇:
>> A(8)
ans =
6
2.7 函數句柄、單元數組與結構
函數句柄是MATLAB數據類型,包含用於引用函數的信息。使用函數句柄的主要優勢之一是能夠在調用中把函數句柄做爲參數傳遞給另外一個函數。
函數句柄有兩種不一樣類型,這兩種類型都用函數句柄算子@來建立。第一種函數句柄類型是命名的函數句柄,爲了建立命名的函數句柄,在算子@後邊寫上所需的函數:
>> f=@sin
f =
包含如下值的 function_handle:
@sin
能夠經過調用函數句柄f來間接調用函數sin:
>> f(pi/4)
ans =
0.7071
>> sin(pi/4)
ans =
0.7071
第二種函數句柄類型是匿名的函數句柄,由MATLAB表達式而不是函數名構成。構建匿名函數的通用格式是:
@(inoput-argument-list) expression
例以下列匿名函數句柄計算輸入值的平方值:
>> g=@(x) x.^2;
>> g(2)
ans =
4
下列函數句柄計算兩個變量平方之和的平方根:
>> r=@(x,y) sqrt(x.^2+y.^2)
r =
包含如下值的 function_handle:
@(x,y)sqrt(x.^2+y.^2)
>> r(1,2)
ans =
2.2361
單元數組(cell array)提供了一種在變量名下組合混合的一組對象(例如數字、字符、矩陣以及其餘單元數組)的方法。例如,假設使用:(1)大小爲$512\times512$像素的uint8圖像f;(2)188*2數組行形式的二維座標序列b;(3)包含兩個字符名的單元數組char_array={'area','centroid'}(花括號用來包含單元數組的內容)。能夠用單元數組將這三種不一樣的實體組織成單個變量C。
C={f,b,char_array}
在提示符處鍵入C,將輸出下列結果:
>>c
C=
[512*512 uint8] [188*2 double] {1*2 cell}
換句話說,顯示的輸出不是各類變量的值,而是對它們的某些特性的描述。爲了看到單元素的所有內容,能夠把單元元素的數值位置附加在花括號中。例如,要查看char_array的內容,鍵入:
>>c{3}
ans=
'area' 'centroid'
在C的元素中用圓括號代替花括號,給出變量的描述:
>>c(3)
ans =
{1*2 cell}
最後須要指出,單元數組包括參數的副本,而不是參數的指針。在前述的例子中,若是C的任何參數在C建立後改變了,那麼改變在C中不會反映過來。
結構與單元數組相似,他們都容許一組不一樣的數據集成到單個變量中。但與單元數組不一樣的是,單元數組中的單元由數字尋址,而結構元素由所謂的「字段」來尋址,例如,若是f是一幅輸入圖像:
function s=image_stats(f)
s.dm=size(f);
s.AI=mens2(f);
s.AIrows=mean(f,2);
s.AIcols=mean(f,1);
2.8 優化代碼
MATLAB是專門爲數組操做而設計的編程語言。有兩種重要的優化MATLAB代碼的方法:預分配數組和向量化循環。
預分配是在進入計算數組元素的for循環以前初始化數組。假設要建立一個MATLAB函數來計算:
$$ f(x)=sin(x/100\pi),x=0,1,2,...,M-1 $$ 下面是這個函數的第一種形式:編程
function y=sinfun1(M)
x=0:M-1;
for k=1:numel(x)
y(k)=sin(x(k)/(100*pi));
end
$M=5$時的輸出是:
>> sinfun1(5)
ans =
0 0.0032 0.0064 0.0095 0.0127
MATLAB函數tic和toc可用來測量函數的執行時間,先調用tic,而後調用這個函數,以後再調用toc:
>> tic;sinfun1(100);toc
時間已過 0.001218 秒。
正如剛纔介紹那樣,調用計時函數再測量時間中可產生較大的變化,在命令提示符測量時尤爲明顯,例如,重複前邊的調用將獲得不一樣的結果:
>> tic;sinfun1(100);toc
時間已過 0.000356 秒。
函數timeit可用於獲得函數調用的可靠且可重複的時間測量,其調用語法是:
s=timeit(f)
其中,f是用於對函數定時的函數句柄,s是調用所須要的秒數。調用函數句柄f時不使用輸入參量。例如,在$M=100$時對sinfun1使用timeit進行計時:
>> M=100;
>> f=@() sinfun1(M);
>> timeit(f)
ans =
2.6024e-05
這個timeit函數調用例子很好地證實了前面介紹地函數句柄地強大功能。由於可以接受沒有輸入的函數句柄,函數timeit與咱們但願計時的函數參數無關。用timeit測量sinfun1的時間,取M=500,1000,1500,...,20000:
M=500:500:20000;
>> for k=1:numel(M)
f=@() sinfun1(M(k));
t(k)=timeit(f);
end
預分配意味着在循環開始以前把它初始化爲但願的輸出大小。一般,採用調用函數zeros來作預分配:
function y=sinfun2(M)
x=0:M-1;
y=zeros(1,numel(x));
for k=1:numel(x)
y(k)=sin(x(k)/(100*pi));
end
比較sinfun1(20000)和sinfun2(20000)所需的時間:
>> timeit(@() sinfun1(20000))
ans =
0.0021
>> timeit(@() sinfun2(20000))
ans =
5.2661e-04
MATLAB中的向量化是使用矩陣/向量算子的組合、索引技術和現有的MATLAB或工具箱函數來徹底消除循環。做爲示例,sinfun的第三種形式:
function y=sinfun3(M)
x=0:M-1;
y=sin(x./(100*pi));
函數sinfun3沒有for循環。在MATLAB舊版本中,用矩陣和向量算子消除循環幾乎總能獲得有意義的加速。然而,MATLAB新版本可自動編譯簡單的for循環,例如sinfun2中的那個,可加快機器代碼。許多for循環在MATLAB的舊版本中很慢,但在向量化版本中再也不慢:
>> timeit(@() sinfun2(20000))
ans =
0.0014
>> timeit(@() sinfun3(20000))
ans =
4.6300e-04
下面,咱們寫兩種MATLAB版本的函數,建立一幅如下面等式爲基礎的合成圖像:$$f(x,y)=Asin(u_0x+v_0y)$$第一個函數twodsin1使用兩個嵌套的for循環計算$f$:
function f=twodsin1(A,u0,v0,M,N)
f=zeros(M,N);
for c=1:N
v0y=v0*(c-1);
for r=1:M
u0x=u0*(r-1)
f(r,c)=A*sin(u0x+v0y);
end
end
在for循環以前,預分配步驟f=zeros(M,N)。使用timeit建立一幅大小爲512*512像素的圖像,看看這個函數用了多長時間:
>> timeit(@() twodsin1(1,1/(4*pi),1/(4*pi),512,512))
ans =
0.0152
能夠用imshow的自動範圍語法[]顯示結果圖像:
>> f=twodsin1(1,1/(4*pi),1/(4*pi),512,512);
>> imshow(f,[])
下面咱們寫一個該函數的向量化版本,格式語法以下:
[C,R]=meshgrid(c,r)
輸入參量c和r分別是水平(行)和垂直(列)座標(首先寫出列)。函數meshgrid把座標向量變換爲兩個數組C和R,它們能夠來計算兩個變量的函數求值結果。例如,下面的命令用meshgrid對整數範圍爲1到3的x和範圍10到14的y計算函數$z=x+y$:
>> [X,Y]=meshgrid(1:3,10:14)
X =
1 2 3
1 2 3
1 2 3
1 2 3
1 2 3
Y =
10 10 10
11 11 11
12 12 12
13 13 13
14 14 14
>> Z=X+Y
Z =
11 12 13
12 13 14
13 14 15
14 15 16
15 16 17
所以,咱們用meshgrid重寫2D且沒有循環的sine函數:
function f=twodsin2(A,u0,v0,M,N)
r=0:M-1;%Row
c=0:N-1;%Column
[C,R]=meshgrid(c,r);
f=A*sin(u0*R+v0*C);
用timeit測量執行速度
>> timeit(@() twodsin2(1,1/(4*pi),1/(4*pi),512,512))
ans =
0.0055
向量化版本的運行時間要少三分之二。
由於MATLAB每個新版本對運行循環的速度都傾向於有所改進,因此在向量化MATLAB代碼時,給出通用的指導是困難的。可是,向量化的代碼經常比基於循環的代碼更易讀。