图像仿射变换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= \begin{bmatrix} {\alpha} & {\beta} & (1-\alpha)\cdot center.x-\beta\cdot center.y\\ {-\beta} & {\alpha} & \beta\cdot center.x+(1-\alpha)\cdot center.y \end{bmatrix} 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

验证正确性:

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

输出为全 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

测试结果如下:

在这里插入图片描述

左侧和右侧分别是 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)

结果如下:

在这里插入图片描述

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

无损的仿射变换

图像旋转: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

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

在这里插入图片描述

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

Ref

  1. 图像预处理之warpaffine与双线性插值及其高性能实现
  2. 图像的几何变换
  3. 图像旋转:getRotationMatrix2D详解–无损失旋转图片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值