OpenCV之Python學習筆記(1)(2): 影象的載入、顯示和儲存 影象元素的訪問、通道分離與合併

garfielder007發表於2016-05-05

OpenCV之Python學習筆記

一直都在用Python+OpenCV做一些演算法的原型。本來想留下發布一些文章的,可是整理一下就有點無奈了,都是寫零散不成系統的小片段。現在看到一本國外的新書《OpenCV Computer Vision with Python》,於是就看一遍,順便把自己掌握的東西整合一下,寫成學習筆記了。更需要的朋友參考。

閱讀須知:

        本文不是純粹的譯文,只是比較貼近原文的筆記;
        請設法購買到出版社出版的書,支援正版。

       從書名就能看出來本書是介紹在Python中使用OpenCV,全書分為5章,兩個附錄:

  • 第一章OpenCV設定,介紹如何在Windows、Mac和Ubuntu上設定Pyhton、OpenCV和相關庫的環境。還討論了OpenCV社群、OpenCV文件以及官方的示例程式碼。
  • 第二章處理檔案、攝像頭和GUI,討論OpenCV的I/O功能,接著使用物件導向的設計編寫一個主應用程式,用於顯示攝像頭實時場景、處理鍵盤輸入、將攝像頭寫入視訊檔案和靜態影象檔案。
  • 第三章影象過濾,介紹使用OpenCV、NumPy和SciPy來編寫影象過濾器。過濾器可用於線性顏色操作、曲線顏色操作、模糊化、銳化和尋找邊緣。本章修改第一章的主程式,將過濾器應用到實時攝像頭場景中。
  • 第四章使用Haar Cascades追蹤人臉,本章將編寫一個層次化的人臉追蹤器,使用OpenCV定點陣圖像中的臉部、眼睛、鼻子和嘴巴。同時還編寫了用於複製和改變影象中某塊區域的大小。同樣,本章也將修改之前的主應用程式,讓其可以用於找到並處理攝像頭場景中的人臉。
  • 第五章檢測前景/背景區域和深度。通過本章將瞭解有關OpenCV(在OpenNI和SensorKinect的支援下)從深度攝像頭中獲得的資料型別的資訊。接著編寫一些函式,使用這些資料對前景區域施加一些限制效果。最後將這些函式整合到主程式中,使得在處理人臉之前先進行細化操作。
  • 附錄A,與Pygame整合。修改主程式,用Pygame替換OpenCV來處理特定的I/O事件。(Pygame提供了更多樣的事件處理函式。)
  • 附錄B,為自定義目標生成Haar Cascades,允許我們檢測一系列的OpenCV工具,來對任何型別的目標或模式構建跟蹤器,而不僅僅是人臉。

本書第一章是介紹在不同作業系統上對OpenCV、Python及相關庫的配置,這裡就不介紹了。下一篇文章將直接從第二章開始介紹。






OpenCV Python教程(1、影象的載入、顯示和儲存)


本文是OpenCV  2 Computer Vision Application Programming Cookbook讀書筆記的第一篇。在筆記中將以Python語言改寫每章的程式碼。

PythonOpenCV的配置這裡就不介紹了。

注意,現在OpenCV for Python就是通過NumPy進行繫結的。所以在使用時必須掌握一些NumPy的相關知識!

影象就是一個矩陣,在OpenCV for Python中,影象就是NumPy中的陣列!

如果讀取影象首先要匯入OpenCV包,方法為:

[python] view plain copy
  1. import cv2  

讀取並顯示影象

在Python中不需要宣告變數,所以也就不需要C++中的cv::Mat xxxxx了。只需這樣:

[python] view plain copy
  1. img = cv2.imread("D:\cat.jpg")  

OpenCV目前支援讀取bmp、jpg、png、tiff等常用格式。更詳細的請參考OpenCV的參考文件。

接著建立一個視窗

[python] view plain copy
  1. cv2.namedWindow("Image")  

然後在視窗中顯示影象

[python] view plain copy
  1. cv2.imshow("Image", img)  

最後還要添上一句:

[python] view plain copy
  1. cv2.waitKey (0)  

如果不添最後一句,在IDLE中執行視窗直接無響應。在命令列中執行的話,則是一閃而過。

完整的程式為:

[python] view plain copy
  1. import cv2   
  2.   
  3. img = cv2.imread("D:\\cat.jpg")   
  4. cv2.namedWindow("Image")   
  5. cv2.imshow("Image", img)   
  6. cv2.waitKey (0)  
  7. cv2.destroyAllWindows()  
最後釋放視窗是個好習慣!

建立/複製影象


新的OpenCV的介面中沒有CreateImage介面。即沒有cv2.CreateImage這樣的函式。如果要建立影象,需要使用numpy的函式(現在使用OpenCV-Python繫結,numpy是必裝的)。如下:

[python] view plain copy
  1. emptyImage = np.zeros(img.shape, np.uint8)  
