【问题标题】:Get rotational shift using phase correlation and log polar transform使用相位相关和对数极坐标变换获得旋转位移
【发布时间】:2019-09-05 08:07:59
【问题描述】:

我一直在编写一个脚本,该脚本使用 cv2 的 phaseCorrelate 方法计算两个图像之间的旋转位移。

我有两张图片,第二张是第一张图片的 90 度旋转版本。加载图像后,我将它们转换为对数极坐标,然后将它们传递给phaseCorrelate 函数。

根据我的阅读,我相信这应该会在两个图像之间产生旋转偏移。

下面的代码描述了实现。


#bitwise right binary shift function
def rshift(val, n): return (val % 0x100000000)

base_img = cv2.imread('img1.jpg')
cur_img = cv2.imread('dataa//t_sv_1.jpg')

curr_img = rotateImage(cur_img, 90)

rows,cols,chan = base_img.shape
x, y, c = curr_img.shape

#convert images to valid type
ref32 = np.float32(cv2.cvtColor(base_img, cv2.COLOR_BGR2GRAY))
curr32 = np.float32(cv2.cvtColor(curr_img, cv2.COLOR_BGR2GRAY))

value = np.sqrt(((rows/2.0)**2.0)+((cols/2.0)**2.0))
value2 = np.sqrt(((x/2.0)**2.0)+((y/2.0)**2.0))

polar_image = cv2.linearPolar(ref32,(rows/2, cols/2), value, cv2.WARP_FILL_OUTLIERS)
log_img = cv2.linearPolar(curr32,(x/2, y/2), value2, cv2.WARP_FILL_OUTLIERS) 

shift = cv2.phaseCorrelate(polar_image, log_img)

sx = shift[0][0]
sy = shift[0][1]
sf = shift[1]

polar_image = polar_image.astype(np.uint8)
log_img = log_img.astype(np.uint8)

cv2.imshow("Polar Image", polar_image)
cv2.imshow('polar', log_img)

#get rotation from shift along y axis
rotation = sy * 180 / (rshift(y, 1));
print(rotation) 

cv2.waitKey(0)
cv2.destroyAllWindows()

我不确定如何解释这个函数的结果。预期的结果是一个类似于 90 度的值,但是,我得到了下面的值。

Output: -0.00717516014538333

如何使输出正确?

【问题讨论】:

  • 您不必使用 180/pi 从弧度转换为度数吗?
  • @fmw42 你好,我在我的代码中实现了这个(弧度到度数的公式),但是,结果仍然没有意义。
  • Stephen Meschke 下面的回答不能解决您的问题吗?
  • @DylanFreeman 你能更好地解释你的问题吗?本质上,问题是给定两张图像,目标是确定两张图像之间的旋转度数?
  • @nathancy 嗨,Nathan,是的,完全正确。

标签: python opencv image-processing computer-vision


【解决方案1】:

一种方法,通常称为傅里叶梅林变换,并发布为:

B. Srinivasa Reddy 和 B.N. Chatterji,“一种基于 FFT 的平移、旋转和比例不变图像配准技术”,IEEE Trans.在图像处理上。 5(8):1266-1271, 1996

使用 FFT 和对数极坐标变换来获得一幅图像的平移、旋转和缩放以匹配另一幅图像。我觉得this tutorial 说的很清楚也很翔实,我在这里做个总结:

  1. 计算两幅图像的 FFT 幅度(首先应用窗口函数以避免 FFT 的周期性问题)。
  2. 计算频域图像幅度的对数极坐标变换(通常首先应用高通滤波器,但我没有看到它的用处)。
  3. 计算两者之间的互相关(实际上是相位相关)。这导致了规模和旋转的知识。
  4. 将缩放和旋转应用到原始输入图像之一。
  5. 在对缩放和旋转进行校正后,计算原始输入图像的互相关(实际上是相位相关)。这导致了翻译知识。

之所以有效,是因为:

  1. FFT 的幅度是平移不变的,我们可以只关注缩放和旋转而不用担心平移。请注意,图像的旋转与 FFT 的旋转相同,并且图像的缩放与 FFT 的缩放相反。

  2. 对数极坐标变换将旋转转换为垂直平移,并将缩放转换为水平平移。相位相关性使我们能够确定这些平移。将它们转换为旋转和缩放并非易事(尤其是缩放很难正确,但一些数学说明了方法)。

