OpenCV筆記(3)(Canny邊緣檢測、高斯金字塔、拉普拉斯金字塔、圖像輪廓、模板匹配)


一、Canny邊緣檢測

Canny邊緣檢測是一系列方法綜合的結果。其中主要包含以下步驟:

1.使用高斯濾波器,平滑圖像,濾除噪聲。

2.計算圖像中每個像素點的梯度強度和方向。

3.應用非極大值抑制(NMS:Non-Maximum Suppression),以消除邊緣檢測帶來的雜散相應。

4.應用雙閾值(Double-Threshold)檢測來確定真實和潛在的邊緣。

5.通過抑制孤立的弱邊緣最終完成邊緣檢測。

 

1.高斯濾波器

平滑圖像。

2.計算梯度和方向

使用X和Y方向的Sobel算子來分別計算XY方向梯度:

每個點的梯度強度有XY方向的梯度計算出來:

計算每個點梯度的方向:

3.使用NMS

有兩種方法,第一種方法(插值法,比較復雜):

    通過計算出的梯度方向,找到與周邊臨近點的邊的交點,然后使用權重計算交點的值,假設g1和g2之間的交點(左上的黑點)處於6/4的位置,那么他的值為M = g1*(1-0.6)+g2*(0.4)。

    當算出左上的黑點和右下的黑點值后,用這兩個點與C的值進行比較,如果都小於C,則C歸為邊界。如果有一個比C大,則丟棄C,這就叫抑制。

 

第二種方法(指定8個方向,不用插值,簡化版):

4.雙閾值檢測

在NMS的基礎上,判斷一個邊界點的梯度強度:

  (1) 如果值大於maxVal,則處理為邊界

  (2) 如果值minVal<梯度值<maxVal,再檢查是否挨着其他邊界點,如果旁邊沒有邊界點,則丟棄,如果連着確定的邊界點,則也認為其為邊界點。

  (3) 梯度值<minVal,舍棄。

 

通過以上步驟,完成Canny邊緣檢測。調用Canny API如下:

# 使用Canny邊界檢測
def use_canny(image):
    # 后面兩個參數代表雙閾值檢測的minVal和maxVal
    img1 = cv.Canny(image, 50, 100)
    cv.imshow('img1', img1)
    # 這里使用更大的minVal和maxVal,細節邊界變少了
    img2 = cv.Canny(image, 170, 250)
    cv.imshow('img2', img2)

二、高斯金字塔

圖像金字塔:Image pyramid

如圖中所示,從0到3是一個下采樣過程(指圖片越來越小的方向),從3到0是一個上采樣過程(將圖片變大的過程),一次下采樣加一次上采樣不等於原圖像,因為會損失一些細節信息。

# 下采樣(圖片變小4倍,即hw各縮小一倍)
def down_sample(image):
    down = cv.pyrDown(image)
    cv.imshow('down', down)


# 上采樣(圖片變大4倍,即hw各變大一倍)
def up_sample(image):
    up = cv.pyrUp(image)
    cv.imshow('up', up)

上采樣+下采樣=原圖??

如下圖所示,很明顯,經上采樣后下采樣得到的圖片(右圖)和原圖(左圖)相比,更加模糊,說明丟失了一些信息。

先經上采樣,后經下采樣的圖像結果如下(大小未發生變化):

原始圖像: (205, 368, 3)
上采樣后: (410, 736, 3) 上采樣+下采樣后: (205, 368, 3)

 

下采樣+上采樣=原圖??

如下圖所示,非常明顯,經下采樣后上采樣得到的圖片和原圖相比,模糊很嚴重,說明這種情況下丟失信息更加嚴重。

當h或w為單數時,下采樣后其將變為單數,如下結果所示:

原始圖像: (205, 368, 3)
下采樣后: (103, 184, 3)
下采樣+上采樣后: (206, 368, 3)

原始圖像和經下采樣上采樣后的圖像大小發生了變化。

三、拉普拉斯金字塔

拉普拉斯金字塔的整個計算過程如上圖所示:

1.左上角的圖片為原始圖片

2.對原始圖像進行高斯平滑

3.執行一次下采樣,圖像變為原來的1/4

4.執行一次上采樣,圖像變為原圖的大小

5.再次執行高斯模糊

6.用原圖像減去高斯模糊后的圖像,得到拉普拉斯圖像

上述過程是某一層拉普拉斯金字塔的計算過程,其他層同樣按這個過程計算。

def lap_pyr(image):
    img = image.copy()
    # 處理h或w為奇數的情況,如果為奇數,丟棄最后一行或列
    h, w = image.shape[0:2]
    if h % 2 != 0:
        img = img[0:h - 1, :, :]
    if w % 2 != 0:
        img = img[:, 0:w - 1, :]

    gaus = cv.GaussianBlur(img, (3, 3), 0)
    down = cv.pyrDown(gaus)
    up = cv.pyrUp(down)
    gaus_up = cv.GaussianBlur(up, (3, 3), 0)
    diff = gaus - gaus_up
    # 返回下一層的原始圖像(即高斯模糊后下采樣得到的圖像),以及本層拉普拉斯圖像
    return down,diff

得到的圖像如上圖,但噪聲很大。
四、圖像輪廓

# 要找一個圖中的輪廓,首先要將圖片轉換為二值圖像
def find_contour(image):
    # 首先將圖片轉換為灰度圖
    gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)
    # 將灰度圖轉換為二值圖像
    ret, thresh = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
    cv.imshow('二值圖像', thresh)
    # 在二值圖像中尋找輪廓,返回的contours中含有多個輪廓
    contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)

    # 復制一份原圖像
    draw_img = image.copy()
    # 在復制的圖上畫出輪廓,顏色為(0,0,255)紅色,-1表示畫出全部輪廓,指定0則表示只畫第一個輪廓,
   # 最后的2表示畫線的粗細
res = cv.drawContours(draw_img, contours, -1, (0, 0, 255), 2) cv.imshow('輪廓圖', res)

圖像結果,左邊為二值圖像,右邊為在原圖的拷貝上畫出的輪廓。

上述代碼中關鍵函數為cv.findContours(img,mode,method):

img:原圖像的二值圖像,見代碼中的轉換

mode:輪廓檢索模式,主要有一下幾種,最常用的是RETR_TREE

  RETR_EXTERNAL:只檢索最外面的輪廓;

  RETR_LIST:檢索所有的輪廓,並將其保存到一條鏈表當中;

  RETR_CCOMP:檢索所有的輪廓,並將他們組織為兩層,頂層是各部分的外部邊界,第二層是空洞的邊界

  RETR_TREE:檢索所有的輪廓,並重構嵌套輪廓的整個層次。

method:輪廓逼近方法,常用的有一下兩種(還有其他不常用的)

  CHAIN_APPROX_NONE:以Freeman鏈碼的方式輸出輪廓,所有其他方法輸出多邊形

  CHAIN_APPROX_SIMPLE:壓縮水平的、垂直的和斜的部分,也就是,函數只保留他們的終點部分。如下圖所示,鎖邊為NONE,右邊為SIMPLE。

 

計算輪廓包含的面積,周長等:

# 計算輪廓包含的面積
print(cv.contourArea(contours[0]))
print(cv.contourArea(contours[1]))
# 計算第一個輪廓的周長,True表示閉合的
print(cv.arcLength(contours[0], True))

輪廓近似:

draw_img2 = image.copy()
# 用周長的0.3倍作為閾值,對輪廓做近似處理,這里以第189個輪廓為例
epsilon = 0.03 * cv.arcLength(contours[188], True)
approx = cv.approxPolyDP(contours[188], epsilon, True)
res2 = cv.drawContours(draw_img2, [approx], -1, (255, 0, 0), 2)
cv.imshow('res2', res2)draw_img2 = image.copy()
# 用周長的0.3倍作為閾值,對輪廓做近似處理,這里以第189個輪廓為例
epsilon = 0.03 * cv.arcLength(contours[188], True)
approx = cv.approxPolyDP(contours[188], epsilon, True)
res2 = cv.drawContours(draw_img2, [approx], -1, (255, 0, 0), 2)
cv.imshow('res2', res2)

 對所有輪廓都做近似處理:

# 將contours中的左右輪廓都做近似處理
new_contours = []
for i in contours:
eps = 0.01 * cv.arcLength(i,True)
approx = cv.approxPolyDP(i,eps,True)
new_contours.append(approx)

draw_img2 = image.copy()
res2 = cv.drawContours(draw_img2, new_contours, -1, (255, 0, 0), 2)
cv.imshow('res2', res2)

外接矩形:

x, y, w, h = cv.boundingRect(contours[188])
rect = cv.rectangle(draw_img2, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv.imshow('rect', rect)

最小矩形:

可以通過輪廓來獲取其最小矩形,用於定位車牌等很有用(車牌不是正的時候)

# 獲取最小矩形
    res = cv.minAreaRect(contours[188])
    # box中保存的是由四個頂點坐標組成的二維數組,通過4個頂點,我們可以將車牌摳出來
    box = cv.boxPoints(res)
    box = np.int32(box)

    # 用polylines將矩形畫出來
    box = box.reshape(-1, 1, 2)
    cv.polylines(draw_img2, [box], True, (0, 0, 255), 2)

外接圓:

# 獲取外接圓
(x, y), r = cv.minEnclosingCircle(contours[188])
center = (int(x), int(y))
radius = int(r)
circle = cv.circle(draw_img2, center, radius, (0, 255, 0), 2)
cv.imshow('circle', circle)

凸包:

convex Hull,獲取輪廓的外接凸包,獲得N個坐標,仍然用line畫出。

res = cv.convexHull(contours[188])
    length = len(res)
    for i in range(length):
        cv.line(draw_img3, tuple(res[i][0]), tuple(res[(i + 1) % length][0]), (0, 255, 255), 2)
    cv.imshow('draw_img3', draw_img3)

 

五、模板匹配

例如,我們要在一張圖中匹配其中的一部分圖像,並找到其具體坐標:

 

# 模板匹配
def template_match(src, temp):
    # 轉換為灰度圖像可以減少計算量
    src = cv.cvtColor(src, cv.COLOR_RGB2GRAY)
    temp = cv.cvtColor(temp, cv.COLOR_RGB2GRAY)
    # 獲取temp的h和w
    h, w = temp.shape[0:2]

    # 將原圖和局部圖輸入,計算出一個差異矩陣res
    res = cv.matchTemplate(src, temp, cv.TM_SQDIFF)

    # 得到這個diff矩陣中的最小值、最大值以及所在位置,都是tuple
    min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
    # 我們使用的是平方誤差,值需要關心最小值所在位置
    img = src.copy()
    # 用白色將對應的框畫出來
    rect = cv.rectangle(img, min_loc, (min_loc[0] + h, min_loc[1] + w), 255, 2)
    cv.imshow('rect', rect)

 

其中求diff矩陣的函數cv.matchTemplate(img,temp,cv.TM_SQDIFF)的第三個參數有以下選擇:

  1.TM_SQDIFF: 計算平方誤差,值越小,越相關

  2.TM_CCORR: 計算相關性,計算出來的值越大,越相關

  3.TM_CCOFFF: 計算相關系數,計算出來的值越大,越相關

  4.TM_SQDIFF_NORMED: 計算歸一化的平方誤差,越接近0越相關

  5.TM_CCPRR_NORMED: 計算歸一化的相關性,越接近1,越相關

  6.TM_CCOFFF_NORMED: 計算歸一化的相關系數,越接近1,越相關


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM