Python 影像處理 OpenCV (14):影像金字塔

極客挖掘機發表於2020-07-13

前文傳送門:

「Python 影像處理 OpenCV (1):入門」

「Python 影像處理 OpenCV (2):畫素處理與 Numpy 操作以及 Matplotlib 顯示影像」

「Python 影像處理 OpenCV (3):影像屬性、影像感興趣 ROI 區域及通道處理」

「Python 影像處理 OpenCV (4):影像算數運算以及修改顏色空間」

「Python 影像處理 OpenCV (5):影像的幾何變換」

「Python 影像處理 OpenCV (6):影像的閾值處理」

「Python 影像處理 OpenCV (7):影像平滑(濾波)處理」

「Python 影像處理 OpenCV (8):影像腐蝕與影像膨脹」

「Python 影像處理 OpenCV (9):影像處理形態學開運算、閉運算以及梯度運算」

「Python 影像處理 OpenCV (10):影像處理形態學之頂帽運算與黑帽運算」

「Python 影像處理 OpenCV (11):Canny 運算元邊緣檢測技術」

「Python 影像處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術」

「Python 影像處理 OpenCV (13): Scharr 運算元和 LOG 運算元邊緣檢測技術」

引言

前面的文章中,我們有用過影像方法或者縮小的函式 resize() ,這個函式既可以放大影像,也可以縮小影像,其中:

  • 縮小影像:一版使用 CV_INETR_AREA (區域插值)來插值。
  • 放大影像,一般使用 CV_INTER_LINEAR (線性插值)來插值。

影像縮放除了可以使用函式 resize() ,還有另外的一種方式 —— 「影像金字塔」。

影像金字塔是什麼?

在說清楚什麼事影像金字塔之前,要先介紹另一個概念:「尺度」。

尺度:先從字面意思來看說的就是尺寸和解析度。

我們在進行影像處理的時候,會經常對源影像的尺寸進行放大或者縮小的變換,進而轉換為我們需要的尺寸的目標影像。

對影像進行放大和縮小的變換的這個過程,稱為尺度調整。

而影像金字塔則是影像多尺度調整表達的一種重要的方式。

影像金字塔是影像多尺度表達的一種,是一種以多解析度來解釋影像的有效但概念簡單的結構。一幅影像的金字塔是一系列以金字塔形狀排列的解析度逐步降低,且來源於同一張原始圖的影像集合。其通過梯次向下取樣獲得,直到達到某個終止條件才停止取樣。我們將一層一層的影像比喻成金字塔,層級越高,則影像越小,解析度越低。

影像金字塔方法的總體思想主要是是:將參加融合的的每幅影像分解為多尺度的金字塔影像序列,將低解析度的影像在上層,高解析度的影像在下層,上層影像的大小為前一層影像大小的 1/4 。層數為 0 , 1 , 2 …… N 。將所有影像的金字塔在相應層上以一定的規則融合,就可得到合成金字塔,再將該合成金字塔按照金字塔生成的逆過程進行重構,得到融合金字塔。

實現方式

通常而言,我們一般討論兩種影像金字塔:「高斯金字塔( Gaussian pyramid )」 和 「拉普拉斯金字塔( Laplacian pyramid )」 。

高斯金字塔( Gaussian pyramid )

高斯金字塔是由底部的最大解析度影像逐次向下取樣得到的一系列影像。最下面的影像解析度最高,越往上影像解析度越低。

高斯金字塔向下取樣:

這個過程實際上就是一個重複高斯平滑並重新對影像取樣的過程。

  1. 對於原始影像先進行一次高斯平滑處理,使用高斯核(5 * 5)進行一次卷積處理。下面是 5 * 5 的高斯核。

\[K = \frac{1}{125} \left[ \begin{matrix} 1 & 4 & 6 & 4 & 1\\ 4 & 16 & 24 & 16 & 4\\ 6 & 24 & 36 & 24 & 6\\ 4 & 16 & 24 & 16 & 4\\ 1 & 4 & 6 & 4 & 1\\ \end{matrix} \right] \]
  1. 接下來是對影像進行取樣,這一步會去除影像中的偶數行和奇數列,從而得到一張影像。
  2. 再然後是重複上面兩步,直到得到最終的目標影像為止。

從上面的步驟可以看出,再每次迴圈中,得到的結果影像只有原影像的 1/4 大小(橫縱向均做隔行取樣)。

注意:向下取樣會逐漸丟失影像資訊,屬於非線性的處理,此過程不可逆,屬於有損處理。

高斯金字塔向上取樣:

  1. 將影像在每個方向擴大為原來的兩倍,新增的行和列以 0 填充。
  2. 使用高斯核(5 * 5)對得到的影像進行一次高斯平滑處理,獲得 「新增畫素」的近似值。

注意:此過程與向下取樣的過程一樣,屬於非線性處理,無法逆轉,屬於有損處理。

此過程得到的影像為放大後的影像,與原圖相比會比較模糊,因為在縮放的過程中丟失了一些影像資訊,如果想在縮小和放大整個過程中減少資訊的丟失。

