openCV入門學習基礎教程第三篇
前文復習:
一、Canny邊緣檢測
該邊緣檢測法步驟如下:
- 使用高斯濾波器,以平滑圖像,濾除噪聲。
- 計算圖像中每個像素點的梯度強度和方向。
- 應用非極大值(Non-Maximum Suppression)抑制,以消除邊緣檢測帶來的雜散響應。
- 應用雙閾值(Double-Threshold)檢測來確定真實的和潛在的邊緣。
- 通過抑制孤立的弱邊緣最終完成邊緣檢測。
高斯濾波
我們要進行邊緣檢測過程中肯定要進行梯度計算,計算梯度時那些噪音點也會影響梯度的變化,因此進行去噪。

梯度和方向
使用sobel算子,進行梯度大小和方向的計算。

非極大值抑制 NMS
算完梯度后,有些可能大點,有些可能小點,在一個核中,把一些小的梯度值抑制掉,只保留大的,即明顯的。
如人臉識別,能夠把臉框起來(有多個框框到臉了,只保留最好的。)
法1:

藍線表示方向,dTmp1和dTmp2都是亞像素點,g1g2g3g4都是知道的,可求dTmp1和dTmp2。 在c比dTmp1和dTmp2都大的情況下,保留c,否則就被抑制掉了。
法2:

比如我們選擇傾斜45°,就過了g1和g4了(上上圖),就不用插值了。當前方向離哪個角度近就用選擇哪個。
右圖中如果A比BC都大,那么A就保存下來了。
雙閾值
對一些所有可能邊界進行過濾,只保留最真實的。

A點大于maxVal 就是邊界 如果有小于minVale的,那就省略掉。
他倆之間的B C分別進行討論,如果c和邊界是連著的,就保留。B沒連著,舍棄。
v1=cv2.Canny(img,80,150)
80 150為下上閾值,一定范圍內,下閾值越小,上閾值越小越容易被當作邊緣。
img=cv2.imread("./data/gd06.jpg",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,80,150)
v2=cv2.Canny(img,50,100)
res = np.hstack((v1,v2))
cv_show('res',res)
img=cv2.imread("./data/gd05.jpg",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,120,250)
v2=cv2.Canny(img,50,100)
res = np.hstack((v1,v2))
cv_show('res',res)
二、 圖像輪廓
上面說的是邊緣,隨便一個線段也能被檢測成邊緣。而輪廓是一個整體,正常是封閉的連在一起的。
2.1 輪廓收集
cv2.findContours(img,mode,method)
- RETR_EXTERNAL :只檢索最外面的輪廓;
- RETR_LIST:檢索所有的輪廓,并將其保存到一條鏈表當中;
- RETR_CCOMP:檢索所有的輪廓,并將他們組織為兩層:頂層是各部分的外部邊界,第二層是空洞的邊界;
- RETR_TREE:檢索所有的輪廓,并重構嵌套輪廓的整個層次;(常用,推薦)
method:輪廓逼近方法
- CHAIN_APPROX_NONE:以Freeman鏈碼的方式輸出輪廓,所有其他方法輸出多邊形(頂點的序列)。
- CHAIN_APPROX_SIMPLE:壓縮水平的、垂直的和斜的部分,也就是,函數(shù)只保留他們的終點部分。

上圖就是兩種method,一個是整個框,一個是點,都能起到定位作用。
為了更高的準確率,我們選擇使用二值圖像:
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 127為分割閾值,超過的部分取255
cv_show('thresh',thresh)這里用到了cv2.threshold 是上一篇文章學的。

# contours輪廓信息 hierarchy層級 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
其返回值有兩個:
- 輪廓的信息:比如上面5個圖案,10個邊緣(內邊緣外邊緣),這10個邊緣信息保存在了contours元組里。
- 層級:hierarchy 暫時用不上,用到了再說。
2.2 輪廓繪制
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
參數(shù)為:圖像,輪廓,輪廓索引(-1默認所有),顏色模式(BGR),線條厚度(不宜太大)
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 127為分割閾值,超過的部分取255
# contours輪廓信息 hierarchy層級
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
#傳入 圖像,輪廓,輪廓索引(-1默認所有),顏色模式(BGR),線條厚度(不宜太大)
# 注意需要copy,不然原圖會變。。。
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv_show('res',res)
仔細看它把10個(內外)輪廓都畫出來了,這與我們傳入的參數(shù) -1 有關。
當然我們也可以選擇其中某個:
draw_img = img.copy()
# 三角的外輪廓
res = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2)
cv_show('res',res)draw_img = img.copy()
# 三角的內輪廓
res = cv2.drawContours(draw_img, contours, 1, (0, 0, 255), 2)
cv_show('res',res)draw_img = img.copy()
# 六邊形外輪廓
res = cv2.drawContours(draw_img, contours, 2, (0, 0, 255), 2)
cv_show('res',res)2.3 輪廓特征
這里就介紹個周長和面積吧,后面用到別的再說。
cnt = contours[0] # 第0個輪廓 三角的外輪廓 #周長,True表示輪廓是閉合的 cv2.arcLength(cnt,True) # 8500.5 #面積 cv2.contourArea(cnt) # 437.9482651948929
2.4 輪廓近似
什么是輪廓近似呢,比如下圖左側圖案很麻煩,我們可以把它近似看成右邊的,就是輪廓近似。

