OpenCV实现y轴图像旋转后输出边角被裁剪,如何得到无裁剪图像?
裁剪问题的核心原因
两个逻辑错误直接导致了边角丢失:
- 你用来算输出画布尺寸的公式,是纯平面二维旋转的包围盒算法,完全不适配当前带透视效果的3D y轴旋转场景。绕y轴做透视投影时图像会产生近大远小的变形,四个角落的投影坐标范围远大于二维旋转的包围盒范围,用旧公式算出来的画布尺寸根本包不住全部像素。
- 生成的透视变换矩阵没有做输出坐标的位置修正,大量有效像素落在新建画布的坐标范围外,被
warpPerspective直接丢弃裁剪。
注意:原代码中
abs_cos/abs_sin计算的二维边界公式,仅适用于无透视变形、正交投影的平面旋转场景,遇到带透视的变换时这个公式的计算结果偏差会非常大。
修正步骤
- 提取原图像的四个角点坐标,通过现有透视变换矩阵计算这些角点映射到输出空间后的实际坐标值
- 根据映射后角点的横纵坐标极值,计算能完整覆盖所有像素的最小画布宽高
- 给透视变换矩阵追加一段平移映射,把所有映射后的像素整体平移,让像素坐标刚好落在新建画布的(0,0)到(bound_w, bound_h)合法区间内
修正后的完整代码
import numpy as np import cv2 import math def rotate_y(img, y): width = img.shape[1] height = img.shape[0] # 2D坐标转3D坐标,将旋转原点移到图像中心 proj2dto3d = np.array([[1, 0, -img.shape[1] / 2], [0, 1, -img.shape[0] / 2], [0, 0, 0], [0, 0, 1]], np.float32) # 绕y轴旋转的3D变换矩阵 ry = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], np.float32) # z轴方向平移,模拟相机与旋转平面的距离 trans = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 100], [0, 0, 0, 1]], np.float32) # 3D坐标投影回2D图像坐标 proj3dto2d = np.array([[100, 0, img.shape[1] / 2, 0], [0, 100, img.shape[0] / 2, 0], [0, 0, 1, 0]], np.float32) ay = float(y * (math.pi / 180)) ry[0, 0] = math.cos(ay) ry[0, 2] = -math.sin(ay) ry[2, 0] = math.sin(ay) ry[2, 2] = math.cos(ay) # 合并得到初始透视变换矩阵 final = proj3dto2d.dot(trans.dot(ry.dot(proj2dto3d))) # 计算原图像四个角点变换后的实际坐标 src_corners = np.array([ [0, 0], [width - 1, 0], [0, height - 1], [width - 1, height - 1] ], dtype=np.float32).reshape(-1, 1, 2) dst_corners = cv2.perspectiveTransform(src_corners, final) # 取坐标极值,计算能完整覆盖所有像素的画布尺寸 x_coords = dst_corners[:, 0, 0] y_coords = dst_corners[:, 0, 1] min_x, max_x = np.min(x_coords), np.max(x_coords) min_y, max_y = np.min(y_coords), np.max(y_coords) bound_w = int(np.ceil(max_x - min_x)) bound_h = int(np.ceil(max_y - min_y)) # 构造平移矩阵,把所有像素移到画布合法坐标范围内 translate_mat = np.array([ [1, 0, -min_x], [0, 1, -min_y], [0, 0, 1] ], dtype=np.float32) final_corrected = translate_mat.dot(final) # 执行透视变换 dst = cv2.warpPerspective( img, final_corrected, (bound_w, bound_h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0) ) return dst
效果说明
修正后的代码会根据旋转角度自动调整输出画布尺寸,所有经过透视旋转的像素都会被完整纳入输出范围,不会再出现边角被裁剪的问题。
内容的提问来源于stack exchange,提问作者Sss
相关产品推荐
相关产品推荐

