python在計算機視覺中常用的包
Python機器視覺程式設計常用資料結構與示例
本文總結了使用Python進行機器視覺(影象處理)程式設計時常用的資料結構,主要包括以下內容:
- 資料結構
- 通用序列操作:索引(indexing)、分片(slicing)、加(adding)、乘(multipying)等
- 列表:建立、list函式、基本操作:賦值、刪除、分片賦值、插入、排序等
- 元組:建立、tuple函式、基本操作
- NumPy陣列:建立陣列、建立影象、獲取形狀、維度、元素個數、元素型別、訪問畫素、通道分離、使用mask
1. 資料結構
資料結構是通過某種方式(例如對元素進行編號)組織在一起的資料元素的集合,這些資料元素可以是數字或者字元,甚至可以是其他資料結構。在Python中最基本的資料結構是序列(sequence)。序列中每個元素被分配一個序號——即元素的位置,也稱為索引(index),第一個元素的索引是0,第二個是1,以此類推。
python包含6種內建序列,最常用的兩種型別是:列表和元組。列表和元組的主要區別在於列表可以修改,元組不可以修改。而用於處理影象的基本資料結構是陣列,由於Python標準庫中的內建陣列只能處理一維陣列並且提供的功能較少,因此做程式設計時常使用NumPy模組的array()陣列表示影象,並進行各類處理。
2.
通用序列操作
所有序列都可以進行某些特定操作,包括:索引(indexing)、分片(slicing)、加(adding)、乘(multipying)以及檢查某個元素是否屬於序列成員(成員資格),除此之外,python還有計算序列長度、找出最大元素和最小元素的內建函式。
(1)索引
序列中所有元素的編號都是從0開始遞增。
- >>>greeting = 'Hello'
- >>>greeting[0]
- 'H'
- >>>greeting[1]
- 'e'
所有序列都可以通過這種方式獲取元素。最後一個元素的編號是-1
- >>>greeting[-1]
- 'o'
如果一個函式呼叫返回一個序列,則可以直接對返回序列進行索引操作
- >>>fourth = raw_input('Year: ')[3]
- Year: 2016
- >>>fourth
- 6
(2)分片
使用分片操作來方位一定範圍內的元素。分片通過冒號隔開兩個索引來實現。
- >>>tag='<a herf="http://www.python.org">Python web site</a>'
- >>>tag[9:30]
- '"http://www.python.org '
- >>>numbers=[1,2,3,4,5,6,7,8,9,10]
- >>>numbers[3:6]
- [4,5,6]
- >>>numbers[0:1]
- [1]
注意索引邊界:第1個索引的元素包含在分片內,第2個索引的元素不在分片內,如果要索引最後一個元素
- >>>numbers[-3:]
- [8,9,10]
- >>>print numbers[-1:]
- [10]
這種方法同樣適用於序列開始的元素:
- >>>numbers[:3]
- [1,2,3]
如果需要複製整個序列,可以將兩個索引都置空:
- >>>numbers[:]
- [1,2,3,4,5,6,7,8,9,10]
我們還可以使用第三個引數設定分片的步長,下面程式碼為從numbers序列中選出從0到10,步長為2的元素
- >>>numbers[0:10:2]
- [1,3,5,7,9]
如果要將每4個元素中的第1個提取出來可以這樣寫
- >>>numbers[::4]
- [1,5,9]
步長為負數將向左提取元素,當使用負數作為步長時開始的點的索引必須大於結束點的索引
- >>>number[8:3:-1]
- [9,8,7,6,5]
- >>>numbers[10:0:-2]
- [10, 8, 6, 4, 2]
(3)序列相加
使用+運算子可以進行序列的連線操作:
- >>>[1,2,3] + [4,5,6]
- [1,2,3,4,5,6]
- >>>'Hello, ' + 'world!'
- 'Hello, world!'
注意同種型別的序列才能連線到一起,列表和字串是無法連線的。
(4)乘法
數字x乘以序列會生成新的序列。新序列中,原來的序列將被重複x次
- >>>'pyhton' * 5
- 'pyhtonpyhtonpyhtonpyhtonpyhton'
- >>>[42]*10
- [42,42,42,42,42,42,42,42,42,42]
(5)None空列表和初始化
空列表可以通過兩個中括號中間什麼都不寫表示[]
如果想建立一個佔用 10個元素空間,卻不包括任何有用內容的列表,可以用:
- >>>[0]*10
None是一個Python的內建值,它的確切含義是這裡什麼都沒有。
- >>>[None]*10
(6)成員資格in
為了檢查一個值是否在列表中,可以使用in運算子,返回布林值真或假:
- >>>permission = 'rw'
- >>>'w'in permission
- True
- >>>'x'in permission
- False
下面的例子,檢查使用者名稱和PIN碼:
- database = [
- ['albert', '1234'],
- ['dilbert','4242'],
- ['smith', '7524'],
- ['jones', '9843']
- ]
- username = raw_input('User name: ')
- pin = raw_input('PIN code: ')
- if [username,pin] in database:
- print'Access granted'
執行結果:
User name: jones
PIN code: 9843
Access granted
(7)長度、最小值和最大值
內建函式len,min,max
- >>>numbers[100,34,678]
- >>>len(numbers)
- 3
- >>>max(numbers)
- 678
- >>>min(numbers)
- 34
- >>>max(2,3)
- 3
- >>>min(2,3,4,5)
- 2
3. 列表
(1)list函式
因為字串不能像列表一樣修改,所以有時候根據字串建立列表很有用
- >>>list('Hello')
- ['H','e','l','l','o']
list適用於所有型別的序列,而不只是列表。
(2)列表基本操作
-
元素賦值
- >>>x=[1,1,1]
- >>>x[1]=2
- >>>x
- [1,2,1]
-
刪除元素
- >>>x=[1,2,3]
- >>>del x[1]
- >>>x
- [1,3]
del也可刪除其他元素,甚至是變數。
-
分片賦值
- >>>name=list('Perl')
- >>>name
- ['P','e','r','l']
- >>>name[2:]=list('ar')
- >>>name
- ['P','e','a','r']
-
通過分片賦值插入元素和刪除元素
- >>>numbers=[1,5]
- >>>numbers[1:1]=[2,3,4]
- >>>numbers
- [1,2,3,4,5]
- >>>numbers[1:4]=[]
- >>>numbers
- [1,5]
(3)列表方法
列表方法的使用:物件.方法(引數)
-
append 在列表末尾追加
- >>>a = [1,2,3]
- >>>a.append['4']
- >>>a
- [1,2,3,4]
-
count 統計某個元素在列表中出現的次數
- >>>['to','go','will','be','to','not'].count('to')
- 2
-
extend 在列表末尾一次性追加另一個序列中的多個值
- >>>a = [1,2,3]
- >>>b = [4,5,6]
- >>>a.extend(b)
- >>>a
- [1,2,3,4,5,6]
- >>>c = [1,2,3]
- >>>d = [4,5,6]
- >>>c+d
- [1,2,3,4,5,6]
- >>>c
- [1,2,3]
-
index 從列表中找出某個值第一個匹配項的索引位置
- >>>slogen= ['we', 'are', 'the', 'champion']
- >>>slogen.index('are')
- 1
- >>> slogen[1]
- 'are'
-
insert 將物件插入到列表中
- >>>numbers=[1,2,3,4,6]
- >>>numbers.insert(4,'five')
- >>>numbers
- [1, 2, 3, 4, 'five', 6]
第2個引數為插入的元素內容
insert方法的操作也可用分片的方法實現元素插入
- >>>numbers=[1,2,3,4,6]
- >>>numbers[4:4]=['five']
- >>>numbers
- [1, 2, 3, 4, 'five', 6]
-
pop 移除列表中的一個元素
pop方法可實現一個常見的資料結構——棧。棧的原理就像堆盤子,只能在頂部放一個盤子,同樣也只能從頂部拿走一個盤子。最後被放入堆疊的元素最先被移除。(此原則稱為後進先出,LIFO)。
pop()方法預設移除列表中的最後一個元素,並返回該元素的值- >>>numbers=[1,2,3,4,5]
- >>>numbers.pop()
- 5
- >>> numbers
- [1, 2, 3, 4]
- >>>numbers=[1,2,3,4,5]
- >>>numbers.pop(0)
- 1
- >>> numbers
- [2, 3, 4, 5]
Python沒有入棧操作,可以用append方法代替。
如果想要實現一個先進先出(FIFO)佇列,可以使用insert(0,...)來替代append方法。或者也可以使用append方法,但必須用pop(0)替代pop()。也可使用collection模組中的deque物件。-
remove 移除列表中的某個值的第一個匹配項
- >>>x=['to','go','will','be','to','not']
- >>>x.remove('to')
- >>>x
- ['go','will','be','to','not']
-
reverse 將列表中的元素反向存放
- >>>x=[1,2,3]
- >>>x.reverse()
- >>>x
- [3,2,1]
-
sort 在原始位置對列表進行排序
在原始位置排序將改變原來的列表,從而讓其中的元素能按一定的順序重新排列,而不是簡單的返回一個排序的列表副本。
- >>>x=[3,1,2,6,4,5,7,9,8]
- >>>x.sort()
- >>>x
- [1,2,3,4,5,6,7,8,9]
- >>>x=[3,1,2,6,4,5,7,9,8]
- >>>y=x[:] #不能直接y=x
- >>>y.sort()
- >>>y
- [1,2,3,4,5,6,7,8,9]
- >>>x
- [3,1,2,6,4,5,7,9,8]
-
sorted 獲取排序列表的副本
- >>>x=[3,1,2,6,4,5,7,9,8]
- >>>y=sorted(x)
- >>>y
- [1,2,3,4,5,6,7,8,9]
- >>>x
- [3,1,2,6,4,5,7,9,8]
-
sort方法的高階排序
希望列表元素能按照特定的方式排序(而不是sort函式預設的方式,即根據Python預設排序規則按升序排列元素),可以通過compare(x,y)的形式自定義比較函式。compare(x,y)函式會在x<y時返回負數,在x>y時返回正數,如果x=y則返回0(根據自己定義)。定義好該函式後,可以提供給sort方法作為引數。內建函式cmp提供了比較函式的預設實現方式:
- >>>cmp(16,12)
- 1
- >>>cmp(10,12)
- -1
- >>>cmp(10,10)
- 0
- >>>numbers=[1,4,2,9]
- >>>numbers.sort(cmp)
- >>>numbers
- [1,2,4,9]
sort方法還有另外兩個引數可選,可以通過某個名字來指定該引數(關鍵字引數):
引數:key
提供一個在排序中使用的函式,該函式不是直接確定物件的大小,而是為每個元素建立一個鍵,然後所有元素根據鍵來排序。
- >>>x=['nor','break','if','then','present']
- >>>x.sort(key=len) # 按字串長度排序
- >>>x
- ['if', 'nor', 'then', 'break', 'present']
引數:reverse
另一個關鍵字引數reverse是簡單的布林值,用於指明是否要進行反向排序- >>> x=[3,1,2,6,4,5,7,9,8]
- >>> x.sort(reverse=True)
- >>>x
- [9, 8, 7, 6, 5, 4, 3, 2, 1]
4.
元組
元組與列表一樣,也是一種序列,但元組是不可變列表,元組不能修改。
元組的作用
- 體現在對映(和集合的成員)中當做鍵使用——列表不行
- 元組在很多內建函式的返回值存在,也就是說我們必須對元組進行處理
(1)建立元組
-
建立一個元組
- >>>1,2,3
- (1,2,3)
元組大部時候通過圓括號括起來
- >>>x=(1,2,3)
- >>>x
- (1,2,3)
-
建立一個空元組
空元組可以用沒有內容的空括號括起來
- >>>()
- ()
-
建立一個包含一個元素的元組
- >>>(10,)
- (10,)
是的,一個元素也需要用逗號。逗號很重要,看下面的例子
- >>>2*(3+2)
- 10
- >>>2*(3+2,)
- (5, 5)
(2)tuple函式
tuple函式以一個序列作為引數並轉換為元組,如果引數本身就是元組,則不發生變化。
- >>>tuple([1,2,3])
- (1,2,3)
- >>>tuple(['a','b','c'])
- ('a','b','c')
- >>>tuple((1,2,3))
- (1,2,3)
(3)元組基本操作
元組除了建立和訪問其元素外,沒有太多其他操作,元組操作與操作其他序列類似。
- >>>x=1,2,3
- >>>x[1]
- 2
- >>>x[0:2]
- (1,2)
5. NumPy的array(陣列)物件
NumPy模組用於python計算機視覺程式設計時的向量、矩陣的表示與操作,是OpenCV
for python的主要資料結構模組。NumPy中的陣列物件array是多維的,可以用來表示向量、矩陣和影象。一個數組物件很像一個列表(或者是列表的列表),但陣列中的元素必須具有相同的資料型別。除非建立陣列物件時指定資料型別,否則資料型別會按照資料的型別自動確定。
本節程式碼假定已經以如下形式匯入OpenCV和NumPy兩個庫
- import cv2
- import numpy as np
(1)np.array()建立陣列
- >>> a = np.array([1, 2, 3, 4])
- >>> b = np.array((5, 6, 7, 8))
- >>> c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
- >>> b
- array([5, 6, 7, 8])
- >>> c
- array([[1, 2, 3, 4],
- [4, 5, 6, 7],
- [7, 8, 9, 10]])
- >>> d = np.arange(15).reshape(3, 5)
- >>> d
- array([[ 0, 1, 2, 3, 4],
- [ 5, 6, 7, 8, 9],
- [10, 11, 12, 13, 14]])
(2)np.array()建立黑白影象
使用np.zeros()建立一幅影象,dtype為元素資料型別,下文有具體分析,8位灰度影象為uint8型。接著用np.ones()建立一幅影象,通過賦值稱為一幅白色影象。
- img1 = np.ones((100,200),dtype=np.uint8)
- img2 = np.ones((100,200),dtype=np.uint8)
- img2[:]=255;
- cv2.imshow('img1',img1)
- cv2.imshow('img2',img2)
- cv2.waitKey(0)
彩色影象的建立需要指定一個3維陣列,具體方法請看下文。
(3)ndarray.shape屬性獲得/修改陣列形狀
-
獲取陣列 shape 屬性
陣列的形狀可以通過其shape 屬性獲得,它是一個描述陣列各個軸長度的元組(tuple),看看上文定義的a,c陣列的shape屬性:
- >>> a.shape
- (4,)
- >>> c.shape
- (3, 4)
-
獲取影象的寬高
影象本質是矩陣,因此可以使用shape屬性獲取影象矩陣的行、列和通道數,如果影象是灰度圖,則沒有第3個引數。我們也可以用ndim方法判斷影象通道數:
- img = cv2.imread('f:/images/cow.jpg')
- rows,cols,channels = img.shape
- print'rows,cols,channels = ',rows,cols,channels
- print'demension = ',img.ndim
- cv2.imshow('test',img)
- cv2.waitKey(0)
執行結果:
rows,cols,channels = 400 600 3
demension = 3
-
修改陣列 shape 屬性
可以通過修改陣列的shape 屬性,在保持陣列元素個數不變的情況下,改變陣列每個軸的長度。下面的例子將陣列c的shape 屬性改為(4,3),注意:從(3,4)改為(4,3)並不是對陣列進行轉置,而只是改變每個軸的大小,陣列元素在記憶體中的位置並沒有改變。
- >>> c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
- >>> c.shape = 4,3
- >>> c
- array([[ 1, 2, 3],
- [ 4, 4, 5],
- [ 6, 7, 7],
- [ 8, 9, 10]])
- >>> c.shape = 2,-1
- >>> c
- array([[ 1, 2, 3, 4, 4, 5],
- [ 6, 7, 7, 8, 9, 10]])
使用陣列的reshape()方法,可以建立指定形狀的新陣列,而原陣列的形狀保持不變
- >>> a = np.array([1, 2, 3, 4])
- >>> e = a.reshape((2,2)) # 也可以用a.reshape(2,2)
- >>> e
- array([[1, 2],
- [3, 4]])
- >>> a
- array([1, 2, 3, 4])
注意:陣列a 和e 其實共享資料儲存空間,因此修改其中任意一個數組的元素都會同時修改另外一個數組的內容:
- >>> a[1] = 100# 將陣列a 的第一個元素改為100
- >>> e # 注意陣列d 中的2 也被改為了100
- array([[ 1, 100],
- [3, 4]])
(4)ndarray.ndim屬性:陣列維度
返回陣列的軸數量,即維度。在Python中維度稱為rank
(5)ndarray.dtype屬性:陣列元素型別
陣列的元素型別可以通過dtype 屬性獲得。前面例子中,建立陣列所用序列的元素都是整數,因此所建立的陣列的元素型別是整型,並且是32bit 的長整型:
- >>> c.dtype
- dtype('int32')
- >>>print img1.dtype
- uint8
NumPy 中的資料型別都有幾種字串表示方式,字串和型別之間的對應關係都儲存在typeDict 字典中,例如'd'、'double'、'float64'都表示雙精度浮點型別:
- >>> np.typeDict["d"]
- <type 'numpy.float64'>
- >>> np.typeDict["double"]
- <type 'numpy.float64'>
- >>> np.typeDict["float64"]
- <type 'numpy.float64'>
完整的型別列表可以通過下面的語句得到,它將typeDict字典中所有的值轉換為一個集合,從而去除其中的重複項:
- >>> print set(np.typeDict.values())
- set([<type 'numpy.float64'>, <type 'numpy.int32'>,
- <type 'numpy.bool_'>, <type 'numpy.float64'>,
- <type 'numpy.uint64'>, <type 'numpy.int64'>,
- <type 'numpy.datetime64'>, <type 'numpy.uint8'>,
- <type 'numpy.timedelta64'>, <type 'numpy.object_'>,
- <type 'numpy.uint16'>, <type 'numpy.string_'>,
- <type 'numpy.uint32'>, <type 'numpy.unicode_'>,
- <type 'numpy.complex128'>, <type 'numpy.uint32'>,
- <type 'numpy.void'>, <type 'numpy.complex64'>,
- <type 'numpy.complex128'>, <type 'numpy.int8'>,
- <type 'numpy.float16'>, <type 'numpy.int16'>,
- <type 'numpy.float32'>, <type 'numpy.int32'>])
(6)ndarray.size屬性:陣列元素個數
陣列中所有元素的個數。這個引數等於shape屬性返回的引數的乘積。
- >>>print img1.shape
- (100,200)
- >>>print img1.size
- 20000
(7)ndarray.itemsize屬性:單個數組元素所佔位元組數
陣列單個元素所佔的位元組數。例如,陣列元素為float64型時,其itemsize=8 (=64/8)。如果是複數complex32型別,則itemsize 4 (=32/8)。- >>>print img1.itemsize