簡介
計算機視覺第一課opencv(一)保姆級教學
計算機視覺第一課opencv(二)保姆級教學
今天繼續學習opencv。
一、?圖像形態學
????????什么是形態學:圖像形態學是一種處理圖像形狀特征的圖像處理技術,主要用于描述和處理圖像中的形狀和結構。形態學可以用于提取圖像中的特征、消除噪聲、改變圖像的形狀等。
1.腐蝕
????????正如這個名字表面上的意思,侵蝕的基本思想就像土壤侵蝕一樣,它侵蝕了前景物體foreground object 的邊界(在這,我們會讓前景保持為白色)。那么侵蝕的作用是什么呢?這樣內核可以在圖像中滑動(在二維卷積中)。只有當內核下的所有像素都為1時,原始圖像中的像素(要么為1,要么為0)才會被認為是1,否則會被侵蝕(變成0)。這樣的結果就是,取決于內核的大小,邊界附近的所有像素都會被丟棄。因此,前景對象的厚度或大小會減少,或者只是簡單地讓圖像中的白色區域減少。它對于去除小的白色噪音(正如我們在顏色空間colorspace 疑問中看到的那樣),分離兩個連接的物體等都很有用。
????????腐蝕作用:可以用來消除小且無意義的物體。在這里,作為一個例子,我將使用一個包含內部全都是1的3x3內核。讓我們看看它產生什么樣的變化。
原圖
# 1、圖像腐蝕,函數為:
# cv2.erode(src, kernel, dst,anchor,iterations,borderType,borderValue)
# src: 輸入的圖像
# kernel: 用于腐蝕的結構元件如果element = Mat(), 則使用3 x 3的矩形結構單元。
# dst: 它是與src相同大小和類型的輸出圖像。
# iterations: 腐蝕操作的迭代次數,默認為1。次數越多, 腐蝕操作執行的次數越多,腐蝕效果越明顯import numpy as np
sun = cv2.imread('sun.png')
cv2.imshow('src',sun)
cv2.waitKey(0)
kernel = np.ones((3,3),np.uint8) # 這里kernel大小,修改為5*5試試
erosion_1 = cv2.erode(sun,kernel,iterations=2) #iterations改為5試試
cv2.imshow('erosion_1',erosion_1)
cv2.waitKey(0)
2.膨脹
????????膨脹操作正好與腐蝕相反。這里,如果內核下至少有一個像素是“1”,那么該像素元素就是“1”。因此,它增加了圖像中的白色區域,或則說增加了前景目標對象的尺寸大小。通常情況下,在去除噪聲以后,在腐蝕操作之后就是膨脹。因為,腐蝕消除了白色的噪音,但它也縮小了我們的前景物體,所以我們需要擴大回它。因為當噪音消失了,原本應該存在的白色面積也不會自主回來。而且膨脹在連接物體的破碎部分時也很有用。
# 2、圖像膨脹, 函數為:
# cv2.dilate(img, kernel, iteration)
# 參數含義:
# img – 目標圖片
# kernel – 進行操作的內核,默認為3×3的矩陣
# iterations – 膨脹次數,默認為1wenzi = cv2.imread('wenzi.png')
cv2.imshow('src1',wenzi)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) # 這里kernel大小
wenzi_new = cv2.dilate(wenzi,kernel,iterations=2)
cv2.imshow('wenzi_new',wenzi_new)
cv2.waitKey(0)
3.開運算
????????先進行腐蝕,再進行膨脹就叫做開運算。 通常情況下,含有噪聲的圖像二值化后,得到的邊界是不平滑的,物體區域具有一些錯判的孔洞,背景區域散布著一些小的噪聲物體。對一個圖像先進行腐蝕運算然后再膨脹的操作過程稱為開運算,它可以消除細小的物體、在纖細點處分離物體、平滑較大物體的邊界時不明顯的改變其面積。
????????在做對指紋識別的時候,遇到這樣的圖片對我們的識別效果影響不好,太多嘈雜的點了,我們就需要對其進行開運算。
# 開運算:先腐蝕后膨脹 作用:平滑物體的輪廓、斷開較窄的狹頸并消除細的突出物。
zhiwen = cv2.imread('zhiwen.png')
cv2.imshow('src2',zhiwen)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #設置kenenl大小
zhiwen_new = cv2.morphologyEx(zhiwen,cv2.MORPH_OPEN,kernel) #開運算
cv2.imshow('zhiwen_new',zhiwen_new)
cv2.waitKey(0)
4.閉運算
????????先膨脹再腐蝕。它經常被用來填充前景物體中的小洞,或者前景物體上面的小黑點。
????????對于這樣的指紋,我們發現它中間好多斷開了,有很多空洞不太符合我們正常的指紋,需要進行閉運算進行處理
# # 閉運算:先膨脹后腐蝕 作用:彌合較窄的間斷和細長的溝壑,消除小的孔洞,填補輪廓線中的斷裂。
zhiwen_duan = cv2.imread('zhiwen_duan.png')
cv2.imshow('src3',zhiwen_duan)
cv2.waitKey(0)
kernel = np.ones((4,4),np.uint8) #設置kenenl大小
zhiwen_new = cv2.morphologyEx(zhiwen_duan,cv2.MORPH_CLOSE,kernel) #閉運算
cv2.imshow('zhiwen_new1',zhiwen_new)
cv2.waitKey(0)
#
5.梯度運算
????????其實就是一幅圖像膨脹和腐蝕的差別。突出顯示圖像中強度變化劇烈的地方
wenzi = cv2.imread('wenzi.png')
cv2.imshow('wenzi_new',wenzi)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #設置kenenl大小
# 膨脹
pz_wenzi=cv2.dilate(wenzi,kernel,iterations=1)
cv2.imshow('pz_wenzi',pz_wenzi)
cv2.waitKey(0)
#腐蝕
fs_wenzi=cv2.erode(wenzi,kernel,iterations=1)
cv2.imshow('fs_wenzi',fs_wenzi)
cv2.waitKey(0)
# 膨脹-腐蝕
bianyuan = cv2.morphologyEx(wenzi,cv2.MORPH_GRADIENT,kernel)
cv2.imshow('bianyuan',bianyuan)
cv2.waitKey(0)
cv2.destroyAllWindows()
6.頂帽
頂帽 = 原始圖像 - 開運算結果(先腐蝕后膨脹)
sun = cv2.imread('sun.png')
cv2.imshow('sun_yuantu',sun)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #設置kenenel大小
#開運算
open_sun=cv2.morphologyEx(sun,cv2.MORPH_OPEN,kernel)
cv2.imshow('open_sun',open_sun)
cv2.waitKey(0)
#頂帽
tophat = cv2.morphologyEx(sun,cv2.MORPH_TOPHAT,kernel)
cv2.imshow('TOPHAT',tophat)
cv2.waitKey(0)
7.黑帽
sun = cv2.imread('sun.png')
cv2.imshow('sun_yuantu',sun)
cv2.waitKey(0)
kernel = np.ones((2,2),np.uint8) #設置kenenel大小
# #閉運算
close_sun=cv2.morphologyEx(sun,cv2.MORPH_CLOSE,kernel)
cv2.imshow('close_sun',close_sun)
cv2.waitKey(0)
#黑帽
blackhat = cv2.morphologyEx(sun,cv2.MORPH_BLACKHAT,kernel)
cv2.imshow('BLACKHAT',blackhat)
cv2.waitKey(0)
二、圖像邊緣檢測
????????邊緣檢測:是圖形圖像處理、計算機視覺和機器視覺中的一個基本工具,通常用于特征提取和特征檢測,旨在檢測一張數字圖像中有明顯變化的邊緣或者不連續的區域。
1.Sobel算子
????????Sobel 算子是一種離散的微分算子,該算子結合了高斯平滑和微分求導運算。該算子利用局部差分尋找邊緣,計算所得的是一個梯度的近似值。Sobel算子包含2組3×3的矩陣,分別為橫向和縱向模板,將之與圖像作平面卷積,即可分別得出橫向及縱向的亮度差分近似值。
# sobel算子
# cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])
# 參數:
# src: 輸入圖像
# ddepth: 輸出圖像的深度(可以理解為數據類型),-1表示與原圖相同的深度
# dx,dy: 當組合為dx=1,dy=0時為x方向的一階導數,當組合為dx=0,dy=1時為y方向的一階導數(如果同時為1,通常效果不佳)
# ksize:(可選參數)Sobel算子的大小,必須是1,3,5或者7,默認為3。yuan = cv2.imread('yuan.png',0)
yuan = cv2.resize(yuan,dsize=None,fy=0.5,fx=0.5)
cv2.imshow('yuan',yuan)
cv2.waitKey(0)
# # x方向上的邊緣
yuan_x = cv2.Sobel(yuan,-1,dx=1,dy=0)
cv2.imshow('yuan_x',yuan_x)
cv2.waitKey(0)
# x方向上的邊緣,包括負數信息(右減),但顯示不出來,因為范圍是(0~255)
yuan_x_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=1,dy=0)#數據uint8改為float64,可保存負數
cv2.imshow('yuan_x_64',yuan_x_64)
cv2.waitKey(0)
# #x方向上的邊緣,包括負數信息,進行取絕對值的操作,右端的負值信息就可以顯示出來了
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)#轉換為絕對值,負數轉換為正數
cv2.imshow('yuan_x_full',yuan_x_full)
cv2.waitKey(0)
# # y方向上的邊緣
yuan_y = cv2.Sobel(yuan,-1,dx=0,dy=1)
cv2.imshow('yuan_y',yuan_y)
cv2.waitKey(0)
# y方向上的邊緣,包括負數信息(下減),但顯示不出來,因為范圍是(0~255)
yuan_y_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=0,dy=1)#數據uint8改為float64,可保存負數
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)#轉換為絕對值,負數轉換為正數
cv2.imshow('yuan_y_full',yuan_y_full)
cv2.waitKey(0)
# # 如果同時使用x,y方向的結果如何呢?(不建議使用!)
yuan_xy = cv2.Sobel(yuan,-1,dx=1,dy=1)
cv2.imshow('yuan_xy',yuan_xy)
cv2.waitKey(0)
# # 使用圖像加權運算組合x和y方向的2個邊緣。
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1,yuan_y_full, 1, gamma=0)
cv2.imshow('yuan_xy_full',yuan_xy_full)
cv2.waitKey(0)
dama = cv2.imread('dama.jpg', 0)#不用灰度試試效果
dama=cv2.resize(dama,dsize=None,fx=0.5,fy=0.5)
dama_x_64 = cv2.Sobel(dama,cv2.CV_64F,dx=1,dy=0)#默認int8改為float64,可保存負數
dama_x_full = cv2.convertScaleAbs(dama_x_64)#轉換為絕對值,負數轉換為正數
dama_y_64 = cv2.Sobel(dama,cv2.CV_64F,dx=0,dy=1)#默認int8改為float64,可保存負數
dama_y_full = cv2.convertScaleAbs(dama_y_64)#轉換為絕對值,負數轉換為正數
dama_xy_sobel_full = cv2.addWeighted(dama_x_full, 1,dama_y_full, 1, 0)cv2.imshow('dama_xy_sobel_full',dama_xy_sobel_full)
cv2.waitKey(0)
2.Scharr 算子
????????Scharr 算子是 Soble 算子在 ksize=3 時的優化,與 Soble 的速度相同,且精度更高。Scharr 算子與 Sobel 算子的不同點是在平滑部分,其中心元素占的權重更重,相當于使用較小標準差的高斯函數,也就是更瘦高的模板。
dama = cv2.imread('dama.jpg',cv2.IMREAD_GRAYSCALE) #Scharr算子
# zl=cv2.cvtColor(zl,cv2.COLOR_BGR2GRAY)
dama_x_64 = cv2.Scharr(dama,cv2.CV_64F,dx=1,dy=0)#默認int8改為float64,可保存負數
dama_x_full = cv2.convertScaleAbs(dama_x_64)#轉換為絕對值,負數轉換為正數
dama_y_64 = cv2.Scharr(dama,cv2.CV_64F,dx=0,dy=1)#默認int8改為float64,可保存負數
dama_y_full = cv2.convertScaleAbs(dama_y_64)#轉換為絕對值,負數轉換為正數
dama_xy_Scharr_full = cv2.addWeighted(dama_x_full, 1,dama_y_full, 1, 0)
cv2.imshow('dama_xy_Scharr_full',dama_xy_Scharr_full)
cv2.waitKey(0)
3.Laplacian算子
????????不再以x和y的方向計算,而是以圓方向計算變化率。因此不需要Gx+Gy。
# ###### cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
# ###### # src: 輸入圖像,可以是灰度圖像,也可以是多通道的彩色圖像
# ###### # ddepth: 輸出圖片的數據深度;
# ###### # ksize: 計算二階導數濾波器的孔徑大小,必須為正奇數,可選項
# ###### # scale: 縮放比例因子,可選項,默認值為 1
# ###### # delta: 輸出圖像的偏移量,可選項,默認值為 0
# ###### #
#
dama= cv2.imread('yuan.png',cv2.IMREAD_GRAYSCALE)
dama_lap = cv2.Laplacian(dama,cv2.CV_64F)
dama_lap_full = cv2.convertScaleAbs(dama_lap)#轉換為絕對值,負數轉換為正數
cv2.imshow('dama_lap_full',dama_lap_full)
cv2.waitKey(0)
4.Canny邊緣檢測
√ 低錯誤率。因為一般的邊緣檢測算子可能存在檢測到偽邊緣的情況,因此Canny算法檢測到的邊緣盡可能地是真實的邊緣。
√ 較好地定位邊緣點。由檢測器標記的邊緣點與真實邊緣點中心盡可能地接近。
√ 單一的邊緣響應。圖像中的邊緣只標記出一次。
1.圖像降噪
????????圖像去噪是進行邊緣檢測的第一步,通過去噪可以去除圖像中的一些噪點,從而使邊緣檢測時免受噪點干擾。高斯濾波
2.梯度計算
????????要進行邊緣檢測,就需要得到圖像梯度信息,根據圖像的梯度幅值和梯度方向來確定邊緣,一般均采用sobel算子對圖像進行梯度幅值與梯度方向計算。
3.非極大值抑制NMS(Non-Maximal Suppression)
????????一階微分在灰度值斜坡過渡時不為零且存在較粗的邊緣,較粗的邊緣會增大邊緣檢測的誤差,因此需要細化邊緣,一種較為常用的方法是非極大值抑制。非極大值抑制:即在梯度圖像中尋找梯度方向上的最大值作為邊緣,不是梯度方向上的最大值則抑制為0。因為梯度方向是灰度變化最大的方向。比較梯度圖像中每一點的灰度值與梯度方向上至少兩個梯度圖像像素點灰度值的大小,根據上述大小關系來確定是否保留該點的灰度值。
4.雙閾值邊界跟蹤
????????雙閾值處理就是根據實際情況需要設置一個灰度高閾值和一個灰度低閾值對NMS后的圖像進行過濾,使得得到的邊緣盡可能是真實的邊緣。雙閾值的基本處理步驟為:
????????當然上面這些不需要我們寫代碼進行一步一步處理,只需要調用Canny邊緣檢測的函數會自己計算、我們只要簡單了解有哪些過程就行。每一個過程有啥用
# ###### canny邊緣檢測
# ###### cv2.Canny(image, threshold1, threshold2[, apertureSize[, L2gradient]])
# ###### # image 為輸入圖像。
# ###### # threshold1 表示處理過程中的第一個閾值。
# ###### # threshold2 表示處理過程中的第二個閾值。
# ###### #
#
zl = cv2.imread('dama.jpg',cv2.IMREAD_GRAYSCALE)
zl=cv2.resize(zl,dsize=None,fy=0.5,fx=0.5)
cv2.imshow('dama',zl)
cv2.waitKey(0)
zl_canny = cv2.Canny(zl, 150, 230)#低,高
cv2.imshow('dama_canny',zl_canny)
cv2.waitKey(0)
總結
????????讀者可以使用自己的圖片進行測試,在邊緣檢測上Canny邊緣檢測相對于前三個具有更好的檢測效果。