旋转NumPy索引实现图像旋转仅在两个角度区间有效
无裁剪旋转图像的索引计算问题
我正在开发一个Python程序,通过逐帧计算图像生成视频,需要实现无裁剪旋转图像的功能。最初参考了OpenCV无裁剪旋转的常规方法,但需要先大幅放大图像区域,现在为了优化速度,尝试仅旋转NumPy索引并尽可能缩小缩放范围。
最初的近似方案会导致轻微裁剪,于是改用cv2.boundingRectangle计算索引尺寸。这个方法在**-45° < 角度 < 45°或135° ≤ 角度 < 225°**区间表现完美,但在剩余角度区间,旋转后的图像无法匹配蓝色边界框,完全覆盖目标红色帧。
import numpy as np import cv2 def diag_bw(x, y): '''生成测试图像的简单函数''' return (x + y)%179 def add_cnt(img, cnt, clr, thick): '''逐行绘制轮廓''' rcoos = [x[::-1] for x in cnt.points().astype(int)] for i in range(4): cv2.line(img, rcoos[i], rcoos[(i+1)%4], clr, thick) def slice_frame(oshape, ishape): # (外框尺寸, 内框尺寸) '''计算内框在外框中的切片范围''' y0 = (oshape[0]-ishape[0])//2 x0 = (oshape[1]-ishape[1])//2 sy = slice(y0, y0 + ishape[0]) sx = slice(x0, x0 + ishape[1]) return sy, sx def bbox_meth1(wica, hica, wifr, hifr, angle): # 画布尺寸、帧尺寸、旋转角度 '''利用cv2.boundingRect()旋转索引i,j''' canvas = np.zeros((hica, wica, 3), np.uint8) # 创建画布用于显示帧外内容 rotim = cv2.RotatedRect((hica//2, wica//2), (hifr, wifr), angle) # 旋转目标帧 add_cnt(canvas, rotim, (0, 55, 0), 1) # 获取旋转后帧的外接矩形尺寸 x, y, w, h = rotim.boundingRect() # 创建覆盖固定帧的旋转外接矩形(borec)并绘制 borec = cv2.RotatedRect((wica//2, hica//2), (w, h), angle) add_cnt(canvas, borec, (255, 0, 0), 2) if (angle <= 45) or (135 <= angle < 225) or (angle > 315): # 旋转矩阵的中心 mx, my = h//2, w//2 M = cv2.getRotationMatrix2D((mx, my), angle, 1.) print(F" {M[0, 2]:6.1f} {M[1, 2]:6.1f} ", end='') print(F" {w:7} {h:7} {mx:7} {my:7} ", end='') i, j = np.indices(np.array([w, h])).astype(np.float32) i = cv2.warpAffine(i, M, (h, w)) j = cv2.warpAffine(j, M, (h, w)) else: # 这些角度下旋转后的图像无法完全覆盖帧 mx, my = w//2, h//2 M = cv2.getRotationMatrix2D((mx, my), angle, 1.)#1.07#1.1) print(F" {M[0, 2]:6.1f} {M[1, 2]:6.1f} ", end='') print(F" {h:7} {w:7} {mx:7} {my:7} ", end='') i, j = np.indices(np.array([w, h])).astype(np.float32) i = cv2.warpAffine(i, M, (w, h)) j = cv2.warpAffine(j, M, (w, h)) print(F"{i.max():7.2f} {j.max():6.1f} {w:6} {h:6} {x:6} {y:6} ", end='') print(F"{borec.points()[1][0]:6.1f}{borec.points()[1][1]:6.1f}", end='') return [i, j], canvas if __name__=='__main__': print(F"{'angle':10}{'M0':7}{'M1':7}{'w':8}{'h':11}", end='') print(F"{'mx':9}{'my':6}{'imax':7}{'jmax':9}{'w':9}{'h':6}", end='') print(F"{'x':6}{'y':7}{'b0':6}{'b1':6}{'ishape1':9}{'ishape0':9}", end='') print(F"{'i1':6}{'i0':6}") hic, wic = 1000, 1000 # 画布尺寸 hi,wi = 270, 480 # 待显示的帧尺寸 angle_start, angle_end, angle_delta = 0, 361, 5 for angle in range(angle_start, angle_end, angle_delta): print(F"{angle:4}", end=' ') # 获取图像生成函数的参数列表和画布 arglist, canvas = bbox_meth1(wic, hic, wi, hi, angle) img = diag_bw(*arglist[:2]) print(F"{img.shape[1]:7} {img.shape[0]:7}", end='') print(F" {arglist[0].shape[1]:5} {arglist[0].shape[0]:7}") # 将生成的图像转换为彩色并绘制到画布的对应区域 canvas[slice_frame((hic, wic), img.shape)] = cv2.applyColorMap(img.astype(np.uint8), 2) # 绘制红色矩形标记目标帧区域 cv2.rectangle(canvas, ((canvas.shape[1]-wi)//2, (canvas.shape[0]-hi)//2), ((canvas.shape[1] + wi)//2, (canvas.shape[0] + hi)//2), (0, 0, 255), 2) cv2.imshow('can', canvas) cv2.waitKey(100)
问题现象截图说明
- 0°(无旋转):计算得到的图像完全覆盖红色帧

- ≤45°:旋转放大后的图像与蓝色框对齐,完全覆盖红色帧

- >45°(以60°为例,至135°区间):计算图像无法匹配蓝色边框,无法填满红色帧

- 90°旋转:误差最大,渲染图像偏移到帧外右下侧,未填充完整帧;该问题在角度超过135°后对称重复,直至135°-225°区间恢复正常

内容的提问来源于stack exchange,提问作者rare
相关产品推荐
相关产品推荐

