Python计算机视觉编程第三章 图像到图像的映射

1.单应性变换

单应性变换是将一个平面内的点映射到另一个平面内的二维投影变换。在这里,平面是指图像或者三维中的平面表示。单应性变换具有很强的实用性,比如图像配准,图像纠正和纹理扭曲,以及创建全景图像,我们将频繁的使用单应性变换。本质上,单应性变换H,按照下面的方程映射二维中的点(齐次坐标意义下):

或者

对于图像平面内(甚至是三维中的点,后面我们会介绍到)的点,齐次坐标是个非常有用的表示方式。点的齐次坐标是依赖于其尺度定义的,所以,x=[x,y,w]=[ax,ay,aw]=[x/w,y/w,1]都表示同一个二维点。因此,单应性矩阵H也仅依赖尺度定义,所以,单应性矩阵具有8个独立的自由度。我们通常使用w=1来归一化点,这样,点具有唯一的图像坐标x和y。这个额外的坐标是的我们可以简单地使用一个矩阵来表示变换。

1.1线性变换

单应性矩阵可以有两幅图像(或者平面)中对应点对计算出来。前面已经提到过,一个完全射影变换具有8个自由度。根据对应点约束,每个对应点对可以写出两个方程,分别对应于x和y坐标。因此,计算单应性矩阵H需要4个对应点对。

DLT(Direct Linear Transformation,直接线性变换)是给定4个点或者更多对应点对矩阵,来计算单应性矩阵H的算法。将单应性矩阵H作用在对应点上,重新写出该方程,我们可以得到下面的方程:

或者Ah=0,其中A是一个具有对应点对二倍数量行数的矩阵。将这些对应点对方程的系数堆叠到一个矩阵红,我们可以使用SVD算法找到H的最小二乘解。下面是算法代码:

def H_from_points(fp, tp):
    """使用线性DLT方法,计算单应性矩阵H,使fp映射到tp。点自动进行归一化"""

    if fp.shape != tp.shape:
        raise RuntimeError('number of points do not match')

    # 对点进行归一化(对数值计算很重要)
    # --- 映射起始点 ---
    m = mean(fp[:2], axis=1)
    maxstd = max(std(fp[:2], axis=1)) + 1e-9
    C1 = diag([1/maxstd, 1/maxstd, 1])
    C1[0][2] = -m[0]/maxstd
    C1[1][2] = -m[1]/maxstd
    fp = dot(C1,fp)
    
    # --- 映射对应点 ---
    m = mean(tp[:2], axis=1)
    maxstd = max(std(tp[:2], axis=1)) + 1e-9
    C2 = diag([1 / maxstd, 1 / maxstd, 1])
    C2[0][2] = -m[0] / maxstd
    C2[1][2] = -m[1] / maxstd
    tp = dot(C2, tp)
    
    # 创建用于线性方法的矩阵,对于每个对应对,在矩阵中会出现两行数值
    nbr_correspondences = fp.shape[1]
    A = zeros((2 * nbr_correspondences, 9))
    for i in range(nbr_correspondences):
        A[2*i] = [-fp[0][i], -fp[1][i],-1,0,0,0,
                  tp[0][i]*fp[0][i],tp[0][i]*fp[1][i],tp[0][i]]
        A[2*i+1] = [0,0,0,-fp[0][i],-fp[1][i],-1,
                    tp[1][i]*fp[0][i],tp[1][i]*fp[1][i],tp[1][i]]
        
    U,S,V = linalg.svd(A)
    H = V[8].reshape((3,3))
    
    #反归一化
    H = dot(linalg.inv(C2),dot(H,C1))
    
    #归一化,然后返回
    return H / H[2,2]

代码先对这些点进行归一化操作,使其均值为0,方差为1。因为算法的稳定性取决于坐标的表示情况和部分数值计算的问题,所以归一化操作非常重要。接下来我们使用对应点对来构造矩阵A。最小二乘解即为矩阵SVD分解后所得矩阵V的最后一行。该行经过变换后得到矩阵H。然后对这个矩阵进行处理和归一化,返回输出。

