Python OpenCV 365 天學習計劃,與橡皮擦一起進入影像領域吧,本篇博客是這個系列的第 42 篇,
該系列文章導航參考:https://blog.csdn.net/hihell/category_10688961.html
Python OpenCV
- 基礎知識鋪墊
- 影像的雙線性插值演算法
- 橡皮擦的小節
基礎知識鋪墊
本篇博客實作雙線性插值演算法的撰寫,順便修改一下 上篇博客 最近鄰插值演算法最后實作與 OpenCV 提供的內置引數不一致問題,
還有一個問題,是執行速度問題,該問題一并在學習雙線性插值演算法之后解決,
影像的雙線性插值演算法
雙線性內插值演算法是一種比較好的影像縮放演算法,它利用了源影像中虛擬點四周四個真實存在的像素值,依據權重來決定目標圖中的一個像素值,
先摘抄一些原理性的描述:
對于一個目標像素,通過反向變換可以得到源影像的虛擬坐標,大概率是浮點坐標,格式為(i+u,j+v),其中 i、j 為整數部分,u、v 為小數部分,取值 [0,1),這時在源影像中 (i+u,j+v) 可以由周邊的四個像素坐標 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1) 計算獲得,也就是存在公式:
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)
這一步的變換被省略了很多內容,橡皮擦也是查閱了很多資料,接下來為你補充上,
先畫一張輔助理解的圖~

