一种方法,通常称为傅里叶梅林变换,并发布为:
B. 斯里尼瓦萨·雷迪 (B. Srinivasa Reddy) 和 B.N. Chatterji,“基于 FFT 的平移、旋转和比例不变图像配准技术”,IEEE 传输。关于图像处理。5(8):1266-1271, 1996
使用 FFT 和对数极坐标变换来获得一幅图像的平移、旋转和缩放以匹配另一幅图像。我发现本教程 https://sthoduka.github.io/imreg_fmt/为了非常清晰和翔实,我将在这里进行总结:
- 计算两个图像的 FFT 幅度(首先应用加窗函数以避免 FFT 的周期性问题)。
- 计算频域图像幅度的对数极坐标变换(通常首先应用高通滤波器,但我还没有看到它的用处)。
- 计算两者之间的互相关(实际上是相位相关)。这导致了关于比例和旋转的知识。
- 将缩放和旋转应用于原始输入图像之一。
- 在缩放和旋转校正后,计算原始输入图像的互相关(实际上是相位相关)。这导致了对翻译的了解。
这是有效的,因为:
FFT 的幅度是平移不变的,我们可以只关注缩放和旋转,而不用担心平移。请注意,图像的旋转与 FFT 的旋转相同,并且图像的缩放与 FFT 的缩放相反。
对数极坐标变换将旋转转换为垂直平移,并将缩放转换为水平平移。相位相关性使我们能够确定这些平移。将它们转换为旋转和缩放并非易事(尤其是缩放很难正确,但一些数学可以说明方法)。
如果上面链接的教程不够清楚,可以看一下附带的C++代码 https://github.com/sthoduka/imreg_fmt/, or at 其他 Python 代码 https://www.lfd.uci.edu/~gohlke/code/imreg.py.html.
OP有兴趣仅在旋转方面上面的方法。如果我们可以假设平移为 0(这意味着我们知道围绕哪个点进行旋转,如果我们不知道原点,我们需要将其估计为平移),那么我们不需要计算幅度FFT 的(记住它用于使问题平移不变),我们可以将对数极坐标变换直接应用于图像。但请注意,我们需要使用旋转中心作为对数极坐标变换的原点。如果我们另外假设缩放比例为 1,我们可以通过线性-极坐标变换进一步简化事情。也就是说,我们只需对半径轴进行对数缩放来估计缩放。
我相信,OP 的做法或多或少是正确的。 OP的代码出错的地方是极坐标变换中半径轴的范围。通过一直到图像的最角落,OpenCV 需要用零填充变换后的图像的部分部分。这些部分由图像的形状决定,而不是由图像的内容决定。也就是说,两个极坐标图像在图像内容和填充的零之间包含完全相同的清晰、高对比度曲线。相位相关性使这些曲线对齐,从而得出 0 度旋转的估计。图像内容或多或少被忽略,因为其对比度低得多。
相反,使半径轴的范围为完全适合图像的最大圆。这样,输出的任何部分都不需要用零填充,并且相位相关可以集中于实际图像内容。此外,考虑到两个图像是彼此的旋转版本,图像角落的数据很可能不匹配,根本不需要考虑这一点!
这是我根据OP的代码快速实现的代码。我在 Lena 中读到,将图像旋转 38 度,计算原始图像和旋转图像的线性极坐标变换,然后计算这两者之间的相位相关性,然后根据垂直平移确定旋转角度。结果是 37.99560,非常接近 38。
import cv2
import numpy as np
base_img = cv2.imread('lena512color.tif')
base_img = np.float32(cv2.cvtColor(base_img, cv2.COLOR_BGR2GRAY)) / 255.0
(h, w) = base_img.shape
(cX, cY) = (w // 2, h // 2)
angle = 38
M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
curr_img = cv2.warpAffine(base_img, M, (w, h))
cv2.imshow("base_img", base_img)
cv2.imshow("curr_img", curr_img)
base_polar = cv2.linearPolar(base_img,(cX, cY), min(cX, cY), 0)
curr_polar = cv2.linearPolar(curr_img,(cX, cY), min(cX, cY), 0)
cv2.imshow("base_polar", base_polar)
cv2.imshow("curr_polar", curr_polar)
(sx, sy), sf = cv2.phaseCorrelate(base_polar, curr_polar)
rotation = -sy / h * 360;
print(rotation)
cv2.waitKey(0)
cv2.destroyAllWindows()
这些是代码显示的四个图像窗口: