图像的几何变换—平移、旋转、镜像、缩放、剪切(原理+调用函数+像素操作)

一、平移

1.调用函数(平移矩阵)

图像平移后的坐标:
[ x ′ y ′ 1 ] = [ 1 0 Δ x 0 1 Δ y 0 0 1 ] ⋅ [ x y 1 ] \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} =\begin{bmatrix} 1&0&\Delta x \\ 0&1&\Delta y \\ 0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} xy1 = 100010ΔxΔy1 xy1
{ x ′ = x + Δ x y ′ = y + Δ y \begin{cases} x' =x + \Delta x \\ y' =y + \Delta y \end{cases} {x=x+Δxy=y+Δy

warpAffine函数用法参考这里:warpAffine函数

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 读取图像
img1 = cv2.imread("flower.jpeg")

# 图像平移
h, w = img1.shape[:2]
M = np.float32([[1, 0, 100], [0, 1, 50]]) # 平移矩阵,y方向向下平移50,x方向向右平移100
dst = cv2.warpAffine(img1, M, (w, h))

# 图像显示
fig, axes = plt.subplots(1, 2, figsize=(10, 8), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(dst[:, :, ::-1])
axes[1].set_title("after translation")

plt.show()

在这里插入图片描述

2.像素操作(遍历赋值)

       算法思想很简单,首先将所有像素点沿x方向向右平移100,一行一行的进行处理,将(0,100)处的彩色值赋值给(0,0)处,(0,101)处的彩色值赋值给(0,1)处,然后把第0行所有列(521列)处理完。之后再处理下一行。同理沿着y方向向下平移50也是差不多这个思想。
       至于这个从520到0也是有讲究的,因为我们为了避免移动过程发生覆盖而数据丢失,所以需要倒序处理。打个比方,第0行,假如我们从0列到520列,那么100列之后的值就会被覆盖,这样我们就不能正确将原来100列之后的初始值进行移动(赋值),而我们从第520列处理就可以完美的避免这个问题。

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt

img1 = cv2.imread('flower.jpeg')
img2 = img1.copy()

# x方向向右平移100
for j in range(521):
    for i in range(520,-1,-1):
        if i<100:
            img2[j, i] = 0
        else:
            img2[j, i] = img2[j, i-100]

# y方向向下平移50
for u in range(521):
    for v in range(520,-1,-1):
        if v<50:
            img2[v, u] = 0
        else:
            img2[v, u] = img2[v-50, u]

# 图像显示
fig, axes = plt.subplots(1, 2, figsize=(10, 8), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(img2[:, :, ::-1])
axes[1].set_title("after translation")

plt.show()

在这里插入图片描述

二、旋转

1.调用函数(旋转矩阵)

在这里插入图片描述
极坐标:
{ x = r ⋅ c o s α y = r ⋅ s i n α \begin{cases} x =r \cdot cos\alpha \\ y =r \cdot sin\alpha \end{cases} {x=rcosαy=rsinα
{ x ′ = r ⋅ c o s ( α + θ ) = r ⋅ c o s α ⋅ c o s θ − r ⋅ s i n α ⋅ s i n θ = x ⋅ c o s θ − y ⋅ s i n θ y ′ = r ⋅ s i n ( α + θ ) = r ⋅ s i n α ⋅ c o s θ + r ⋅ c o s α ⋅ s i n θ = x ⋅ s i n θ + y ⋅ c o s θ \begin{cases} x' =r \cdot cos(\alpha+\theta)\\ \hspace{0.4cm} =r \cdot cos\alpha \cdot cos\theta - r \cdot sin\alpha \cdot sin\theta\\ \hspace{0.4cm} =x \cdot cos\theta - y \cdot sin\theta\\ \\ y' =r \cdot sin(\alpha+\theta)\\ \hspace{0.4cm} =r \cdot sin\alpha \cdot cos\theta + r \cdot cos\alpha \cdot sin\theta\\ \hspace{0.4cm} =x \cdot sin\theta + y \cdot cos\theta\\ \end{cases} x=rcos(α+θ)=rcosαcosθrsinαsinθ=xcosθysinθy=rsin(α+θ)=rsinαcosθ+rcosαsinθ=xsinθ+ycosθ
直角坐标:
[ x ′ y ′ 1 ] = [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] ⋅ [ x y 1 ] \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} =\begin{bmatrix} cos\theta&-sin\theta&0 \\ sin\theta&cos\theta&0 \\ 0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} xy1 = cosθsinθ0sinθcosθ0001 xy1
{ x ′ = x ⋅ c o s θ − y ⋅ s i n θ y ′ = x ⋅ s i n θ + y ⋅ c o s θ \begin{cases} x' =x \cdot cos\theta - y \cdot sin\theta\\ y' =x \cdot sin\theta + y \cdot cos\theta \end{cases} {x=xcosθysinθy=xsinθ+ycosθ

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt
import numpy as np
import math

# 读取图像
img1 = cv2.imread("blossom.jpeg")

# 图像旋转
h, w = img1.shape[:2]
M = np.float32([[math.cos(math.pi/4), -math.sin(math.pi/4), 0], [math.sin(math.pi/4), math.cos(math.pi/4), 0]]) # 顺时针旋转45°
dst = cv2.warpAffine(img1, M, (w, h))

# 图像显示
fig, axes = plt.subplots(1, 2, figsize=(10, 8), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(dst[:, :, ::-1])
axes[1].set_title("after rotation")

plt.show()

在这里插入图片描述

2.像素操作(反向映射)

       此程序运用了一种反向映射的思想,这样可以确保旋转后的位置坐标都有彩色值,我们根据旋转矩阵的逆,可以找到旋转后的图像坐标所对应原图像的彩色值,从而对其进行赋值即可
       其中我们需要注意两个问题,一是我们旋转后的坐标位置所对应原图像的坐标可能是越界的(找不到坐标与其对应);二是我们求的坐标向量可能是负数,需要舍去。

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt
import numpy as np
import math

# 读取图像
img1 = cv2.imread("blossom.jpeg")
img2 = img1.copy()

# 图像旋转(反向映射)
RM1 = np.float32([[math.cos(math.pi/4), math.sin(math.pi/4), 0], [-math.sin(math.pi/4), math.cos(math.pi/4), 0], [0, 0, 1]]) # 计算出的旋转矩阵
RM2 = np.linalg.inv(RM1) # 求解旋转矩阵的逆

for i in range(521):
    for j in range(521):
        D = np.dot(RM2, [[i], [j], [1]]) # 旋转后的图像坐标位置 相对应的 原图像坐标位置
        if int(D[0])>=521 or int(D[1])>=521: # 旋转后的图像坐标 相对应的 原图像坐标位置 越界
            img2[i, j] = 0
        elif int(D[0]) < 0 or int(D[1]) < 0: # 旋转后的图像坐标 相对应的 原图像坐标位置 负值
            img2[i, j] = 0
        else:
            img2[i, j] = img1[int(D[0]), int(D[1])]

# 图像显示
fig, axes = plt.subplots(1, 2, figsize=(10, 8), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(img2[:, :, ::-1])
axes[1].set_title("after rotation")

plt.show()

在这里插入图片描述

三、镜像

水平镜像:
{ x ′ = w i d t h − 1 − x y ′ = y \begin{cases} x' =width-1-x\\ y' =y \end{cases} {x=width1xy=y
[ x ′ y ′ 1 ] = [ − 1 0 w i d t h − 1 0 1 0 0 0 1 ] ⋅ [ x y 1 ] \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} =\begin{bmatrix} -1&0&width-1 \\ 0&1&0 \\ 0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} xy1 = 100010width101 xy1
垂直镜像:
{ x ′ = x y ′ = h e i g h t − 1 − y \begin{cases} x' =x\\ y' =height-1-y \end{cases} {x=xy=height1y
[ x ′ y ′ 1 ] = [ 1 0 0 0 − 1 h e i g h t − 1 0 0 1 ] ⋅ [ x y 1 ] \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} =\begin{bmatrix} 1&0&0 \\ 0&-1&height-1 \\ 0&0&1 \end{bmatrix} \cdot \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} xy1 = 1000100height11 xy1

1.调用函数(镜像矩阵)

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 读取图像
img1 = cv2.imread("technology building.jpeg")

# 图像水平镜像
h, w = img1.shape[:2]
M = np.float32([[-1, 0, 520], [0, 1, 0]])
dst1 = cv2.warpAffine(img1, M, (w, h))

# 图像垂直镜像
h, w = img1.shape[:2]
M = np.float32([[1, 0, 0], [0, -1, 520]])
dst2 = cv2.warpAffine(img1, M, (w, h))

# 图像显示
fig, axes = plt.subplots(1, 3, figsize=(80, 10), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(dst1[:, :, ::-1])
axes[1].set_title("after horizontal-mirror")
axes[2].imshow(dst2[:, :, ::-1])
axes[2].set_title("after vertical-mirror")

plt.show()

在这里插入图片描述

2.像素操作(反向映射)

import cv2
import matplotlib.pyplot as plt
import numpy as np

# 读取图像
img1 = cv2.imread("technology building.jpeg")
img2 = img1.copy()
img3 = img1.copy()

# 图像水平镜像
RM1 = np.float32([[1, 0, 0], [0, -1, 520], [0, 0, 1]]) # 计算出的旋转矩阵
RM2 = np.linalg.inv(RM1) # 求解旋转矩阵的逆
for i in range(521):
    for j in range(521):
        D = np.dot(RM2, [[i], [j], [1]]) # 旋转后的图像坐标位置 相对应的 原图像坐标位置
        if int(D[0])>=521 or int(D[1])>=521: # 旋转后的图像坐标 相对应的 原图像坐标位置 越界
            img2[i, j] = 0
        elif int(D[0]) < 0 or int(D[1]) < 0: # 旋转后的图像坐标 相对应的 原图像坐标位置 负值
            img2[i, j] = 0
        else:
            img2[i, j] = img1[int(D[0]), int(D[1])]

# 图像垂直镜像
RM3 = np.float32([[-1, 0, 520], [0, 1, 0], [0, 0, 1]]) # 计算出的旋转矩阵
RM4 = np.linalg.inv(RM3) # 求解旋转矩阵的逆
for i in range(521):
    for j in range(521):
        D = np.dot(RM4, [[i], [j], [1]]) # 旋转后的图像坐标位置 相对应的 原图像坐标位置
        if int(D[0])>=521 or int(D[1])>=521: # 旋转后的图像坐标 相对应的 原图像坐标位置 越界
            img3[i, j] = 0
        elif int(D[0]) < 0 or int(D[1]) < 0: # 旋转后的图像坐标 相对应的 原图像坐标位置 负值
            img3[i, j] = 0
        else:
            img3[i, j] = img1[int(D[0]), int(D[1])]

# 图像显示
fig, axes = plt.subplots(1, 3, figsize=(80, 10), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(img2[:, :, ::-1])
axes[1].set_title("after horizontal-mirror")
axes[2].imshow(img3[:, :, ::-1])
axes[2].set_title("after vertical-mirror")

plt.show()

在这里插入图片描述

四、缩放

1.缩小(八邻域均值)

       算法思想很简单,就是我们遍历原图像的每一个像素,原图像(1080×1437)的每一个像素与自己所在的八邻域(9个像素值)取平均值,然后将像素放进模板(360×479)中形成新的图像。相当于我们用一个3×3的卷积核对原图像进行卷积操作
       一共需要注意两个问题,一是我们先对边界元素扩充一行一列,这样原图像边界也可以很好的进行卷积操作而不越界;二是我们在进行求和会超出255而越界(每个通道像素值0-255,比如我们用230+230,不会得到460,而是会变成460-255-1=204),如果不处理,就会让我们的缩小图像变成黑色,所以我这里是先将所有值缩小10倍相加再放大10呗,当然你也可以选择使用其它方式。

程序实现及运行结果:

import cv2

# 读取图像
img1 = cv2.imread("city.jpeg")
img3 = cv2.imread("img3.png") # 制作一个模板

# 图像缩小
img2 = cv2.copyMakeBorder(img1, 1, 1, 1, 1, cv2.BORDER_REFLECT) # 上下左右各扩充1,复制最近像素

# 索引列表(位置坐标)
list1 = list(range(1, 1080, 3))
list2 = list(range(360))
list3 = list(range(1, 1435, 3))
list4 = list(range(479))

# 一个像素点自身与八邻域取均值
for i,u in zip(list1, list2):
    for j,v in zip(list3, list4):
        # 防止越界
        img3[u, v] = ((img2[i,j]/10 + img2[i-1,j-1]/10 + img2[i-1,j]/10 + img2[i,j+1]/10 + img2[i,j-1]/10 + img2[i,j+1]/10 + img2[i+1,j-1]/10 + img2[i+1,j]/10 + img2[i+1,j+1]/10) / 9)*10

# 保存图像
cv2.imwrite('shrink.jpeg',img3)

original:
在这里插入图片描述
shrink:
在这里插入图片描述

2.放大(双线性插值)

单线性插值:
在这里插入图片描述

y − y 0 x − x 0 = y 1 − y 0 x 1 − x 0 \boxed {\frac {y-y_0} {x-x_0}=\frac {y_1-y_0} {x_1-x_0}} xx0yy0=x1x0y1y0
y = x 1 − x x 1 − x 0 ⋅ y 0 + x − x 0 x 1 − x 0 ⋅ y 1 = d 1 d ⋅ y 0 + d 0 d ⋅ y 1 \boxed {y = \frac {x_1-x} {x_1-x_0} \cdot y_0+\frac {x-x_0} {x_1-x_0} \cdot y_1}={ \frac {d_1} {d} \cdot y_0+\frac {d_0} {d} \cdot y_1} y=x1x0x1xy0+x1x0xx0y1=dd1y0+dd0y1
       这个公式表示的结果就是:x1和x0分别到x的距离作为权重,用于对y0和y1的加权。d0/d > d1/d,而y1到y < y0到y 说明y1比重更大,所以乘以d0/d。

双线性插值:
在这里插入图片描述
①x方向的单线性插值去分别计算R1、R2的像素值:
f ( x , y 1 ) = x 2 − x x 2 − x 1 ⋅ f ( Q 11 ) + x − x 0 x 1 − x 0 ⋅ f ( Q 21 ) \boxed {f(x,y_1) = \frac {x_2-x} {x_2-x_1} \cdot f(Q_{11})+\frac {x-x_0} {x_1-x_0} \cdot f(Q_{21})} f(x,y1)=x2x1x2xf(Q11)+x1x0xx0f(Q21)
f ( x , y 2 ) = x 2 − x x 2 − x 1 ⋅ f ( Q 12 ) + x − x 0 x 1 − x 0 ⋅ f ( Q 22 ) \boxed {f(x,y_2)= \frac {x_2-x} {x_2-x_1} \cdot f(Q_{12})+\frac {x-x_0} {x_1-x_0} \cdot f(Q_{22})} f(x,y2)=x2x1x2xf(Q12)+x1x0xx0f(Q22)

②y方向的单线性插值计算P点的像素值:
f ( x , y ) ≈ y 2 − y y 2 − y 1 ⋅ f ( x , y 1 ) + y − y 1 y 2 − y 1 ⋅ f ( x , y 2 ) \boxed {f(x,y) \approx \frac {y_2-y} {y_2-y_1} \cdot f(x,y_1)+\frac {y-y_1} {y_2-y_1} \cdot f(x,y_2)} f(x,y)y2y1y2yf(x,y1)+y2y1yy1f(x,y2)

算法设计:
在这里插入图片描述
       假设我们取原图像的四个像素(如图所示15、16行,20、21列)(黑圈和红圈),像素值分别为P1、P2、P3、P4。
       以我们需要插值在(14.3,20.3)为例子,根据双线性插值原理计算:
       Q12 = 0.3×P2 + 0.7×P1
       Q34 = 0.3×P4 + 0.7×P3
       PQ = 0.3×Q34 + 0.7×Q12
       这样我们就可以得到绿色三角的像素值,根据每个插值的像素和原图像四个像素的距离得出它们各自的系数(权重),并能得到中间的(黄蓝紫黄)的像素值。
       最后,我们再把这四个像进行相应的外扩(如第三块图所示),就可以把原来2×2的图像放大成一幅4×4的图像。(继承了原图像的4个像素值,利用双线性插值得到了新的12个像素值)

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt

# 定义一个类(双线性插值计算)
class bilinear_interpolation:
    P1 = P2 = P3 = P4 = 0

    # 计算图中绿色三角像素值
    def green(self):
        Q12_green = 0.3 * self.P2 + 0.7 * self.P1
        Q34_green = 0.3 * self.P4 + 0.7 * self.P3
        PQ_green = 0.3 * Q34_green + 0.7 * Q12_green
        return PQ_green

    # 计算图中蓝色三角像素值
    def blue(self):
        Q12_blue = 0.6 * self.P2 + 0.4 * self.P1
        Q34_blue = 0.6 * self.P4 + 0.4 * self.P3
        PQ_blue = 0.3 * Q34_blue + 0.7 * Q12_blue
        return PQ_blue

    # 计算图中紫色三角像素值
    def purple(self):
        Q12_purple = 0.3 * self.P2 + 0.7 * self.P1
        Q34_purple = 0.3 * self.P4 + 0.7 * self.P3
        PQ_purple = 0.6 * Q34_purple + 0.4 * Q12_purple
        return PQ_purple

    # 计算图中黄色三角像素值
    def yellow(self):
        Q12_yellow = 0.6 * self.P2 + 0.4 * self.P1
        Q34_yellow = 0.6 * self.P4 + 0.4 * self.P3
        PQ_yellow= 0.6 * Q34_yellow + 0.4 * Q12_yellow
        return PQ_yellow


BI = bilinear_interpolation()

# 读取图像
img1 = cv2.imread("sunset.jpg")
img2 = cv2.imread("img4.png") # 制作一个模板

# 索引列表(位置坐标)
list1 = list(range(0, 322, 2))
list2 = list(range(0, 644, 4))
list3 = list(range(0, 500, 2))
list4 = list(range(0, 1000, 4))

# 放大图像(双线性插值)
for i,u in zip(list1, list2):
    for j,v in zip(list3, list4):
        img2[u, v], img2[u, v+3], img2[u+3, v], img2[u+3, v+3] = img1[i, j], img1[i, j+1], img1[i+1, j], img1[i+1, j+1] # 原图像未更改像素值
        BI.P1 = img1[i, j]; BI.P2 = img1[i, j + 1]; BI.P3 = img1[i + 1, j]; BI.P4 = img1[i + 1, j + 1]

        img2[u, v+1] = img2[u+1, v] = img2[u+1, v+1] =BI.green()
        img2[u, v+2] = img2[u+1, v+3] = img2[u+1, v+2] =BI.blue()
        img2[u+2, v] = img2[u+3, v+1] = img2[u+2, v+1] =BI.purple()
        img2[u+3, v+2] = img2[u+2, v+3] = img2[u+2, v+2] =BI.yellow()

# 保存图像
cv2.imwrite('enlarged.jpeg',img2)

original:
在这里插入图片描述
enlarged:
在这里插入图片描述

五、剪切

索引切片

程序实现及运行结果:

import cv2
import matplotlib.pyplot as plt

# 读取图像
img1 = cv2.imread("podoid.jpeg")
print(img1.shape) # (1290, 1080, 3)
# 图像剪切
img2 = img1[420:800, 300:800]
print(img2.shape) # (380, 500, 3)

# 图像显示
fig, axes = plt.subplots(1, 2, figsize=(10, 8), dpi=100)
axes[0].imshow(img1[:, :, ::-1])
axes[0].set_title("original")
axes[1].imshow(img2[:, :, ::-1])
axes[1].set_title("shear")

plt.show()

在这里插入图片描述

本文代码已开源,欢迎大家进行二次开发:https://gitee.com/xiaolong_ROS/Graphics-Processing-and-Machine-Vision

如有错误或者不足之处,欢迎大家留言指正!

  • 0
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☆下山☆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值