1.2仿射变换

由于仿射变换具有6个自由度,因此我们需要三个对应点来估计矩阵H。通过将最后两个元素设置为0,即h7=h8=0,仿射变换可以用上面的DLT算法估计得出。

def Haffine_from_points(fp, tp):
    """计算H仿射变换,使得tp是fp经过仿射变换H得到的"""

    if fp.shape != tp.shape:
        raise RuntimeError('number of points do not match')
        
    # 对点进行归一化(对数值计算很重要)
    # --- 映射起始点 ---
    m = mean(fp[:2], axis=1)
    maxstd = max(std(fp[:2], axis=1)) + 1e-9
    C1 = diag([1/maxstd, 1/maxstd, 1]) 
    C1[0][2] = -m[0]/maxstd
    C1[1][2] = -m[1]/maxstd
    fp_cond = dot(C1,fp)
    
    # --- 映射对应点 ---
    m = mean(tp[:2], axis=1)
    C2 = C1.copy() # 两个点集,必须都进行相同的缩放
    C2[0][2] = -m[0]/maxstd
    C2[1][2] = -m[1]/maxstd
    tp_cond = dot(C2,tp)
    
    # 因为归一化后点的均值为0,所以平移量为0
    A = concatenate((fp_cond[:2],tp_cond[:2]), axis=0)
    U,S,V = linalg.svd(A.T)
    
    # 如Hartley和Zisserman著的Multiplr View Geometry In Computer,Scond Edition所示,
    # 创建矩阵B和C
    tmp = V[:2].T
    B = tmp[:2]
    C = tmp[2:4]
    
    tmp2 = concatenate((dot(C,linalg.pinv(B)),zeros((2,1))), axis=1) 
    H = vstack((tmp2,[0,0,1]))
    
    # 反归一化
    H = dot(linalg.inv(C2),dot(H,C1))
    
    return H / H[2,2]

2.图像扭曲

对图像块应用仿射变换,我们将其称为图像扭曲(或者仿射扭曲)。该操作不仅经常应用在计算机图形学中,而且经常出现在计算机视觉算法中。扭曲操作可以使用SciPy 工具包中的 ndimage 包来简单完成。命令:
transformed_im = ndimage.affine_transform(im,A,b,size)使用如上所示的一个线性变换 A 和一个平移向量 b 来对图像块应用仿射变换。选项参数 size 可以用来指定输出图像的大小。默认输出图像设置为和原始图像同样大小。
仿射变换可以用于很多应用,比如图像扭曲。

或者

实现代码:

from matplotlib.pyplot import *
from numpy import array
from scipy import ndimage
from PIL import Image

im = array(Image.open('dog.jpg').convert('L'))
H = array([[1.4,0.05,-100],[0.05,1.5,-100],[0,0,1]])
im2 = ndimage.affine_transform(im, H[:2,:2],(H[0,2],H[1,2]))

gray()
subplot(121)
imshow(im)
axis('off')
subplot(122)
imshow(im2)
axis('off')
show()

运行结果:

2.1图像中的图像

仿射扭曲的一个简单例子是,将图像或者图像的一部分放置在另一幅图像中,使得它们能够和指定的区域或者标记物对齐。
将函数 image_in_image() 添加到 warp.py 文件中。该函数的输入参数为两幅图像和一个坐标。该坐标为将第一幅图像放置到第二幅图像中的角点坐标:

def image_in_image(im1, im2, tp):
    """使用仿射变换将im1放置在im2上,使im1图像的角和tp尽可能的靠近
        tp是齐次表示的,并且是按照从左上角逆时针计算的"""
    
    # 扭曲的点
    m,n = im1.shape[:2]
    fp = array([[0,m,m,0],[0,0,n,n],[1,1,1,1]])
    
    # 计算仿射变换,并且将其应用于图像im1中
    H = Haffine_from_points(tp,fp)
    im1_t = ndimage.affine_transform(im1,H[:2,:2],
                    (H[0,2],H[1,2]),im2.shape[:2])
    alpha = (im1_t > 0)
    
    return (1-alpha)*im2 + alpha*im1_t

