仿射變換簡介
什么是放射變換
圖像上的仿射變換, 其實就是圖片中的一個像素點,通過某種變換,移動到另外一個地方。
從數學上來講, 就是一個向量空間進行一次線形變換並加上平移向量, 從而變換到另外一個向量空間的過程。
向量空間m : m=(x,y)
向量空間n : n=(x′,y′)
向量空間從m到n的變換 n=A∗m+b
整理得到:

將A跟b 組合在一起就組成了仿射矩陣 M。 它的維度是2∗3

使用不同的矩陣M,就獲得了不同的2D仿射變換效果。
在opencv中,實現2D仿射變換, 需要借助warpAffine
函數。
cv2.warpAffine(image, M, (image.shape[1], image.shape[0])
復制代碼
接下來,帶你結合具體的2D仿射變換,分析其變換矩陣。
圖像平移
公式推導
平移可以說是最簡單的一種空間變換。其表達式為:

其中(b0,b1) 是偏移量。
例程
如果是向右平移10個像素, 向下平移30個像素的話, 那么變換矩陣M可以為:

演示代碼
向右平移10個像素, 向下平移30個像素:
import cv2
import numpy as np
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 聲明變換矩陣 向右平移10個像素, 向下平移30個像素
M = np.float32([[1, 0, 10], [0, 1, 30]])
# 進行2D 仿射變換
shifted = cv2.warpAffine(img, M, (width, height))
cv2.imwrite('shift_right_10_down_30.jpg', shifted)
復制代碼
原始圖像:

向右平移10個像素, 向下平移30個像素圖像:

向左平移10個像素, 向上平移30個像素:
# 聲明變換矩陣 向左平移10個像素, 向上平移30個像素
M = np.float32([[1, 0, -10], [0, 1, -30]])
# 進行2D 仿射變換
shifted = cv2.warpAffine(img, M, (width, height))
cv2.imwrite('shift_right_-10_down_-30.jpg', shifted)
復制代碼
仿射變換圖像:

圖像平移v2
我們可以用translate
這個函數把這個操作封裝一下:
def translate(image, x, y):
M = np.float32([[1, 0, x], [0, 1, y]])
shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
return shifted
復制代碼
完成一些的代碼:
import cv2
import numpy as np
img = cv2.imread('cat.png')
def translate(image, x, y):
M = np.float32([[1, 0, x], [0, 1, y]])
shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
return shifted
shifted = translate(img, 10, 30)
cv2.imwrite('shift_right_10_down_30.png', shifted)
復制代碼
處理結果同上。。。
圖像旋轉
利用getRotationMatrix2D實現旋轉
opencv中getRotationMatrix2D
函數可以直接幫我們生成M
而不需要我們在程序里計算三角函數:
getRotationMatrix2D(center, angle, scale)
復制代碼
參數解析
center
旋轉中心點(cx, cy)
你可以隨意指定angle
旋轉的角度 單位是角度 逆時針方向為正方向 , 角度為正值代表逆時針scale
縮放倍數. 值等於1.0
代表尺寸不變
該函數返回的就是仿射變換矩陣M
示例代碼
import cv2
import numpy as np
# 獲取旋轉矩陣
rotateMatrix = cv2.getRotationMatrix2D((100, 200), 90, 1.0)
#設置numpy矩陣的打印格式
np.set_printoptions(precision=2,suppress=True)
print(rotateMatrix)
OUTPUT
[[ 0\. 1\. -100.]
[ -1\. 0\. 300.]]
復制代碼
為了使用方便, 你也可以封裝一下旋轉過程
def rotate(image, angle, center = None, scale = 1.0):
(h, w) = image.shape[:2]
if center is None:
center = (w / 2, h / 2)
M = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(image, M, (w, h))
return rotated
復制代碼
演示代碼
# -*- coding: utf-8 -*-
'''
圍繞原點處旋轉 (圖片左上角) 正方向為逆時針
利用getRotationMatrix2D函數生成仿射矩陣
'''
import numpy as np
import cv2
from math import cos,sin,radians
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
height, width, channel = img.shape
# 求得圖片中心點, 作為旋轉的軸心
cx = int(width / 2)
cy = int(height / 2)
# 旋轉的中心
center = (cx, cy)
new_dim = (width, height)
# 進行2D 仿射變換
# 圍繞原點 逆時針旋轉30度
M = cv2.getRotationMatrix2D(center=center,angle=30, scale=1.0)
rotated_30 = cv2.warpAffine(img, M, new_dim)
# 圍繞原點 逆時針旋轉30度
M = cv2.getRotationMatrix2D(center=center,angle=45, scale=1.0)
rotated_45 = cv2.warpAffine(img, M, new_dim)
# 圍繞原點 逆時針旋轉30度
M = cv2.getRotationMatrix2D(center=center,angle=60, scale=1.0)
rotated_60 = cv2.warpAffine(img, M, new_dim)
plt.subplot(221)
plt.title("Src Image")
plt.imshow(img[:,:,::-1])
plt.subplot(222)
plt.title("Rotated 30 Degree")
plt.imshow(rotated_30[:,:,::-1])
plt.subplot(223)
plt.title("Rotated 45 Degree")
plt.imshow(rotated_45[:,:,::-1])
plt.subplot(224)
plt.title("Rotated 60 Degree")
plt.imshow(rotated_60[:,:,::-1])
plt.show()
復制代碼
原始圖形:

圖像旋轉圖像(逆時針30度、45度、60度):

利用wrapAffine實現縮放
數學原理推導
圍繞原點進行旋轉


由此我們得出

所以對應的變換矩陣為

注意,這里我們進行公式推導的時候,參照的原點是在左下角, 而在OpenCV中圖像的原點在圖像的左上角, 所以我們在代碼里面對theta取反。
我們可以利用math
包中的三角函數。但是有一點需要注意 :三角函數輸入的角度是弧度制而不是角度制。
我們需要使用radians(x)
函數, 將角度轉變為弧度。
import math
math.radians(180)
3.141592653589793
復制代碼
代碼演示
# -*- coding: utf-8 -*-
'''
圍繞原點處旋轉 (圖片左上角) 正方向為逆時針
'''
import numpy as np
import cv2
import math
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
height, width, channel = img.shape
def getRotationMatrix2D(theta):
# 角度值轉換為弧度值
# 因為圖像的左上角是原點 需要×-1
theta = math.radians(-1*theta)
M = np.float32([
[math.cos(theta), -math.sin(theta), 0],
[math.sin(theta), math.cos(theta), 0]])
return M
# 進行2D 仿射變換
# 圍繞原點 順時針旋轉30度
M = getRotationMatrix2D(30)
rotated_30 = cv2.warpAffine(img, M, (width, height))
# 圍繞原點 順時針旋轉45度
M = getRotationMatrix2D(45)
rotated_45 = cv2.warpAffine(img, M, (width, height))
# 圍繞原點 順時針旋轉60度
M = getRotationMatrix2D(60)
rotated_60 = cv2.warpAffine(img, M, (width, height))
plt.subplot(221)
plt.title("Src Image")
plt.imshow(img[:,:,::-1])
plt.subplot(222)
plt.title("Rotated 30 Degree")
plt.imshow(rotated_30[:,:,::-1])
plt.subplot(223)
plt.title("Rotated 45 Degree")
plt.imshow(rotated_45[:,:,::-1])
plt.subplot(224)
plt.title("Rotated 60 Degree")
plt.imshow(rotated_60[:,:,::-1])
plt.show()
復制代碼
原始圖像:

旋轉之后演示圖:

圍繞任意點進行旋轉
數學原理推導
那么如何圍繞任意點進行旋轉呢?
可以先把當前的旋轉中心點平移到原點處, 在原點處旋轉后再平移回去。
假定旋轉中心為 (cx,cy)

其中

所以

代碼演示
# -*- coding: utf-8 -*-
'''
圍繞畫面中的任意一點旋轉
'''
import numpy as np
import cv2
from math import cos,sin,radians
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
height, width, channel = img.shape
theta = 45
def getRotationMatrix2D(theta, cx=0, cy=0):
# 角度值轉換為弧度值
# 因為圖像的左上角是原點 需要×-1
theta = radians(-1 * theta)
M = np.float32([
[cos(theta), -sin(theta), (1-cos(theta))*cx + sin(theta)*cy],
[sin(theta), cos(theta), -sin(theta)*cx + (1-cos(theta))*cy]])
return M
# 求得圖片中心點, 作為旋轉的軸心
cx = int(width / 2)
cy = int(height / 2)
# 進行2D 仿射變換
# 圍繞原點 逆時針旋轉30度
M = getRotationMatrix2D(30, cx=cx, cy=cy)
rotated_30 = cv2.warpAffine(img, M, (width, height))
# 圍繞原點 逆時針旋轉45度
M = getRotationMatrix2D(45, cx=cx, cy=cy)
rotated_45 = cv2.warpAffine(img, M, (width, height))
# 圍繞原點 逆時針旋轉60度
M = getRotationMatrix2D(60, cx=cx, cy=cy)
rotated_60 = cv2.warpAffine(img, M, (width, height))
plt.subplot(221)
plt.title("Src Image")
plt.imshow(img[:,:,::-1])
plt.subplot(222)
plt.title("Rotated 30 Degree")
plt.imshow(rotated_30[:,:,::-1])
plt.subplot(223)
plt.title("Rotated 45 Degree")
plt.imshow(rotated_45[:,:,::-1])
plt.subplot(224)
plt.title("Rotated 60 Degree")
plt.imshow(rotated_60[:,:,::-1])
plt.show()
復制代碼
旋轉效果:
圍繞圖片中心點旋轉30度至60度

圖像縮放
利用resize函數實現縮放
opencv其實有專門進行圖像縮放的函數resize
。
resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
復制代碼
參數解析
src
輸入圖片dsize
輸出圖片的尺寸dst
輸出圖片fx
x軸的縮放因子fy
y軸的縮放因子interpolation
插值方式INTER_NEAREST
- 最近鄰插值INTER_LINEAR
- 線性插值(默認)INTER_AREA
- 區域插值INTER_CUBIC
- 三次樣條插值INTER_LANCZOS4
- Lanczos插值
在使用的時候, 我們可以傳入指定的圖片的尺寸dsize
'''
使用resize函數對圖像進行縮放
'''
import cv2
import numpy as np
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 聲明新的維度
new_dimension = (400, 400)
# 指定新圖片的維度與插值算法(interpolation)
resized = cv2.resize(img, new_dimension)
cv2.imwrite('lena_resize_400_400.png', resized)
復制代碼
原始圖像:

縮放后的圖像:

或者指定縮放因子fx,fy
將dsize
設置為 None
, 然后指定fx fy
import cv2
import numpy as np
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 指定新圖片的維度與插值算法(interpolation)
resized = cv2.resize(img, None, fx=1.5, fy=2)
cv2.imwrite('lena_resize_fx_fy.jpg', resized)
復制代碼
運行結果如下:

或者指定輸出圖片,並傳入輸出圖片的size:
'''
根據fx跟fy進行圖像縮放
'''
import cv2
import numpy as np
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 指定輸出圖片
dst = np.zeros((100, 100, 3), dtype='uint8')
# 指定新圖片的維度與插值算法(interpolation)
cv2.resize(img, dst=dst, dsize=(dst.shape[1], dst.shape[0]), fx=1.5, fy=2)
cv2.imwrite('lena_resize_from_dst.jpg', dst)
復制代碼
運行結果如下:

更詳細的使用說明見opencv-resize 文檔
為了方便使用, 我們也可以將其封裝成函數
def resize(image, width = None, height = None, inter = cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
if height is None:
r = width / float(w)
dim = (width, int(h * r))
if width and height:
dim = (width, height)
resized = cv2.resize(image, dim, interpolation = inter)
return resized
復制代碼
分辨率 從 5 5 放大到 1000 1000, 選擇不同的插值算法,對應的演示效果:
'''
差值算法對比
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = np.uint8(np.random.randint(0,255,size=(5,5)))
height,width= img.shape
# 聲明新的維度
new_dimension = (1000, 1000)
plt.subplot(231)
plt.title("SRC Image")
plt.imshow(img,cmap='seismic')
plt.subplot(232)
resized = cv2.resize(img, new_dimension, interpolation = cv2.INTER_NEAREST)
plt.title("INTER_NEAREST")
plt.imshow(resized,cmap='seismic')
plt.subplot(233)
resized = cv2.resize(img, new_dimension, interpolation = cv2.INTER_LINEAR)
plt.title("INTER_LINEAR")
plt.imshow(resized,cmap='seismic')
plt.subplot(234)
resized = cv2.resize(img, new_dimension, interpolation = cv2.INTER_AREA)
plt.title("INTER_AREA")
plt.imshow(resized,cmap='seismic')
plt.subplot(235)
resized = cv2.resize(img, new_dimension, interpolation = cv2.INTER_CUBIC)
plt.title("INTER_CUBIC")
plt.imshow(resized,cmap='seismic')
plt.subplot(236)
resized = cv2.resize(img, new_dimension, interpolation = cv2.INTER_LANCZOS4)
plt.title("INTER_LANCZOS4")
plt.imshow(resized,cmap='seismic')
plt.show()
復制代碼


利用wrapAffine實現縮放
數學原理
對圖像的伸縮變換的變換矩陣M為

其中,
fx
:代表x軸的焦距(縮放因子)
fy
:代表y軸的焦距(縮放因子)
則可以得出以下式子:

具體代碼演示
源代碼:
'''
使用仿射矩陣實現
'''
import numpy as np
import cv2
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# x軸焦距 1.5倍
fx = 1.5
# y軸焦距 2倍
fy = 2
# 聲明變換矩陣 向右平移10個像素, 向下平移30個像素
M = np.float32([[fx, 0, 0], [0, fy, 0]])
# 進行2D 仿射變換
resized = cv2.warpAffine(img, M, (int(width*fx), int(height*fy)))
cv2.imwrite('resize_raw.jpg', resized)
復制代碼
運行效果:
原始圖像:


我們 利用random 模塊生成一個5×5的隨機矩陣。
# 生成一個隨機噪點
img = np.uint8(np.random.randint(0,255,size=(5,5)))
復制代碼
源代碼:
'''
仿射矩陣實現縮放 fx,fy
'''
import numpy as np
import cv2
from matplotlib import pyplot as plt
# 生成一個隨機噪點
img = np.uint8(np.random.randint(0,255,size=(5,5)))
height,width = img.shape
# x軸焦距 1.5倍
fx = 1.5
# y軸焦距 2倍
fy = 2
# 聲明變換矩陣 向右平移10個像素, 向下平移30個像素
M = np.float32([[fx, 0, 0], [0, fy, 0]])
# 進行2D 仿射變換
resized = cv2.warpAffine(img, M, (int(width*fx), int(height*fy)))
print(img)
print(resized)
# 數據可視化
plt.subplot(121)
plt.imshow(img, cmap="gray")
plt.subplot(122)
plt.imshow(resized,cmap="gray")
plt.show()
復制代碼
原圖:
[[224 25 25 165 16]
[ 37 170 114 16 101]
[181 5 7 94 41]
[206 167 23 133 115]
[217 115 154 97 65]]
復制代碼
縮放后:
[[224 93 25 25 117 114 16]
[131 109 88 70 83 80 59]
[ 37 124 151 114 50 45 101]
[109 95 78 61 57 61 71]
[181 66 6 7 64 76 41]
[194 123 62 15 80 101 78]
[206 180 118 23 95 127 115]
[212 165 123 89 106 106 90]
[217 150 128 154 117 86 65]
[109 75 64 77 58 43 33]]
復制代碼
為了更加直觀的感受, 我們可以進行數據可視化。
我們使用matplotlib
進行繪制 resize前與resize之后的圖片。

圖像翻轉
使用flip函數實現翻轉
flip 函數原型
flip(src, flipCode[, dst]) -> dst
復制代碼
參數解析
src
輸入圖片flipCode
翻轉代碼1
水平翻轉 Horizontally (圖片第二維度是column)0
垂直翻轉 Vertically (圖片第一維是row)-1
同時水平翻轉與垂直反轉 Horizontally & Vertically
為了方便使用, 你也可以封裝成下面的函數
def flip(image, direction):
if direction == "h":
flipped = cv2.flip(image, 1)
elif direction == "v":
flipped = cv2.flip(image, 0)
else:
# both horizontally and vertically
flipped = cv2.flip(image, -1)
復制代碼
具體源碼及效果展示
'''
反轉Demo
'''
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
def bgr2rbg(img):
'''
將顏色空間從BGR轉換為RBG
'''
return img[:,:,::-1]
# 水平翻轉
flip_h = cv2.flip(img, 1)
# 垂直翻轉
flip_v = cv2.flip(img, 0)
# 同時水平翻轉與垂直翻轉
flip_hv = cv2.flip(img, -1)
plt.subplot(221)
plt.title('SRC')
plt.imshow(bgr2rbg(img))
plt.subplot(222)
plt.title('Horizontally')
plt.imshow(bgr2rbg(flip_h))
plt.subplot(223)
plt.title('Vertically')
plt.imshow(bgr2rbg(flip_v))
plt.subplot(224)
plt.title('Horizontally & Vertically')
plt.imshow(bgr2rbg(flip_hv))
plt.show()
復制代碼

利用numpy的索引實現翻轉
利用numpy
中ndarray
的索引, 我們可以非常方便地實現圖像翻轉。
# 水平翻轉
flip_h = img[:,::-1]
# 垂直翻轉
flip_v = img[::-1]
# 水平垂直同時翻轉
flip_hv = img[::-1, ::-1]
復制代碼
具體源碼及效果展示
'''
使用numpy的索引進行圖像反轉
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 水平翻轉
flip_h = img[:,::-1]
# 垂直翻轉
flip_v = img[::-1]
# 水平垂直同時翻轉
flip_hv = img[::-1, ::-1]
def bgr2rbg(img):
'''
將顏色空間從BGR轉換為RBG
'''
return img[:,:,::-1]
plt.subplot(221)
plt.title('SRC')
plt.imshow(bgr2rbg(img))
plt.subplot(222)
plt.title('Horizontally')
plt.imshow(bgr2rbg(flip_h))
plt.subplot(223)
plt.title('Vertically')
plt.imshow(bgr2rbg(flip_v))
plt.subplot(224)
plt.title('Horizontally & Vertically')
plt.imshow(bgr2rbg(flip_hv))
plt.show()
12345678910111213141516171819202122232425262728293031323334353637383940414243
復制代碼

利用wrapAffine實現翻轉
圖像翻轉的數學原理
注:
width
代表圖像的寬度;height
代表圖像的高度
水平翻轉的變換矩陣

垂直翻轉的變換矩陣

同時進行水平翻轉與垂直翻轉

具體源碼及效果展示
'''
使用仿射矩陣實現反轉
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('lena1.jpg')
height,width,channel = img.shape
# 水平翻轉
M1 = np.float32([[-1, 0, width], [0, 1, 0]])
flip_h = cv2.warpAffine(img, M1, (width, height))
# 垂直翻轉
M2 = np.float32([[1, 0, 0], [0, -1, height]])
flip_v = cv2.warpAffine(img, M2, (width, height))
# 水平垂直同時翻轉
M3 = np.float32([[-1, 0, width], [0, -1, height]])
flip_hv = cv2.warpAffine(img, M3, (width, height))
def bgr2rbg(img):
'''
將顏色空間從BGR轉換為RBG
'''
return img[:,:,::-1]
plt.subplot(221)
plt.title('SRC')
plt.imshow(bgr2rbg(img))
plt.subplot(222)
plt.title('Horizontally')
plt.imshow(bgr2rbg(flip_h))
plt.subplot(223)
plt.title('Vertically')
plt.imshow(bgr2rbg(flip_v))
plt.subplot(224)
plt.title('Horizontally & Vertically')
plt.imshow(bgr2rbg(flip_hv))
plt.show()
復制代碼
