通过旋转NumPy的索引来生成旋转后的图像,这个方法只在四个区域中的两个区域起作用
我正在编写一个Python程序,逐帧用某个函数计算图像,以生成视频。
有时需要对图像进行旋转(不裁剪边缘)。起初,我按照在 OpenCV Python: Rotate image without cropping sides 中所述的方法对图像进行旋转,但在旋转之前显著增大了图像区域。
现在我在努力让程序更快,并尝试仅对索引进行旋转,并尽量把尺寸缩小到最小必要的程度。
不过,a/m方案只能得到近似结果(图像会略微裁切),因此我改为使用 cv2.boundingRectangle 来计算索引的大小。对于在 -45 < angle < 45 到 135 <= angle < 225 之间的角度,这种方法工作得很完美。然而,在其余扇区,旋转后的图像再也无法与(蓝色的)边界框对齐,且不能完全覆盖整个画面。
这是我简化后的代码。我还引入了canvas,用来显示帧外发生的情况(红色矩形)。只有红框内的数据会被使用。它应该被旋转并放大后的图像完全覆盖:
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 cv2's .boundingRect()'''
canvas = np.zeros((hica, wica, 3), np.uint8) # To display contours outside frame
rotim = cv2.RotatedRect((hica//2, wica//2), (hifr, wifr), angle) # Rotate frame
add_cnt(canvas, rotim, (0, 55, 0), 1)
# Dimension of enclosing rectangle (at angle) for rotated frame
x, y, w, h = rotim.boundingRect()
# Rotate + add enclosing rectangle (borec) covering complete fixed frame
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):
# Center of rotation for RotationMatrix
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: # For these angles the rotated image doesn't fully cover frame
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 # 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)
下面是一些图片来说明这个问题:
0度,未旋转。计算出的图像完全覆盖了帧。
角度达到45度时,旋转并放大的图像仍与蓝色框对齐,因此也能完全覆盖(红色)框。
在角度超过45度(直到135度;这里是60度)时,计算出的图像不再与蓝色边界对齐,也不能像应有的那样完全覆盖红框。
在旋转角度为90度时,误差最大;渲染出的图像部分地向画面右下侧偏移,超出边框,而不是将其填满。对于更高的旋转角度,过程对称地重复,直到渲染的图像再次与框对齐(从135度到225度)。
解决方案
我已经写了很多关于你代码其他部分的内容。然后我把那些都删掉了。这些问题是次要的。你可以自己推断解决,不需要让我指出。
你的主要问题在于你认为可以在点/索引数组上使用 warpAffine()。
warpAffine() 是用来对图像进行扭曲(warp),而不是用于点的数组。
如果你想把变换应用到一个点的数组(列表),请使用 cv::transform()。
在对 transform() 进行变换之前和之后,你需要重新调整数组的形状,因为它期望的输入是列向量形式或Nx2矩阵,而不是一个 HxW 矩阵(HxWx2或 2xHxW,或在 np.indices() 完成后你那里得到的任何形状)。