首先在 X 方向上進行兩次線性插值計算,然后在 Y 方向上進行一次插值計算,
在計算之前,又要補充知識了,叫做線性插值,已知資料 ( x 0 , y 0 ) (x_0,y_0) (x0?,y0?) 和 ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?),要計算 [ x 0 , x 1 ] [x_0,x_1] [x0?,x1?] 區間內某一位置 x 在直線上的 y 值,公式如下:
y ? y 0 x ? x 0 = y 1 ? y 0 x 1 ? x 0 \cfrac{y-y_0}{x-x_0}=\cfrac{y_1-y_0}{x_1-x_0} x?x0?y?y0??=x1??x0?y1??y0??
公式進行變形得到:
y = x 1 ? x x 1 ? x 0 y 0 + x ? x 0 x 1 ? x 0 y 1 y=\cfrac{x_1-x}{x_1-x_0}y_0+\cfrac{x-x_0}{x_1-x_0}y_1 y=x1??x0?x1??x?y0?+x1??x0?x?x0??y1?
變換之后大概等用 x 0 x_0 x0? 和 x 1 x_1 x1? 的距離作為一個權重,用于 y 0 y_0 y0? 和 y 1 y_1 y1? 的加權,雙線性插值就是在兩個方向上做線性插值,
繼續看上圖,在點 1 與點 2 區間內尋找一點,依據公式可得:
f ( 插 值 點 1 ) ≈ x 2 ? x x 2 ? x 1 f ( 點 1 ) + x ? x 1 x 2 ? x 1 f ( 點 2 ) f(插值點1)\approx\cfrac{x_2-x}{x_2-x_1}f(點1)+\cfrac{x-x_1}{x_2-x_1}f(點2) f(插值點1)≈x2??x1?x2??x?f(點1)+x2??x1?x?x1??f(點2) 其中插值點 1 = ( x , y 1 ) (x,y_1) (x,y1?)
同樣的演算法獲取插值點 2:
f ( 插 值 點 2 ) ≈ x 2 ? x x 2 ? x 1 f ( 點 3 ) + x ? x 1 x 2 ? x 1 f ( 點 4 ) f(插值點2)\approx\cfrac{x_2-x}{x_2-x_1}f(點3)+\cfrac{x-x_1}{x_2-x_1}f(點4) f(插值點2)≈x2??x1?x2??x?f(點3)+x2??x1?x?x1??f(點4) 其中插值點 2 = ( x , y 2 ) (x,y_2) (x,y2?)
接下來在 Y 方向進行線性插值計算:
f ( P ) ≈ y 2 ? y y 2 ? y 1 f ( 插 值 點 1 ) + y ? y 1 y 2 ? y 1 f ( 插 值 點 2 ) f(P)\approx\cfrac{y_2-y}{y_2-y_1}f(插值點1)+\cfrac{y-y_1}{y_2-y_1}f(插值點2) f(P)≈y2??y1?y2??y?f(插值點1)+y2??y1?y?y1??f(插值點2)
將上述式子展開,就可以得到最后的結果了,這個沒多少難度,寫的時候與看的時候都仔細點就好:
f
(
x
,
y
)
≈
f
(
點
1
)
(
x
2
?
x
)
(
y
2
?
y
)
(
x
2
?
x
1
)
(
y
2
?
y
1
)
+
f
(
點
2
)
(
x
?
x
1
)
(
y
2
?
y
)
(
x
2
?
x
1
)
(
y
2
?
y
1
)
+
f
(
點
3
)
(
x
2
?
x
)
(
y
?
y
1
)
(
x
2
?
x
1
)
(
y
2
?
y
1
)
+
f
(
點
4
)
(
x
?
x
1
)
(
y
?
y
1
)
(
x
2
?
x
1
)
(
y
2
?
y
1
)
f(x,y)\approx\cfrac{f(點1)(x_2-x)(y_2-y)}{(x_2-x_1)(y_2-y_1)}+\cfrac{f(點2)(x-x_1)(y_2-y)}{(x_2-x_1)(y_2-y_1)}+\cfrac{f(點3)(x_2-x)(y-y_1)}{(x_2-x_1)(y_2-y_1)}+\cfrac{f(點4)(x-x_1)(y-y_1)}{(x_2-x_1)(y_2-y_1)}
f(x,y)≈(x2??x1?)(y2??y1?)f(點1)(x2??x)(y2??y)?+(x2??x1?)(y2??y1?)f(點2)(x?x1?)(y2??y)?+(x2??x1?)(y2??y1?)f(點3)(x2??x)(y?y1?)?+(x2??x1?)(y2??y1?)f(點4)(x?x1?)(y?y1?)?
該式子可以進一步的簡化,因為兩個相鄰點插值是 1,所以簡化如下:
f
(
x
,
y
)
≈
f
(
點
1
)
(
x
2
?
x
)
(
y
2
?
y
)
+
f
(
點
2
)
(
x
?
x
1
)
(
y
2
?
y
)
+
f
(
點
3
)
(
x
2
?
x
)
(
y
?
y
1
)
+
f
(
點
4
)
(
x
?
x
1
)
(
y
?
y
1
)
f(x,y)\approx f(點1)(x_2-x)(y_2-y)+f(點2)(x-x_1)(y_2-y)+f(點3)(x_2-x)(y-y_1)+f(點4)(x-x_1)(y-y_1)
f(x,y)≈f(點1)(x2??x)(y2??y)+f(點2)(x?x1?)(y2??y)+f(點3)(x2??x)(y?y1?)+f(點4)(x?x1?)(y?y1?)
在將所有點的坐標帶入
f
(
x
,
y
)
≈
f
(
x
1
,
y
1
)
(
x
2
?
x
)
(
y
2
?
y
)
+
f
(
x
2
,
y
1
)
(
x
?
x
1
)
(
y
2
?
y
)
+
f
(
x
1
,
y
2
)
(
x
2
?
x
)
(
y
?
y
1
)
+
f
(
x
2
,
y
2
)
(
x
?
x
1
)
(
y
?
y
1
)
f(x,y)\approx f(x_1,y_1)(x_2-x)(y_2-y)+f(x_2,y_1)(x-x_1)(y_2-y)+f(x_1,y_2)(x_2-x)(y-y_1)+f(x_2,y_2)(x-x_1)(y-y_1)
f(x,y)≈f(x1?,y1?)(x2??x)(y2??y)+f(x2?,y1?)(x?x1?)(y2??y)+f(x1?,y2?)(x2??x)(y?y1?)+f(x2?,y2?)(x?x1?)(y?y1?)
將 (x,y) 替換成最開始的寫法 (i+u,j+v) ,其他的坐標分別為 點 1~點 4 分別為:(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1) ,帶入上述公式,變化結果如所示:
f
(
i
+
u
,
j
+
v
)
≈
f
(
i
,
j
)
(
i
+
1
?
(
i
+
u
)
)
(
j
+
1
?
(
j
+
v
)
)
+
f
(
i
+
1
,
j
)
(
i
+
u
?
i
)
(
j
+
1
?
(
j
+
v
)
)
+
f
(
i
,
j
+
1
)
(
i
+
1
?
(
i
+
u
)
)
(
j
+
v
?
j
)
+
f
(
i
+
1
,
j
+
1
)
(
i
+
u
?
i
)
(
j
+
v
?
j
)
f(i+u,j+v)\approx f(i,j)(i+1-(i+u))(j+1-(j+v))+f(i+1,j)(i+u-i)(j+1-(j+v))+f(i,j+1)(i+1-(i+u))(j+v-j)+f(i+1,j+1)(i+u-i)(j+v-j)
f(i+u,j+v)≈f(i,j)(i+1?(i+u))(j+1?(j+v))+f(i+1,j)(i+u?i)(j+1?(j+v))+f(i,j+1)(i+1?(i+u))(j+v?j)+f(i+1,j+1)(i+u?i)(j+v?j)
別暈,估計這是全網最清晰的轉換方式了:
f
(
i
+
u
,
j
+
v
)
≈
f
(
i
,
j
)
(
1
?
u
)
(
1
?
v
)
+
f
(
i
+
1
,
j
)
u
(
1
?
v
)
+
f
(
i
,
j
+
1
)
(
1
?
u
)
v
+
f
(
i
+
1
,
j
+
1
)
u
v
f(i+u,j+v)\approx f(i,j)(1-u)(1-v)+f(i+1,j)u(1-v)+f(i,j+1)(1-u)v+f(i+1,j+1)uv
f(i+u,j+v)≈f(i,j)(1?u)(1?v)+f(i+1,j)u(1?v)+f(i,j+1)(1?u)v+f(i+1,j+1)uv
到這里就與本篇博客最開始的公式呼應上了,
所以通過目標影像反推出來的一點,可以通過四個點的坐標進行計算,每個坐標前面的叫做權重,假設存在這樣一個像素坐標為 (1,1),反推在源圖中得到的坐標是 (0.75,0.75),由于影像中不可能存在浮點坐標,所以獲取周圍四個坐標分別是 (0,0)(0,1)(1,0)(1,1),由于 (0.75,0.75) 距離 (1,1) 最近,所以 (1,1) 點對該像素顏色作用最大,相應的 (1,1) 點對應的點是 f(i+1,i+1) ,該變數前面的系數權重為 0.75*0.75 ,結果最大,這個說明是通過真實的資料去說明,
拿到計算方式之后,就可以通過代碼實作雙線性插值演算法了,
先通過內置的縮放函式,測驗一下運行時間:
if __name__ == '__main__':
src = cv2.imread('./t.png')
start = time.time()
dst = cv2.resize(src, (600, 600))
print('內置函式運行時間:%f' % (time.time() - start))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
得到的時間為 內置函式運行時間:0.002000 ,非常快,
接下來就是自寫函式驗證了,代碼的說明我寫在了注釋中,你可以研究一下,注意公式的運用
import cv2
import numpy as np
import time
def resize_demo(src, new_size):
# 目標影像寬高
dst_h, dst_w = new_size
# 源影像寬高
src_h, src_w = src.shape[:2]
# 如果影像大小一致,直接復制回傳即可
if src_h == dst_h and src_w == dst_w:
return src.copy()
# 計算縮放比例
scale_x = float(src_w) / dst_w
scale_y = float(src_h) / dst_h
# 遍歷目標影像
dst = np.zeros((dst_h, dst_w, 3), dtype=np.uint8)
# return dst
# 對通道進行回圈
# for n in range(3):
# 對 height 回圈
for dst_y in range(dst_h):
# 對 width 回圈
for dst_x in range(dst_w):
# 目標在源上的坐標
src_x = dst_x * scale_x
src_y = dst_y * scale_y
# 計算在源圖上 4 個近鄰點的位置
# i,j
i = int(np.floor(src_x))
j = int(np.floor(src_y))
u = src_x-i
v = src_y-j
if j == src_h-1:
j = src_h-2
if i == src_w-1:
i = src_h-2
# f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)
dst[dst_y, dst_x] = (1-u)*(1-v)*src[j, i]+u*(1-v) * \
src[j+1, i] + (1-u)*v*src[j, i+1]+u*v*src[j+1, i+1]
# dst[dst_y, dst_x] = 0.25*src[j, i]+0.25 * \
# src[j+1, i] + 0.25*src[j, i+1]+0.25*src[j+1, i+1]
# dst[dst_y,dst_x,n] = 255
return dst
if __name__ == '__main__':
src = cv2.imread('./t.png')
start = time.time()
dst = resize_demo(src, (500, 600))
print('自寫函式運行時間:%f' % (time.time() - start))
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
代碼運行消耗了 2s 多,確實比較費時間,
橡皮擦的小節
希望今天的 1 個小時你有所識訓,我們下篇博客見~
相關閱讀
技術專欄
- Python 爬蟲 100 例教程,超棒的爬蟲教程,立即訂閱吧
- Python 爬蟲小課,精彩 9 講
今天是持續寫作的第 84 / 100 天,
如果你想跟博主建立親密關系,可以關注同名公眾號 夢想橡皮擦,近距離接觸一個逗趣的互聯網高級網蟲,
博主 ID:夢想橡皮擦,希望大家點贊、評論、收藏,
CSDN認證博客專家
高級產品經理
互聯網從業者
業余編程愛好者
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/260962.html
標籤:AI
下一篇:明年,我要用 AI 給全村寫對聯
