【OpenCV(01)】基本圖像操作、繪制,讀取視頻
【OpenCV(02)】圖像顏色處理,灰度化,二值化,仿射變換
【OpenCV(03)】插值方法,邊緣填充,透視變換,水印制作,噪點消除
【OpenCV(04)】梯度處理,邊緣檢測,繪制輪廓,凸包特征檢測,輪廓特征查找
目錄
- 直方圖
- 什么是直方圖
- 繪制直方圖
- 直方圖均衡化
- 自適應均衡化
- 對比度受限的自適應均衡化
- 模板匹配
- 匹配方法
- 平方差匹配
- 歸一化平方差匹配
- 相關匹配
- 歸一化相關匹配
- 相關系數匹配
- 歸一化相關系數匹配
- 繪制輪廓
- 霍夫變換
- 標準霍夫變換
- 統計概率霍夫直線變換
- 霍夫圓變換
- 圖像亮度變換
- 亮度變換
- 線性變換
- 直接像素值修改
- 形態學變換
直方圖
什么是直方圖
- 直方圖:反映圖像像素分布的統計圖,橫坐標就是圖像像素的取值,縱坐標是該像素的個數。也就是對一張圖像中不同像素值的像素個數的統計。
- 增加對比度:黑的更黑,白的更白。
繪制直方圖
- 繪制直方圖:hist=cv2.calcHist(images, channels, mask, histSize, ranges)
images
:輸入圖像列表,可以是一幅或多幅圖像(通常是灰度圖像或者彩色圖像的各個通道)。channels
:一個包含整數的列表,指示在每個圖像上計算直方圖的通道編號。如果輸入圖像是灰度圖,它的值就是 [0];如果是彩色圖像的話,傳入的參數可以是 [0],[1],[2] 它們分別對應著通道 B,G,R。mask
(可選):一個與輸入圖像尺寸相同的二值掩模圖像,其中非零元素標記了參與直方圖計算的區域,None為全部計算。histSize
:一個整數列表,也就是直方圖的區間個數(BIN 的數目)。用中括號括起來,例如:[256]。ranges
:每維數據的取值范圍,它是一個二維列表,每一維對應一個通道的最小值和最大值,例如對灰度圖像可能是[0, 256]
。
返回值hist 是一個長度為255的數組,數組中的每個值表示圖像中對應灰度等級的像素計數
-
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
獲取直方圖的最小值、最大值及其對應最小值的位置索引、最大值的位置索引 -
cv2.line(img, pt1, pt2, color, thickness)
- img:原始圖像,即要在上面畫線的numpy數組(一般為uint8類型)。
- pt1 和 pt2:分別為線段的起點和終點坐標,它們都是元組類型,例如
(x1, y1)
和(x2, y2)
分別代表線段兩端的橫縱坐標。 - color:線段的顏色,通常是一個包含三個元素的元組
(B, G, R)
表示BGR色彩空間的像素值,也可以是灰度圖像的一個整數值。 - thickness:線段的寬度,默認值是1,如果設置為負數,則線寬會被填充。
import cv2 as cv
import numpy as np#讀圖
bg = cv.imread('E:\hqyj\code\opencv\images\\bg.png')
#創建黑圖,繪制直方圖
black = np.zeros((256,256,3),np.uint8)#統計像素
hist = cv.calcHist([bg],[0],None,[256],[0,256])
print(hist)#獲取直方圖的最小值、最大值及其對應的最小值的位置索引、最大值的位置索引
minval,maxval,minloc,maxloc = cv.minMaxLoc(hist)#由于直方圖只有一列,列索引始終為 0,行索引則對應于具體的灰度值
#最小值 461.0 出現在位置 (0, 249),即灰度值為 249 的像素頻率為 0。
#最大值 32380.0 出現在位置 (0, 5),即灰度值為 5 的像素頻率為 5000
print(minval,maxval,minloc,maxloc)#定義直方圖的高
h_hist = np.int32(256)#循環遍歷每一個灰度值
for i in range(256):#當前像素的個數占直方圖的高,歸一化處理l = int(hist[i].item()*h_hist/maxval)point1 = (i,256-l)point2 = (i,256)cv.line(black,point1,point2,(255,255,0),1)cv.imshow('dst',black)
cv.waitKey(0)
cv.destroyAllWindows()
直方圖均衡化
遍歷圖像的像素統計出灰度值的個數、比例與累計比例,并重新映射到0-255范圍(也可以是其他范圍)內,其實從觀感上就可以發現,下面兩幅圖中前面那幅圖對比度不高,偏灰白。
直方圖均衡化作用:
- 增強對比度
- 提高圖像質量
自適應均衡化
自適應直方圖均衡化(Adaptive Histogram Equalization, AHE),通過調整圖像像素值的分布,使得圖像的對比度和亮度得到改善。
具體過程如下所示:
設有一個3*3的圖像,其灰度圖的像素值如上圖所示,現在我們要對其進行直方圖均衡化,首先就是統計其每個像素值的個數、比例以及其累計比例。如下圖所示。
接下來我們就要進行計算,就是將要縮放的范圍(通常是縮放到0-255,所以就是255-0)乘以累計比例,得到新的像素值,并將新的像素值放到對應的位置上,比如像素值為50的像素點,將其累計比例乘以255,也就是0.33乘以255得到84.15,取整后得到84,并將84放在原圖像中像素值為50的地方,像素值為100、210、255的計算過程類似,最終會得到如下圖所示的結果,這樣就完成了最基本的直方圖均衡化的過程。
dst = cv.equalizeHist(imgGray)
imgGray為需要直方圖均衡化的灰度圖,返回值為處理后的圖像
import cv2 as cv#讀圖
img = cv.imread('E:\hqyj\code\opencv\images\\zhifang.png',cv.IMREAD_GRAYSCALE)
#直方圖均衡化
dst = cv.equalizeHist(img)
cv.imshow('img',img)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
對比度受限的自適應均衡化
因為全局調整亮度和對比度的原因,臉部太亮,大部分細節都丟失了。自適應均衡化就是用來解決這一問題的:它在每一個小區域內(默認8×8)進行直方圖均衡化。當然,如果有噪點的話,噪點會被放大,需要對小區域內的對比度進行了限制,所以這個算法全稱叫:對比度受限的自適應直方圖均衡化(Contrast Limited Adaptive Histogram Equalization, CLAHE)。
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)
- clipLimit(可選):對比度限制參數,用于控制直方圖均衡化過程中對比度增強的程度。如果設置一個大于1的值(如2.0或4.0),CLAHE會限制對比度增強的最大程度,避免過度放大噪聲。如果不設置,OpenCV會使用一個默認值。
- tileGridSize(可選):圖像分塊的大小,通常是一個包含兩個整數的元組,如
(8, 8)
,表示將圖像劃分成8x8的小塊進行獨立的直方圖均衡化處理。分塊大小的選擇會影響到CLAHE的效果以及處理速度。
創建CLAHE對象后,可以使用 .apply()
方法對圖像進行CLAHE處理:
img=clahe.apply(image)
- image:要均衡化的圖像。
- img均衡后的圖像
import cv2 as cvimg = cv.imread('E:\hqyj\code\opencv\images\\zhifang.png',cv.IMREAD_GRAYSCALE)
#創建clahe對象
clahe = cv.createCLAHE(clipLimit=2.0,tileGridSize=(8,8))
#使用clach調用apply()方法
cl1 = clahe.apply(img)
cv.imshow('old',img)
cv.imshow('dst',cl1)
cv.waitKey(0)
cv.destroyAllWindows()
模板匹配
-
不會有邊緣填充。
-
類似于卷積,滑動比較,挨個比較象素。
-
返回結果大小是:目標圖大小-模板圖大小-1。
import cv2 as cv
import numpy as np#讀圖
img = cv.imread('E:\hqyj\code\opencv\images\\game.png')
gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#print(gray_img.shape) # (266, 283)temp = cv.imread('E:\hqyj\code\opencv\images\\temp.png')
gray_temp = cv.cvtColor(temp,cv.COLOR_BGR2GRAY)
#print(gray_temp.shape) # (24, 18)
h,w = temp.shape[:2]#模板匹配 拿到匹配結果矩陣
res = cv.matchTemplate(gray_img,gray_temp,cv.TM_CCOEFF_NORMED)
#print(res.shape) # (243, 266)#設置閾值
thresh = 0.8
#獲取匹配上的結果的索引
loc = np.where(res >= thresh)
#print(loc) # (array([ 63,......,143]), array([ 87,......,173]))#解包,拿到成對的x y索引
#print(zip(*loc)) # <zip object at 0x000001C601C79BC0>
for i in zip(*loc):#print(i) #x,y = i[1],i[0]#print(x,y)cv.rectangle(img, (x,y), (x+w,y+h), (0,255,255), 2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
temp(模板圖):
game(原圖):
在game圖中用temp圖作為模板圖匹配得到目標圖:
匹配方法
res=cv2.matchTemplate(image, templ, method)
-
image:原圖像,這是一個灰度圖像或彩色圖像(在這種情況下,匹配將在每個通道上獨立進行)。
-
templ:模板圖像,也是灰度圖像或與原圖像相同通道數的彩色圖像。
-
method:匹配方法,可以是以下之一:
- cv2.TM_CCOEFF(平方差匹配)
- cv2.TM_CCOEFF_NORMED(歸一化平方差匹配)
- cv2.TM_CCORR(相關匹配)
- cv2.TM_CCORR_NORMED(歸一化相關匹配)
- cv2.TM_SQDIFF(相關系數匹配)
- cv2.TM_SQDIFF_NORMED(cv2.TM_CCOEFF_NORMED)
- 這些方法決定了如何度量模板圖像與原圖像子窗口之間的相似度。
-
返回值res
函數在完成圖像模板匹配后返回一個結果矩陣,這個矩陣的大小與原圖像不相同。矩陣的每個元素表示原圖像中相應位置與模板圖像匹配的相似度。
匹配方法不同,返回矩陣的值的含義也會有所區別。以下是幾種常用的匹配方法及其返回值含義:
-
cv2.TM_SQDIFF
或cv2.TM_SQDIFF_NORMED
:返回值越接近0,表示匹配程度越好。最小值對應的最佳匹配位置。
-
cv2.TM_CCORR
或cv2.TM_CCORR_NORMED
:返回值越大,表示匹配程度越好。最大值對應的最佳匹配位置。
-
cv2.TM_CCOEFF
或cv2.TM_CCOEFF_NORMED
:返回值越大,表示匹配程度越好。最大值對應的最佳匹配位置。
-
平方差匹配
cv2.TM_SQDIFF
以模板圖與目標圖所對應的像素值使用平方差公式來計算,其結果越小,代表匹配程度越高,計算過程舉例如下。
注意:模板匹配過程皆不需要邊緣填充,直接從目標圖像的左上角開始計算。
歸一化平方差匹配
cv2.TM_SQDIFF_NORMED
與平方差匹配類似,只不過需要將值統一到0到1,計算結果越小,代表匹配程度越高,計算過程舉例如下。
相關匹配
cv2.TM_CCORR
使用對應像素的乘積進行匹配,乘積的結果越大其匹配程度越高,計算過程舉例如下。
歸一化相關匹配
cv2.TM_CCORR_NORMED
與相關匹配類似,只不過是將其值統一到0到1之間,值越大,代表匹配程度越高,計算過程舉例如下。
相關系數匹配
cv2.TM_CCOEFF
需要先計算模板與目標圖像的均值,然后通過每個像素與均值之間的差的乘積再求和來表示其匹配程度,1表示完美的匹配,-1表示最差的匹配,計算過程舉例如下。
歸一化相關系數匹配
cv2.TM_CCOEFF_NORMED
也是將相關系數匹配的結果統一到0到1之間,值越接近1代表匹配程度越高,計算過程舉例如下。
繪制輪廓
找的目標圖像中匹配程度最高的點,我們可以設定一個匹配閾值來篩選出多個匹配程度高的區域。
- loc=np.where(array > 0.8) #loc包含array中所有大于0.8的元素索引的數組
np.where(condition) 是 NumPy 的一個函數,當條件為真時,返回滿足條件的元素的索引。
- *zip(loc)
*loc
是解包操作,將loc
中的多個數組拆開,作為單獨的參數傳遞給zip
。zip
將這些數組按元素一一配對,生成一個迭代器,每個元素是一個元組,表示一個坐標點。
x=list([[1,2,3,4,3],[23,4,2,4,2]])
print(list(zip(*x))) #[(1, 23), (2, 4), (3, 2), (4, 4), (3, 2)]
霍夫變換
原圖:(后面代碼都是基于此圖執行的)
標準霍夫變換
lines=cv2.HoughLines(image, rho, theta, threshold)
image
:輸入圖像,通常為二值圖像,其中白點表示邊緣點,黑點為背景。rho
:r的精度,以像素為單位,表示霍夫空間中每一步的距離增量, 值越大,考慮越多的線。theta
:角度θ的精度,通常以弧度為單位,表示霍夫空間中每一步的角度增量。值越小,考慮越多的線。threshold
:累加數閾值,只有累積投票數超過這個閾值的候選直線才會被返回。
返回值:cv2.HoughLines
函數返回一個二維數組,每一行代表一條直線在霍夫空間中的參數 (rho, theta)
。
import cv2 as cv
import numpy as np#讀圖
img=cv.imread('E:\hqyj\code\opencv\images\\huofu.png')#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)#邊緣檢測
edges = cv.Canny(binary,30,70)#霍夫變換 返回的是[r,theta]
lines = cv.HoughLines(edges,0.8,np.pi/180,90)
print(lines) #[[[25.6 1.5707964]]# [[21.6 1.5707964]] # [[42.4 2.146755 ]] # [[46.4 2.146755 ]]]for line in lines:r,theta = line[0]sin_theta = np.sin(theta)cos_theta = np.cos(theta)x1,x2 = 0,img.shape[1]y1 = int([r-x1*cos_theta]/sin_theta)y2 = int([r-x2*cos_theta]/sin_theta)cv.line(img,(x1,y1),(x2,y2),(0,0,255),1)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
統計概率霍夫直線變換
lines=cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=0, maxLineGap=0)
image
:輸入圖像,通常為二值圖像,其中白點表示邊緣點,黑點為背景。rho
:極徑分辨率,以像素為單位,表示極坐標系中的距離分辨率。theta
:極角分辨率,以弧度為單位,表示極坐標系中角度的分辨率。threshold
:閾值,用于過濾掉弱檢測結果,只有累計投票數超過這個閾值的直線才會被返回。lines
(可選):一個可初始化的輸出數組,用于存儲檢測到的直線參數。minLineLength
(可選):最短長度閾值,比這個長度短的線會被排除。maxLineGap
(可選):同一直線兩點之間的最大距離。當霍夫變換檢測到一系列接近直角的線段時,這些線段可能是同一直線的不同部分。maxLineGap
參數指定了在考慮這些線段屬于同一直線時,它們之間最大可接受的像素間隔。
返回值lines:cv2.HoughLinesP
函數返回一個二維數組,每個元素是一個包含4個元素的數組,分別表示每條直線的起始點和結束點在圖像中的坐標(x1, y1, x2, y2)。
import cv2 as cv
import numpy as np#讀圖
img=cv.imread('E:\hqyj\code\opencv\images\\huofu.png')#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)#邊緣檢測
edges = cv.Canny(binary,30,70)
lines = cv.HoughLinesP(edges,0.8,np.pi/180,90,minLineLength=50,maxLineGap=20)
print(lines)for line in lines:x1,y1,x2,y2 = line[0]cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
霍夫圓變換
circles=cv2.HoughCircles(image, method, dp, minDist, param1, param2)
-
image
:輸入圖像,通常是灰度圖像。 -
method
:使用的霍夫變換方法:霍夫梯度法,可以是cv2.HOUGH_GRADIENT
,這是唯一在OpenCV中用于圓檢測的方法。 -
dp
:累加器分辨率與輸入圖像分辨率之間的降采樣比率,用于加速運算但不影響準確性。設置為1表示霍夫梯度法中累加器圖像的分辨率與原圖一致 -
minDist
:檢測到的圓心之間的最小允許距離,以像素為單位。在霍夫變換檢測圓的過程中,可能會檢測到許多潛在的圓心。minDist
參數就是為了過濾掉過于接近的圓檢測結果,避免檢測結果過于密集。當你設置一個較小的minDist
值時,算法會嘗試找出盡可能多的圓,即使是彼此靠得很近的圓也可能都被檢測出來。相反,當你設置一個較大的minDist
值時,算法會傾向于只檢測那些彼此間存在一定距離的獨立的圓。 -
param1
和param2
:這兩個參數是在使用cv2.HOUGH_GRADIENT
方法時的特定參數,分別為:param1
(可選):閾值1,決定邊緣強度的閾值。param2
:閾值2,控制圓心識別的精確度。較大的該值會使得檢測更嚴格的圓。param2
通常被稱為圓心累積概率的閾值。在使用霍夫梯度方法時,param2
設置的是累加器閾值,它決定了哪些候選圓點集合被認為是有效的圓。較高的param2
值意味著對圓的檢測更嚴格,只有在累加器中積累了足夠高的響應值才認為是真實的圓;較低的param2
值則會降低檢測的門檻,可能會檢測到更多潛在的圓,但也可能包含更多的誤檢結果。
返回值:cv2.HoughCircles
返回一個二維numpy數組,包含了所有滿足條件的圓的參數。
import cv2 as cv
import numpy as np#讀圖
img=cv.imread('E:\hqyj\code\opencv\images\\huofu.png')#灰度化
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)#二值化
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)#邊緣檢測
edges = cv.Canny(binary,30,70)#霍夫圓變換
circles = cv.HoughCircles(edges,cv.HOUGH_GRADIENT,1,20,param1=20,param2=20,minRadius=0,maxRadius=0)
print(circles)for circle in circles:# 獲取圓心和半徑x,y,r = circle[0]print(x,y,r)x,y,r = np.int_(np.round(x)),np.int_(np.round(y)),np.int_(np.round(r))print(x,y,r)cv.circle(img,(x,y),r,(0,255,255),2)cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()
圖像亮度變換
亮度變換
對比度調整:圖像暗處像素強度變低,圖像亮處像素強度變高,從而拉大中間某個區域范圍的顯示精度。
亮度調整:圖像像素強度整體變高或者變低。
上圖中,(a)把亮度調高,就是圖片中的所有像素值加上了一個固定值;(b)把亮度調低,就是圖片中的所有像素值減去了一個固定值;(c)增大像素對比度(白的地方更白,黑的地方更黑);(d)減小像素對比度(整幅圖都趨于一個顏色);
OpenCV調整圖像對比度和亮度時,公式為:g(i,j)=αf(i,j)+βg(i,j)=\alpha f(i,j)+\betag(i,j)=αf(i,j)+β。但是不能淺顯的講α\alphaα是控制對比度,β\betaβ是控制亮度的。
對比度:需要通過α、β\alpha、\betaα、β一起控制。
亮度:通過β\betaβ控制。
線性變換
使用 cv2.addWeighted()
函數,可以對圖像的像素值進行加權平均,進而改變圖像的整體亮度。亮度增益可以通過向每個像素值添加一個正值來實現。
cv2.addWeighted(src1, alpha, src2, beta, gamma)
-
src1
:第一張輸入圖像,它將被賦予權重alpha
。 -
alpha
:第一個輸入圖像的權重。 -
src2
:第二張輸入圖像,它將被賦予權重beta
。 -
beta
:第二個輸入圖像的權重。 -
gamma
:一個標量,將被添加到權重求和的結果上,可用于調整總體亮度。計算公式為: dst = src1 * alpha + src2 * beta + gamma
import cv2 as cv#讀圖
cat = cv.imread('E:\hqyj\code\opencv\images\\cat1.png')#線性變換
dst = cv.addWeighted(cat,0.8,cat,0,100)
cv.imshow('dst',dst)
cv.imshow('cat',cat)cv.waitKey(0)
cv.destroyAllWindows()
直接像素值修改
如果只需要增加或減少固定的亮度值,可以直接遍歷圖像像素并對每個像素值進行加減操作。
使用的API:
numpy.clip(a, a_min, a_max)
用于對數組中的元素進行限定,將超出指定范圍的元素值截斷至指定的最小值和最大值之間
-
a
:輸入數組。 -
a_min
:指定的最小值,數組中所有小于a_min
的元素將被替換為a_min
。 -
a_max
:指定的最大值,數組中所有大于a_max
的元素將被替換為a_max
。
#讀圖
cat = cv.imread('E:\hqyj\code\opencv\images\\cat1.png')#創建窗口 用于顯示滑條
window_name = 'slide'
cv.namedWindow(window_name)
img = cv.imread('E:\hqyj\code\opencv\images\\cat1.png')def chage(p):x = p/256*511-255dst=np.uint8(np.clip(img+x,0,255))cv.imshow(window_name,dst)print(x)#創建滑動條
initial_value = 127
chage(initial_value)
cv.createTrackbar('add_p',window_name,initial_value,255,chage)cv.waitKey(0)
cv.destroyAllWindows()
形態學變換
import cv2 as cv
import numpy as np#讀圖
car = cv.imread('E:\hqyj\code\opencv\images\\circle.png')#二值化
_,car = cv.threshold(car,127,255,cv.THRESH_BINARY_INV)
cv.imshow('car',car)#定義核 n*n的一個小區域
kernel = np.ones((5,5),np.uint8)#腐蝕 用全1核覆蓋,碰到前景時核中有背景值,就變為背景(縮小前景)
erosion = cv.erode(car,kernel,iterations = 1)
cv.imshow('erosion',erosion)#膨脹 用全1核覆蓋,碰到背景時核中有前景值,就變為前景(擴大前景)
dilation = cv.dilate(car,kernel,iterations = 1)
cv.imshow('dilation',dilation)#開運算 先腐蝕后膨脹
#作用:分離物體,消除小區域。特點:消除噪點,去除小的干擾塊,而不影響原來的圖像
opening = cv.morphologyEx(car,cv.MORPH_OPEN,kernel)
cv.imshow('opening',opening)#閉運算 先膨脹后腐蝕
#作用:是消除/“閉合”物體里面的孔洞。特點:可以填充閉合區域
closing = cv.morphologyEx(car,cv.MORPH_CLOSE,kernel)
cv.imshow('closing',closing)#禮帽運算 原圖和開運算的差
tophat = cv.morphologyEx(car,cv.MORPH_TOPHAT,kernel)
cv.imshow('tophat',tophat)#黑帽運算 原圖和閉運算的差
blackhat = cv.morphologyEx(car,cv.MORPH_BLACKHAT,kernel)
cv.imshow('blackhat',blackhat)#形態學梯度 膨脹和腐蝕的差
gradient = cv.morphologyEx(car,cv.MORPH_GRADIENT,kernel)
cv.imshow('gradient',gradient)cv.imshow('closing',closing)
cv.waitKey(0)
cv.destroyAllWindows()