使用仿射变换将一幅图像放置到另一幅图像中:

from PCV.geometry.warp import image_in_image
from matplotlib.pyplot import *
from numpy import array
from PIL import Image

im1 = array(Image.open('dog.jpg').convert('L'))
im2 = array(Image.open('flower.jpg').convert('L'))

gray()
subplot(131)
imshow(im1)
axis('equal')
axis('off')
subplot(132)
imshow(im2)
axis('equal')
axis('off')

# 选定一些目标点
tp = array([[264, 538, 540, 264], [40, 36, 605, 605], [1, 1, 1, 1]])

im3 = image_in_image(im1,im2,tp)
subplot(133)
imshow(im3)
axis('equal')
axis('off')
show()

运行结果:

2.2对三角形区域进行仿射变换

函数 Haffine_from_points() 会返回给定对应点对的最优仿射变换。在上面的例子中,对应点对为图像和广告牌的角点。如果透视效应比较弱,那么这种方法会返回很好的结果。在具有很强透视效应的情况下,在广告牌图像上使用射影变换输出图像的情况。在这种情况下,我们不可能使用同一个仿射变换将全部 4 个角点变换到它们的目标位置。所以,当你打算使用仿射变换时,有一个很有用的技巧。
对于三个点,仿射变换可以将一幅图像进行扭曲,使这三对对应点对可以完美地匹配上。这是因为,仿射变换具有 6 个自由度,三个对应点对可以给出 6 个约束条件(对于这三个对应点对,x 和 y 坐标必须都要匹配)。所以,如果你真的打算使用仿射变换将图像放置到公告牌上,可以将图像分成两个三角形,然后对它们分别进行扭曲图像操作。下面是具体实现的代码:

from matplotlib.pyplot import *
from numpy import array
from PIL import Image
from scipy import ndimage
import warp
import homography

im1 = array(Image.open('fj.jpg').convert('L'))
im2 = array(Image.open('billboard.jpg').convert('L'))

# 选定 im1 角上的一些点
m,n = im1.shape[:2]
fp = array([[0,m,m,0],[0,0,n,n],[1,1,1,1]])

# 选定一些目标点
tp = array([[50, 188, 175, 33], [60, 50, 375, 370], [1, 1, 1, 1]])

# 第一个三角形
tp2 = tp[:,:3]
fp2 = fp[:,:3]

# 计算 H
H = homography.Haffine_from_points(tp2,fp2)
im1_t = ndimage.affine_transform(im1,H[:2,:2],(H[0,2],H[1,2]),im2.shape[:2])

# 三角形的 alpha
alpha = warp.alpha_for_triangle(tp2,im2.shape[0],im2.shape[1])
im3 = (1-alpha)*im2 + alpha*im1_t

# 第二个三角形
tp2 = tp[:,[0,2,3]]
fp2 = fp[:,[0,2,3]]

# 计算 H
H = homography.Haffine_from_points(tp2,fp2)
im1_t = ndimage.affine_transform(im1,H[:2,:2],(H[0,2],H[1,2]),im2.shape[:2])

# 三角形的 alpha 图像
alpha = warp.alpha_for_triangle(tp2,im2.shape[0],im2.shape[1])
im4 = (1-alpha)*im3 + alpha*im1_t

figure()
gray()
imshow(im4)
axis('equal')
axis('off')
show()

运行结果:

比较完全图像的仿射扭曲和使用两个三角形的仿射弯曲效果。图像放置在广告牌上,并且带有一些透视效应。对于整幅图像使用仿射扭曲效果不好。为了看清楚,我们将右边的两个角点放大(上)。使用包含两个三角形的仿射变换可以很好地将图像完全放置到广告牌上(下)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值