英文:
Difference in rotated image when using cv.rotate and cv.warpAffine
问题
我观察到使用cv.warpAffine
方法和cv.rotate
方法进行旋转时存在差异。生成的图像不同 - 虽然cv.rotate
是完美的,即旋转后的图像没有部分被截断,但cv.warpAffine
生成的图像有部分被截断。附上示例图像和这两种方法的结果。
配置信息:python:3.9.13;opencv-python 4.7.0;Windows 10
示例:
原始图像:
使用cv.rotate
后的图像:
使用cv.warpAffine
后的图像:
重现问题的代码:
>>> img = cv.imread(<图像路径>, cv.IMREAD_GRAYSCALE)
>>> rot_img = cv.rotate(img, cv.ROTATE_90_COUNTERCLOCKWISE)
>>> cv.imwrite('cv_rotate.jpg', rot_img)
True
>>> img_center = (img.shape[1]//2, img.shape[0]//2)
>>> M = cv.getRotationMatrix2D(img_center, 90, 1)
>>> rot_img = cv.warpAffine(img, M, (img.shape[0], img.shape[1]))
>>> cv.imwrite('cv_warpAffine.jpg', rot_img)
True
我期望这两种方法生成相同的输出。为什么会有差异?我尝试了一些其他方法,怀疑是不是弄混了(行、列)格式和(x、y)格式(即使在生成ndarray输出时,OpenCV为什么要使用不同的样式我也不知道),但这些方法没有起作用。请有人告诉我这里的问题是什么?
英文:
I have observed that there is a difference between rotation using cv.warpAffine method and the cv.rotate method. The resulting images are different - while cv.rotate is perfect i.e. rotated image with no parts of the image getting cutoff, cv.warpAffine generates an image that has part of the image cutoff. The example image and the results of the 2 methods are attached.
Configuration: python:3.9.13; opencv-python 4.7.0; Windows 10
Samples:
original:
cv.rotate:
cv.warpAffine:
Code to reproduce problem
>>> img = cv.imread(<path to image>, cv.IMREAD_GRAYSCALE)
>>> rot_img = cv.rotate(img, cv.ROTATE_90_COUNTERCLOCKWISE)
>>> cv.imwrite('cv_rotate.jpg', rot_img)
True
>>> img_center = (img.shape[1]//2, img.shape[0]//2)
>>> M = cv.getRotationMatrix2D(img_center, 90, 1)
>>> rot_img = cv.warpAffine(img, M, (img.shape[0], img.shape[1]))
>>> cv.imwrite('cv_warpAffine.jpg', rot_img)
True
I was expecting both the methods to generate the same output. Why is there a difference? I have tried a few other ways anticipating that I am messing up the (row, col) format with the (x, y) format (why OpenCV uses a different style even when producing ndarray output I don't know) but those did not work. Can someone please let me know what's the issue here?
答案1
得分: 2
如果您只需要90度的旋转,请使用 cv.rotate()
。它以90度的步长旋转,不会重新采样或插值。
在评论中,Cris Luengo建议使用 np.rot90()
,这可能是一项非常便宜的操作,因为NumPy可以计算新的步幅并为您提供原始数据的视图。cv.rotate()
不会这样做,因为内部使用的 cv::Mat
不像NumPy数组那样灵活。
如果旋转中心正确,您可以使用 cv.getRotationMatrix2D()
来实现所需的结果。
如果选择矩形的中心作为旋转中心,那只会围绕该点旋转图像,结果就是您已经看到的:结果是绿色矩形,红色矩形表示图像内容。
如果源和结果的左上角原点重合,矩形旋转90度的“正确”旋转中心取决于矩形的边长和旋转方向。考虑两个正方形,一个对应于矩形的每个边长。旋转中心位于一个正方形的中心,取决于旋转方向。
...
>>> ih, iw = img.shape[:2]
>>> img_center = (iw-1)/2, (iw-1)/2
>>> M = cv.getRotationMatrix2D(img_center, +90, 1)
...
>>> img_center = (ih-1)/2, (ih-1)/2
>>> M = cv.getRotationMatrix2D(img_center, -90, 1)
您还可以从基本元素构建自己的变换:
def translate(tx=0, ty=0):
T = np.eye(3)
T[:2, 2] = (tx, ty)
return T
def rotate(angle):
T = np.eye(3)
T[:2, :] = cv.getRotationMatrix2D((0,0), angle, 1)
# 它在X右、Y下方向上逆时针旋转
return T
angle = 90
(ih, iw) = img.shape[:2]
ow, oh = ih, iw # 仅适用于90度旋转
icx, icy = (iw-1)/2, (ih-1)/2
ocx, ocy = (ow-1)/2, (oh-1)/2
T = translate(+ocx, +ocy) @ rotate(angle) @ translate(-icx, -icy)
rot_img = cv.warpAffine(img, M=T[:2], dsize=(ow, oh))
要扭曲点而不是图像,有用于仿射变换的 cv.transform()
函数。
点需要作为形状为 (N, 2)
或 (N, 1, 2)
的NumPy数组给出。
对于透视变换,有 cv.perspectiveTransform()
。它会处理“除以w”的操作。
英文:
If you only need 90 degree rotations, stick with cv.rotate()
. This rotates in steps of 90 degrees. It does not resample or interpolate.
In the comments, Cris Luengo suggests np.rot90()
, which potentially is an extremely cheap operation because numpy can just calculate new strides and give you a view on the original data. cv.rotate()
does not do that because the internally used cv::Mat
isn't as flexible as a numpy array.
You can achieve the desired result with cv.getRotationMatrix2D()
if the center of rotation is correct.
If you pick the center of the rectangle, that will just rotate the image around that point, and result in what you've already seen: the result is the green rectangle, with the red rectangle representing the image content.
If the top left origins of source and result coincide, the "correct" center of rotation for a 90 degree rotation of a rectangle depends on the rectangle's side lengths and the direction of the rotation. Consider two squares, one for each side length of the rectangle. The center of rotation lies in the center of one or the other square, depending on direction.
...
>>> ih, iw = img.shape[:2]
>>> img_center = (iw-1)/2, (iw-1)/2
>>> M = cv.getRotationMatrix2D(img_center, +90, 1)
...
>>> img_center = (ih-1)/2, (ih-1)/2
>>> M = cv.getRotationMatrix2D(img_center, -90, 1)
You can also build your own transformation from primitives:
def translate(tx=0, ty=0):
T = np.eye(3)
T[:2, 2] = (tx, ty)
return T
def rotate(angle):
T = np.eye(3)
T[:2, :] = cv.getRotationMatrix2D((0,0), angle, 1)
# it's rotating counterclockwise in X-right, Y-down
return T
angle = 90
(ih, iw) = img.shape[:2]
ow,oh = ih,iw # only valid for 90 degree rotation
icx, icy = (iw-1)/2, (ih-1)/2
ocx, ocy = (ow-1)/2, (oh-1)/2
T = translate(+ocx, +ocy) @ rotate(angle) @ translate(-icx, -icy)
rot_img = cv.warpAffine(img, M=T[:2], dsize=(ow, oh))
To warp points instead of images, there is the function cv.transform()
for affine transformations.
Points need to be given as a numpy array of shape (N, 2)
or (N, 1, 2)
.
For perspective transforms, there is cv.perspectiveTransform()
. It takes care of the "division by w".
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论