如果上面链接的教程不够清楚,可以看the C++ code that comes with it,或者this other Python code


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()

这些是代码显示的四个图像窗口:

【讨论】:

  • 嗨,克里斯,这太棒了,非常感谢!我非常感谢所付出的努力。
【解决方案2】:

我创建了一个图形,显示了多次旋转的相位相关值。这已被编辑以反映 Cris Luengo 的评论。裁剪图像以去除方形插入的边缘。

import cv2
import numpy as np
paths = ["lena.png", "rotate45.png", "rotate90.png", "rotate135.png", "rotate180.png"]

import os
os.chdir('/home/stephen/Desktop/rotations/')

images, rotations, polar = [],[], []

for image_path in paths:
    alignedImage = cv2.imread('lena.png')
    rotatedImage = cv2.imread(image_path)

    rows,cols,chan = alignedImage.shape
    x, y, c = rotatedImage.shape

    x,y,w,h = 220,220,360,360
    alignedImage = alignedImage[y:y+h, x:x+h].copy()
    rotatedImage = rotatedImage[y:y+h, x:x+h].copy()

    #convert images to valid type
    ref32 = np.float32(cv2.cvtColor(alignedImage, cv2.COLOR_BGR2GRAY))
    curr32 = np.float32(cv2.cvtColor(rotatedImage, cv2.COLOR_BGR2GRAY))

    value = np.sqrt(((rows/2.0)**2.0)+((cols/2.0)**2.0))
    value2 = np.sqrt(((x/2.0)**2.0)+((y/2.0)**2.0))

    polar_image = cv2.linearPolar(ref32,(rows/2, cols/2), value, cv2.WARP_FILL_OUTLIERS)
    log_img = cv2.linearPolar(curr32,(x/2, y/2), value2, cv2.WARP_FILL_OUTLIERS) 

    shift = cv2.phaseCorrelate(polar_image, log_img)
    (sx, sy), sf = shift

    polar_image = polar_image.astype(np.uint8)
    log_img = log_img.astype(np.uint8)

    sx, sy, sf = round(sx, 4), round(sy, 4), round(sf, 4)
    text = image_path + "\n" + "sx: " + str(sx) + " \nsy: " + str(sy) + " \nsf: " + str(sf)

    images.append(rotatedImage)
    rotations.append(text)
    polar.append(polar_image)

【讨论】:

  • 这个答案没有代码是没有帮助的。请出示您的代码。
  • OP 在 90 度的倍数方面存在问题。在您的示例中,您可以看到方形插图的边缘如何在极坐标变换中引起非常重要的对比度,相关性会比实际图像内容更关注这一点。您应该忽略最右边约 60% 的极坐标变换图像,它只包含方形图像的边缘和图像角落中不太重要的东西。也就是说,只在图像的圆形部分寻找旋转。
  • @CrisLuengo,我裁剪了图像并将新图编辑到我的帖子中。我仍然没有得到有意义的价值观。我也需要裁剪极坐标图像吗?
  • 不要使用(rows/2, cols/2) 作为polar_image 的原点,而是使用(x/2, y/2),因为那是图像的中心。然后你的极地图像应该看起来更像以前。接下来,将极坐标图像裁剪为一个矩形区域,该区域不包含图像外部的任何像素。 -- 也许你可以通过将maxRadius 参数设置为min(x/2,y/2) 来实现。
  • @StephenMeschke 感谢您抽出宝贵的时间斯蒂芬,我真的很感激。有没有办法将移位值转换为度数以确定旋转?谢谢,
【解决方案3】:

这是一种确定两个图像之间旋转偏移的方法(以度为单位)。这个想法是找到每个图像相对于水平线的倾斜角度。如果我们能找到这个倾斜的角度,那么我们就可以计算出两幅图像之间的角度差。这里有一些示例图片来说明这个概念

原始未旋转图像

逆时针旋转 10 度 (neg_10) 和逆时针旋转 35 度 (neg_35)

顺时针旋转 7.9 度 (pos_7_9) 和顺时针旋转 21 度 (pos_21)