這個的原理其實也很簡單,我發(fā)揮我的繪畫功能給你們展示一下:

把曲線AB近似成直線AB,至于要d<T(閾值)即可。
我們近似一下下圖:

邊緣繪制:
img = cv2.imread('contours2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv_show('res',res)
epsilon = 0.02*cv2.arcLength(cnt,True) # 周長百分比 0.02
# 參數(shù):輪廓 閾值,一般用周長百分比
# 返回:輪廓
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show('res',res)approx = cv2.approxPolyDP(cnt,epsilon,True)
- 參數(shù):輪廓 閾值,一般用周長百分比
- 返回:輪廓

把閾值變大一點:
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show('res',res)
2.5 外接圖形
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# binary,
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
# 外接矩形 得到x,y,w,h就能把矩形畫出來了
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv_show('img',img)x,y,w,h = cv2.boundingRect(cnt)

三、圖像金字塔
3.1 高斯金字塔

高斯金字塔:向下采樣(縮小)

上面金字塔最底層44 往上一層變成22 長寬變?yōu)樵瓉硪话朊娣e變?yōu)樵瓉?/4,去除所有偶數(shù)行和列就是為了這一操作。
高斯金字塔:向上采樣(擴大)

用0填充后,用卷積核把那幾個值分布在0上,獲得近似值。
img=cv2.imread("./data/gd01.jpg")
cv_show('img',img)
up=cv2.pyrUp(img)
cv_show('up',up)
up2=cv2.pyrUp(up)
cv_show('up2',up2)
down=cv2.pyrDown(img)
cv_show('down',down)上面其實就是圖片的放大與縮小。
下面要注意,縮小放大后的圖片會變得模糊,畢竟之前我們是用0填充,卷積核計算獲得的近似值。
# 先擴大后縮小后,由于當時是用0填充的,會損失信息。
up=cv2.pyrUp(img)
up_down=cv2.pyrDown(up)
# 顯然變得模糊了
cv_show('up_down',np.hstack((img,up_down)))
明顯右邊模糊了,我們把兩張圖片做個差值:
# 做差就能看出區(qū)別
up=cv2.pyrUp(img)
up_down=cv2.pyrDown(up)
cv_show('img-up_down',img-up_down)
這就是不同的部分。
3.2 拉普拉斯金字塔
和上面最后縮小放大后的圖片一樣,拉普拉斯這個每一層都是 原始-縮小(放大了的圖片) 第二層用第一層的結果去減了。
down=cv2.pyrDown(img)
down_up=cv2.pyrUp(down)
l_1=img-down_up
cv_show('l_1',l_1)
四、直方圖
4.1 像素直方圖繪制

左側灰度圖像素點 右側像素點直方圖
cv2.calcHist(images,channels,mask,histSize,ranges)
- images: 原圖像圖像格式為 uint8 或 ?oat32。當傳入函數(shù)時應 用中括號 [] 括來例如[img]
- channels: 同樣用中括號括來它會告函數(shù)我們統(tǒng)幅圖像的直方圖。如果入圖像是灰度圖它的值就是 [0]如果是彩+ 色圖像 的傳入的參數(shù)可以是 [0][1][2] 它們分別對應著 BGR。
- mask: 掩模圖像。統(tǒng)計整幅圖像的直方圖就mask = None。但是如果你想統(tǒng)計圖像某一分的直方圖的你就制作一個掩模圖像并使用它。
- histSize:BIN 的數(shù)目。也應用中括號括來如0-10是一個柱子 11-20是一個柱
- ranges: 像素值范圍常為 [0256]
img = cv2.imread('./data/gd01.jpg',0) #0表示灰度圖
hist = cv2.calcHist([img],[0],None,[256],[0,256])
plt.hist(img.ravel(),256);
plt.show()
img = cv2.imread('./data/gd01.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256]) 
mask操作
我們取一小塊區(qū)域,然后獲得圖片中這一塊區(qū)域的圖像
# 創(chuàng)建mast
mask = np.zeros(img.shape[:2], np.uint8)
print (mask.shape)
mask[100:300, 100:400] = 255
cv_show('mask',mask)
masked_img = cv2.bitwise_and(img, img, mask=mask)#與操作
cv_show('masked_img',masked_img)
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256]) hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256]) plt.subplot(221), plt.imshow(img, 'gray') plt.subplot(222), plt.imshow(mask, 'gray') plt.subplot(223), plt.imshow(masked_img, 'gray') plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask) plt.xlim([0, 256]) plt.show()

