OpenCV Python教程之圖像元素的訪問、通道分離與合並
轉載請詳細注明原作者及出處,謝謝!
訪問像素
像素的訪問和訪問numpy中ndarray的方法完全一樣,灰度圖為:
img[j,i] = 255
其中j,i分別表示圖像的行和列。對於BGR圖像,為:
img[j,i,0]= 255 img[j,i,1]= 255 img[j,i,2]= 255
第三個數表示通道。
下面通過對圖像添加人工的椒鹽現象來進一步說明OpenCV Python中需要注意的一些問題。完整代碼如下:
import cv2 import numpy as np def salt(img, n): for k in range(n): i = int(np.random.random() * img.shape[1]); j = int(np.random.random() * img.shape[0]); if img.ndim == 2: img[j,i] = 255 elif img.ndim == 3: img[j,i,0]= 255 img[j,i,1]= 255 img[j,i,2]= 255 return img if __name__ == '__main__': img = cv2.imread("圖像路徑") saltImage = salt(img, 500) cv2.imshow("Salt", saltImage) cv2.waitKey(0) cv2.destroyAllWindows()
處理后能得到類似下面這樣帶有模擬椒鹽現象的圖片:
上面的代碼需要注意幾點:
1、與C++不同,在Python中灰度圖的img.ndim = 2,而C++中灰度圖圖像的通道數img.channel() =1
2、為什么使用np.random.random()?
這里使用了numpy的隨機數,Python自身也有一個隨機數生成函數。這里只是一種習慣,np.random模塊中擁有更多的方法,而Python自帶的random只是一個輕量級的模塊。不過需要注意的是np.random.seed()不是線程安全的,而Python自帶的random.seed()是線程安全的。如果使用隨機數時需要用到多線程,建議使用Python自帶的random()和random.seed(),或者構建一個本地的np.random.Random類的實例。
分離、合並通道
由於OpenCV Python和NumPy結合的很緊,所以即可以使用OpenCV自帶的split函數,也可以直接操作numpy數組來分離通道。直接法為:
import cv2 import numpy as np img = cv2.imread("D:/cat.jpg") b, g, r = cv2.split(img) cv2.imshow("Blue", r) cv2.imshow("Red", g) cv2.imshow("Green", b) cv2.waitKey(0) cv2.destroyAllWindows()
其中split返回RGB三個通道,如果只想返回其中一個通道,可以這樣:
b = cv2.split(img)[0] g = cv2.split(img)[1] r = cv2.split(img)[1]
最后的索引指出所需要的通道。
也可以直接操作NumPy數組來達到這一目的:
import cv2 import numpy as np img = cv2.imread("D:/cat.jpg") b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) b[:,:] = img[:,:,0] g[:,:] = img[:,:,1] r[:,:] = img[:,:,2] cv2.imshow("Blue", r) cv2.imshow("Red", g) cv2.imshow("Green", b) cv2.waitKey(0) cv2.destroyAllWindows()
注意先要開辟一個相同大小的圖片出來。這是由於numpy中數組的復制有些需要注意的地方,具體事例如下:
>>> c= np.zeros(img.shape, dtype=img.dtype) >>> c[:,:,:] = img[:,:,:] >>> d[:,:,:] = img[:,:,:] >>> c is a False >>> d is a False >>> c.base is a False >>> d.base is a #注意這里!!! True
這里,d只是a的鏡像,具體請參考《 NumPy簡明教程(二,數組3)》中的“復制和鏡像”一節。
通道合並
同樣,通道合並也有兩種方法。第一種是OpenCV自帶的merge函數,如下:
merged = cv2.merge([b,g,r]) #前面分離出來的三個通道
接着是NumPy的方法:
mergedByNp = np.dstack([b,g,r])
注意:這里只是演示,實際使用時請用OpenCV自帶的merge函數!用NumPy組合的結果不能在OpenCV中其他函數使用,因為其組合方式與OpenCV自帶的不一樣,如下:
merged = cv2.merge([b,g,r]) print "Merge by OpenCV" print merged.strides mergedByNp = np.dstack([b,g,r]) print "Merge by NumPy " print mergedByNp.strides
結果為:
Merge by OpenCV (1125, 3, 1) Merge by NumPy (1, 500, 187500)
NumPy數組的strides屬性表示的是在每個維數上以字節計算的步長。這怎么理解呢,看下面這個簡單點的例子:
>>> a = np.arange(6) >>> a array([0, 1, 2, 3, 4, 5]) >>> a.strides (4,)
a數組中每個元素都是NumPy中的整數類型,占4個字節,所以第一維中相鄰元素之間的步長為4(個字節)。
同樣,2維數組如下:
>>> b = np.arange(12).reshape(3,4) >>> b array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> b.strides (16, 4)
從里面開始看,里面是一個4個元素的一維整數數組,所以步長應該為4。外面是一個含有3個元素,每個元素的長度是4×4=16。所以步長為16。
下面來看下3維數組:
>>> c = np.arange(27).reshape(3,3,3)
其結果為:
array([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8]], [[ 9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, 19, 20], [21, 22, 23], [24, 25, 26]]])
根據前面了解的,推斷下這個數組的步長。從里面開始算,應該為(3×4×3,3×4,4)。驗證一下:
>>> c.strides (36, 12, 4)
完整的代碼為:
import cv2 import numpy as np img = cv2.imread("D:/cat.jpg") b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype) b[:,:] = img[:,:,0] g[:,:] = img[:,:,1] r[:,:] = img[:,:,2] merged = cv2.merge([b,g,r]) print "Merge by OpenCV" print merged.strides print merged mergedByNp = np.dstack([b,g,r]) print "Merge by NumPy " print mergedByNp.strides print mergedByNp cv2.imshow("Merged", merged) cv2.imshow("MergedByNp", merged) cv2.imshow("Blue", r) cv2.imshow("Red", g) cv2.imshow("Green", b) cv2.waitKey(0) cv2.destroyAllWindows()
未完待續...
轉載請詳細注明原作者及出處,謝謝!