import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
一、圖像平滑
1、2D卷積
我們可以對 2D 圖像實施低通濾波(LPF),高通濾波(HPF)等。
LPF 幫助我們?nèi)コ胍?,模糊圖像。HPF 幫助我們找到圖像的邊緣。
OpenCV 提供的函數(shù) cv.filter2D() 可以讓我們對一幅圖像進(jìn)行卷積操作。
'''
下面我們將對一幅圖像使用平均濾波器(kernel核中的參數(shù)和為1,所有參數(shù)值相同),
將核放在圖像的一個像素 A 上,求與核對應(yīng)的圖像上 25(5x5)個像素的和,在取平均數(shù),用這個平均數(shù)替代像素 A 的值。
重復(fù)以上操作直到將圖像的每一個像素值都更新一遍。
'''
img = cv.imread('open_cv_logo.png')
kernel = np.ones((5,5),np.float32) / 25
print(kernel)
[[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]
[0.04 0.04 0.04 0.04 0.04]]
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(img),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging'),plt.xticks([]), plt.yticks([])
plt.show()
?
?
img = cv.imread('./data/xiaoren.png')
plt.figure(figsize=(10,5))
kernel = np.array(
[
[1,2,1],
[0,-8,0],
[1,2,1]
]
)
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(dst,cv.COLOR_BGR2RGB)),plt.title('Define kernel'),plt.xticks([]), plt.yticks([])
plt.show()
?
?
2、圖像模糊
使用低通濾波器可以達(dá)到圖像模糊的目的。這對與去除噪音很有幫助。
其實就是去除圖像中的高頻成分(比如:噪音,邊界)。所以邊界也會被模糊一點(diǎn)。(當(dāng)然,也有一些模糊技術(shù)不會模糊掉邊界)。
OpenCV 提供了四種模糊技術(shù)。
'''
1、平均
這是由一個歸一化卷積框完成的。用卷積框覆蓋區(qū)域所有像素的平均值來代替中心元素。
可以使用函數(shù) cv2.blur() 和 cv2.boxFilter() 來完這個任務(wù)。
我們需要設(shè)定卷積框的寬和高。
下面是一個 3x3 的歸一化卷積框:
K = [
[1,1,1],
[1,1,1],
[1,1,1]
] / 9
如果你不想使用歸一化卷積框,你應(yīng)該使用 cv2.boxFilter(),這時要傳入?yún)?shù) normalize=False。
'''
img = cv.imread('./data/xiaoren.png')
blur = cv.blur(img,ksize=(11,11))
plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(blur, cv.COLOR_BGR2RGB)),plt.title('Blurred'),plt.xticks([]), plt.yticks([])
plt.show()
?
?
'''
2、高斯模糊
把卷積核換成高斯核(簡單來說,方框不變,將原來每個方框的值是相等的,現(xiàn)在里面的值是符合高斯分布的,方框中心的值最大,其余方框根據(jù)
距離中心元素的距離遞減,構(gòu)成一個高斯小山包。原來的求平均數(shù)現(xiàn)在變成求加權(quán)平均數(shù),全就是方框里的值)。
實現(xiàn)的函數(shù)是 cv2.GaussianBlur()。
我們需要指定高斯核的寬和高(必須是奇數(shù))。以及高斯函數(shù)沿 X,Y 方向的標(biāo)準(zhǔn)差。
如果我們只指定了 X 方向的的標(biāo)準(zhǔn)差,Y 方向也會取相同值。如果兩個標(biāo)準(zhǔn)差都是 0,那么函數(shù)會根據(jù)核函數(shù)的大小自己計算。
高斯濾波可以有效的從圖像中去除高斯噪音。
'''
plt.figure(figsize=(20,10))
img = cv.imread('./data/koala.png')
#0 是指根據(jù)窗口大?。?,5)來計算高斯函數(shù)標(biāo)準(zhǔn)差
blur = cv.GaussianBlur(img, ksize=(5,5), sigmaX=10.0, sigmaY=10.0)
plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Original'),plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(blur, cv.COLOR_BGR2RGB)),plt.title('Blurred'),plt.xticks([]), plt.yticks([])
plt.show()
?
?
'''
3、中值模糊
顧名思義就是用與卷積框?qū)?yīng)像素的中值來替代中心像素的值。這個濾波器經(jīng)常用來去除椒鹽噪聲。
(即將卷積域內(nèi)的所有像素按照從小到大排序,然后獲取中間值作為卷積的輸出。)
前面的濾波器都是用計算得到的一個新值來取代中心像素的值,而中值濾波是用中心像素周圍(也可以使他本身)的值來取代他。
他能有效的去除噪聲。卷積核的大小也應(yīng)該是一個奇數(shù)。
'''
# 加載圖像
img = cv.imread('./data/xiaoren.png')
# 加噪聲數(shù)據(jù)
noisy_img = np.random.normal(10, 10, (img.shape[0], img.shape[1], img.shape[2]))
noisy_img = np.clip(noisy_img, 0, 255).astype(np.uint8)
img = img + noisy_img
# 轉(zhuǎn)換為灰度圖像
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 做一個中值過濾
dst = cv.medianBlur(img, ksize=5)
# 可視化
plt.subplot(121)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(122)
plt.imshow(dst, 'gray')
plt.title('medianBlur')
plt.show()
?
'''
4、雙邊濾波
函數(shù) cv2.bilateralFilter() 能在保持邊界清晰的情況下有效的去除噪音。
但是這種操作與其他濾波器相比會比較慢。
我們已經(jīng)知道高斯濾波器是求中心點(diǎn)鄰近區(qū)域像素的高斯加權(quán)平均值。
這種高斯濾波器只考慮像素之間的空間關(guān)系,而不會考慮像素值之間的關(guān)系(像素的相似度)。
所以這種方法不會考慮一個像素是否位于邊界。因此邊界也不會模糊掉,而這正不是我們想要。
雙邊濾波在同時使用空間高斯權(quán)重和灰度值相似性高斯權(quán)重。
空間高斯函數(shù)確保只有鄰近區(qū)域的像素對中心點(diǎn)有影響,灰度值相似性高斯函數(shù)確保只有與中心像素灰度值相近的才會被用來做模糊運(yùn)算。
所以這種方法會確保邊界不會被模糊掉,因為邊界處的灰度值變化比較大。
'''
# 雙邊濾波: 中間的紋理刪除,保留邊緣信息
# 加載圖像
img = cv.imread('./data/xiaoren.png')
# 做一個雙邊濾波
dst = cv.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('bilateralFilter')
plt.show()
?
二、形態(tài)學(xué)轉(zhuǎn)換
主要包括腐蝕、擴(kuò)張、打開、關(guān)閉等操作;主要操作是基于kernel核的操作
常見的核主要有:矩陣、十字架、橢圓結(jié)構(gòu)的kernel
kernel1 = cv.getStructuringElement(cv.MORPH_RECT, ksize=(5,5))
print("矩形kernel:\n{}".format(kernel1))
kernel2 = cv.getStructuringElement(cv.MORPH_CROSS, ksize=(5,5))
print("十字架kernel:\n{}".format(kernel2))
kernel3 = cv.getStructuringElement(cv.MORPH_ELLIPSE, ksize=(5,5))
print("橢圓kernel:\n{}".format(kernel3))
矩形kernel:
[[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]]
十字架kernel:
[[0 0 1 0 0]
[0 0 1 0 0]
[1 1 1 1 1]
[0 0 1 0 0]
[0 0 1 0 0]]
橢圓kernel:
[[0 0 1 0 0]
[1 1 1 1 1]
[1 1 1 1 1]
[1 1 1 1 1]
[0 0 1 0 0]]
1、腐蝕
腐蝕的意思是將邊緣的像素點(diǎn)進(jìn)行一些去除的操作;
腐蝕的操作過程就是讓kernel核在圖像上進(jìn)行滑動,當(dāng)內(nèi)核中的所有像素被視為1時,原始圖像中對應(yīng)位置的像素設(shè)置為1,否則設(shè)置為0.
- 其主要效果是:可以在圖像中減少前景圖像(白色區(qū)域)的厚度,有助于減少白色噪音,可以用于分離兩個連接的對象
- 一般應(yīng)用與只有黑白像素的灰度情況
'''
第一種方式
'''
kernel = cv.getStructuringElement(cv.MORPH_ERODE, (5,5))
dst = cv.morphologyEx(img, cv.MORPH_ERODE, kernel)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('erode')
plt.show()
?
?
'''
第二種方式
'''
img = cv.imread('./data/j.png',0)
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5),np.uint8)
# b. 腐蝕操作
dst = cv.erode(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('erode')
plt.show()
?
?
2、擴(kuò)張、膨脹
和腐蝕的操作相反,其功能是增加圖像的白色區(qū)域的值
只要在kernel中所有像素中有可以視為1的像素值,那么就將原始圖像中對應(yīng)位置的像素值設(shè)置為1,否則設(shè)置為0。
通常情況下,在去除噪音后,可以通過擴(kuò)張在恢復(fù)圖像的目標(biāo)區(qū)域信息。
'''
第一種方式
'''
kernel = cv.getStructuringElement(cv.MORPH_DILATE, (5,5))
dst = cv.morphologyEx(img, cv.MORPH_DILATE, kernel)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('dilate')
plt.show()
?
'''
第二種方式
'''
img = cv.imread('./data/j.png',0)
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5),np.uint8)
# b. 膨脹操作
dst = cv.dilate(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('dilate')
plt.show()
?
?
3、Open
Open其實指的就是先做一次腐蝕,然后再做一次擴(kuò)張操作,一般用于去除噪音數(shù)據(jù)。
# 加載圖像
img = cv.imread('./data/j.png', 0)
# 加載噪音數(shù)據(jù)(白色噪音)
rows, cols = img.shape
for i in range(100):
x = np.random.randint(cols)
y = np.random.randint(rows)
img[y,x] = 255
'''
第一種方式
'''
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5),np.uint8)
# b. 先進(jìn)行腐蝕操作
dst1 = cv.erode(img,kernel,iterations=1,borderType=cv.BORDER_REFLECT)
# c. 后進(jìn)行膨脹操作
dst2 = cv.dilate(dst1,kernel,iterations=1,borderType=cv.BORDER_REFLECT)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst2, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
?
?
'''
第二種方式
'''
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5), np.uint8)
# b. Open操作
dst = cv.morphologyEx(img, op=cv.MORPH_OPEN, kernel=kernel, iterations=1)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
?
?
4、Closing
Closing其實指的就是先做一次擴(kuò)張,再做一次腐蝕
對前景圖像中的如果包含黑色點(diǎn),有去除的效果。
# 加載圖像
img = cv.imread('./data/j.png', 0)
# 加載噪音數(shù)據(jù)
rows, cols = img.shape
# 加白色點(diǎn)
for i in range(100):
x = np.random.randint(cols)
y = np.random.randint(rows)
img[y,x] = 255
# 加黑色點(diǎn)
for i in range(1000):
x = np.random.randint(cols)
y = np.random.randint(rows)
img[y,x] = 0
'''
第一種方式
'''
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5), np.uint8)
# b. 再膨脹
dst = cv.dilate(img, kernel, iterations=1)
# c. 先腐蝕
dst = cv.erode(dst, kernel, iterations=1)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
?
?
'''
第二種方式
'''
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5), np.uint8)
# b. Closing操作
dst = cv.morphologyEx(img, op=cv.MORPH_CLOSE, kernel=kernel, iterations=1)
# c. 可視化
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('open')
plt.show()
?
?
5、Morphological Gradient(形態(tài)梯度)
在膨脹和腐蝕圖像之間獲取一個差集,也就是Gradient=Dilate - Erode;
該操作的作用能夠提取邊緣特征信息。
img = cv.imread('./data/j.png', 0)
# a. 定義一個核(全部設(shè)置為1表示對核中5*5區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((5,5), np.uint8)
# b. 形態(tài)梯度(dilate - erode)
dst = cv.morphologyEx(img, op=cv.MORPH_GRADIENT, kernel=kernel, iterations=1)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original')
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.title('Gradient')
plt.show()
?
?
6、Top Hat
在原始圖像和Open操作的圖像上做一個差集,也就是Top Hat=image - Open;
該操作的主要作用是可以提取一些非交叉點(diǎn)的信息。一般不用。
img = cv.imread('./data/j.png', 0)
# a. 定義一個核(全部設(shè)置為1表示對核中9*9區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((9,9), np.uint8)
# b1. Open(先腐蝕,再擴(kuò)展)
dst1 = cv.morphologyEx(img, op=cv.MORPH_OPEN, kernel=kernel, iterations=1)
# b2. Top Hat(src - open)
dst2 = cv.morphologyEx(img, op=cv.MORPH_TOPHAT, kernel=kernel, iterations=1)
# 可視化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(132)
plt.imshow(dst1, 'gray')
plt.title('Open')
plt.subplot(133)
plt.imshow(dst2, 'gray')
plt.title('TopHat')
plt.show()
?
?
7、Black Hat
在Close操作的圖像和原始圖像上做一個差集,也就是Black Hat = Close -image;
該操作的主要作用是可以提取一些交叉點(diǎn)附件的位置特征信息。一般不用。
# a. 定義一個核(全部設(shè)置為1表示對核中9*9區(qū)域的所有像素均進(jìn)行考慮,設(shè)置為0表示不考慮)
kernel = np.ones((9,9), np.uint8)
# b1. Close(先膨脹,再腐蝕)
dst1 = cv.morphologyEx(img, op=cv.MORPH_CLOSE, kernel=kernel, iterations=1)
# b2. Black Hat(close - src)
dst2 = cv.morphologyEx(img, op=cv.MORPH_BLACKHAT, kernel=kernel, iterations=1)
# dst2 = dst1 - img
# c. 可視化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(132)
plt.imshow(dst1, 'gray')
plt.title('Close')
plt.subplot(133)
plt.imshow(dst2, 'gray')
plt.title('BlackHat')
plt.show()
?
?
三、圖像梯度
除了前面介紹的普通濾波/卷積操作外,在圖像空域上而言,還存在一些比較重要的特征信息,
比如:邊緣(Edge)特征信息;
邊緣信息指的就是像素值明顯變化的區(qū)域,具有非常豐富的語義信息,常用于物體識別等領(lǐng)域
通過對圖像梯度的操作,可以發(fā)現(xiàn)圖像的邊緣信息
在OpenCV中提供了三種類型的高通濾波器,常見處理方式:Sobel、Scharr以及Laplacian導(dǎo)數(shù)
1、Sobel
主要就是梯度和高斯平滑的結(jié)合,在求解梯度之前,首先進(jìn)行一個高斯平滑的操作。
# 加載圖像
img = cv.imread('./data/xiaoren.png', 0)
# 畫幾條線條
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(0,0), pt2=(cols,rows), color=0, thickness=1)
print("")
?
# x方向的的Sobel過濾,ksize:一般取值為3,5,7;
# 第二個參數(shù):ddepth,給定輸出的數(shù)據(jù)類型的取值范圍,默認(rèn)為unit8的,取值為[0,255],如果給定-1,表示輸出的數(shù)據(jù)類型和輸入一致。
sobelx = cv.Sobel(img, 6, dx=1, dy=0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, dx=0, dy=1, ksize=5)
sobelx2 = cv.Sobel(img, cv.CV_64F, dx=2, dy=0, ksize=5)
sobely2 = cv.Sobel(img, cv.CV_64F, dx=0, dy=2, ksize=5)
sobel = cv.Sobel(img, cv.CV_64F, dx=1, dy=1, ksize=5)
sobelx_y = cv.Sobel(sobelx, cv.CV_64F, dx=0, dy=1, ksize=5)
sobely_x = cv.Sobel(sobely, cv.CV_64F, dx=1, dy=0, ksize=5)
# c. 可視化
plt.figure(figsize=(20,10))
plt.subplot(241)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(242)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')
plt.subplot(243)
plt.imshow(sobely, 'gray')
plt.title('sobely')
plt.subplot(244)
plt.imshow(sobelx2, 'gray')
plt.title('sobelx2')
plt.subplot(245)
plt.imshow(sobely2, 'gray')
plt.title('sobely2')
plt.subplot(246)
plt.imshow(sobel, 'gray')
plt.title('sobel')
plt.subplot(247)
plt.imshow(sobelx_y, 'gray')
plt.title('sobelx_y')
plt.subplot(248)
plt.imshow(sobely_x, 'gray')
plt.title('sobely_x')
plt.show()
?
?
'''
自定義kernel,實現(xiàn)sobel
'''
kernel = np.asarray([
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
])
# 做一個卷積操作
# 第二個參數(shù)為:ddepth,一般為-1,表示不限制,默認(rèn)值即可。
sobely = cv.filter2D(img, 6, kernel)
sobelx = cv.filter2D(img, 6, kernel.T)
plt.figure(figsize=(10,5))
# 可視化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(132)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')
plt.subplot(133)
plt.imshow(sobely, 'gray')
plt.title('sobely')
plt.show()
?
?
'''
sobelx
=
[
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
]
= [-1 0 1](水平梯度) X [1 2 1].T(高斯平滑)
sobely
=
[
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
]
= [1 2 1](高斯平滑) X [-1 0 1].T(垂直梯度)
'''
# kernel = np.asarray([
# [-1, -2, -1],
# [0, 0, 0],
# [1, 2, 1]
# ])
kernel1 = np.asarray([[1,2,1]])
kernel2 = np.asarray([[-1],[0],[1]])
# 做一個卷積操作
# 第二個參數(shù)為:ddepth,一般為-1,表示不限制,默認(rèn)值即可。
# sobelx = cv.filter2D(img, 6, kernel.T)
# 先高斯平滑
a = cv.filter2D(img, 6, kernel1.T)
# 再水平梯度
sobelx = cv.filter2D(a, 6, kernel2.T)
# sobely = cv.filter2D(img, 6, kernel)
# 先高斯平滑
a = cv.filter2D(img, 6, kernel1)
# 再垂直梯度
sobely = cv.filter2D(a, 6, kernel2)
plt.figure(figsize=(10,5))
# 可視化
plt.subplot(131)
plt.imshow(img, 'gray')
plt.title('Original')
plt.subplot(132)
plt.imshow(sobelx, 'gray')
plt.title('sobelx')
plt.subplot(133)
plt.imshow(sobely, 'gray')
plt.title('sobely')
plt.show()
?
?
2、Scharr
Scharr可以認(rèn)為是一種特殊的Sobel方式, 實際上就是一種特殊的kernel
# 加載圖像
img = cv.imread('./data/xiaoren.png', 0)
# 畫幾條線條
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
print("")
# Scharr中,dx和dy必須有一個為0,一個為1
scharr_x = cv.Scharr(img, cv.CV_64F, dx = 1, dy = 0)
scharr_y = cv.Scharr(img, cv.CV_64F, dx = 0, dy = 1)
scharr_x_y = cv.Scharr(scharr_x, cv.CV_64F, dx = 0, dy = 1)
scharr_y_x = cv.Scharr(scharr_y, cv.CV_64F, dx = 1, dy = 0)
# c. 可視化
plt.figure(figsize=(20,10))
plt.subplot(231)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(232)
plt.imshow(scharr_x, 'gray')
plt.title('scharr_x')
plt.subplot(233)
plt.imshow(scharr_y, 'gray')
plt.title('scharr_y')
plt.subplot(234)
plt.imshow(scharr_x_y, 'gray')
plt.title('scharr_x_y')
plt.subplot(235)
plt.imshow(scharr_y_x, 'gray')
plt.title('scharr_y_x')
plt.show()
?
?
?
3、Laplacian
使用拉普拉斯算子進(jìn)行邊緣提取
# 加載圖像
img = cv.imread('./data/xiaoren.png', 0)
# 畫幾條線條
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
print("")
# ksize設(shè)置為3
ksize = 3
sobel_x = cv.Sobel(img, cv.CV_64F, dx=1, dy=0, ksize=ksize)
sobel_y = cv.Sobel(img, cv.CV_64F, dx=0, dy=1, ksize=ksize)
laplacian = cv.Laplacian(img, cv.CV_64F, ksize=ksize)
# 對laplacian取絕對值,并且準(zhǔn)換為uint8格式
laplacian_v2 = np.uint8(np.absolute(laplacian))
scharr_x = cv.Scharr(img, cv.CV_64F, dx=1, dy=0)
scharr_y = cv.Scharr(img, cv.CV_64F, dx=0, dy=1)
# c. 可視化
plt.figure(figsize=(20,10))
plt.subplot(241)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(242)
plt.imshow(sobel_x, 'gray')
plt.title('sobel_x')
plt.subplot(243)
plt.imshow(sobel_y, 'gray')
plt.title('sobel_y')
plt.subplot(244)
plt.imshow(laplacian, 'gray')
plt.title('laplacian')
plt.subplot(245)
plt.imshow(laplacian_v2, 'gray')
plt.title('laplacian_v2')
plt.subplot(246)
plt.imshow(scharr_x, 'gray')
plt.title('scharr_x')
plt.subplot(247)
plt.imshow(scharr_y, 'gray')
plt.title('scharr_y')
plt.show()
?
?
?
'''
在Sobel檢測中,depth對于結(jié)果的影響,當(dāng)輸出的depth設(shè)置為比較低的數(shù)據(jù)格式,那么當(dāng)梯度值計算為負(fù)值的時候,就會將其重置為0,從而導(dǎo)致失真。
在Laplacian檢測中,該問題不大。
'''
# 構(gòu)建一個圖像
# 構(gòu)建黑底白框的圖像
img = np.zeros((300,300), np.uint8)
img[100:200,100:200] = 255
# 構(gòu)建白底黑框的圖像
# img = np.ones((300,300), np.uint8) * 255
# img[100:200,100:200] = 0
ksize = 5
# 做Sobel的操作
dst1 = cv.Sobel(img, cv.CV_8U, 1, 0, ksize=ksize)
dst2 = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=ksize)
dst3 = np.uint8(np.absolute(dst2))
# 做Laplacian的操作
# dst1 = cv.Laplacian(img, cv.CV_8U, ksize=ksize)
# dst2 = cv.Laplacian(img, cv.CV_64F, ksize=ksize)
# dst3 = np.uint8(np.absolute(dst2))
# c. 可視化
plt.figure(figsize=(10,5))
plt.subplot(221)
plt.imshow(img, 'gray')
plt.title('img')
plt.subplot(222)
plt.imshow(dst1, 'gray')
plt.title('dst1')
plt.subplot(223)
plt.imshow(dst2, 'gray')
plt.title('dst2')
plt.subplot(224)
plt.imshow(dst3, 'gray')
plt.title('dst3')
plt.show()
?
?
4、Canday算法
Canny算法是一種比Sobel和Laplacian效果更好的一種邊緣檢測算法;在Canny算法中,主要包括以下幾個階段:
- Noise Reduction:降噪,使用5*5的kernel做Gaussian filter降噪;
- Finding Intensity Gradient of the Image:求圖像像素的梯度值;
- Non-maximum Suppression:刪除可能不構(gòu)成邊緣的像素,即在漸變方向上相鄰區(qū)域的像素梯度值是否是最大值,如果不是,則進(jìn)行刪除。
- Hysteresis Thresholding:基于閾值來判斷是否屬于邊;大于maxval的一定屬于邊,小于minval的一定不屬于邊,在這個中間的可能屬于邊的邊緣。
# 加載圖像
img = cv.imread('./data/xiaoren.png', 0)
# 畫幾條線條
rows, cols = img.shape
cv.line(img, pt1=(0,rows//3), pt2=(cols,rows//3), color=0, thickness=5)
cv.line(img, pt1=(0,2*rows//3), pt2=(cols,2*rows//3), color=0, thickness=5)
cv.line(img, pt1=(cols//3,0), pt2=(cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(2*cols//3,0), pt2=(2*cols//3,rows), color=0, thickness=5)
cv.line(img, pt1=(0,0), pt2=(cols,rows), color=0, thickness=1)
print("")
# 做一個Canny邊緣檢測(OpenCV中是不包含高斯去燥的)
# a. 高斯去燥
blur = cv.GaussianBlur(img, (5,5),0)
# b. Canny邊緣檢測
edges = cv.Canny(blur,threshold1=10, threshold2=250)
# 可視化
plt.figure(figsize=(20,10))
plt.subplot(131)
plt.imshow(img,cmap = 'gray')
plt.title('Original Image')
plt.subplot(132)
plt.imshow(blur,cmap = 'gray')
plt.title('Gaussian Blur Image')
plt.subplot(133)
plt.imshow(edges,cmap = 'gray')
plt.title('Canny Edge Image')
plt.show()
?
?
?
5、輪廓信息
- 輪廓信息可以簡單的理解為圖像曲線的連接點(diǎn)信息,在目標(biāo)檢測以及識別中有一定的作用。
- 輪廓信息的查找最好是基于灰度圖像或者邊緣特征圖像,因為基于這樣的圖像比較容易找連接點(diǎn)信息;
NOTE:在OpenCV中,查找輪廓是在黑色背景中查找白色圖像的輪廓信息。
# 加載圖像
img = cv.imread('./data/xiaoren.png')
# 將圖像轉(zhuǎn)換為灰度圖像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 做一個圖像反轉(zhuǎn)(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)
# 做一個二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)
# 發(fā)現(xiàn)輪廓信息
# 第一個參數(shù)是原始圖像,第二個參數(shù)是輪廓的檢索模型,第三個參數(shù)是輪廓的近似方法
# 第一個返回值是輪廓,第二個參數(shù)值為層次信息
# CHAIN_APPROX_SIMPLE指的是對于一條直線上的點(diǎn)而言,僅僅保留端點(diǎn)信息,而CHAIN_APPROX_NONE保留所有點(diǎn)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print("總的輪廓數(shù)目:{}".format(len(contours)))
print('hierarchy.shape = ', hierarchy.shape)
# 在圖像中繪制圖像
# 當(dāng)contourIdx為-1的時候,表示繪制所有輪廓,當(dāng)為大于等于0的時候,表示僅僅繪制某一個輪廓
# 這里的返回值img3和img是同一個對象,在當(dāng)前版本中
# img3 = cv.drawContours(img, contours, contourIdx=-1, color=(0, 0, 255), thickness=2)
max_idx = np.argmax([len(t) for t in contours])
# print(max_idx)
img3 = cv.drawContours(img, contours, contourIdx=max_idx, color=(0, 0, 255), thickness=2)
# 可視化
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')
plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')
plt.show()
總的輪廓數(shù)目:283
hierarchy.shape = (1, 283, 4)
6、輪廓信息說明
# 構(gòu)建黑底白框的圖像
img = np.zeros((300,300), np.uint8)
img[10:290,10:290] = 255
img[50:200, 50:200] = 0
img[55:100, 55:100] = 255
img[120:190, 120:150] = 255
img[130:160, 130:145] = 0
img[210:250, 210:250] = 0
img[250:270, 150:180] = 0
img[205:220, 205:220] = 0
# 可視化
plt.imshow(img, 'gray')
plt.title('img')
plt.show()
?
?
ret, thresh = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
img3 = cv.drawContours(img, contours, contourIdx=-1, color=128, thickness=2)
print("總的輪廓數(shù)目:{}".format(len(contours)))
print('hierarchy.shape = ', hierarchy.shape)
hierarchy
# 是一個[1, n, 4]格式,n為輪廓的數(shù)目,這個中間保存的是輪廓包含信息
# 每個輪廓的層次信息是一個4維的向量值,
# 第一個值表示當(dāng)前輪廓的上一個同層級的輪廓下標(biāo),
# 第二值表示當(dāng)前輪廓的下一個同層級的輪廓下標(biāo),
# 第三個表示當(dāng)前輪廓的第一個子輪廓的下標(biāo),
# 第四個就表示當(dāng)前輪廓的父輪廓的下標(biāo)
總的輪廓數(shù)目:7
hierarchy.shape = (1, 7, 4)
array([[[-1, -1, 1, -1],
[ 2, -1, -1, 0],
[ 3, 1, -1, 0],
[-1, 2, 4, 0],
[ 6, -1, 5, 3],
[-1, -1, -1, 4],
[-1, 4, -1, 3]]], dtype=int32)
7、輪廓屬性
獲取得到輪廓坐標(biāo)后,就可以基于輪廓來計算面積、周長等屬性。
# 加載圖像
img = cv.imread('./data/xiaoren.png')
# 將圖像轉(zhuǎn)換為灰度圖像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 做一個圖像反轉(zhuǎn)(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)
# 做一個二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)
# 發(fā)現(xiàn)輪廓信息
# 第一個參數(shù)是原始圖像,第二個參數(shù)是輪廓的檢索模型,第三個參數(shù)是輪廓的近似方法
# 第一個返回值是輪廓,第二個參數(shù)值為層次信息
# CHAIN_APPROX_SIMPLE指的是對于一條直線上的點(diǎn)而言,僅僅保留端點(diǎn)信息,而CHAIN_APPROX_NONE保留所有點(diǎn)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
idx = np.argmax([len(t) for t in contours])
cnt = contours[idx]
print(cnt.shape)
print(cnt[:2,:,:])
# 繪制輪廓
cv.drawContours(img, contours, contourIdx=idx, color=(0, 0, 255), thickness=2)
# 計算面積
area = cv.contourArea(cnt)
# 計算周長
perimeter = cv.arcLength(cnt, closed=True)
print("面積為:{}, 周長為:{}".format(area, perimeter))
'''
1、獲取最大的矩形邊緣框, 返回值為矩形框的左上角的坐標(biāo)以及寬度和高度
'''
x,y,w,h = cv.boundingRect(cnt)
# 繪圖
cv.rectangle(img, pt1=(x,y), pt2=(x+w,y+h), color=(255,0,0), thickness=2)
'''
2、繪制最小矩形(所有邊緣在矩形內(nèi))、得到矩形的點(diǎn)(左下角、左上角、右上角、右下角<順序不一定>)、繪圖
'''
# minAreaRect:求得一個包含點(diǎn)集cnt的最小面積的矩形,這個矩形可以有一點(diǎn)的旋轉(zhuǎn)偏轉(zhuǎn)的,輸出為矩形的四個坐標(biāo)點(diǎn)
# rect為三元組,
# 第一個元素為旋轉(zhuǎn)中心點(diǎn)的坐標(biāo)
# 第二個元素為矩形的高度和寬度
# 第三個元素為旋轉(zhuǎn)大小,正數(shù)表示順時針選擇,負(fù)數(shù)表示逆時針旋轉(zhuǎn)
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int64(box)
cv.drawContours(img, [box], 0, (0, 255, 0), 2)
'''
3、繪制最小的圓(所有邊緣在圓內(nèi))
'''
(x,y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 0, 255), 5)
'''
4、繪制最小的橢圓(所有邊緣不一定均在圓內(nèi))
'''
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 5)
# 可視化
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')
plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')
plt.show()
(1050, 1, 2)
[[[306 220]]
[[306 229]]]
面積為:65960.5, 周長為:2487.3636897802353
'''
5、旋轉(zhuǎn)后繪制最小橢圓
'''
# 加載圖像
img = cv.imread('./data/xiaoren.png')
# 旋轉(zhuǎn)
rect = [(302, 420), (317.06085205078125, 372.9076232910156), 18]
M = cv.getRotationMatrix2D(center=rect[0], angle=rect[-1], scale=1)
img = cv.warpAffine(img, M, (cols, rows), borderValue=[255,255,255])
# 將圖像轉(zhuǎn)換為灰度圖像
img1 = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 做一個圖像反轉(zhuǎn)(0 -> 255, 255 -> 0)
img1 = cv.bitwise_not(img1)
# 做一個二值化
ret, thresh = cv.threshold(img1, 127, 255, cv.THRESH_BINARY)
# 發(fā)現(xiàn)輪廓信息
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
idx = np.argmax([len(t) for t in contours])
cnt = contours[idx]
# 繪制輪廓
cv.drawContours(img, contours, contourIdx=idx, color=(0, 0, 255), thickness=2)
# 繪制最小矩形(所有邊緣在矩形內(nèi))、得到矩形的點(diǎn)(左下角、左上角、右上角、右下角<順序不一定>)、繪圖
# minAreaRect:求得一個包含點(diǎn)集cnt的最小面積的矩形,這個矩形可以有一點(diǎn)的旋轉(zhuǎn)偏轉(zhuǎn)的,輸出為矩形的四個坐標(biāo)點(diǎn)
# rect為三元組,第一個元素為旋轉(zhuǎn)中心點(diǎn)的坐標(biāo)
# rect為三元組,第二個元素為矩形的高度和寬度
# rect為三元組,第三個元素為旋轉(zhuǎn)大小,正數(shù)表示順時針選擇,負(fù)數(shù)表示逆時針旋轉(zhuǎn)
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int64(box)
cv.drawContours(img, [box], 0, (0, 255, 0), 2)
print(rect)
# 可視化
plt.figure(figsize=(10,5))
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.title('Original Image')
plt.subplot(122)
plt.imshow(thresh,cmap = 'gray')
plt.title('thresh')
plt.show()
((300.05328369140625, 390.378662109375), (290.5243225097656, 397.2838134765625), 88.93909454345703)
文章來源:http://www.zghlxwxcb.cn/news/detail-471300.html
8、直方圖
'''
OpenCV中的直方圖的主要功能是可以查看圖像的像素信息以及提取直方圖中各個區(qū)間的像素值的數(shù)目作為當(dāng)前圖像的特征屬性進(jìn)行機(jī)器學(xué)習(xí)模型
'''
# 加載圖像
img = cv.imread('./data/koala.png', 0)
# 兩種方式基本結(jié)果基本類似
# 1、基于OpenCV的API計算直方圖
hist1 = cv.calcHist([img], channels=[0], mask=None, histSize=[256], ranges=[0,256])
# 2、基于NumPy計算直方圖
hist2, bins = np.histogram(img.ravel(), 256, [0, 256])
# 和np.histogram一樣的計算方式,但是效率快
hist3 = np.bincount(img.ravel(), minlength=256)
# 可視化
plt.figure(figsize=(20,10))
plt.subplot(231)
plt.imshow(img, 'gray')
plt.title('Original Image')
plt.subplot(232)
# 可以直接使用matpliab中的hist API直接畫直方圖
plt.plot(hist1)
plt.title('hist1')
plt.subplot(233)
plt.plot(hist2)
plt.title('hist2')
plt.subplot(234)
plt.plot(hist3)
plt.title('hist3')
# 可以直接使用matpliab中的hist API直接畫直方圖
plt.subplot(235)
plt.hist(img.ravel(), 256, [0, 256])
plt.title('plt.hist')
plt.show()
?
?文章來源地址http://www.zghlxwxcb.cn/news/detail-471300.html
到了這里,關(guān)于OpenCV基礎(chǔ)操作(5)圖像平滑、形態(tài)學(xué)轉(zhuǎn)換、圖像梯度的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!