4.2 直方圖均衡化


如上圖,像素點都集中在一個地方,我們在不破壞其特征的前提下讓高瘦的分布變得矮胖一點,亮度什么的也就都會發(fā)生些變化。

注:這里的映射是 累積概率*取值范圍(255-0)
img = cv2.imread('./data/gd01.jpg',0) #0表示灰度圖 #clahe
plt.hist(img.ravel(),256);
plt.show()
equ = cv2.equalizeHist(img) plt.hist(equ.ravel(),256) plt.show()

res = np.hstack((img,equ))
cv_show('res',res)
圖片比之前亮一些了。
4.3 自適應直方圖均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clahe = clahe.apply(img)
res = np.hstack((img,equ,res_clahe))
cv_show('res',res)
五、傅里葉變換
我們生活在時間的世界中,早上7:00起來吃早飯,8:00去擠地鐵,9:00開始上班...
以時間為參照就是時域分析,但是在頻域中一切都是靜止的!
傅里葉變換的作用
- 高頻:變化劇烈的灰度分量,例如邊界
- 低頻:變化緩慢的灰度分量,例如一片大海
濾波
- 低通濾波器:只保留低頻,會使得圖像模糊
- 高通濾波器:只保留高頻,會使得圖像細節(jié)增強
opencv中主要就是cv2.dft()和cv2.idft(),輸入圖像需要先轉換成np.float32 格式
得到的結果中頻率為0的部分會在左上角,通常要轉換到中心位置,通過shift變換。
cv2.dft()返回的結果是雙通的(實部,虛部),通常還需要轉換成圖像格式才能展示(0,255)。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('./data/gd06.jpg',0)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
img = cv2.imread('./data/gd06.jpg',0)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置
# 低通濾波
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show() 
img = cv2.imread('./data/gd06.jpg',0)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置
# 高通濾波
mask = np.ones((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0
# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show() 
六、模板匹配
模板匹配和卷積原理很像,模板在原圖像上從原點開始滑動,計算模板與(圖像被模板覆蓋的地方)的差別程度,這個差別程度的計算方法在opencv里有6種,然后將每次計算的結果放入一個矩陣里,作為結果輸出。
假如原圖形是AxB大小,而模板是axb大小,則輸出結果的矩陣是(A-a+1)x(B-b+1)
img = cv2.imread('./data/gd04.jpg', 0)
template = cv2.imread('./data/gd_face.jpg', 0)
h, w = template.shape[:2] 
它們的關系就是(A-a+1)x(B-b+1)
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
- TM_SQDIFF:計算平方不同,計算出來的值越小,越相關
- TM_CCORR:計算相關性,計算出來的值越大,越相關
- TM_CCOEFF:計算相關系數(shù),計算出來的值越大,越相關
- TM_SQDIFF_NORMED:計算歸一化平方不同,計算出來的值越接近0,越相關
- TM_CCORR_NORMED:計算歸一化相關性,計算出來的值越接近1,越相關
- TM_CCOEFF_NORMED:計算歸一化相關系數(shù),計算出來的值越接近1,越相關
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
# res.shape 為(139, 458)
# 最小值最大值及其坐標位置 因為用的cv2.TM_SQDIFF 所以越小越好
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 
比如我要用到的圖像和臉:


for meth in methods:
img2 = img.copy()
# 匹配方法的真值
method = eval(meth)
print (method)
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 如果是平方差匹配TM_SQDIFF或歸一化平方差匹配TM_SQDIFF_NORMED,取最小值
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 畫矩形
cv2.rectangle(img2, top_left, bottom_right, 255, 2)
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.xticks([]), plt.yticks([]) # 隱藏坐標軸
plt.subplot(122), plt.imshow(img2, cmap='gray')
plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()





匹配多個對象
能匹配一個當然也能匹配多個:
img_rgb = cv2.imread('./data/mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('./data/mario_coin.jpg', 0)
h, w = template.shape[:2]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于%80的坐標
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]): # *號表示可選參數(shù)
bottom_right = (pt[0] + w, pt[1] + h)
cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)
cv2.imshow('img_rgb', img_rgb)
cv2.waitKey(0)
匹配金幣:

總結
到此這篇關于openCV入門學習基礎教程的文章就介紹到這了,更多相關openCV第三篇內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python中numpy.empty()函數(shù)實例講解
在本篇文章里小編給大家分享的是一篇關于python中numpy.empty()函數(shù)實例講解內容,對此有興趣的朋友們可以學習下。2021-02-02

