• 图像仿射变换OpenCV API与自行代码实现


    图像仿射变换OpenCV API与自行代码实现

    OpenCV相关API接口梳理

    • M = cv2.getRotationMatrix2D(rot_center, theta, scale)

      计算二维变换矩阵

      • 输入:旋转中心 rot_center、逆时针旋转角度 theta、缩放系数 scale
      • 输出:仿射变换矩阵 M
    • img_warpaffine = cv2.warpAffine(img, M, (out_w, out_h))

      根据变换矩阵 M M M 完成图像仿射变换

      • 输入:待变换的图像 img、变换矩阵 M、输出图像尺寸 (out_w, out_h)
      • 输出:变换后的图像 img_warpaffine
    • M_inv = cv2.invertAffineTransform(M)

      计算变换矩阵的反矩阵

      • 输入:变换矩阵 M
      • 输出:M 的你矩阵 M_inv

    相关API自行实现

    getRotationMatrix2D

    图像的几何变换 给出了在一般情况下,二维仿射变换矩阵的计算方法:
    M = [ α β ( 1 − α ) ⋅ c e n t e r . x − β ⋅ c e n t e r . y − β α β ⋅ c e n t e r . x + ( 1 − α ) ⋅ c e n t e r . y ] M= [αβ(1α)center.xβcenter.yβαβcenter.x+(1α)center.y] M=[αββα(1α)center.xβcenter.yβcenter.x+(1α)center.y]
    其中 c e n t e r center center 是旋转中心的坐标, s c a l e scale scale 是缩放的尺度, θ \theta θ 是逆时针旋转的角度, α = s c a l e ⋅ cos ⁡ θ ,   β = s c a l e ⋅ sin ⁡ θ \alpha=scale\cdot \cos\theta,\ \beta=scale\cdot \sin\theta α=scalecosθ, β=scalesinθ

    具体的公式推导,在这篇文章中有介绍:图像预处理之warpaffine与双线性插值及其高性能实现

    据此,我们可以自己实现 getRotationMatrix2D 方法:

    def myGetRotationMatrix2d(rot_center, theta, scale):
        rad = np.radians(theta)
        alpha = scale * np.cos(rad)
        beta = scale * np.sin(rad)
        M = np.array([
            [alpha, beta, (1 - alpha) * rot_center[0] - beta * rot_center[1]],
            [-beta, alpha, beta * rot_center[0] + (1 - alpha) * rot_center[1]]
        ])
        return M
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    验证正确性:

    # 计算变换矩阵
    M = cv2.getRotationMatrix2D(rot_center, theta, scale)
    my_M = myGetRotationMatrix2d(rot_center, theta, scale)
    print(np.isclose(M, my_M))
    
    • 1
    • 2
    • 3
    • 4

    输出为全 True,可知我们自己计算的变换矩阵 M M M 与 OpenCV 计算结果是一致的。

    warpAffine

    图像预处理之warpaffine与双线性插值及其高性能实现 这篇文章还介绍了如何自行实现 warpAffine 函数,本文中就不再介绍推导过程,而直接给出代码。

    def pyWarpAffine(image, M, dst_size, constant=(0, 0, 0)):
    
        # 注意输入的M矩阵格式,是Origin->Dst
        # 而这里需要的是Dst->Origin,所以要取逆矩阵
        M = cv2.invertAffineTransform(M)
        constant = np.array(constant)
        ih, iw   = image.shape[:2]
        dw, dh   = dst_size
        dst      = np.full((dh, dw, 3), constant, dtype=np.uint8)
        irange   = lambda p: p[0] >= 0 and p[0] < iw and p[1] >= 0 and p[1] < ih
    
        for y in range(dh):
            for x in range(dw):
    
                homogeneous = np.array([[x, y, 1]]).T
                ox, oy = M @ homogeneous
    
                low_ox = int(np.floor(ox))
                low_oy = int(np.floor(oy))
                high_ox = low_ox + 1
                high_oy = low_oy + 1
    
                # p0     p1
                #
                # p2     p3
    
                pos = ox - low_ox, oy - low_oy
                p0_area = (1 - pos[0]) * (1 - pos[1])
                p1_area = pos[0] * (1 - pos[1])
                p2_area = (1 - pos[0]) * pos[1]
                p3_area = pos[0] * pos[1]
    
                p0 = low_ox, low_oy
                p1 = high_ox, low_oy
                p2 = low_ox, high_oy
                p3 = high_ox, high_oy
                p0_value = image[p0[1], p0[0]] if irange(p0) else constant
                p1_value = image[p1[1], p1[0]] if irange(p1) else constant
                p2_value = image[p2[1], p2[0]] if irange(p2) else constant
                p3_value = image[p3[1], p3[0]] if irange(p3) else constant
                dst[y, x] = p0_area * p0_value + p1_area * p1_value + p2_area * p2_value + p3_area * p3_value
        return dst
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    测试结果如下:

    在这里插入图片描述

    左侧和右侧分别是 OpenCV 的 warpAffine 函数的变换结果与自行实现的 warpAffine 函数的变换结果。可以看到,基本是一致的。

    反变换与无损的仿射变换

    反变换

    我们可以通过cv2.invertAffineTransform 计算仿射变换矩阵的反变换,从而将变换后的图像再变换回来:

    # 反变换
    M_inv = cv2.invertAffineTransform(M)
    cat_cv_inv = cv2.warpAffine(cat_cv, M_inv, (h, w))
    cv2.imwrite("opencv_res_inv.jpg", cat_cv_inv)
    
    • 1
    • 2
    • 3
    • 4

    结果如下:

    在这里插入图片描述

    明显可以看到,虽然图片旋转回来了,大小也变回来了,但是由于超出了输出图像的指定大小,在变换的过程中有些边角的信息丢失掉了。那么,我们能否实现无损的图像旋转呢?

    无损的仿射变换

    图像旋转:getRotationMatrix2D详解–无损失旋转图片 这篇文章实现了信息无损的图像仿射变换,代码如下:

    def opencv_full_rotate(img, angle):
        h, w = img.shape[: 2]
        center = (w / 2, h / 2)
        scale = 1.0
        M = cv2.getRotationMatrix2D(center, angle, scale)
        # 2.2 新的宽高,radians(angle) 把角度转为弧度 sin(弧度)
        rad = np.radians(angle)
        new_H = int(w * abs(np.sin(rad)) + h * abs(np.cos(rad)))
        new_W = int(h * abs(np.sin(rad)) + w * abs(np.cos(rad)))
        # 2.3 平移
        M[0, 2] += (new_W - w) / 2
        M[1, 2] += (new_H - h) / 2
        rotate = cv2.warpAffine(img, M, (new_W, new_H), borderValue=(0, 0, 0))
        return rotate
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    就是要保证变换后的图像都在输出尺寸范围内,测试结果如下:

    在这里插入图片描述

    可以看到,实现了无损的图像仿射变换。

    Ref

    1. 图像预处理之warpaffine与双线性插值及其高性能实现
    2. 图像的几何变换
    3. 图像旋转:getRotationMatrix2D详解–无损失旋转图片
  • 相关阅读:
    【Flink】一文解析Flink如何实现状态管理和容错机制
    K线形态识别_穿头破脚
    React原理剖析之diff算法,一点也不难!!!
    缩放图片算法优化 sse
    蓝桥杯2022 [蓝桥杯2022初赛] 求和与纸张尺寸
    【DSCTF2022】pwn补题记录
    安装下载Anaconda注意事项以及卸载
    el-upload上传图片到七牛云或阿里云
    SAST-数据流分析方法-理论
    2021年电工杯数学建模A题高铁牵引供电系统运行数据分析及等值建模求解全过程论文及程序
  • 原文地址:https://blog.csdn.net/weixin_44966641/article/details/126925176