­

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

前文傳送門:

「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_'

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

參考

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

//zhuanlan.zhihu.com/p/80362140