讀取顯示圖像
# 讀取並顯示圖像
import cv2
path_to_image = r'pby.jpg'
"""
第二個參數
1 讀取彩色,默認
0 讀取灰度圖
-1 加載圖像,包括alpha通道
"""
original_image = cv2.imread(path_to_image, 1)
cv2.imshow('original image', original_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', original_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
保存圖像
# 保存圖像
import cv2
img = cv2.imread('pby.jpg', 0)
cv2.imshow('image', img)
k = cv2.waitKey(0)
if k == 27: # 等待ESC退出
cv2.destroyAllWindows()
elif k == ord('s'): # 等待關鍵字,保存和退出
cv2.imwrite('gray.png', img)
cv2.destroyAllWindows()
使用matplotlib
本文章不深入講解matplotlib的使用,后續更新matplotlib的詳細使用教程
"""
OpenCV加載的彩色圖像處於BGR模式。但是Matplotlib以RGB模式顯示。
"""
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('pby.jpg', 0)
plt.imshow(img, cmap='gray', interpolation='bicubic')
plt.xticks([]), plt.yticks([]) # 隱藏 x 軸和 y 軸上的刻度值
plt.show()
訪問攝像頭
import cv2 as cv
cap = cv.VideoCapture(0) # 參數可以是0-3,0表示默認攝像頭
print("width is %s" % cap.get(cv.CAP_PROP_FRAME_WIDTH))
print("height is %s" % cap.get(cv.CAP_PROP_FRAME_HEIGHT))
if cap.set(cv.CAP_PROP_FRAME_WIDTH, 320):
print('width now sets to 320')
if cap.set(cv.CAP_PROP_FRAME_HEIGHT, 240):
print('height now sets to 240')
if not cap.isOpened():
print("Cannot open camera")
exit()
while True:
# 逐幀捕獲
ret, frame = cap.read()
# 如果正確讀取幀,ret為True
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# 顯示結果幀
cv.imshow('frame', gray)
if cv.waitKey(1) == ord('q'):
break
# 完成所有操作后,釋放捕獲器
cap.release()
cv.destroyAllWindows()
讀取視頻文件
import cv2 as cv
cap = cv.VideoCapture('output.avi') # 傳入視頻路徑
while cap.isOpened():
ret, frame = cap.read()
# 如果正確讀取幀,ret為True
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
cv.imshow('frame', gray)
if cv.waitKey(25) == ord('q'):
break
cap.release()
cv.destroyAllWindows()
寫視頻文件
import cv2 as cv
cap = cv.VideoCapture(0)
# if cap.set(cv.CAP_PROP_FRAME_WIDTH, 320):
# print('width now sets to 320')
# if cap.set(cv.CAP_PROP_FRAME_HEIGHT, 240):
# print('height now sets to 240')
fourcc = cv.VideoWriter_fourcc(*'XVID') # 定義編解碼器並創建VideoWriter對象
out = cv.VideoWriter('output.avi', fourcc, 20.0, (640, 480))
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
# gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
out.write(frame)
cv.imshow('frame', frame)
if cv.waitKey(1) == ord('q'):
break
# 完成工作后釋放所有內容
cap.release()
out.release()
cv.destroyAllWindows()
在圖像上畫形狀、文字
import cv2 as cv
import numpy as np
# 創建黑色的圖像
img = np.zeros((512, 512, 3), np.uint8)
# 繪制一條厚度為5的藍色對角線
cv.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
# 畫矩形
cv.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
# 畫圓圈
cv.circle(img, (447, 63), 63, (0, 0, 255), -1)
# 畫橢圓
cv.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)
# 畫多邊形
pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
pts = pts.reshape((-1, 1, 2))
pts2 = np.array([[255, 240], [198, 189], [99, 345]], np.int32)
pts2 = pts2.reshape(-1, 1, 2)
cv.polylines(img, [pts], True, (0, 255, 255)) # 閉合多邊形
cv.polylines(img, [pts2], False, (0, 255, 255)) # 只連接不閉合
# 向圖片中添加文本
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv.LINE_AA)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()
用鼠標畫
import cv2 as cv
import numpy as np
import random
def main():
# 鼠標回調函數
def draw_circle(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDBLCLK:
cv.circle(img, (x, y), random.randint(50, 200), (255, 0, 0), -1)
# 創建一個黑色的圖像,一個窗口,並綁定到窗口的功能
img = np.zeros((512, 512, 3), np.uint8)
cv.namedWindow('image')
cv.setMouseCallback('image', draw_circle)
while True:
cv.imshow('image', img)
if cv.waitKey(20) & 0xFF == 27: # 按esc退出
break
cv.destroyAllWindows()
if __name__ == '__main__':
main()
軌跡欄的使用
import cv2 as cv
import numpy as np
def nothing(x):
pass
# 創建一個黑色的圖像
img = np.zeros((300, 512, 3), np.uint8)
cv.namedWindow('image')
# 創建顏色變化的軌跡欄
cv.createTrackbar('R', 'image', 0, 255, nothing)
cv.createTrackbar('G', 'image', 0, 255, nothing)
cv.createTrackbar('B', 'image', 0, 255, nothing)
# 為 ON/OFF 功能創建開關
switch = 'OFF/ON'
cv.createTrackbar(switch, 'image', 0, 1, nothing)
while True:
cv.imshow('image', img)
k = cv.waitKey(1) & 0xFF
if k == 27:
break
# 得到四條軌跡的當前位置
r = cv.getTrackbarPos('R', 'image')
g = cv.getTrackbarPos('G', 'image')
b = cv.getTrackbarPos('B', 'image')
s = cv.getTrackbarPos(switch, 'image')
if s == 0:
img[:] = 0
else:
img[:] = [b, g, r]
cv.destroyAllWindows()
訪問像素點
# 訪問像素點
def access_px():
img = cv.imread('pby.jpg') # 加載彩色圖像,默認是彩色圖像
px = img[100, 100] # 通過行和列坐標來訪問像素值
print(px) # [37 61 73]
# 僅訪問藍色像素
blue = img[100, 100, 0] # opencv讀取圖片是BGR格式
print(blue) # 37
img[100, 100] = [255, 255, 255] # 修改該坐標對應的像素值
print(img[100, 100]) # [255 255 255]
# 上面的方法通常用於選擇數組的區域,例如前5行和后3列。對於單個像素訪問,Numpy數組方法array.item()和array.itemset())被認為更好,但是它們始終返回標量。
# 如果要訪問所有B,G,R值,則需要分別調用所有的array.item()
# 訪問 RED 值
print(img.item(10, 10, 2)) # 60
# 修改 RED 值
img.itemset((10, 10, 2), 100)
print(img.item(10, 10, 2)) # 100
訪問圖像屬性
# 訪問圖像屬性
def access_properties():
img = cv.imread('pby.jpg')
print(img.shape) # 訪問圖片的形狀,返回行,列,通道數(如果讀取的是彩色圖片)如果圖像是灰度的,則返回的元組僅包含行數和列數
print(img.size) # 獲取圖片像素總數
print(img.dtype) # 獲取圖像數據類型
裁剪感興趣區域
# 圖像感興趣區域
def roi():
img = cv.imread('pby.jpg')
ball = img[280:340, 330:390]
cv.imshow('ball', ball)
cv.waitKey(3000)
cv.destroyAllWindows()
拆分合並通道
# 拆分/合並 通道
def split_merge():
img = cv.imread('pby.jpg')
b, g, r = cv.split(img) # 拆分圖像通道,此操作比較耗時
b2 = img[:, :, 0] # 前面兩個切片划定區域,第三個數取0,1,2 指定通道
cv.imshow('b2', b2)
img[:, :, 2] = 0 # 指定紅色通道,並修改所有像素值為0
cv.imshow('changed every red px into 0', img)
# cv.imshow('b', b)
# cv.imshow('g', g)
# cv.imshow('r', r)
img_merge = cv.merge((b, g, r)) # 合並圖像通道
cv.imshow('merged image', img_merge)
cv.waitKey(0)
cv.destroyAllWindows()
添加邊框
def board():
BLUE = [255, 0, 0]
img1 = cv.imread('opencv-logo.png')
replicate = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_WRAP)
constant = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_CONSTANT, value=BLUE)
plt.subplot(231), plt.imshow(img1, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
# 圖像由matplotlib顯示 因此紅色和藍色通道將互換
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()
執行結果:
圖像加法&圖像融合
def add_func():
x = np.uint8([250])
y = np.uint8([10])
print(x) # [250]
print(y) # [10]
print(cv.add(x, y)) # [[255]] # 250+10 = 260 => 255 OpenCV加法是飽和運算
print(x + y) # [4] Numpy加法是模運算
# 圖像融合 也是圖像加法,但是對圖像賦予不同的權重,以使其具有融合或透明的感覺。
def addWeighted_func():
img1 = cv.imread('1.jpg')
img2 = cv.imread('2.jpg')
cv.imshow('img1', img1)
cv.imshow('img2', img2)
dst = cv.addWeighted(img1, 0.5, img2, 0.5, 0)
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()
addWeighted_func
的執行結果:
位操作&掩碼
def bit_operation():
img1 = cv.imread('1.jpg')
img2 = cv.imread('opencv-logo.png')
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
# 現在創建logo的掩碼,並同時創建其相反掩碼
img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
cv.imshow('img2gray', img2gray)
cv.imshow('mask', mask)
mask_inv = cv.bitwise_not(mask)
cv.imshow('mask_inv', mask_inv)
# 現在將ROI中logo的區域塗黑
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
# 僅從logo圖像中提取logo區域
img2_fg = cv.bitwise_and(img2, img2, mask=mask)
# 將logo放入ROI並修改主圖像
dst = cv.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst
cv.imshow('res', img1)
cv.waitKey(0)
cv.destroyAllWindows()
執行結果:
以下圖像依次為
灰度圖
掩碼一
掩碼二
圖像加法
使用opencv衡量代碼性能
import cv2 as cv
# 使用opencv衡量代碼性能
img1 = cv.imread('pby.jpg')
e1 = cv.getTickCount()
for i in range(5, 49, 2):
img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1) / cv.getTickFrequency()
print(t) # 3.6950288
改變顏色空間
OpenCV中有超過150種顏色空間轉換方法。但是我們將研究只有兩個最廣泛使用的,
BGR ↔ 灰色 和 BGR↔HSV。
對於顏色轉換,我們使用cv函數。cvtColor(input_image, flag),其中flag決定轉換的類型。
對於BGR→灰度轉換,我們使用標志cv.COLOR_BGR2GRAY。
類似地,對於BGR→HSV,我們使用標志cv.COLOR_BGR2HSV。
要獲取其他標記,只需在Python終端中運行以下命令
import cv2 as cv
# OpenCV中有超過150種顏色空間轉換方法。但是我們將研究只有兩個最廣泛使用的,
# BGR ↔ 灰色 和 BGR↔HSV。
# 對於顏色轉換,我們使用cv函數。cvtColor(input_image, flag),其中flag決定轉換的類型。
# 對於BGR→灰度轉換,我們使用標志cv.COLOR_BGR2GRAY。
# 類似地,對於BGR→HSV,我們使用標志cv.COLOR_BGR2HSV。
# 要獲取其他標記,只需在Python終端中運行以下命令
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags) # ['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', ....,'COLOR_YUV420sp2RGBA', 'COLOR_mRGBA2RGBA']
HSV的色相范圍為[0,179],飽和度范圍為[0,255],值范圍為[0,255]。不同的軟件使用不同的規模。因此,如果你要將OpenCV值和它們比較,你需要將這些范圍標准化。
HSV顏色模型
HSV(Hue, Saturation, Value)是根據顏色的直觀特性由A. R. Smith在1978年創建的一種顏色空間, 也稱六角錐體模型(Hexcone Model)。、這個模型中顏色的參數分別是:色調(H),飽和度(S),亮度(V)。
色調H:用角度度量,取值范圍為0°~360°,從紅色開始按逆時針方向計算,紅色為0°,綠色為120°,藍色為240°。它們的補色是:黃色為60°,青色為180°,品紅為300°;
飽和度S:取值范圍為0.0~1.0;
亮度V:取值范圍為0.0(黑色)~1.0(白色)。
RGB和CMY顏色模型都是面向硬件的,而HSV(Hue Saturation Value)顏色模型是面向用戶的。
HSV模型的三維表示從RGB立方體演化而來。設想從RGB沿立方體對角線的白色頂點向黑色頂點觀察,就可以看到立方體的六邊形外形。六邊形邊界表示色彩,水平軸表示純度,明度沿垂直軸測量。
HSV顏色分量范圍
一般對顏色空間的圖像進行有效處理都是在HSV空間進行的,然后對於基本色中對應的HSV分量需要給定一個嚴格的范圍,下面是通過實驗計算的模糊范圍(准確的范圍在網上都沒有給出)。
H: 0— 180
S: 0— 255
V: 0— 255
此處把部分紅色歸為紫色范圍:
HSV六棱錐
H參數表示色彩信息,即所處的光譜顏色的位置。該參數用一角度量來表示,紅、綠、藍分別純度S為一比例值,范圍從0到1,它表示成所選顏色的純度和該顏色最大的純度之間的比率。S=0時,只有灰度。相隔120度。互補色分別相差180度。
V表示色彩的明亮程度,范圍從0到1。有一點要注意:它和光強度之間並沒有直接的聯系。
HSV對用戶來說是一種直觀的顏色模型。我們可以從一種純色彩開始,即指定色彩角H,並讓V=S=1,然后我們可以通過向其中加入黑色和白色來得到我們需要的顏色。增加黑色可以減小V而S不變,同樣增加白色可以減小S而V不變。例如,要得到深藍色,V=0.4 S=1 H=240度。要得到淡藍色,V=1 S=0.4 H=240度。
一般說來,人眼最大能區分128種不同的色彩,130種色飽和度,23種明暗度。如果我們用16Bit表示HSV的話,可以用7位存放H,4位存放S,5位存放V,即745或者655就可以滿足我們的需要了。由於HSV是一種比較直觀的顏色模型,所以在許多圖像編輯工具中應用比較廣泛,如Photoshop(在Photoshop中叫HSB)等等,但這也決定了它不適合使用在光照模型中,許多光線混合運算、光強運算等都無法直接使用HSV來實現。
追蹤指定顏色的物體
def track_color():
cap = cv.VideoCapture(0)
while True:
# 讀取幀
_, frame = cap.read()
# 轉換顏色空間 BGR 到 HSV
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
# 定義HSV中藍色的范圍
lower_blue = np.array([110, 50, 50])
upper_blue = np.array([130, 255, 255])
# 設置HSV的閾值使得只取藍色
mask = cv.inRange(hsv, lower_blue, upper_blue)
# 將掩膜和圖像逐像素相加
res = cv.bitwise_and(frame, frame, mask=mask)
cv.imshow('frame', frame)
cv.imshow('mask', mask)
cv.imshow('res', res)
k = cv.waitKey(5) & 0xFF
if k == 27:
break
cv.destroyAllWindows()
執行結果如下:
可以看到還是有不少噪點的,這里是用電腦的前置攝像頭拍攝,手機純藍色背景,受光照影響,顯示效果不好
找到指定顏色的HSV值
def get_hsv_value():
# 綠色的圖片
green = np.uint8([[[0, 255, 0]]])
# 獲取綠色的hsv值
hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
print(hsv_green) # [[[60 255 255]]]
現在把 [H- 10,100,100] 和 [H+ 10,255, 255] 分別作為下界和上界即可追蹤指定顏色了
圖像幾何變換
1.縮放
# 縮放圖片
def resize():
img = cv.imread('pby.jpg')
res1 = cv.resize(img, None, fx=0.25, fy=0.25, interpolation=cv.INTER_CUBIC)
cv.imshow('res1', res1)
# 或者
height, width = img.shape[:2]
res2 = cv.resize(img, (int(0.25 * width), int(0.25 * height)), interpolation=cv.INTER_CUBIC)
cv.imshow('res2', res2)
cv.waitKey(0)
cv.destroyAllWindows()
2.平移
# 平移
def translate():
img = cv.imread('pby.jpg', 0)
rows, cols = img.shape
M = np.float32([[1, 0, 100], [0, 1, 50]])
dst = cv.warpAffine(img, M, (cols, rows))
cv.imshow('img', dst)
cv.waitKey(0)
cv.destroyAllWindows()
3.旋轉
# 旋轉
def rotate():
img = cv.imread('pby.jpg', 0)
rows, cols = img.shape
# cols-1 和 rows-1 是坐標限制
M = cv.getRotationMatrix2D(((cols - 1) / 2.0, (rows - 1) / 2.0), 90, 1)
print(M)
# [[ 6.12323400e-17 1.00000000e+00 -1.13686838e-13]
# [-1.00000000e+00 6.12323400e-17 1.07900000e+03]]
dst = cv.warpAffine(img, M, (cols, rows))
cv.imshow('img', dst)
cv.waitKey(0)
cv.destroyAllWindows()
4.仿射變換
# 仿射變換
# 在仿射變換中,原始圖像中的所有平行線在輸出圖像中仍將平行。為了找到變換矩陣,我們需要輸入圖像中的三個點及其在輸出圖像中的對應位置。
# 然后cv.getAffineTransform將創建一個2x3矩陣,該矩陣將傳遞給cv.warpAffine
def affine():
img = cv.imread('drawing.png')
rows, cols, ch = img.shape
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv.getAffineTransform(pts1, pts2)
print(M)
dst = cv.warpAffine(img, M, (cols, rows))
cv.imshow('input', img)
cv.imshow('output', dst)
cv.waitKey(0)
cv.destroyAllWindows()
5.透視變換
# 透視變換
# 對於透視變換,您需要3x3變換矩陣。即使在轉換后,直線也將保持直線。要找到此變換矩陣,需要在輸入圖像上有4個點,在輸出圖像上需要相應的點。
# 在這四個點中,其中三個不應共線。然后通過函數cv.getPerspectiveTransform找到變換矩陣。然后將cv.warpPerspective應用於此3x3轉換矩陣
def perspective_transform():
img = cv.imread('sudoku.png')
# rows, cols, ch = img.shape
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv.getPerspectiveTransform(pts1, pts2)
dst = cv.warpPerspective(img, M, (300, 300))
cv.imshow('img', img)
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()
# plt.subplot(121), plt.imshow(img), plt.title('Input')
# plt.subplot(122), plt.imshow(dst), plt.title('Output')
# plt.show()
圖像閾值處理
簡單閾值處理
threshold
函數用於應用閾值。其參數分別為:
1.源圖像(必須為灰度圖)
2.閾值
3.超出閾值時所分配的最大值
4.閾值類型
閾值類型主要有以下幾種:
import cv2 as cv
from matplotlib import pyplot as plt
# 簡單閾值,對於每個像素,應用相同的閾值。如果像素值小於閾值,則將其設置為0,否則將其設置為最大值。
def simple_threshold():
img = cv.imread('pby.jpg') # 讀取圖片
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 轉成灰度圖
# threshold函數 用於應用閾值。其參數分別為:1.源圖像(必須為灰度圖)2.閾值 3.超出閾值時所分配的最大值4.閾值類型
ret, thresh1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img_gray, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img_gray, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img_gray, 127, 255, cv.THRESH_TOZERO_INV)
titles = ['Original Image', 'gray image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
images = [img_rgb, img_gray, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(7):
plt.subplot(2, 4, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
運行結果如下:
自適應閾值處理
# 當同一幅圖像上的不同部分具有不同亮度時。這種情況下我們需要采用自適應閾值。此時的閾值是根據圖像上的每一個小區域計算與其對應的閾值。
# 因此在同一幅圖像上的不同區域采用的是不同的閾值,從而使我們能在亮度不同的情況下得到更好的結果。
def adaptive_threshold():
img = cv.imread('pby.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 中值濾波
img_gray = cv.medianBlur(img_gray, 5)
ret, th1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
# 11 為 Block size,即鄰域大小,用於計算閾值的窗口大小, 2 為 常數,可以理解為偏移量
th2 = cv.adaptiveThreshold(img_gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
th3 = cv.adaptiveThreshold(img_gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding',
'Adaptive Gaussian Thresholding']
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
images = [img_rgb, th1, th2, th3]
for i in range(4):
plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
中值濾波
這里介紹一下中值濾波的概念
無論是直接獲取的灰度圖像,還是由彩色圖像轉換得到的灰度圖像,里面都有噪聲的存在,噪聲對圖像質量有很大的影響。進行中值濾波不僅可以去除孤點噪聲,而且可以保持圖像的邊緣特性,不會使圖像產生顯著的模糊,比較適合於實驗中的人臉圖像。
中值濾波是一種非線性的信號處理方法,因此中值濾波器也就是一種非線性的濾波器。在一定條件下,其可以克服線性濾波器處理圖像細節模糊的問題,而且它對濾除脈沖干擾和圖像掃描噪聲非常有效,但是,對點、線、尖頂等細節較多的圖像,則會引起圖像信息的丟失。中值濾波器最先被應用於一維信號的處理中,后來被人們引用到二維圖像的處理中來。
中值濾波是對一個滑動窗口內的諸像素灰度值排序,用其中值代替窗口中心像素的原來灰度值,它是一種非線性的圖像平滑法,它對脈沖干擾級椒鹽噪聲的抑制效果好,在抑制隨機噪聲的同時能有效保護邊緣少受模糊。
中值濾波可以過濾尖峰脈沖。目的在於我們對於濾波后的數據更感興趣。濾波后的數據保留的原圖像的變化趨勢,同時去除了尖峰脈沖對分析造成的影響。
以一維信號的中值濾波舉例。對灰度序列80、120、90、200、100、110、70,如果按大小順序排列,其結果為70、80、90、10O、110、120、200,其中間位置上的灰度值為10O,則該灰度序列的中值即為100。一維信號中值濾波實際上就是用中值代替規定位置(一般指原始信號序列中心位置)的信號值。對前面所舉的序列而言,中值濾波的結果是用中值100替代序列80、120、90、200、100、110、70中的信號序列中心位置值200,得到的濾波序列就是80、120、90、100、100、110、70。如果在此序列中200是一個噪聲信號,則用此方法即可去除這個噪聲點。
二維中值濾波算法是對於一幅圖像的像素矩陣,取以目標像素為中心的一個子矩陣窗口,這個窗口可以是33 ,55 等根據需要選取,對窗口內的像素灰度排序,取中間一個值作為目標像素的新灰度值。窗口示例如ooooxoooo上面x為目標像素,和周圍o組成3*3矩陣Array,然后對這9個元素的灰度進行排序,以排序后的中間元素Array[4]為x的新灰度值,如此就完成對像素x的中值濾波,再迭代對其他需要的像素進行濾波即可。
中值濾波的基本思想是,把局部區域的像素按灰度等級進行排序,取該領域中灰度的中值作為當前像素的灰度值。
中值濾波的步驟為:
-
將濾波模板(含有若干個點的滑動窗口)在圖像中漫游,並將模板中心與圖中某個像素位置重合;
-
讀取模板中各對應像素的灰度值;
-
將這些灰度值從小到大排列;
-
取這一列數據的中間數據,將其賦給對應模板中心位置的像素。如果窗口中有奇數個元素,中值取元素按灰度值大小排序后的中間元素灰度值。如果窗口中有偶數個元素,中值取元素按灰度值大小排序后,中間兩個元素灰度的平均值。
因為圖像為二維信號,中值濾波的窗口形狀和尺寸對濾波器效果影響很大,不同圖像內容和不同應用要求往往選用不同的窗口形狀和尺寸。
由以上步驟,可以看出,中值濾波對孤立的噪聲像素即椒鹽噪聲、脈沖噪聲具有良好的濾波效果。由於其並不是簡單的取均值,所以,它產生的模糊也就相對比較少。
二值化
# otsu 二值化
# 在使用全局閾值時,我們就是隨便給了一個數來做閾值,那我們怎么知道我們選取的這個數的好壞呢?答案就是不停的嘗試。
# 如果是一副雙峰圖像(簡單來說雙峰圖像是指圖像直方圖中存在兩個峰)呢?我們豈不是應該在兩個峰之間的峰谷選一個值作為閾值?
# 這就是 Otsu 二值化要做的。簡單來說就是對一副雙峰圖像自動根據其直方圖計算出一個閾值。(對於非雙峰圖像,這種方法得到的結果可能會不理想)。
# 這里用到的函數還是 cv2.threshold(),但是需要多傳入一個參數(flag):cv2.THRESH_OTSU。這時要把閾值設為 0。然后算法會找到最優閾值,
# 這個最優閾值就是返回值 retVal。如果不使用 Otsu 二值化,返回的retVal 值與設定的閾值相等。
# 下面的例子中,輸入圖像是一副帶有噪聲的圖像。
# 第一種方法,我們設127 為全局閾值。
# 第二種方法,我們直接使用 Otsu 二值化。
# 第三種方法,我們首先使用一個 5x5 的高斯核除去噪音,然后再使用 Otsu 二值化。
def otsu():
img = cv.imread('noisy.png')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# global thresholding
ret1, th1 = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY)
# Otsu's thresholding
ret2, th2 = cv.threshold(img_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
# (5,5)為高斯核的大小,0 為標准差
blur = cv.GaussianBlur(img_gray, (5, 5), 0)
# 閾值一定要設為 0!
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# plot all the images and their histograms
images = [img_gray, 0, th1,
img_gray, 0, th2,
blur, 0, th3]
titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]
# 這里使用了 pyplot 中畫直方圖的方法,plt.hist, 要注意的是它的參數是一維數組
# 所以這里使用了(numpy)ravel 方法,將多維數組轉換成一維,也可以使用 flatten 方法
# ndarray.flat 1-D iterator over an array.
# ndarray.flatten 1-D array copy of the elements of an array in row-major order.
for i in range(3):
plt.subplot(3, 3, i * 3 + 1), plt.imshow(images[i * 3], 'gray')
plt.title(titles[i * 3]), plt.xticks([]), plt.yticks([])
plt.subplot(3, 3, i * 3 + 2), plt.hist(images[i * 3].ravel(), 256)
plt.title(titles[i * 3 + 1]), plt.xticks([]), plt.yticks([])
plt.subplot(3, 3, i * 3 + 3), plt.imshow(images[i * 3 + 2], 'gray')
plt.title(titles[i * 3 + 2]), plt.xticks([]), plt.yticks([])
plt.show()
執行結果:
otsu二值化工作原理
# 手動計算閾值
def calculate_otsu():
img = cv.imread('noisy.png')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
blur = cv.GaussianBlur(img_gray, (5, 5), 0)
# find normalized_histogram, and its cumulative distribution function
# 計算歸一化直方圖
# CalcHist(image, accumulate=0, mask=NULL)
hist = cv.calcHist([blur], [0], None, [256], [0, 256])
hist_norm = hist.ravel() / hist.max()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1, 256):
p1, p2 = np.hsplit(hist_norm, [i]) # probabilities
q1, q2 = Q[i], Q[255] - Q[i] # cum sum of classes
b1, b2 = np.hsplit(bins, [i]) # weights
# finding means and variances
m1, m2 = np.sum(p1 * b1) / q1, np.sum(p2 * b2) / q2
v1, v2 = np.sum(((b1 - m1) ** 2) * p1) / q1, np.sum(((b2 - m2) ** 2) * p2) / q2
# calculates the minimization function
fn = v1 * q1 + v2 * q2
if fn < fn_min:
fn_min = fn
thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu, = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
print(thresh)
print(ret)
圖像平滑處理
自定義濾波器進行卷積
# 在介紹四種模糊(平滑)濾波器之前,先使用自定義濾波器對圖像進行卷積處理
def custom_filter():
img = cv2.imread('opencv-logo.png')
kernel = np.ones((5, 5), np.float32) / 25
print(kernel)
# cv.Filter2D(src, dst, kernel, anchor=(-1, -1))
# depth –desired depth of the destination image;
# if it is negative, it will be the same as src.depth();
# the following combinations of src.depth() and depth are supported:
# src.depth() = CV_8U, depth = -1/CV_16S/CV_32F/CV_64F
# src.depth() = CV_16U/CV_16S, depth = -1/CV_32F/CV_64F
# src.depth() = CV_32F, depth = -1/CV_32F/CV_64F
# src.depth() = CV_64F, depth = -1/CV_64F
# when depth=-1, the output image will have the same depth as the source.
dst = cv2.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()
執行結果
打印輸出
[[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]]
均值濾波器
# 用卷積框覆蓋區域所有像素的平均值來代替中心元素。
def average_filter():
img = cv2.imread('opencv-logo.png')
blur = cv2.blur(img, (5, 5))
plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
plt.show()
高斯濾波器
# 高斯核(簡單來說,方框不變,將原來每個方框的值是相等的,現在里面的值是符合高斯分布的,方框中心的值最大,其余方框根據
# 距離中心元素的距離遞減,構成一個高斯小山包。原來的求平均數現在變成求加權平均數,權就是方框里的值)
# 高斯濾波器是求中心點鄰近區域像素的高斯加權平均值。這種高斯濾波器只考慮像素之間的空間關系,而不會考慮像素值之間的關系(像素的相似度)。
# 所以這種方法不會考慮一個像素是否位於邊界。因此邊界也會模糊掉
def gaussian_blur():
img = cv2.imread('opencv-logo.png')
blur = cv2.GaussianBlur(img, (5, 5), 0, 0)
plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
plt.show()
中值濾波器
# 用與卷積框對應像素的中值來替代中心像素的值。這個濾波器經常用來去除椒鹽噪聲。前面的濾波器都是用計算得到的一個新值來取代中
# 心像素的值,而中值濾波是用中心像素周圍(也可以使他本身)的值來取代他。他能有效的去除噪聲。卷積核的大小也應該是一個奇數。
def median_blur():
img = cv2.imread('opencv-logo.png')
blur = cv2.medianBlur(img, (5, 5))
plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
plt.show()
雙邊濾波器
# 雙邊濾波在同時使用空間高斯權重和灰度值相似性高斯權重。空間高斯函數確保只有鄰近區域的像素對中心點有影響,灰度值相似性高斯函數確保只有
# 與中心像素灰度值相近的才會被用來做模糊運算。所以這種方法會確保邊界不會被模糊掉,因為邊界處的灰度值變化比較大。
# 通常用於處理圖片紋理,同時保留邊界
def bilateral_blur():
img = cv2.imread('opencv-logo.png')
# cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)
# d – Diameter of each pixel neighborhood that is used during filtering.
# If it is non-positive, it is computed from sigmaSpace
# 9 鄰域直徑,兩個 75 分別是空間高斯函數標准差,灰度值相似性高斯函數標准差
blur = cv2.bilateralFilter(img, 9, 75, 75)
plt.subplot(121), plt.imshow(img), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred'), plt.xticks([]), plt.yticks([])
plt.show()
形態學操作 腐蝕&膨脹&開運算&閉運算&形態學梯度&禮帽&黑帽
腐蝕和膨脹
def erode_dilate():
img = cv2.imread('j.png', cv2.IMREAD_COLOR)
kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1) # 腐蝕
dilation = cv2.dilate(img, kernel, iterations=1) # 膨脹
titles = ['original image', 'eroded image', 'dilated image']
images = [img, erosion, dilation]
for i in range(3):
plt.subplot(1, 3, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
執行結果:
開運算【先腐蝕再膨脹】
# 先進行腐蝕再進行膨脹就叫做開運算,常用來去除噪聲
def morphologyEx_open():
img = cv2.imread('j with noise.png', cv2.IMREAD_COLOR)
kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
titles = ['original image', 'opening image']
images = [img, opening]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
運行結果
閉運算【先膨脹再腐蝕】
# 先膨脹再腐蝕。它經常被用來填充前景物體中的小洞,或者前景物體上的小黑點。
def morphologyEx_close():
img = cv2.imread('j with noise2.png', cv2.IMREAD_COLOR)
kernel = np.ones((5, 5), np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
titles = ['original image', 'closing image']
images = [img, closing]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
形態學梯度
# 一幅圖像膨脹與腐蝕的差,看上去就像是前景物體的輪廓
def morphologyEx_gradient():
img = cv2.imread('j.png', cv2.IMREAD_COLOR)
kernel = np.ones((5, 5), np.uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
titles = ['original image', 'gradient image']
images = [img, gradient]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
禮帽&黑帽
# 原始圖像與進行開運算之后得到的圖像的差。
# tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# 進行閉運算之后得到的圖像與原始圖像的差
# blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
def morphologyEx_tophat_blackhat():
img = cv2.imread('j.png', cv2.IMREAD_COLOR)
kernel = np.ones((5, 5), np.uint8)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) # 禮帽
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) # 黑帽
titles = ['original image', 'tophat image', 'blackhat image']
images = [img, tophat, blackhat]
for i in range(3):
plt.subplot(1, 3, i + 1), plt.imshow(images[i], 'gray'), plt.title(titles[i]),
plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
形態學操作之間的關系
-
opening:
dst = open(src,element) = dilate(erode(src,element),element)
-
closing:
dst = close(src,element) = erode(dilate(src,element),element)
-
morphological gradient:
dst = morph_grad(src,element) = dilate(src,element) - erode(src,element)
-
tophat:
dst = tophat(src,element) = src - open(src,element)
-
blackhat:
dst = blackhat(src,element) = close(src,element) - src
結構化元素
在前面的例子中我們使用 Numpy 構建了結構化元素,它是正方形的。但有時我們需要構建一個橢圓形/圓形的核。為了實現這種要求,提供了 OpenCV 函數 cv2.getStructuringElement()。你只需要告訴他你需要的核的形狀 和大小。
# Rectangular Kernel
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array(
[[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]], dtype=uint8)
# Elliptical Kernel
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([
[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]], dtype=uint8)
# Cross-shaped Kernel
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array(
[[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]], dtype=uint8)
圖像梯度
梯度簡單來說就是求導。 OpenCV
提供了三種不同的梯度濾波器,或者說高通濾波器:Sobel, Scharr 和 Laplacian。Sobel,Scharr 其實就是求一階或二階導數。Scharr 是對 Sobel(使用小的卷積核求解梯度角度時)的優化。Laplacian 是求二階導數。
Sobel算子和Scharr算子
Sobel 算子是高斯平滑與微分操作的結合體,所以它的抗噪聲能力很好。 你可以設定求導的方向(xorder 或 yorder)。還可以設定使用的卷積核的大 小(ksize)。如果 ksize=-1,會使用 3x3 的 Scharr 濾波器,它的效果要 比 3x3 的 Sobel 濾波器好(而且速度相同,所以在使用 3x3 濾波器時應該盡量使用 Scharr 濾波器)。3x3 的 Scharr 濾波器卷積核如下
Laplacian 算子
拉普拉斯算子可以使用二階導數的形式定義,可假設其離散實現類似於二階 Sobel 導數,事實上,OpenCV 在計算拉普拉斯算子時直接調用 Sobel 算子。計算公式如下:
拉普拉斯濾波器使用的卷積核:
def sobel_laplacian():
img = cv2.imread('dave.png', cv2.IMREAD_GRAYSCALE)
# cv2.CV_64F 輸出圖像的深度(數據類型),可以使用-1, 與原圖像保持一致 np.uint8
laplacian = cv2.Laplacian(img, cv2.CV_64F)
# 參數 1,0 為只在 x 方向求一階導數,最大可以求 2 階導數。
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
# 參數 0,1 為只在 y 方向求一階導數,最大可以求 2 階導數。
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
plt.subplot(2, 2, 1), plt.imshow(img, cmap='gray'), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap='gray'), plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobel_x, cmap='gray'), plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobel_y, cmap='gray'), plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
在查看上面這個例子的注釋時不知道你有沒有注意到:當我們可以通過參數 -1 來設定輸出圖像的深度(數據類型)與原圖像保持一致,但是我們在代碼中使用的卻是 cv2.CV_64F。這是為什么呢?想象一下一個從黑到白的邊界 的導數是整數,而一個從白到黑的邊界點導數卻是負數。如果原圖像的深度是 np.int8 時,所有的負值都會被截斷變成 0,換句話說就是把邊界丟失掉。 所以如果這兩種邊界你都想檢測到,最好的辦法就是將輸出的數據類型設置的更高,比如 cv2.CV_16S,cv2.CV_64F 等。取絕對值然后再把它轉回到 cv2.CV_8U。下面的示例演示了輸出圖片的深度不同造成的不同效果。
def depth_of_output():
img = cv2.imread('boxs.png', cv2.IMREAD_GRAYSCALE)
# Output dtype = cv2.CV_8U
sobel_x8u = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=5)
# 也可以將參數設為-1
# sobel_x8u = cv2.Sobel(img,-1,1,0,ksize=5)
# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobel_x64f = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobel_x64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1, 3, 1), plt.imshow(img, cmap='gray'), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(sobel_x8u, cmap='gray'), plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap='gray'), plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()
運行結果:
Canny邊緣檢測
Canny 邊緣檢測是一種非常流行的邊緣檢測算法,是 John F.Canny 在 1986 年提出的。它是一個有很多步構成的算法。
1.噪聲去除
由於邊緣檢測很容易受到噪聲影響,所以第一步是使用 5x5 的高斯濾波器去除噪聲
2.計算圖像梯度
對平滑后的圖像使用 Sobel 算子計算水平方向和豎直方向的一階導數(圖像梯度)(Gx 和 Gy)。根據得到的這兩幅梯度圖(Gx 和 Gy)找到邊界的梯度和方向,公式如下
梯度的方向一般總是與邊界垂直。梯度方向被歸為四類:垂直,水平,和 兩個對角線
3.非極大值抑制
在獲得梯度的方向和大小之后,應該對整幅圖像做一個掃描,去除那些非邊界上的點。對每一個像素進行檢查,看這個點的梯度是不是周圍具有相同梯度方向的點中最大的。
現在你得到的是一個包含“窄邊界”的二值圖像
4.滯后閾值
現在要確定那些邊界才是真正的邊界。這時我們需要設置兩個閾值: minVal 和 maxVal。
當圖像的灰度梯度高於 maxVal 時被認為是真的邊界, 那些低於 minVal 的邊界會被拋棄。
如果介於兩者之間的話,就要看這個點是否與某個被確定為真正的邊界點相連,如果是就認為它也是邊界點,如果不是就拋棄。
A 高於閾值 maxVal 所以是真正的邊界點,C 雖然低於 maxVal 但高於 minVal 並且與 A 相連,所以也被認為是真正的邊界點。而 B 就會被拋棄,因為他不僅低於 maxVal 而且不與真正的邊界點相連。所以選擇合適的 maxVal 和 minVal 對於能否得到好的結果非常重要。 在這一步,一些小的噪聲點也會被除去,因為我們假設邊界都是一些長的線段。
def canny():
img = cv2.imread('tree.jpg', cv2.IMREAD_GRAYSCALE)
edges = cv2.Canny(img, 80, 100)
plt.subplot(121), plt.imshow(img, cmap='gray'), plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(edges, cmap='gray'), plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()
運行結果: