目標
• 獲取像素值並修改
• 獲取圖像的屬性(信息)
• 圖像的 ROI()
• 圖像通道的拆分及合併
幾乎全部這些操做與 Numpy 的關係都比與 OpenCV 的關係更加緊密,所以熟練 Numpy 能夠幫助咱們寫出性能更好的代碼。
(示例將會在 Python 終端中展現,由於他們大部分都只有一行代碼)python
9.1 獲取並修改像素值
首先咱們須要讀入一幅圖像:算法
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')編程
你能夠根據像素的行和列的座標獲取他的像素值。對 BGR 圖像而言,返回值爲 B,G,R 的值。對灰度圖像而言,會返回他的灰度值(亮度?intensity)數組
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
px=img[100,100]
print(px)
blue=img[100,100,0]
print(blue)緩存
你能夠以相似的方式修改像素值。app
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
img[100,100]=[255,255,255]
print(img[100,100])
## [255 255 255]函數
警告:Numpy 是通過優化了的進行快速矩陣運算的軟件包。因此咱們不推薦逐個獲取像素值並修改,這樣會很慢,能有矩陣運算就不要用循環。
注意:上面提到的方法被用來選取矩陣的一個區域,好比說前 5 行的後 3列。對於獲取每個像素值,也許使用 Numpy 的 array.item() 和 array.itemset() 會更好。可是返回值是標量。若是你想得到全部 B,G,R 的
值,你須要使用 array.item() 分割他們。oop
獲取像素值及修改的更好方法。post
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
print(img.item(10,10,2))
img.itemset((10,10,2),100)
print(img.item(10,10,2))
# 59
# 100
9.2 獲取圖像屬性
圖像的屬性包括:行,列,通道,圖像數據類型,像素數目等img.shape 能夠獲取圖像的形狀。他的返回值是一個包含行數,列數,通道數的元組。
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
print(img.shape)
##(342, 548, 3)
注意:若是圖像是灰度圖,返回值僅有行數和列數。因此經過檢查這個返回值就能夠知道加載的是灰度圖仍是彩色圖。
img.size 能夠返回圖像的像素數目:
注意:在debug時 img.dtype 很是重要。由於在 OpenCV Python 代碼中常常出現數據類型的不一致。
9.3 圖像 ROI
有時你須要對一幅圖像的特定區域進行操做。例如咱們要檢測一副圖像中眼睛的位置,咱們首先應該在圖像中找到臉,再在臉的區域中找眼睛,而不是直接在一幅圖像中搜索。這樣會提升程序的準確性和性能。
ROI 也是使用 Numpy 索引來得到的。如今咱們選擇球的部分並把他拷貝到圖像的其餘區域。
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
ball=img[280:340,330:390]
img[273:333,100:160]=ball
img=cv2.imshow('test', img)
cv2.waitKey(0)
看看結果吧:
9.4 拆分及合併圖像通道
有時咱們須要對 BGR 三個通道分別進行操做。這是你就須要把 BGR 拆分紅單個通道。有時你須要把獨立通道的圖片合併成一個 BGR 圖像。你能夠這樣作:
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') b,g,r=cv2.split(img) img=cv2.merge(b,g,r)
或者:
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') b=img[:,:,0]
假如你想使全部像素的紅色通道值都爲 0,你沒必要先拆分再賦值。你能夠直接使用 Numpy 索引,這會更快。
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') img[:,:,2]=0
警告:cv2.split() 是一個比較耗時的操做。只有真正須要時才用它,能用Numpy 索引就儘可能用。
9.5 爲圖像擴邊(填充)
若是你想在圖像周圍建立一個邊,就像相框同樣,你可使用 cv2.copyMakeBorder()函數。這常常在卷積運算或 0 填充時被用到。這個函數包括以下參數:
• src 輸入圖像
• top, bottom, left, right 對應邊界的像素數目。
• borderType 要添加那種類型的邊界,類型以下:
– cv2.BORDER_CONSTANT 添加有顏色的常數值邊界,還須要下一個參數(value)。
– cv2.BORDER_REFLECT 邊界元素的鏡像。好比: fedcba|abcde-fgh|hgfedcb
– cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT跟上面同樣,但稍做改動。例如: gfedcb|abcdefgh|gfedcba
– cv2.BORDER_REPLICATE 重複最後一個元素。例如: aaaaaa|abcdefgh|hhhhhhh
– cv2.BORDER_WRAP 不知道怎麼說了, 就像這樣: cdefgh|abcdefgh|abcdefg
• value 邊界顏色,若是邊界的類型是 cv2.BORDER_CONSTANT
爲了更好的理解這幾種類型請看下面的演示程序。
import cv2 import numpy as np from matplotlib import pyplot as plt BLUE=[255,0,0] img1=cv2.imread('opencv_logo.png') replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE) reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT) reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101) wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP) constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_CONSTANT,value=BLUE) plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL') plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE') plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT') plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101') plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP') plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT') plt.show()
結果以下(因爲是使用 matplotlib 繪製,因此交換 R 和 B 的位置,OpenCV 中是按 BGR,matplotlib 中是按 RGB 排列):
目標
• 學習圖像上的算術運算,加法,減法,位運算等。
• 咱們將要學習的函數與有:cv2.add(),cv2.addWeighted() 等。
10.1 圖像加法
你可使用函數 cv2.add() 將兩幅圖像進行加法運算,固然也能夠直接使用 numpy,res=img1+img。兩幅圖像的大小,類型必須一致,或者第二個圖像可使一個簡單的標量值。
注意:OpenCV 中的加法與 Numpy 的加法是有所不一樣的。OpenCV 的加法是一種飽和操做,而 Numpy 的加法是一種模操做。
例以下面的兩個例子:
x = np.uint8([250])
y = np.uint8([10]) print cv2.add(x,y) # 250+10 = 260 => 255 [[255]] print x+y # 250+10 = 260 % 256 = 4 [4]
這種差異在你對兩幅圖像進行加法時會更加明顯。OpenCV 的結果會更好一點。因此咱們儘可能使用 OpenCV 中的函數。
10.2 圖像混合
這其實也是加法,可是不一樣的是兩幅圖像的權重不一樣,這就會給人一種混合或者透明的感受。圖像混合的計算公式以下:
g (x) = (1 − α)f 0 (x) + αf 1 (x)
經過修改 α 的值(0 → 1),能夠實現很是酷的混合。
如今咱們把兩幅圖混合在一塊兒。第一幅圖的權重是 0.7,第二幅圖的權重是 0.3。函數 cv2.addWeighted() 能夠按下面的公式對圖片進行混合操做。
dst = α · img1 + β · img2 + γ
這裏 γ 的取值爲 0。
img1 = cv2.imread('ml.png')
img2 = cv2.imread('opencv_logo.jpg') dst = cv2.addWeighted(img1,0.7,img2,0.3,0) cv2.imshow('dst',dst) cv2.waitKey(0) cv2.destroyAllWindows()
dst = cv2.addWeighted(img1,0.7,img2,0.3,0)
error: C:\projects\opencv-python\opencv\modules\core\src\arithm.cpp:659:
error: (-209) The operation is neither 'array op array' (where arrays have
the same size and the same number of channels), nor 'array op scalar',
nor 'scalar op array' in function cv::arithm_op
下面就是結果:# 這個運行有問題
10.3 按位運算
這裏包括的按位操做有:AND,OR,NOT,XOR 等。當咱們提取圖像的一部分,選擇非矩形 ROI 時這些操做會頗有用(下一章你就會明白)。下面的例子就是教給咱們如何改變一幅圖的特定區域。我想把 OpenCV 的標誌放到另外一幅圖像上。若是我使用加法,顏色會改變,若是使用混合,會獲得透明效果,可是我不想要透明。若是他是矩形我能夠象上一章那樣使用 ROI。可是他不是矩形。可是咱們能夠經過下面的按位運算實現:
import cv2
import numpy as np
# Load two images
img1 = cv2.imread('messi5.jpg')
img2 = cv2.imread('opencv-logo-white.png')
# I want to put logo on top-left corner, So I create a ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
# Now create a mask of logo and create its inverse mask also
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
# Now black-out the area of logo in ROI
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
# Take only region of logo from logo image.
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
# Put logo in ROI and modify the main image
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv2.imshow('res',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
結果以下。左面的圖像是咱們建立的掩碼。右邊的是最終結果。爲了幫助你們理解我把上面程序的中間結果也顯示了出來,特別是 img1_bg 和 img2_fg。
練習
1. 建立一個幻燈片用來演示一幅圖如何平滑的轉換成另外一幅圖(使用函數cv2.addWeighted)
目標
在圖像處理中你每秒鐘都要作大量的運算,因此你的程序不只要能給出正確的結果,同時還必需要快。因此這節咱們將要學習:
• 檢測程序的效率
• 一些可以提升程序效率的技巧
• 你要學到的函數有:cv2.getTickCount,cv2.getTickFrequency等
除了 OpenCV,Python 也提供了一個叫 time 的的模塊,你能夠用它來測量程序的運行時間。另一個叫作 profile 的模塊會幫你獲得一份關於你的程序的詳細報告,其中包含了代碼中每一個函數運行須要的時間,以及每一個函數被調用的次數。若是你正在使用 IPython 的話,全部這些特色都被以一種用戶友好的方式整合在一塊兒了。咱們會學習幾個重要的,要想學到更加詳細的知識就打開更多資源中的連接吧。
11.1 使用 OpenCV 檢測程序效率
cv2.getTickCount 函數返回從參考點到這個函數被執行的時鐘數。因此當你在一個函數執行先後都調用它的話,你就會獲得這個函數的執行時間(時鐘數)。
cv2.getTickFrequency 返回時鐘頻率,或者說每秒鐘的時鐘數。因此你能夠按照下面的方式獲得一個函數運行了多少秒:
e1 = cv2.getTickCount()
# your code execution
e2 = cv2.getTickCount() time = (e2 - e1)/ cv2.getTickFrequency()
咱們將會用下面的例子演示。下面的例子是用窗口大小不一樣(5,7,9)的核函數來作中值濾波:
img1 = cv2.imread('messi5.jpg')
e1 = cv2.getTickCount() for i in xrange(5,49,2): img1 = cv2.medianBlur(img1,i) e2 = cv2.getTickCount() t = (e2 - e1)/cv2.getTickFrequency() print t # Result I got is 0.521107655 seconds
注 意: 你 也 可 以 中 time 模 塊 實 現 上 面 的 功 能。 但 是 要 用 的 函 數 是time.time() 而不是 cv2.getTickCount。比較一下這兩個結果的差異吧。
11.2 OpenCV 中的默認優化
OpenCV 中的不少函數都被優化過(使用 SSE2,AVX 等)。也包含一些沒有被優化的代碼。若是咱們的系統支持優化的話要儘可能利用只一點。在編譯時優化是被默認開啓的。所以 OpenCV 運行的就是優化後的代碼,若是你把優化關閉的話就只能執行低效的代碼了。你可使用函數 cv2.useOptimized()來查看優化是否被開啓了,使用函數 cv2.setUseOptimized() 來開啓優化。
讓咱們來看一個簡單的例子吧。
# check if optimization is enabled
In [5]: cv2.useOptimized()
Out[5]: True In [6]: %timeit res = cv2.medianBlur(img,49) 10 loops, best of 3: 34.9 ms per loop # Disable it In [7]: cv2.setUseOptimized(False) In [8]: cv2.useOptimized() Out[8]: False In [9]: %timeit res = cv2.medianBlur(img,49) 10 loops, best of 3: 64.1 ms per loop
看見了嗎,優化後中值濾波的速度是原來的兩倍。若是你查看源代碼的話,你會發現中值濾波是被 SIMD 優化的。因此你能夠在代碼的開始處開啓優化(你要記住優化是默認開啓的)。
11.3 在 IPython 中檢測程序效率
有時你須要比較兩個類似操做的效率,這時你可使用 IPython 爲你提供的魔法命令%time。他會讓代碼運行好幾回從而獲得一個準確的(運行)時間。它也能夠被用來測試單行代碼的。
例如,你知道下面這同一個數學運算用哪一種行式的代碼會執行的更快嗎?
x = 5; y = x ∗ ∗2
x = 5; y = x ∗ x
x = np.uint([5]); y = x ∗ x
y = np.squre(x)
咱們能夠在 IPython 的 Shell 中使用魔法命令找到答案。
In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop In [12]: %timeit y=x*x 10000000 loops, best of 3: 58.3 ns per loop In [15]: z = np.uint8([5]) In [17]: %timeit y=z*z 1000000 loops, best of 3: 1.25 us per loop In [19]: %timeit y=np.square(z) 1000000 loops, best of 3: 1.16 us per loop
居然是第一種寫法,它竟然比 Nump 快了 20 倍。若是考慮到數組構建的話,能達到 100 倍的差。
注意:Python 的標量計算比 Nump 的標量計算要快。對於僅包含一兩個元素的操做 Python 標量比 Numpy 的數組要快。可是當數組稍微大一點時Numpy 就會勝出了。
咱們來再看幾個例子。咱們來比較一下 cv2.countNonZero() 和
np.count_nonzero()。
In [35]: %timeit z = cv2.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop In [36]: %timeit z = np.count_nonzero(img) 1000 loops, best of 3: 370 us per loop
看見了吧,OpenCV 的函數是 Numpy 函數的 25 倍。
注意:通常狀況下 OpenCV 的函數要比 Numpy 函數快。因此對於相同的操做最好使用 OpenCV 的函數。固然也有例外,尤爲是當使用 Numpy 對視圖(而非複製)進行操做時。
11.4 更多 IPython 的魔法命令
還有幾個魔法命令能夠用來檢測程序的效率,profiling,line profiling,內存使用等。他們都有完善的文檔。因此這裏只提供了超連接。感興趣的能夠本身學習一下。
11.5 效率優化技術
有些技術和編程方法可讓咱們最大的發揮 Python 和 Numpy 的威力。
咱們這裏僅僅提一下相關的,你能夠經過超連接查找更多詳細信息。咱們要說的最重要的一點是:首先用簡單的方式實現你的算法(結果正確最重要),當結果正確後,再使用上面的提到的方法找到程序的瓶頸來優化它。
1. 儘可能避免使用循環,尤爲雙層三層循環,它們天生就是很是慢的。
2. 算法中儘可能使用向量操做,由於 Numpy 和 OpenCV 都對向量操做進行了優化。
3. 利用高速緩存一致性。
4. 沒有必要的話就不要複製數組。使用視圖來代替複製。數組複製是很是浪費資源的。
就算進行了上述優化,若是你的程序仍是很慢,或者說大的訓話不可避免的話,你你應該嘗試使用其餘的包,好比說 Cython,來加速你的程序。
更多資源1. Python Optimization Techniques2. Scipy Lecture Notes - Advanced Numpy3. Timing and Profiling in IPython