基于NumPy索引旋转生成图像仅在四个角度区间中的两个区间生效的技术问题排查
解决OpenCV旋转图像无裁剪且完全覆盖目标帧的问题
我看了你遇到的问题:在非[-45°,45°]、[135°,225°]区间的角度下,旋转后的图像无法对齐蓝色包围框,也不能完全覆盖红色目标帧,比如90°时偏移明显。这主要是因为RotatedRect的坐标使用错误,以及角度分支下的旋转矩阵和索引映射逻辑不一致导致的。
核心问题分析
- RotatedRect坐标混淆:OpenCV的
RotatedRect采用笛卡尔坐标(x,y),但图像的shape是(高度y,宽度x),你之前构造RotatedRect时把center写成了(hica//2, wica//2)(y在前x在后),导致旋转后的矩形位置完全错误。 - 角度分支的逻辑漏洞:分角度处理时,旋转中心、
warpAffine的目标尺寸匹配错误,导致索引映射后的坐标范围偏离预期,生成的图像无法覆盖目标帧。 - 依赖boundingRect的误差:
boundingRect返回的尺寸虽然数值正确,但结合错误的center后,无法正确对齐目标区域。
修正后的解决方案
下面是修改后的完整代码,我重构了bbox_meth1函数,去掉了角度分支,统一处理所有角度的旋转逻辑,确保旋转后的图像无裁剪且完全覆盖红色帧:
import numpy as np import cv2 def diag_bw(x, y): '''simple img-generation''' return (x + y)%179 def add_cnt(img, cnt, clr, thick): '''display a contour cnt line by line''' 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): # (shape of outer-, -inner frame) '''y-, x-slices of ishape in oshape''' 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): # Dimensions of canvas, of frame, angle '''rotate i, j using inverse rotation mapping''' canvas = np.zeros((hica, wica, 3), np.uint8) # To display contours outside frame # 修正:RotatedRect的center采用笛卡尔坐标(x,y),对应图像的列和行 rotim = cv2.RotatedRect((wica//2, hica//2), (wifr, hifr), angle) add_cnt(canvas, rotim, (0, 55, 0), 1) # 计算旋转后最小包围框的尺寸(用三角函数更可靠) theta = np.deg2rad(angle) cos_theta = np.abs(np.cos(theta)) sin_theta = np.abs(np.sin(theta)) w_bbox = int(wifr * cos_theta + hifr * sin_theta) h_bbox = int(wifr * sin_theta + hifr * cos_theta) # 绘制包围框 borec = cv2.RotatedRect((wica//2, hica//2), (w_bbox, h_bbox), angle) add_cnt(canvas, borec, (255, 0, 0), 2) # 生成包围框的网格索引,并转换为以中心为原点的坐标 j_grid, i_grid = np.meshgrid(np.arange(w_bbox), np.arange(h_bbox)) j_grid = j_grid.astype(np.float32) - w_bbox//2 i_grid = i_grid.astype(np.float32) - h_bbox//2 # 构建逆旋转矩阵:将包围框坐标旋转回原帧坐标 M = cv2.getRotationMatrix2D((0, 0), -angle, 1.0) coords = np.stack([j_grid, i_grid], axis=-1) coords_rot = cv2.transform(coords, M) # 将旋转后的坐标映射回原帧的中心 j_rot = coords_rot[..., 0] + wifr//2 i_rot = coords_rot[..., 1] + hifr//2 # 打印调试信息 print(f" {M[0,2]:6.1f} {M[1,2]:6.1f} ", end='') print(f" {w_bbox:7} {h_bbox:7} {w_bbox//2:7} {h_bbox//2:7} ", end='') return [i_rot, j_rot], canvas if __name__=='__main__': print(f"{'angle':10}{'M0':7}{'M1':7}{'w':8}{'h':11}{'mx':9}{'my':6}{'ishape1':9}{'ishape0':9}{'i1':6}{'i0':6}") hic, wic = 1000, 1000 # Dimension of canvas hi,wi = 270, 480 # Dimension of frame to be displayed angle_start, angle_end, angle_delta = 0, 361, 5 for angle in range(angle_start, angle_end, angle_delta): print(f"{angle:4}", end=' ') # List of arguments for image creating function, canvas 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}") # Transform img-slice of canvas to color -image by applying cv2-colormap canvas[slice_frame((hic, wic), img.shape)] = cv2.applyColorMap(img.astype(np.uint8), 2) # Add red rectangle to canvas to highlight frame 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) cv2.destroyAllWindows()
关键修改点说明
- RotatedRect坐标修正:将
RotatedRect的center改为(wica//2, hica//2),确保符合OpenCV的笛卡尔坐标规范,旋转后的矩形位置准确。 - 三角函数计算包围框尺寸:通过
cosθ和sinθ直接计算旋转后最小包围框的宽高,避免boundingRect的角度相关误差,结果更可靠。 - 逆旋转坐标映射:先生成包围框的网格索引,再通过逆旋转将这些索引映射回原帧的坐标,这样生成的图像自然包含原帧旋转后的所有内容,无裁剪。
- 移除角度分支:统一的逻辑适用于所有0-360度的角度,避免分支带来的逻辑不一致问题。
测试这段代码后,你会发现所有角度下旋转后的图像都能完全覆盖红色目标帧,蓝色包围框也能正确对齐,90度等特殊角度的偏移问题也会消失。
内容的提问来源于stack exchange,提问作者rare
相关产品推荐
相关产品推荐

