Python 影象處理 OpenCV (14):影象金字塔
阿新 • • 發佈:2020-07-13
![](https://cdn.geekdigging.com/opencv/opencv_header.png)
前文傳送門:
[「Python 影象處理 OpenCV (1):入門」](https://www.geekdigging.com/2020/05/17/5513454552/)
[「Python 影象處理 OpenCV (2):畫素處理與 Numpy 操作以及 Matplotlib 顯示影象」](https://www.geekdigging.com/2020/05/18/4936041986/)
[「Python 影象處理 OpenCV (3):影象屬性、影象感興趣 ROI 區域及通道處理」](https://www.geekdigging.com/2020/05/19/1227329671/)
[「Python 影象處理 OpenCV (4):影象算數運算以及修改顏色空間」](https://www.geekdigging.com/2020/05/21/1757913240/)
[「Python 影象處理 OpenCV (5):影象的幾何變換」](https://www.geekdigging.com/2020/05/23/4331122737/)
[「Python 影象處理 OpenCV (6):影象的閾值處理」](https://www.geekdigging.com/2020/06/03/6651375581/)
[「Python 影象處理 OpenCV (7):影象平滑(濾波)處理」](https://www.geekdigging.com/2020/06/06/8676263283/)
[「Python 影象處理 OpenCV (8):影象腐蝕與影象膨脹」](https://www.geekdigging.com/2020/06/08/5731186312/)
[「Python 影象處理 OpenCV (9):影象處理形態學開運算、閉運算以及梯度運算」](https://www.geekdigging.com/2020/06/11/5023174082/)
[「Python 影象處理 OpenCV (10):影象處理形態學之頂帽運算與黑帽運算」](https://www.geekdigging.com/2020/06/18/9182078666/)
[「Python 影象處理 OpenCV (11):Canny 運算元邊緣檢測技術」](https://www.geekdigging.com/2020/06/25/4009152544/)
[「Python 影象處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術」](https://www.geekdigging.com/2020/06/26/7999051794/)
[「Python 影象處理 OpenCV (13): Scharr 運算元和 LOG 運算元邊緣檢測技術」](https://www.geekdigging.com/2020/07/09/4977894293/)
## 引言
前面的文章中,我們有用過影象方法或者縮小的函式 `resize()` ,這個函式既可以放大影象,也可以縮小影象,其中:
* 縮小影象:一版使用 `CV_INETR_AREA` (區域插值)來插值。
* 放大影象,一般使用 `CV_INTER_LINEAR` (線性插值)來插值。
影象縮放除了可以使用函式 `resize()` ,還有另外的一種方式 —— 「影象金字塔」。
![](https://cdn.geekdigging.com/opencv/14/jinzita_header.jpg)
## 影象金字塔是什麼?
在說清楚什麼事影象金字塔之前,要先介紹另一個概念:「尺度」。
尺度:先從字面意思來看說的就是尺寸和解析度。
我們在進行影象處理的時候,會經常對源影象的尺寸進行放大或者縮小的變換,進而轉換為我們需要的尺寸的目標影象。
對影象進行放大和縮小的變換的這個過程,稱為尺度調整。
而影象金字塔則是影象多尺度調整表達的一種重要的方式。
影象金字塔是影象多尺度表達的一種,是一種以多解析度來解釋影象的有效但概念簡單的結構。一幅影象的金字塔是一系列以金字塔形狀排列的解析度逐步降低,且來源於同一張原始圖的影象集合。其通過梯次向下取樣獲得,直到達到某個終止條件才停止取樣。我們將一層一層的影象比喻成金字塔,層級越高,則影象越小,解析度越低。
![](https://cdn.geekdigging.com/opencv/14/tuxiang_jinzita_1.jpg)
影象金字塔方法的總體思想主要是是:將參加融合的的每幅影象分解為多尺度的金字塔影象序列,將低解析度的影象在上層,高解析度的影象在下層,上層影象的大小為前一層影象大小的 1/4 。層數為 0 , 1 , 2 …… N 。將所有影象的金字塔在相應層上以一定的規則融合,就可得到合成金字塔,再將該合成金字塔按照金字塔生成的逆過程進行重構,得到融合金字塔。
## 實現方式
通常而言,我們一般討論兩種影象金字塔:「高斯金字塔( Gaussian pyramid )」 和 「拉普拉斯金字塔( Laplacian pyramid )」 。
### 高斯金字塔( Gaussian pyramid )
高斯金字塔是由底部的最大解析度影象逐次向下取樣得到的一系列影象。最下面的影象解析度最高,越往上影象解析度越低。
![](https://cdn.geekdigging.com/opencv/14/gaussian_pyramind.jpg)
高斯金字塔向下取樣:
這個過程實際上就是一個重複高斯平滑並重新對影象取樣的過程。
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]
$$
2. 接下來是對影象進行取樣,這一步會去除影象中的偶數行和奇數列,從而得到一張影象。
3. 再然後是重複上面兩步,直到得到最終的目標影象為止。
從上面的步驟可以看出,再每次迴圈中,得到的結果影象只有原影象的 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` 的差異資訊。
![](https://cdn.geekdigging.com/opencv/14/lapulasi_jinzita.jpg)
下面這個公式是差異的記錄過程:
$$
L_i = G_i - Up(Down(G_i))
$$
## OpenCV 函式
OpenCV 為向上取樣和向下取樣提供了兩個函式: `pyrDown()` 和 `pyrUp()` 。
`pyrDown()` 的原函式如下:
```python
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()` 的原函式如下:
```python
def pyrUp(src, dst=None, dstsize=None, borderType=None)
```
* src: 表示輸入影象。
* dst: 表示輸出影象,它與src型別、大小相同。
* dstsize: 表示降取樣之後的目標影象的大小。
* borderType: 表示表示影象邊界的處理方式。
引數釋義和上面的 `pyrDown()` 保持一致。
下面是高斯金字塔和拉普拉斯金字塔的程式碼示例:
```python
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()
```
![](https://cdn.geekdigging.com/opencv/14/result.png)
上面這段程式有一點需要注意,我當前使用 `opencv-python` 的版本是 `4.3.0.36` ,理論上在向上取樣的過程中,目標大小隻需要滿足關係 `|dstsize.width - src.cols * 2| ≤ (dstsize.width mod 2)` 即可。
實際上經過測試,輸入影象是必須使用正方形,長方形的影象會直接爆出如下錯誤:
```shell
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/