在新的OpenCV-Python繫結中,影象使用NumPy陣列的屬性來表示影象的尺寸和通道資訊。如果輸出img.shape,將得到(500, 375, 3),這裡是以OpenCV自帶的cat.jpg為示例。最後的3表示這是一個RGB影象。

也可以複製原有的影象來獲得一副新影象。

[python] view plain copy
  1. emptyImage2 = img.copy();  
如果不怕麻煩,還可以用cvtColor獲得原影象的副本。
[python] view plain copy
  1. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  2. #emptyImage3[...]=0  
後面的emptyImage3[...]=0是將其轉成空白的黑色影象。

儲存影象 

儲存影象很簡單,直接用cv2.imwrite即可。

cv2.imwrite("D:\\cat2.jpg", img)

第一個引數是儲存的路徑及檔名,第二個是影象矩陣。其中,imwrite()有個可選的第三個引數,如下:

cv2.imwrite("D:\\cat2.jpg", img,[int(cv2.IMWRITE_JPEG_QUALITY), 5])

第三個引數針對特定的格式: 對於JPEG,其表示的是影象的質量,用0-100的整數表示,預設為95。 注意,cv2.IMWRITE_JPEG_QUALITY型別為Long,必須轉換成int。下面是以不同質量儲存的兩幅圖:

對於PNG,第三個參數列示的是壓縮級別。cv2.IMWRITE_PNG_COMPRESSION,從0到9,壓縮級別越高,影象尺寸越小。預設級別為3:

[python] view plain copy
  1. cv2.imwrite("./cat.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])   
  2. cv2.imwrite("./cat2.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  
儲存的影象尺寸如下:

還有一種支援的影象,一般不常用。

完整的程式碼為:

[python] view plain copy
  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread("./cat.jpg")  
  5. emptyImage = np.zeros(img.shape, np.uint8)  
  6.   
  7. emptyImage2 = img.copy()  
  8.   
  9. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  10. #emptyImage3[...]=0  
  11.   
  12. cv2.imshow("EmptyImage", emptyImage)  
  13. cv2.imshow("Image", img)  
  14. cv2.imshow("EmptyImage2", emptyImage2)  
  15. cv2.imshow("EmptyImage3", emptyImage3)  
  16. cv2.imwrite("./cat2.jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 5])  
  17. cv2.imwrite("./cat3.jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
  18. cv2.imwrite("./cat.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])  
  19. cv2.imwrite("./cat2.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  
  20. cv2.waitKey (0)  
  21. cv2.destroyAllWindows()  

參考資料:
《OpenCV References Manuel》
《OpenCV  2 Computer Vision Application Programming Cookbook》
《OpenCV Computer Vision with Python》




OpenCV Python教程(2、影象元素的訪問、通道分離與合併)


OpenCV Python教程之影象元素的訪問、通道分離與合併

轉載請詳細註明原作者及出處,謝謝!

訪問畫素

畫素的訪問和訪問numpy中ndarray的方法完全一樣,灰度圖為:

[python] view plain copy
  1. img[j,i] = 255  
其中j,i分別表示影象的行和列。對於BGR影象,為:
[python] view plain copy
  1. img[j,i,0]= 255  
  2. img[j,i,1]= 255  
  3. img[j,i,2]= 255  
第三個數表示通道。

下面通過對影象新增人工的椒鹽現象來進一步說明OpenCV Python中需要注意的一些問題。完整程式碼如下:

[python] view plain copy
  1. import cv2  
  2. import numpy as np  
  3.   
  4. def salt(img, n):  
  5.     for k in range(n):  
  6.         i = int(np.random.random() * img.shape[1]);  
  7.         j = int(np.random.random() * img.shape[0]);  
  8.         if img.ndim == 2:   
  9.             img[j,i] = 255  
  10.         elif img.ndim == 3:   
  11.             img[j,i,0]= 255  
  12.             img[j,i,1]= 255  
  13.             img[j,i,2]= 255  
  14.     return img  
  15.   
  16. if __name__ == '__main__':  
  17.     img = cv2.imread("影象路徑")  
  18.     saltImage = salt(img, 500)  
  19.     cv2.imshow("Salt", saltImage)  
  20.     cv2.waitKey(0)  
  21.     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陣列來分離通道。直接法為:

[python] view plain copy
  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread("D:/cat.jpg")  
  5. b, g, r = cv2.split(img)  
  6. cv2.imshow("Blue", r)  
  7. cv2.imshow("Red", g)  
  8. cv2.imshow("Green", b)  
  9. cv2.waitKey(0)  
  10. cv2.destroyAllWindows()  
其中split返回RGB三個通道,如果只想返回其中一個通道,可以這樣:
[python] view plain copy
  1. b = cv2.split(img)[0]  
  2. g = cv2.split(img)[1]  
  3. r = cv2.split(img)[2]  
最後的索引指出所需要的通道。

也可以直接操作NumPy陣列來達到這一目的:

[python] view plain copy
  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread("D:/cat.jpg")  
  5.   
  6. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  8. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  9.   
  10. b[:,:] = img[:,:,0]  
  11. g[:,:] = img[:,:,1]  
  12. r[:,:] = img[:,:,2]  
  13.   
  14. cv2.imshow("Blue", r)  
  15. cv2.imshow("Red", g)  
  16. cv2.imshow("Green", b)  
  17. cv2.waitKey(0)  
  18. cv2.destroyAllWindows()  
注意先要開闢一個相同大小的圖片出來。這是由於numpy中陣列的複製有些需要注意的地方,具體事例如下:
[python] view plain copy
  1. >>> c= np.zeros(img.shape, dtype=img.dtype)  
  2. >>> c[:,:,:] = img[:,:,:]  
  3. >>> d[:,:,:] = img[:,:,:]  
  4. >>> c is a  
  5. False  
  6. >>> d is a  
  7. False  
  8. >>> c.base is a  
  9. False  
  10. >>> d.base is a #注意這裡!!!  
  11. True  
這裡,d只是a的映象,具體請參考《NumPy簡明教程(二,陣列3)》中的“複製和映象”一節。

通道合併

同樣,通道合併也有兩種方法。第一種是OpenCV自帶的merge函式,如下:

[python] view plain copy
  1. merged = cv2.merge([b,g,r]) #前面分離出來的三個通道  
接著是NumPy的方法:
[python] view plain copy
  1. mergedByNp = np.dstack([b,g,r])   
注意:這裡只是演示,實際使用時請用OpenCV自帶的merge函式!用NumPy組合的結果不能在OpenCV中其他函式使用,因為其組合方式與OpenCV自帶的不一樣,如下:
[python] view plain copy
  1. merged = cv2.merge([b,g,r])  
  2. print "Merge by OpenCV"   
  3. print merged.strides  
  4.   
  5. mergedByNp = np.dstack([b,g,r])   
  6. print "Merge by NumPy "   
  7. print mergedByNp.strides  
結果為:
[python] view plain copy
  1. Merge by OpenCV  
  2. (112531)  
  3. Merge by NumPy  
  4. (1500187500)  
NumPy陣列的strides屬性表示的是在每個維數上以位元組計算的步長。這怎麼理解呢,看下面這個簡單點的例子:
[python] view plain copy
  1. >>> a = np.arange(6)  
  2. >>> a  
  3. array([012345])  
  4. >>> a.strides  
  5. (4,)  
a陣列中每個元素都是NumPy中的整數型別,佔4個位元組,所以第一維中相鄰元素之間的步長為4(個位元組)。

同樣,2維陣列如下:

[python] view plain copy
  1. >>> b = np.arange(12).reshape(3,4)  
  2. >>> b  
  3. array([[ 0,  1,  2,  3],  
  4.        [ 4,  5,  6,  7],  
  5.        [ 8,  91011]])  
  6. >>> b.strides  
  7. (164)  
從裡面開始看,裡面是一個4個元素的一維整數陣列,所以步長應該為4。外面是一個含有3個元素,每個元素的長度是4×4=16。所以步長為16。

下面來看下3維陣列:

[python] view plain copy
  1. >>> c = np.arange(27).reshape(3,3,3)  
其結果為:
[python] view plain copy
  1. array([[[ 0,  1,  2],  
  2.         [ 3,  4,  5],  
  3.         [ 6,  7,  8]],  
  4.   
  5.        [[ 91011],  
  6.         [121314],  
  7.         [151617]],  
  8.   
  9.        [[181920],  
  10.         [212223],  
  11.         [242526]]])  
根據前面瞭解的,推斷下這個陣列的步長。從裡面開始算,應該為(3×4×3,3×4,4)。驗證一下:
[python] view plain copy
  1. >>> c.strides  
  2. (36124)  
完整的程式碼為:
[python] view plain copy
  1. import cv2  
  2. import numpy as np  
  3.   
  4. img = cv2.imread("D:/cat.jpg")  
  5.   
  6. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  8. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  9.   
  10. b[:,:] = img[:,:,0]  
  11. g[:,:] = img[:,:,1]  
  12. r[:,:] = img[:,:,2]  
  13.   
  14. merged = cv2.merge([b,g,r])  
  15. print "Merge by OpenCV"   
  16. print merged.strides  
  17. print merged  
  18.   
  19. mergedByNp = np.dstack([b,g,r])   
  20. print "Merge by NumPy "   
  21. print mergedByNp.strides  
  22. print mergedByNp  
  23.   
  24. cv2.imshow("Merged", merged)  
  25. cv2.imshow("MergedByNp", merged)  
  26. cv2.imshow("Blue", b)  
  27. cv2.imshow("Red", r)  
  28. cv2.imshow("Green", g)  
  29. cv2.waitKey(0)  
  30. cv2.destroyAllWindows()  


from: http://blog.csdn.net/sunny2038/article/category/904451

相關文章