【问题标题】:How to detect a shift between images如何检测图像之间的偏移
【发布时间】:2014-09-06 05:45:40
【问题描述】:

我正在分析多个图像,并且需要能够判断它们是否与参考图像相比发生了偏移。目的是判断相机是否在拍摄图像之间移动。理想情况下,我希望能够纠正偏移以便仍然进行分析,但至少我需要能够确定图像是否偏移并在超过某个阈值时将其丢弃。

以下是我想检测的图像变化的一些示例:

我将使用第一张图片作为参考,然后将以下所有图片与其进行比较,以确定它们是否发生了偏移。这些图像是灰度的(它们只是使用热图以彩色显示)并存储在二维 numpy 数组中。任何想法我怎么能做到这一点?我更喜欢使用我已经安装的包(scipy、numpy、PIL、matplotlib)。

【问题讨论】:

标签: python image image-processing scipy


【解决方案1】:

正如Lukas Graf 所暗示的,您正在寻找互相关。如果:

  1. 图片的比例没有太大变化。
  2. 图像中没有旋转变化。
  3. 图像中没有明显的照明变化。

对于简单的翻译,互相关非常好。

最简单的互相关工具是scipy.signal.correlate。但是,它使用简单的方法进行互相关,对于边长为 n 的二维图像,该方法为 O(n^4)。实际上,使用您的图像需要很长时间。

更好的是scipy.signal.fftconvolve,因为卷积和相关性密切相关。

类似这样的:

import numpy as np
import scipy.signal

def cross_image(im1, im2):
   # get rid of the color channels by performing a grayscale transform
   # the type cast into 'float' is to avoid overflows
   im1_gray = np.sum(im1.astype('float'), axis=2)
   im2_gray = np.sum(im2.astype('float'), axis=2)

   # get rid of the averages, otherwise the results are not good
   im1_gray -= np.mean(im1_gray)
   im2_gray -= np.mean(im2_gray)

   # calculate the correlation image; note the flipping of onw of the images
   return scipy.signal.fftconvolve(im1_gray, im2_gray[::-1,::-1], mode='same')

im2_gray[::-1,::-1] 的有趣索引将其旋转 180°(水平和垂直镜像)。这就是卷积和相关的区别,相关是对第二个信号进行镜像的卷积。

现在,如果我们只是将第一个(最上面的)图像与其自身相关联,我们会得到:

这给出了图像自相似性的度量。最亮的点在 (201, 200),它位于 (402, 400) 图像的中心。

可以找到最亮的点坐标:

np.unravel_index(np.argmax(corr_img), corr_img.shape)

最亮像素的线性位置由argmax返回,但必须用unravel_index转换回二维坐标。

接下来,我们通过将第一张图片与第二张图片相关联来尝试相同的方法:

相关性图像看起来相似,但最佳相关性已移至 (149,200),即图像中向上 52 个像素。这是两个图像之间的偏移量。


这似乎适用于这些简单的图像。但是,也可能存在错误的相关峰,并且此答案开头概述的任何问题都可能破坏结果。

在任何情况下,您都应该考虑使用窗口函数。功能的选择并不那么重要,只要用到了一些东西。此外,如果您遇到小旋转或缩放变化的问题,请尝试将几个小区域与周围图像相关联。这将为您在图像的不同位置提供不同的位移。

【讨论】:

  • 感谢您的详尽回答。我有几个问题: 1. im2_gray[::-1,::-1] 做什么? 2. 您是如何找到“最佳相关性”点的?
  • 很好的答案!现在来的任何人都应该注意,现在 scipy.signal.convolve 是更好的选择,因为它选择了最快的算法,对较大的矩阵使用 fftconvolve,对较小的矩阵使用卷积方法。
【解决方案2】:

另一种解决方法是计算两个图像中的筛选点,使用 RANSAC 去除异常值,然后使用最小二乘估计器求解平移。

【讨论】:

    【解决方案3】:

    正如 Bharat 所说,另一个正在使用筛选功能和 Ransac:

    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    
    def crop_region(path, c_p):
        """
          This function crop the match region in the input image
          c_p: corner points
        """    
        # 3 or 4 channel as the original
        img = cv2.imread(path, -1)
    
        # mask 
        mask = np.zeros(img.shape, dtype=np.uint8) 
    
        # fill the the match region 
        channel_count = img.shape[2]  
        ignore_mask_color = (255,)*channel_count
        cv2.fillPoly(mask, c_p, ignore_mask_color)
    
        # apply the mask
        matched_region = cv2.bitwise_and(img, mask)
    
        return matched_region
    
    def features_matching(path_temp,path_train):
        """
              Function for Feature Matching + Perspective Transformation
        """       
        img1 = cv2.imread(path_temp, 0)   # template
        img2 = cv2.imread(path_train, 0)   # input image
    
        min_match=10
    
        # SIFT detector
        sift = cv2.xfeatures2d.SIFT_create()
    
        # extract the keypoints and descriptors with SIFT
    
        kps1, des1 = sift.detectAndCompute(img1,None)
        kps2, des2 = sift.detectAndCompute(img2,None)
    
        FLANN_INDEX_KDTREE = 0
        index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
        search_params = dict(checks = 50)
    
        flann = cv2.FlannBasedMatcher(index_params, search_params)
    
        matches = flann.knnMatch(des1, des2, k=2)
    
        # store all the good matches (g_matches) as per Lowe's ratio 
        g_match = []
        for m,n in matches:
            if m.distance < 0.7 * n.distance:
                g_match.append(m)
        if len(g_match)>min_match:
            src_pts = np.float32([ kps1[m.queryIdx].pt for m in g_match ]).reshape(-1,1,2)
            dst_pts = np.float32([ kps2[m.trainIdx].pt for m in g_match ]).reshape(-1,1,2)
    
            M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
            matchesMask = mask.ravel().tolist()
    
            h,w = img1.shape
            pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
            dst = cv2.perspectiveTransform(pts,M)
    
            img2 = cv2.polylines(img2, [np.int32(dst)], True, (0,255,255) , 3, cv2.LINE_AA)
    
        else:
            print "Not enough matches have been found! - %d/%d" % (len(g_match), min_match)
            matchesMask = None
    
        draw_params = dict(matchColor = (0,255,255), 
                           singlePointColor = (0,255,0),
                           matchesMask = matchesMask, # only inliers
                           flags = 2)
        # region corners    
        cpoints=np.int32(dst)
        a, b,c = cpoints.shape
    
        # reshape to standard format
        c_p=cpoints.reshape((b,a,c))  
    
        # crop matching region
        matching_region = crop_region(path_train, c_p)
    
        img3 = cv2.drawMatches(img1, kps1, img2, kps2, g_match, None, **draw_params)
        return (img3,matching_region)
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多