对于每张图像,我们想要确定相对于水平线的倾斜角度,负值逆时针旋转,正值顺时针旋转

这是确定倾斜角度的辅助函数

def compute_angle(image):
    # Convert to grayscale, invert, and Otsu's threshold
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = 255 - gray
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

    # Find coordinates of all pixel values greater than zero
    # then compute minimum rotated bounding box of all coordinates
    coords = np.column_stack(np.where(thresh > 0))
    angle = cv2.minAreaRect(coords)[-1]

    # The cv2.minAreaRect() function returns values in the range
    # [-90, 0) so need to correct angle
    if angle < -45:
        angle = -(90 + angle)
    else:
        angle = -angle

    # Rotate image to horizontal position 
    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
              borderMode=cv2.BORDER_REPLICATE)

    return (angle, rotated)

确定每张图像的倾斜角度后,我们可以简单地计算差异

angle1, rotated1 = compute_angle(image1)
angle2, rotated2 = compute_angle(image2)

# Both angles are positive
if angle1 >= 0 and angle2 >= 0:
    difference_angle = abs(angle1 - angle2)
# One positive, one negative
elif (angle1 < 0 and angle2 > 0) or (angle1 > 0 and angle2 < 0):
    difference_angle = abs(angle1) + abs(angle2)
# Both negative
elif angle1 < 0 and angle2 < 0:
    angle1 = abs(angle1)
    angle2 = abs(angle2)
    difference_angle = max(angle1, angle2) - min(angle1, angle2)

这是正在发生的事情的分步介绍。使用pos_21neg_10compute_angle() 函数将返回倾斜角度和归一化图像

对于pos_21,我们对图像进行归一化并确定倾斜角度。左(前)-&gt;右(后)

20.99871826171875

neg_10 类似,我们也对图像进行归一化并确定倾斜角度。左(前)-&gt;右(后)

-10.007980346679688

现在我们有两个角度,我们可以计算差角。这是结果

31.006698608398438


这是其他组合的结果。使用neg_10neg_35 我们得到

24.984039306640625

使用pos_7_9pos_21

13.09155559539795

最后是pos_7_9neg_35

42.89918231964111

这是完整的代码

import cv2
import numpy as np

def rotational_shift(image1, image2):
    def compute_angle(image):
        # Convert to grayscale, invert, and Otsu's threshold
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        gray = 255 - gray
        thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

        # Find coordinates of all pixel values greater than zero
        # then compute minimum rotated bounding box of all coordinates
        coords = np.column_stack(np.where(thresh > 0))
        angle = cv2.minAreaRect(coords)[-1]

        # The cv2.minAreaRect() function returns values in the range
        # [-90, 0) so need to correct angle
        if angle < -45:
            angle = -(90 + angle)
        else:
            angle = -angle

        # Rotate image to horizontal position 
        (h, w) = image.shape[:2]
        center = (w // 2, h // 2)
        M = cv2.getRotationMatrix2D(center, angle, 1.0)
        rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
                  borderMode=cv2.BORDER_REPLICATE)

        return (angle, rotated)

    angle1, rotated1 = compute_angle(image1)
    angle2, rotated2 = compute_angle(image2)

    # Both angles are positive
    if angle1 >= 0 and angle2 >= 0:
        difference_angle = abs(angle1 - angle2)
    # One positive, one negative
    elif (angle1 < 0 and angle2 > 0) or (angle1 > 0 and angle2 < 0):
        difference_angle = abs(angle1) + abs(angle2)
    # Both negative
    elif angle1 < 0 and angle2 < 0:
        angle1 = abs(angle1)
        angle2 = abs(angle2)
        difference_angle = max(angle1, angle2) - min(angle1, angle2)

    return (difference_angle, rotated1, rotated2)

if __name__ == '__main__':
    image1 = cv2.imread('pos_7_9.png')
    image2 = cv2.imread('neg_35.png')

    angle, rotated1, rotated2 = rotational_shift(image1, image2)
    print(angle)

【讨论】:

    猜你喜欢
    • 2022-08-17
    • 2020-03-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-25
    • 2015-01-17
    • 1970-01-01
    • 1970-01-01
    • 2017-03-06
    相关资源
    最近更新 更多