如果在縮放過程中想要減少影像資訊的丟失,這就引出了第二個影像金字塔 —— 「拉普拉斯金字塔」 。

拉普拉斯金字塔( Laplacian pyramid )

拉普拉斯金字塔可以認為是殘差金字塔,用來儲存下采樣後圖片與原始圖片的差異。

上面我們介紹了基於高斯金字塔,一個原始影像 Gi ,先進行向下取樣得到 G(i-1) ,再對 G(i-1) 進行向上取樣得到 Up(Down(Gi)) ,最終得到的 Up(Down(Gi)) 與原始的 Gi 是存在差異的。

這是因為向下取樣丟失的資訊並不能由向上取樣來進行恢復,高斯金字塔是一種有損的取樣方式。

如果我們想要完全恢復原始影像,那麼我們在進行取樣的時候就需要保留差異資訊。

這就是拉普拉斯金字塔的核心思想,每次向下取樣後,將再次向上取樣,得到向上取樣的 Up(Down(Gi)) 後,記錄 Up(Down(Gi))Gi 的差異資訊。

下面這個公式是差異的記錄過程:

\[L_i = G_i - Up(Down(G_i)) \]

OpenCV 函式

OpenCV 為向上取樣和向下取樣提供了兩個函式: pyrDown()pyrUp()

pyrDown() 的原函式如下:

def pyrDown(src, dst=None, dstsize=None, borderType=None)
  • src: 表示輸入影像。
  • dst: 表示輸出影像,它與src型別、大小相同。
  • dstsize: 表示降取樣之後的目標影像的大小。
  • borderType: 表示表示影像邊界的處理方式。

注意:dstsize 引數是有預設值的,呼叫函式的時候不指定第三個引數,那麼這個值是按照 Size((src.cols+1)/2, (src.rows+1)/2) 計算的。而且不管如何指定這個引數,一定必須保證滿足以下關係式:|dstsize.width * 2 - src.cols| ≤ 2; |dstsize.height * 2 - src.rows| ≤ 2。也就是說降取樣的意思其實是把影像的尺寸縮減一半,行和列同時縮減一半。

pyrUp() 的原函式如下:

def pyrUp(src, dst=None, dstsize=None, borderType=None)
  • src: 表示輸入影像。
  • dst: 表示輸出影像,它與src型別、大小相同。
  • dstsize: 表示降取樣之後的目標影像的大小。
  • borderType: 表示表示影像邊界的處理方式。

引數釋義和上面的 pyrDown() 保持一致。

下面是高斯金字塔和拉普拉斯金字塔的程式碼示例:

import cv2 as cv

#高斯金字塔
def gaussian_pyramid(image):
    level = 3      #設定金字塔的層數為3
    temp = image.copy()  #拷貝影像
    gaussian_images = []  #建立一個空列表
    for i in range(level):
        dst = cv.pyrDown(temp)   #先對影像進行高斯平滑,然後再進行降取樣(將影像尺寸行和列方向縮減一半)
        gaussian_images.append(dst)  #在列表末尾新增新的物件
        cv.imshow("gaussian"+str(i), dst)
        temp = dst.copy()
    return gaussian_images


#拉普拉斯金字塔
def laplacian_pyramid(image):
    gaussian_images = gaussian_pyramid(image)    #做拉普拉斯金字塔必須用到高斯金字塔的結果
    level = len(gaussian_images)
    for i in range(level-1, -1, -1):
        if (i-1) < 0:
            expand = cv.pyrUp(gaussian_images[i], dstsize = image.shape[:2])
            laplacian = cv.subtract(image, expand)
            # 展示差值影像
            cv.imshow("laplacian_down_"+str(i), laplacian)
        else:
            expand = cv.pyrUp(gaussian_images[i], dstsize = gaussian_images[i-1].shape[:2])
            laplacian = cv.subtract(gaussian_images[i-1], expand)
            # 展示差值影像
            cv.imshow("laplacian_down_"+str(i), laplacian)


src = cv.imread('maliao.jpg')
print(src.shape)
# 先將影像轉化成正方形,否則會報錯
input_image = cv.resize(src, (560, 560))
# 設定為 WINDOW_NORMAL 可以任意縮放
cv.namedWindow('input_image', cv.WINDOW_AUTOSIZE)
cv.imshow('input_image', src)
laplacian_pyramid(src)
cv.waitKey(0)
cv.destroyAllWindows()

上面這段程式有一點需要注意,我當前使用 opencv-python 的版本是 4.3.0.36 ,理論上在向上取樣的過程中,目標大小隻需要滿足關係 |dstsize.width - src.cols * 2| ≤ (dstsize.width mod 2) 即可。

實際上經過測試,輸入影像是必須使用正方形,長方形的影像會直接爆出如下錯誤:

error: (-215:Assertion failed) std::abs(dsize.width - ssize.width*2) == dsize.width % 2 && std::abs(dsize.height - ssize.height*2) == dsize.height % 2 in function 'cv::pyrUp_'

具體原因並沒有想通,希望哪位知道的大佬可以解釋下。

參考

https://blog.csdn.net/zhu_hongji/article/details/81536820

https://zhuanlan.zhihu.com/p/80362140

相關文章