我试图在python中实现一个算法,通过一个因子来缩放图像或者将它们旋转一个给定的角度(或同时两个)。我使用opencv来处理图像,我知道opencv内置了这些功能,但是我想自己这样做以更好地理解图像转换。我相信我正确地计算了旋转矩阵。但是,当我尝试实现仿射变换时,它并没有正确出现。
import numpy as np
import cv2
import math as m
import sys
img = cv2.imread(sys.argv[1])
angle = sys.argv[2]
#get rotation matrix
def getRMat((cx, cy), angle, scale):
a = scale*m.cos(angle*np.pi/180)
b = scale*(m.sin(angle*np.pi/180))
u = (1-a)*cx-b*cy
v = b*cx+(1-a)*cy
return np.array([[a,b,u], [-b,a,v]])
#determine shape of img
h, w = img.shape[:2]
#print h, w
#determine center of image
cx, cy = (w / 2, h / 2)
#calculate rotation matrix
#then grab sine and cosine of the matrix
mat = getRMat((cx,cy), -int(angle), 1)
print mat
cos = np.abs(mat[0,0])
sin = np.abs(mat[0,1])
#calculate new height and width to account for rotation
newWidth = int((h * sin) + (w * cos))
newHeight = int((h * cos) + (w * sin))
#print newWidth, newHeight
mat[0,2] += (newWidth / 2) - cx
mat[1,2] += (newHeight / 2) - cy
#this is how the image SHOULD look
dst = cv2.warpAffine(img, mat, (newWidth, newHeight))
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
#apply transform
#attempt at my own warp affine function...still buggy tho
def warpAff(image, matrix, (width, height)):
dst = np.zeros((width, height, 3), dtype=np.uint8)
oldh, oldw = image.shape[:2]
#print oldh, oldw
#loop through old img and transform its coords
for x in range(oldh):
for y in range(oldw):
#print y, x
#transform the coordinates
u = int(x*matrix[0,0]+y*matrix[0,1]+matrix[0,2])
v = int(x*matrix[1,0]+y*matrix[1,1]+matrix[1,2])
#print u, v
#v -= width / 1.5
if (u >= 0 and u < height) and (v >= 0 and v < width):
dst[u,v] = image[x,y]
return dst
dst = warpAff(img, mat, (newWidth, newHeight))
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
你正在向后应用旋转。
这意味着对于20°的角度,而不是顺时针旋转20度,逆时针旋转20度。这本身就很容易解决 - 只是否定角度。
但这也意味着,对于每个目标像素,如果没有源像素精确旋转到它,最终会得到一个全黑像素。您可以通过使用任何插值算法来解决这个问题,但它会使事情变得更复杂。
如果我们只是改变过程,而不是为每个(u, v)
计算目标(x, y)
,我们计算每个目的地(x, y)
的源(u, v)
,这解决了两个问题:
def warpAff(image, matrix, width, height):
dst = np.zeros((width, height, 3), dtype=np.uint8)
oldh, oldw = image.shape[:2]
# Loop over the destination, not the source, to ensure that you cover
# every destination pixel exactly 1 time, rather than 0-4 times.
for u in range(width):
for v in range(height):
x = u*matrix[0,0]+v*matrix[0,1]+matrix[0,2]
y = u*matrix[1,0]+v*matrix[1,1]+matrix[1,2]
intx, inty = int(x), int(y)
# We could interpolate here by using something like this linear
# interpolation matrix, but let's keep it simple and not do that.
# fracx, fracy = x%1, y%1
# interp = np.array([[fracx*fracy, (1-fracx)*fracy],
# [fracx*(1-fracy), (1-fracx)*(1-fracy)]])
if 0 < x < oldw and 0 < y < oldh:
dst[u, v] = image[intx, inty]
return dst
现在唯一剩下的问题是你没有向后移动,所以当我们转动其他所有东西时,我们最终会朝错误的方向移动图像。修复这个问题很简单:
mat[0,2] += cx - (newWidth / 2)
mat[1,2] += cy - (newHeight / 2)
您还有一个问题:您的代码(以及此更新的代码)仅适用于方形图像。你的高度和宽度多次向后,它们几乎全部抵消,但显然其中一个没有。一般来说,你将你的数组视为(width, height)
而不是(height, width)
,但你最终比较(原始版本)或循环(新版本)(height, width)
。因此,如果高度和宽度不同,您最终会尝试写入数组的末尾。
试图找到所有这些并修复它们的工作可能与刚开始并从一开始就始终如一地执行它一样多:
mat = getRMat(cx, cy, int(angle), 1)
cos = np.abs(mat[0,0])
sin = np.abs(mat[0,1])
newWidth = int((h * sin) + (w * cos))
newHeight = int((h * cos) + (w * sin))
mat[0,2] += cx - (newWidth / 2)
mat[1,2] += cy - (newHeight / 2)
def warpAff2(image, matrix, width, height):
dst = np.zeros((height, width, 3), dtype=np.uint8)
oldh, oldw = image.shape[:2]
for u in range(width):
for v in range(height):
x = u*matrix[0,0]+v*matrix[0,1]+matrix[0,2]
y = u*matrix[1,0]+v*matrix[1,1]+matrix[1,2]
intx, inty = int(x), int(y)
if 0 < intx < oldw and 0 < inty < oldh:
pix = image[inty, intx]
dst[v, u] = pix
return dst
dst = warpAff2(img, mat, newWidth, newHeight)
值得注意的是,有更简单(和更有效)的方法来实现它。如果构建3x3方阵,则可以对乘法进行矢量化。此外,您可以更简单地创建矩阵,只需将移位矩阵@旋转矩阵与非移位矩阵相乘,而不是事后手动修复。但希望这个版本,因为它尽可能接近你的原版,应该是最容易理解的。