本文介紹圖像統計功能相關的函數,包含統計元素中非零值的數量、最小值、最大值、和、均值、標准差,以及單行或單列的最小值、最大值、和、均值。
1、非0值數量 countNonZero
countNonZero()用來統計元素值為非0值的像素點個數。
接口形式:
cv2.countNonZero(src) -> retval
- 參數含義:
- src:輸入圖像,必須為單通道圖像;
- retval:非零像素值個數
下面是一個統計lena灰度圖和一個5×5對角矩陣中非零元素數量的例子:
import numpy as np
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_src = cv2.imread('..\\samples\\data\\lena.jpg',cv2.IMREAD_GRAYSCALE)
count = cv2.countNonZero(img_src)
print('非零像素點個數:',count)
arr = np.eye(5)
print('np.eye(5):\n',arr)
count = cv2.countNonZero(arr)
print('非零像素點個數:',count)
運行結果:
VX公眾號: 桔子code / juzicode.com
cv2.__version__: 4.5.3
非零像素點個數: 262144
np.eye(5):
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
非零像素點個數: 5
這個5×5的對角陣,只有對角線上的5個元素非零,得到的非零像素點個數為5。
用absdiff()計算了2幅圖像差異后得到的新圖像,再用countNonZero()計算這個新圖像中非0的像素點個數,可以比較出2幅圖像的差異,OpenCV-Python教程:形態學變換~開閉操作,頂帽黑帽,形態學梯度,擊中擊不中(morphologyEx) 中比較開操作和先腐蝕后膨脹圖像差異時有具體的例子。
OpenCV 4.5版本中雖然沒有提供零值元素數量的統計函數,但是零值元素數量可以由元素總數減去非零值數量得到:
arr = np.eye(5)
print('np.eye(5):\n',arr)
count = cv2.countNonZero(arr)
count_zero = arr.shape[0]*arr.shape[0]-count
print('非零像素點個數:',count)
print('零像素點個數:',count_zero)
運行結果:
np.eye(5):
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
非零像素點個數: 5
零像素點個數: 20
2、最小最大值及其位置 minMaxLoc
minMaxLoc()函數返回圖像中的元素值的最小值和最大值,以及最小值和最大值的坐標。
接口形式:
cv2.minMaxLoc(src[, mask])->minVal, maxVal, minLoc, maxLoc
- 參數含義:
- src:輸入圖像,必須為單通道圖像;
- mask:掩碼;
- minVal, maxVal, minLoc, maxLoc:依次為最小值,最大值,最小值的坐標,最大值的坐標;
下面是一個求lena圖像R通道極值的例子:
import cv2
print('VX公眾號: 桔子code / juzicode.com')
img = cv2.imread('..\\lena.jpg')[:,:,2] #提取其中R通道
ret = cv2.minMaxLoc(img)
print('minMaxLoc(img): ',ret)
運行結果:
minMaxLoc(img): (49.0, 255.0, (265, 198), (415, 8))
返回結果是一個4元組,第0個元素為最小值,第1個元素為最大值,第2個元素為最小值的坐標:(265, 198),第3個元素是最大值的坐標:(415, 8)。
返回minLoc和maxLoc的坐標位置是以OpenCV中(x,y)的形式組織的,但是在numpy中下標訪問是按照array[行][列]形式,類似於array[y][x]的形式,所以minLoc和maxLoc的坐標值不能直接用於numpy的下標訪問,需要對調后才可以使用,通過下面的例子可以得到驗證,在前面的例子中極值的位置是(265, 198), (415, 8):
#錯誤的下標訪問,未對調的方式
print('img[265][198]:',img[265][198])
print('img[415][8]:',img[415][8])
#正確的下標訪問,對調后作為numpy數組下標
print('img[198][265]:',img[198][265])
print('img[8][415]:',img[8][415])
運行結果:
minMaxLoc(img): (49.0, 255.0, (265, 198), (415, 8))
img[265][198]: 90
img[415][8]: 171
img[198][265]: 49 #對調后的下標訪問得到正確的最小值
img[8][415]: 255 #對調后的下標訪問得到正確的最大值
如果存在多個最大值或者最小值,返回是誰的位置?
minMaxLoc()內部是按照行掃描方式,如果找到一個最小值,后面沒有比這個數值更小的數值,那最小值的位置就是最開始出現的那個位置,即使后面出現了這個最小數值相等的數值,找最大值也一樣。比如一串數字1,2,3,4,5,1,7,這里最小值為1,最小值的位置就是第1個數值1,即使后面第6個數值也為1。掃描完上一行之后再掃描下一行,即使后面的行出現相等數值也不會改變原來找到的最小值或最大值的位置。下面是一個存在多個最大值或最小值的例子:
import numpy as np
import cv2
print('cv2.__version__:',cv2.__version__)
print('VX公眾號: 桔子code / juzicode.com')
arr = np.array([[1,1,0,2,0],[0,20,20,11,15]])
print('arr:\n',arr)
ret = cv2.minMaxLoc(arr)
print('minMaxLoc(arr): ',ret)
運行結果:
VX公眾號: 桔子code / juzicode.com
arr:
[[ 1 1 0 2 0]
[ 0 20 20 11 15]]
minMaxLoc(arr): (0.0, 20.0, (2, 0), (1, 1))
這個例子中第0行和第1行都有最小值0,掃描時先將第0行第2列的0的位置保留下來,后面如果沒有小於0的數值,這個位置值就不再變化;同樣地找到第1行第1列的20時,后面沒有比20更大的數值,所以最大值的位置就是第1行第1列。
3、元素值之和sumElems
sumElems()統計所有元素值之和,如果有多通道,分通道計算,返回的是一個四元組,依次對應圖像可能包含的第0,1,2,3通道,如果單通道圖像則只有下標0對應的元素有意義,如果是3通道則只有前3個元素有意義。
接口形式:
cv2.sumElems(src) -> retval
- 參數含義:
- src:輸入圖像,可以是單通道,3通道或4通道圖像;
- retval:返回的是一個4元組,分別對應各通道元素的和。
注意在C++接口中,函數的名稱為sum(),對應的接口形式為:
Scalar cv::sum(InputArray src)
下面是一個統計lena圖和一個5×5對角陣中所有元素和的例子:
import numpy as np
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_src = cv2.imread('..\\samples\\data\\lena.jpg')#,cv2.IMREAD_GRAYSCALE)
val = cv2.sumElems(img_src)
print('lena像素元素和:',val )
arr = np.eye(5)
val = cv2.sumElems(arr)
print('np.eye(5)元素和:',val)
運行結果:
lena像素元素和: (27629713.0, 26099764.0, 47115221.0, 0.0)
np.eye(5)元素和: (5.0, 0.0, 0.0, 0.0)
這個例子中lena圖像讀出后是一個3通道彩色圖像,所以用sumElems()計算和后,返回結果的下標0,1,2對應其B,G,R通道的元素的和,其下標3的值為0.0沒有意義,np.eye(5)生成的是“單通道圖像”,返回結果只有其下標為0的值才有意義。
4、平均值mean
mean()用來統計單個通道內像素值的平均值,如果有多個通道,分通道計算。
接口形式:
cv2.mean(src[, mask]) ->retval
- 參數含義:
- src:輸入圖像,可以是單通道,3通道或4通道圖像;
- mask:可選的掩碼;
- retval:返回的是一個4元組,分別對應各通道元素的平均值。
下面這個例子計算lena圖和5×5對角陣中所有元素的平均值:
import numpy as np
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_src = cv2.imread('..\\samples\\data\\lena.jpg')
val = cv2.mean(img_src)
print('lena像素平均值:',val)
arr = np.eye(5)
val = cv2.mean(arr)
print('np.eye(5)平均值:',val)
運行結果:
lena像素平均值: (105.39899063110352, 99.56269836425781, 179.73030471801758, 0.0)
np.eye(5)平均值: (0.2, 0.0, 0.0, 0.0)
返回結果也是一個四元組,可以參考sumElems()理解。
5、平均值與標准差meanStdDev
meanStdDev()用來統計單通道內像素值的平均值和標准差,一次調用返回2個結果。
接口形式:
cv2.meanStdDev(src[, mean[, stddev[, mask]]]) ->mean, stddev
- 參數含義:
- src:輸入圖像,必須為單通道圖像;
- mask:可選的掩碼;
- mean:平均值;
- stddev:標准差;
meanStdDev()返回的是一個元組,下標0為平均值mean,下標1為標准差stddev。
下面這個例子計算lena圖和5×5對角陣中元素的平均值和標准差:
import numpy as np
import cv2
print('VX公眾號: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img_src = cv2.imread('..\\samples\\data\\lena.jpg')#,cv2.IMREAD_GRAYSCALE)
val = cv2.meanStdDev(img_src)
print(type(val))
print('val[0]:',type(val[0]))
print('val[0][0]:',type(val[0][0]))
print('lena圖像的平均值:\n',val[0])
print('lena圖像的標准差:\n',val[1])
print('lena圖像B通道的平均值:\n',val[0][0][0])
print('lena圖像B通道的標准差:\n',val[1][0][0])
arr = np.eye(5)
val = cv2.meanStdDev(arr)
print('np.eye(5)的平均值:',val[0])
print('np.eye(5)的標准差:',val[1])
print('np.eye(5)的平均值:',val[0][0][0])
print('np.eye(5)的標准差:',val[1][0][0])
運行結果:
<class 'tuple'>
val[0]: <class 'numpy.ndarray'>
val[0][0]: <class 'numpy.ndarray'>
lena圖像的平均值:
[[105.39899063]
[ 99.56269836]
[179.73030472]]
lena圖像的標准差:
[[33.74205485]
[52.87345828]
[49.01569488]]
lena圖像B通道的平均值:
105.39899063110352
lena圖像B通道的標准差:
33.74205485167219
np.eye(5)的平均值: [[0.2]]
np.eye(5)的標准差: [[0.4]]
np.eye(5)的平均值: 0.2
np.eye(5)的標准差: 0.4
從上面的例子也可以看到,meanStdDev()返回的元組中,下標0是平均值,下標1是標准差。平均值或標准差是一個numpy.ndarray類型的數據,其大小跟原始圖像通道數一樣,比如lena圖像的通道數是3,5×5對角陣的通道數是1。平均值和標准差中的某一個通道的數值仍然是numpy.ndarray類型的數據,其大小為1。所以當要取其某一個通道的數值時,則需要用3次下標的方式才能得到,比如用val[0][0][0]才能得到其B通道的平均值。
對比mean()方法計算的平均值,meanStdDev()得到的平均值數值大小是一樣的。但是需要注意的是mean()方法返回的是一個包含4個元素的元組,其元組長度是固定的,而meanStdDev()方法得到的平均值是一個numpy數組,包含元素的個數依賴於輸入圖像的通道數。
6、單行/列的極值、和、均值 reduce
reduce用來統計二維數組的每一行或某一列中的最小值、最大值、平均值、和。這里reduce的含義也可以理解為將二維矩陣壓縮成一維向量,壓縮后的值根據入參類型可以是最小值、最大值、平均值或者和。
接口形式:
cv2.reduce(src, dim, rtype[, dst[, dtype]]) ->dst
- 參數含義:
- src:源圖像,可以是單通道也可以是多通道,多通道時分通道計算;
- dim:如果為0表示統計每列的數據等價於壓縮成行(row),如果為1表示統計每行的數據等價於壓縮成列(column);
- rtype:reduce操作的類型;
- dst:目標圖像;
- dtype:目標圖像的類型,如果不指定默認為-1表示用源圖像src的數據類型;
dim參數的理解:如果為0表示生成新的數據將是一個行向量,所以是在每一列上操作,將單個的列壓縮成一個數值從而組成一個行向量;如果為1則表示生成新的數據是一個列向量,在每一行上操作,將單個的行壓縮成一個數值從而組成一個列向量。
rtype表示的reudce操作的類型如下:
rtype標志 | 含義 |
REDUCE_SUM | 所有行或列的和 |
REDUCE_AVG | 所有行或列的平均值 |
REDUCE_MAX | 所有行或列的最大值 |
REDUCE_MIN | 所有行或列的最小值 |
下面這個例子分別統計行和列的最小值、最大值:
import numpy as np
import cv2
print('cv2.__version__:',cv2.__version__)
print('VX公眾號: 桔子code / juzicode.com')
arr = np.array([[1,1,0,2,0],[0,20,20,11,15],[5,5,5,5,5]],dtype=np.uint8)
print(arr.shape,arr.dtype)
print('arr:\n',arr)
#reduce為行向量,計算最小最大值
row_min = cv2.reduce(arr,0,cv2.REDUCE_MIN)
print('row_min: \n',row_min)
row_max = cv2.reduce(arr,0,cv2.REDUCE_MAX)
print('row_max: \n',row_max)
#reduce為列向量,計算最小最大值
col_min = cv2.reduce(arr,1,cv2.REDUCE_MIN)
print('col_min: \n',col_min)
col_max = cv2.reduce(arr,1,cv2.REDUCE_MAX)
print('col_max: \n',col_max)
運行結果:
VX公眾號: 桔子code / juzicode.com
(3, 5) uint8
arr:
[[ 1 1 0 2 0]
[ 0 20 20 11 15]
[ 5 5 5 5 5]]
row_min:
[[0 1 0 2 0]]
row_max:
[[ 5 20 20 11 15]]
col_min:
[[0]
[0]
[5]]
col_max:
[[ 2]
[20]
[ 5]]
下面這個例子計算和、均值:
import numpy as np
import cv2
print('cv2.__version__:',cv2.__version__)
print('VX公眾號: 桔子code / juzicode.com')
arr = np.array([[1,1,0,2,0],[0,20,20,11,15],[5,5,5,5,5]],dtype=np.uint8)
print(arr.shape,arr.dtype)
print('arr:\n',arr)
#reduce為行向量,計算和、均值
row_sum = cv2.reduce(arr,0,cv2.REDUCE_SUM,dtype=cv2.CV_32S)
print('row_sum: \n',row_sum)
row_avg = cv2.reduce(arr,0,cv2.REDUCE_AVG,dtype=cv2.CV_32F)
print('row_avg: \n',row_avg)
#reduce為列向量,計算和、均值
col_sum = cv2.reduce(arr,1,cv2.REDUCE_SUM,dtype=cv2.CV_32S)
print('col_sum: \n',col_sum)
col_avg = cv2.reduce(arr,1,cv2.REDUCE_AVG,dtype=cv2.CV_32F)
print('col_avg: \n',col_avg)
運行結果:
arr:
[[ 1 1 0 2 0]
[ 0 20 20 11 15]
[ 5 5 5 5 5]]
row_sum:
[[ 6 26 25 18 20]]
row_avg:
[[2. 8.666667 8.333334 6. 6.666667]]
col_sum:
[[ 4]
[66]
[25]]
col_avg:
[[ 0.8]
[13.2]
[ 5. ]]
在計算和、均值的時候跟計算最大值、最小值不一樣地方是入參dtype必須指定。因為最大值、最小值的數據類型仍然是原來的數據類型,可以不指定其新生成數據的類型,默認和源圖像數據類型一樣,所以在前一個例子中並沒有指定dtype的類型。但是計算和的時候有可能會超出源數據類型所表示的范圍,所以dtype再不指定就會提示錯誤了。
有個例外的地方是在計算均值時,如果不指定dtype的類型使用默認的源圖像數據類型,雖然不會超出源圖像數據類型所能表示的范圍,但是均值涉及到除法和浮點計算,得到的數據精度可能是不准確的。像上面這個例子雖然也可以不指定數據類型默認用源圖像的數據類型CV_8U,但是計算均值得到的是經過四舍五入后的整數,下面這個例子展示了這個差異:
#計算均值不指定類型,默認用源數據的CV_8U:
row_avg = cv2.reduce(arr,0,cv2.REDUCE_AVG)#,dtype=cv2.CV_32F)
print('row_avg: \n',row_avg)
col_avg = cv2.reduce(arr,1,cv2.REDUCE_AVG)#,dtype=cv2.CV_32F)
print('col_avg: \n',col_avg)
-----運行結果:
row_avg:
[[2 9 8 6 7]] #對比使用更高精度的數據類型: [[2. 8.666667 8.333334 6. 6.666667]]
col_avg:
[[ 1]
[13]
[ 5]]
小結:countNonZero()用來統計的是非零元素的數量;minMaxLoc()返回位置參數是按照OpenCV格式組織的,在numpy數組中使用時需要對調下標組織形式,返回的坐標是按行掃描方式得到的最開始的坐標;sumElems()用來計算單個通道內所有元素的和,其原生的C接口函數為sum();meanStdDev()返回的平均值和標准差是一個numpy數組,其元素長度依賴輸入圖像的通道數,這點和mean()、sumElems()計算的結果默認包含4個元素有所區別;reduce()計算單行/列的和、均值時dtype類型需要指定為能精確表示的數據類型。