第三章 图像到图像的映射

news2024/11/23 3:51:11

文章目录

  • 第三章 图像到图像的映射
    • 3.1 单应性变换
      • 3.1.1 直接线性变换算法
      • 3.1.2 仿射变换
    • 3.2 图像扭曲
      • 3.2.1 图像中的图像
      • 3.2.2 分段仿射扭曲
      • 3.2.3 图像配准
    • 3.3 创建全景图
      • 3.3.1 RANSAC
      • 3.3.2 稳健的单应性矩阵估计
      • 3.3.3 拼接图像

第三章 图像到图像的映射

本章讲解图像之间的变换,以及一些计算变换的实用方法。这些变换可以用于图像扭曲变形和图像配准。

3.1 单应性变换

单应性变换是将一个平面内的点映射到另一个平面内的二维投影变换。在这里,平面是指图像或者三维中的平面表面。本质上,按照下面的方程映射二维中的点(齐次坐标意义下): [ x ′ y ′ w ′ ] = [ h 1 h 2 h 3 h 4 h 5 h 6 h 7 h 8 h 9 ] [ x y w ] 或 x ′ = H x \begin{bmatrix}x'\\y'\\w'\end{bmatrix}=\begin{bmatrix}h_1&h_2&h_3\\h_4&h_5&h_6\\h_7&h_8&h_9\end{bmatrix}\begin{bmatrix}x\\y\\w\end{bmatrix}\quad\quad\text{或}\quad\mathbf{x}'=H\mathbf{x} xyw = h1h4h7h2h5h8h3h6h9 xyw x=Hx

下面的函数可以实现对点进行归一化和转换齐次坐标的功能

import numpy as np


def normalize(points):
    """ 在齐次坐标意义下,对点集进行归一化,使最后一行为 1 """
    for row in points:
        row /= points[-1]
    return points


def make_homog(points):
    """ 将点集(dim×n 的数组)转换为齐次坐标表示 """
    return np.vstack((points, np.ones((1, points.shape[1]))))

复习一下数字图像处理中的放射变换

仿射变换 变换名称 仿射矩阵 坐标公式 恒等变换 [ 1 0 0 0 1 0 0 0 1 ] x = v y = w 尺度变换 [ c x 0 0 0 c y 0 0 0 1 ] x = c x v y = c y w 旋转变换 [ cos ⁡ θ sin ⁡ θ 0 − sin ⁡ θ cos ⁡ θ 0 0 0 1 ] x = v cos ⁡ θ − w sin ⁡ θ y = v sin ⁡ θ + w cos ⁡ θ 平移变换 [ 1 0 0 0 1 0 t x t y 1 ] x = v + t x y = w + t y 垂直偏移变换 [ 1 0 0 s v 1 0 0 0 1 ] x = v + s v w y = w 水平偏移变换 [ 1 s h 0 0 1 0 0 0 1 ] x = v y = s h v + w 仿射变换 \\ \begin{array} {ccc} \hline 变换名称 & 仿射矩阵 & 坐标公式\\ \hline 恒等变换 & \begin{bmatrix}1&0&0\\ 0&1&0\\ 0&0&1\end{bmatrix} &\begin{gathered}x=v \\y=w \end{gathered}\\ 尺度变换 & \begin{bmatrix}c_x&0&0\\ 0&c_y&0\\ 0&0&1\end{bmatrix} & \begin{gathered} x=c_xv \\ y=c_yw \end{gathered} \\ 旋转变换 & \begin{bmatrix}\cos\theta&\sin\theta&0\\ -\sin\theta&\cos\theta&0\\ 0&0&1\end{bmatrix} & \begin{gathered} x=v\cos\theta-w\sin\theta \\ y=v\sin\theta+w\cos\theta \end{gathered} \\ 平移变换 & \begin{bmatrix}1&0&0\\ 0&1&0\\ t_x&t_y&1\end{bmatrix} &\begin{gathered}x=v+t_x \\y=w+t_y \end{gathered}\\ 垂直偏移变换 & \begin{bmatrix}1&0&0\\ s_v&1&0\\ 0&0&1\end{bmatrix} &\begin{gathered}x=v+s_vw \\y=w \end{gathered}\\ 水平偏移变换 & \begin{bmatrix}1&s_h&0\\ 0&1&0\\ 0&0&1\end{bmatrix} &\begin{gathered}x=v \\y=s_hv+w \end{gathered}\\ \hline \end{array} 仿射变换变换名称恒等变换尺度变换旋转变换平移变换垂直偏移变换水平偏移变换仿射矩阵 100010001 cx000cy0001 cosθsinθ0sinθcosθ0001 10tx01ty001 1sv0010001 100sh10001 坐标公式x=vy=wx=cxvy=cywx=vcosθwsinθy=vsinθ+wcosθx=v+txy=w+tyx=v+svwy=wx=vy=shv+w

import math
import cv2
import numpy as np
import matplotlib.pyplot as plt


def plt_show(*args):
    for ttt in range(len(args)):
        img = args[ttt]
        if (len(img.shape) == 3):
            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
        elif (len(img.shape) == 2):
            img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
        plt.subplot(231+ttt), plt.imshow(img)

img = cv2.imread('cv.png', 0)

# cv2.imwrite('cv.png',img)
n, m = img.shape
n += 100
m += 100
points = []
for i in range(len(img)):
    for j in range(len(img[0])):
        if img[i][j]:
            points.append([i, j])
angle = 3.14/6
mats = [np.array([1, 0, 0, 0, 1, 0, 0, 0, 1]).reshape(3, 3),  # 恒等变换
        np.array([1.2, 0, 0, 0, 1.2, 0, 0, 0, 1]).reshape(3, 3),  # 尺度变换
        np.array([math.cos(angle), math.sin(angle), 0,  # 旋转变换
                 -math.sin(angle), math.cos(angle), 0,
                 0, 0, 1]).reshape(3, 3),
        np.array([1, 0, 0, 0, 1, 0, 30, 50, 1]).reshape(3, 3),  # 平移变换
        np.array([1, 0, 0, 1.1, 1, 0, 0, 0, 1]).reshape(3, 3),  # 垂直变换
        np.array([1, 1.1, 0, 0, 1, 0, 0, 0, 1]).reshape(3, 3),  # 水平变换
        ]
imgs = []

for mat in mats:
    img_t = np.zeros([n, m])
    for v, w in points:
        x, y, z = np.dot([v, w, 1], mat)
        try:  # 避免新坐标越界
            img_t[int(x)][int(y)] = 254
        except:
            pass
    imgs.append(img_t.astype(np.uint8))
plt_show(imgs[0], imgs[1], imgs[2], imgs[3], imgs[4], imgs[5])

在这里插入图片描述

上图分别是:原图、放大、旋转、平移、垂直偏移、水平偏移。这些是基本的仿射变换。

3.1.1 直接线性变换算法

单应性矩阵可以由两幅图像(或者平面)中对应点对计算出来。一个完全射影变换具有 8 个自由度。根据对应点约束,每个对应点对可以写出两个方程,分别对应于 x 和 y 坐标。因此,计算单应性矩阵 H 需要4个对应点对。DLT(Direct Linear Transformation,直接线性变换)是给定4个或者更多对应点对矩阵,来计算单应性矩阵 H 的算法。我们可以得到下面的方程: [ − x 1 − y 1 − 1 0 0 0 x 1 x 1 ′ y 1 x 1 ′ x 1 ′ 0 0 0 − x 1 − y 1 − 1 x 1 y 1 ′ y 1 y 1 ′ y 1 ′ − x 2 − y 2 − 1 0 0 0 x 2 x 2 ′ y 2 x 2 ′ x 2 ′ 0 0 0 − x 2 − y 2 − 1 x 2 y 2 ′ y 2 y 2 ′ y 2 ′ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ] [ h 1 h 2 h 3 h 4 h 5 h 5 h 6 h 7 h 8 h 9 ] = 0 \begin{bmatrix}-x_1&-y_1&-1&0&0&0&x_1x_1^{\prime}&y_1x_1^{\prime}&x_1^{\prime}\\0&0&0&-x_1&-y_1&-1&x_1y_1^{\prime}&y_1y_1^{\prime}&y_1^{\prime}\\-x_2&-y_2&-1&0&0&0&x_2x_2^{\prime}&y_2x_2^{\prime}&x_2^{\prime}\\0&0&0&-x_2&-y_2&-1&x_2y_2^{\prime}&y_2y_2^{\prime}&y_2^{\prime}\\&\vdots&&\vdots&\vdots&\vdots&\vdots&\vdots\end{bmatrix}\begin{bmatrix}h_1\\h_2\\h_3\\h_4\\h_5\\h_5\\h_6\\h_7\\h_8\\h_9\end{bmatrix}=\mathbf{0} x10x20y10y2010100x10x20y10y20101x1x1x1y1x2x2x2y2y1x1y1y1y2x2y2y2x1y1x2y2 h1h2h3h4h5h5h6h7h8h9 =0 即 Ah=0。

我们可以使用 SVD(Singular Value Decomposition,奇异值分解)算法找到 H 的最小二乘解。下面是该算法的代码

import numpy as np


def H_from_points(fp, tp):
    """ 使用线性 DLT 方法,计算单应性矩阵 H,使 fp 映射到 tp。点自动进行归一化 """
    if fp.shape != tp.shape:
        raise RuntimeError('number of points do not match')
    # 对点进行归一化(对数值计算很重要)
    # --- 映射起始点 ---
    m = np.mean(fp[:2], axis=1)
    maxstd = np.max(np.std(fp[:2], axis=1)) + 1e-9
    C1 = np.diag([1/maxstd, 1/maxstd, 1])
    C1[0][2] = -m[0]/maxstd
    C1[1][2] = -m[1]/maxstd
    fp = np.dot(C1, fp)
    # --- 映射对应点 ---
    m = np.mean(tp[:2], axis=1)
    maxstd = np.max(np.std(tp[:2], axis=1)) + 1e-9
    C2 = np.diag([1/maxstd, 1/maxstd, 1])
    C2[0][2] = -m[0]/maxstd
    C2[1][2] = -m[1]/maxstd
    tp = np.dot(C2, tp)
    # 创建用于线性方法的矩阵,对于每个对应对,在矩阵中会出现两行数值
    nbr_correspondences = fp.shape[1]
    A = np.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 = np.linalg.svd(A)
    H = V[8].reshape((3, 3))
    # 反归一化
    H = np.dot(np.linalg.inv(C2), np.dot(H, C1))
    # 归一化,然后返回
    return H / H[2, 2]

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

3.1.2 仿射变换

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

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

3.2 图像扭曲

对图像块应用仿射变换,我们将其称为图像扭曲(或者仿射扭曲)。该操作不仅经常应用在计算机图形学中,而且经常出现在计算机视觉算法中。扭曲操作可以使用
SciPy 工具包中的 ndimage 包来简单完成。命令:

transformed_im = ndimage.affine_transform(im,A,b,size)
from scipy import ndimage
from PIL import Image  

img = cv2.cvtColor(cv2.imread('am.png'), cv2.COLOR_BGR2RGB)
H = np.array([[1.4,0.2,-100],[0.1,1.5,-100],[0,0,1]])

im2 = ndimage.affine_transform( cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),  H[:2,:2],(H[0,2],H[1,2]))

plt.gray()
plt.figure(figsize=(8,4))

plt.subplot(1,2,1)
plt.imshow(img)
plt.axis('off')
plt.subplot(1,2,2)
plt.imshow(im2)
plt.axis('off')

plt.show()

<Figure size 640x480 with 0 Axes>

在这里插入图片描述

3.2.1 图像中的图像

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

该图像定义了每个像素从各个图像中获取的像素值成分多少。这里我们基于以下事实,扭曲的图像是在扭曲区域边界之外以 0 来填充的图像,来创建一个二值的 alpha 图像。

def image_in_image(im1, im2, tp):
    """ 使用仿射变换将 im1 放置在 im2 上,使 im1 图像的角和 tp 尽可能的靠近
    tp 是齐次表示的,并且是按照从左上角逆时针计算的 """
    # 扭曲的点
    m, n = im1.shape[:2]
    fp = np.array([[0, m, m, 0], [0, 0, n, n], [0, 0, 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


# 仿射扭曲 im1 到 im2 的例子'
im1 = cv2.cvtColor(cv2.imread('am.png'), cv2.COLOR_BGR2GRAY)
im2 = cv2.cvtColor(cv2.imread('letter.png'), cv2.COLOR_BGR2GRAY)

# 选定一些目标点
tp = np.array([[80,600,300,600],[40,40,600,600],[1,1,1,1]])

im3 = image_in_image(im1, im2, tp)
plt.gray()
plt.figure(figsize=(10,5))


plt.subplot(131)
plt.imshow(im1)
plt.axis('off')

plt.subplot(132)
plt.imshow(im2)
plt.axis('off')

plt.subplot(133)
plt.imshow(im3)
plt.axis('off')

plt.show()
<Figure size 640x480 with 0 Axes>

在这里插入图片描述

这效果很差,代码有些调不过来,改成用cv2实现。

def update(img,a,b,t):
    for i in range(img.shape[0]):
        for j in range(img.shape[1]):
            if abs(img[i][j][0]-a[0])<=t and abs(img[i][j][1]-a[1])<=t and abs(img[i][j][2]-a[2])<=t :
                img[i][j][0],img[i][j][1],img[i][j][2]=b[0],b[1],b[2]
    return img

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取im1和im2
im1 = cv2.cvtColor(cv2.imread('am.png'), cv2.COLOR_BGR2RGB)
im1=update(im1,[255,255,255],[177,165,123],10)
im2 = cv2.cvtColor(cv2.imread('letter.png'), cv2.COLOR_BGR2RGB)
# 获取im1和im2的尺寸
height1, width1,_ = im1.shape
height2, width2,_ = im2.shape

# 指定im2中的四个点和它们在im1中的对应点
points_im2 = np.array([[219, 160], [1116, 134], [144, 1526], [1214, 1528]], dtype=np.float32)
points_im1 = np.array([[0, 0], [width1, 0], [0, height1], [width1, height1]], dtype=np.float32)

# 计算透视变换矩阵M
M = cv2.getPerspectiveTransform(points_im1, points_im2)

# 使用透视变换将im1拉伸
im1_stretched = cv2.warpPerspective(im1, M, (width2, height2))

# 创建一个新的图像im3,将im1_stretched放入其中
im3 = np.copy(im2)
alpha = (im1_stretched > 0)
im3 = (1 - alpha) * im2 + alpha * im1_stretched

# 显示im1_stretched和im3
plt.gray()
plt.figure(figsize=(20, 8))

plt.subplot(131)
plt.imshow(im1, vmin=0, vmax=255)
plt.axis('off')
plt.title('im1')

plt.subplot(132)
plt.imshow(im2, vmin=0, vmax=255)
plt.axis('off')
plt.title('im2')

plt.subplot(133)
plt.imshow(im3, vmin=0, vmax=255)
plt.axis('off')
plt.title('im3')

plt.show()

<Figure size 640x480 with 0 Axes>

在这里插入图片描述

这就完美地将im1放入im2,效果很好

3.2.2 分段仿射扭曲

三角形图像块的仿射扭曲可以完成角点的精确匹配。让我们看一下对应点对集合之间最常用的扭曲方式:分段仿射扭曲。给定任意图像的标记
点,通过将这些点进行三角剖分,然后使用仿射扭曲来扭曲每个三角形,我们可以将图像和另一幅图像的对应标记点扭曲对应。对于任何图形和图像处理库来说,这些都是最基本的操作。为了三角化这些点,我们经常使用狄洛克三角剖分方法。在 Matplotlib中有狄洛克三角剖分,我们可以用下面的方式使用它:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri

# 生成服从标准正态分布的随机数据
x, y = np.random.normal(0, 1, (2, 100))

# 创建绘图
plt.figure(figsize=(15,7))
plt.subplot(121)
plt.plot(x,y, '*')
plt.axis('off')

plt.subplot(122)
triang = tri.Triangulation(x, y)
plt.triplot(triang, linestyle='-', color='r') 
plt.plot(x, y, '*')
plt.axis('off')

plt.show()

在这里插入图片描述

上图是狄洛克三角剖分示例。

3.2.3 图像配准

没有找到 jkface.xml 文件

3.3 创建全景图

在同一位置(即图像的照相机位置相同)拍摄的两幅或者多幅图像是单应性相关的 (如图 3-9 所示)。 我们经常使用该约束将很多图像缝补起来,拼成一个大的图像来创建全景图像

3.3.1 RANSAC

RANSAC 是“RANdom SAmple Consensus”(随机一致性采样)的缩写。该方法是 用来找到正确模型来拟合带有噪声数据的迭代方法。 给定一个模型,例如点集之间的单应性矩阵,RANSAC 基本的思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。

3.3.2 稳健的单应性矩阵估计

我们在任何模型中都可以使用 RANSAC 模块。在使用 RANSAC 模块时, 我们只需 要在相应 Python 类中实现 fit() 和 get_error() 方法,剩下就是正确地使用 ransac.py。 我们这里使用可能的对应点集来自动找到用于全景图像的单应性矩阵。

现显示图片

imname = ['Univ'+str(i+1)+'.jpg' for i in range(5)]
plt.figure(figsize=(15,8))
for i in range(5):
    img=cv2.cvtColor(cv2.imread(imname[i]), cv2.COLOR_BGR2RGB)
    plt.subplot(2,3,i+1)
    plt.axis('off')
    plt.imshow(img)

在这里插入图片描述

之后使用 SIFT 特征自动找到匹配对应

import cv2

sift = cv2.SIFT_create()

l = {}
d = {}

for i in range(5):
    image = cv2.imread(imname[i], cv2.IMREAD_GRAYSCALE)
    keypoints = sift.detect(image, None)
    keypoints, descriptors = sift.compute(image, keypoints)
    l[i], d[i] = keypoints, descriptors

matches = {}
for i in range(4):
    bf = cv2.BFMatcher()
    matches[i] = bf.knnMatch(d[i+1], d[i], k=2)
'''homograph.py'''
from numpy import *
from scipy import ndimage
    

class RansacModel(object):
    """ Class for testing homography fit with ransac.py from
        http://www.scipy.org/Cookbook/RANSAC"""
    
    def __init__(self,debug=False):
        self.debug = debug
        
    def fit(self, data):
        """ Fit homography to four selected correspondences. """
        
        # transpose to fit H_from_points()
        data = data.T
        
        # from points
        fp = data[:3,:4]
        # target points
        tp = data[3:,:4]
        
        # fit homography and return
        return H_from_points(fp,tp)
    
    def get_error( self, data, H):
        """ Apply homography to all correspondences, 
            return error for each transformed point. """
        
        data = data.T
        
        # from points
        fp = data[:3]
        # target points
        tp = data[3:]
        
        # transform fp
        fp_transformed = dot(H,fp)
        
        # normalize hom. coordinates
        fp_transformed = normalize(fp_transformed)
                
        # return error per point
        return sqrt( sum((tp-fp_transformed)**2,axis=0) )
        

def H_from_ransac(fp,tp,model,maxiter=1000,match_theshold=10):
    """ Robust estimation of homography H from point 
        correspondences using RANSAC (ransac.py from
        http://www.scipy.org/Cookbook/RANSAC).
        
        input: fp,tp (3*n arrays) points in hom. coordinates. """
    
    from PCV.tools import ransac
    
    # group corresponding points
    data = vstack((fp,tp))
    
    # compute H and return
    H,ransac_data = ransac.ransac(data.T,model,4,maxiter,match_theshold,10,return_all=True)
    return H,ransac_data['inliers']


def H_from_points(fp,tp):
    """ Find homography H, such that fp is mapped to tp
        using the linear DLT method. Points are conditioned
        automatically. """
    
    if fp.shape != tp.shape:
        raise RuntimeError('number of points do not match')
        
    # condition points (important for numerical reasons)
    # --from points--
    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)
    
    # --to points--
    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)
    
    # create matrix for linear method, 2 rows for each correspondence pair
    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))    
    
    # decondition
    H = dot(linalg.inv(C2),dot(H,C1))
    
    # normalize and return
    return H / H[2,2]


def Haffine_from_points(fp,tp):
    """ Find H, affine transformation, such that 
        tp is affine transf of fp. """
    
    if fp.shape != tp.shape:
        raise RuntimeError('number of points do not match')
        
    # condition points
    # --from points--
    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)
    
    # --to points--
    m = mean(tp[:2], axis=1)
    C2 = C1.copy() #must use same scaling for both point sets
    C2[0][2] = -m[0]/maxstd
    C2[1][2] = -m[1]/maxstd
    tp_cond = dot(C2,tp)
    
    # conditioned points have mean zero, so translation is zero
    A = concatenate((fp_cond[:2],tp_cond[:2]), axis=0)
    U,S,V = linalg.svd(A.T)
    
    # create B and C matrices as Hartley-Zisserman (2:nd ed) p 130.
    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]))
    
    # decondition
    H = dot(linalg.inv(C2),dot(H,C1))
    
    return H / H[2,2]


def normalize(points):
    """ Normalize a collection of points in 
        homogeneous coordinates so that last row = 1. """

    for row in points:
        row /= points[-1]
    return points
    
    
def make_homog(points):
    """ Convert a set of points (dim*n array) to 
        homogeneous coordinates. """
        
    return vstack((points,ones((1,points.shape[1])))) 
 
# 将匹配转换成齐次坐标点的函数
def convert_points(j):
    ndx = [i[0].queryIdx for i in matches[j]]
    fp = np.array([l[j+1][i].pt for i in ndx])
    ndx2 = [i[0].trainIdx for i in matches[j]]
    tp = np.array([l[j][i].pt for i in ndx2])
    return fp, tp

# 估计单应性矩阵
model = RansacModel()

fp,tp = convert_points(0)
H_01 = H_from_ransac(fp,tp,model)[0] # im0 到 im1 的单应性矩阵

fp,tp = convert_points(1)
H_12 = H_from_ransac(fp,tp,model)[0] # im1 到 im2 的单应性矩阵


tp,fp = convert_points(2) # 注意:点是反序的
H_32 = H_from_ransac(fp,tp,model)[0] # im3 到 im2 的单应性矩阵

tp,fp = convert_points(3) # 注意:点是反序的
H_43 = H_from_ransac(fp,tp,model)[0] # im4 到 im3 的单应性矩阵

3.3.3 拼接图像


def panorama(H,fromim,toim,padding=2400,delta=2400):
    """ 使用单应性矩阵 H(使用 RANSAC 健壮性估计得出),协调两幅图像,创建水平全景图像。结果
        为一幅和 toim 具有相同高度的图像。padding 指定填充像素的数目,delta 指定额外的平移量
    """

    # 检查图像是灰度图像,还是彩色图像

    is_color = len(fromim.shape) == 3
    # 用于 geometric_transform() 的单应性变换

    def transf(p):
        p2 = np.dot(H,[p[0],p[1],1])
        return (p2[0]/p2[2],p2[1]/p2[2])

    if H[1,2]<0: # fromim 在右边
        print( 'warp - right')
        # 变换 fromim
        if is_color:
            # 在目标图像的右边填充 0
            toim_t = np.hstack((toim,np.zeros((toim.shape[0],padding,3))))
            fromim_t = np.zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
            for col in range(3):
                fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
                    transf,(toim.shape[0],toim.shape[1]+padding))

        else:
            # 在目标图像的右边填充 0
            toim_t = hstack((toim,np.zeros((toim.shape[0],padding))))
            fromim_t = ndimage.geometric_transform(fromim,transf,
                        (toim.shape[0],toim.shape[1]+padding))

    else:
        print( 'warp - left')
        # 为了补偿填充效果,在左边加入平移量
        H_delta = array([[1,0,0],[0,1,-delta],[0,0,1]])
        H = np.dot(H,H_delta)
        # fromim 变换
        if is_color:
            # 在目标图像的左边填充 0
            toim_t = hstack((zeros((toim.shape[0],padding,3)),toim))
            fromim_t = zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))
            for col in range(3):
                fromim_t[:,:,col] = ndimage.geometric_transform(fromim[:,:,col],
                    transf,(toim.shape[0],toim.shape[1]+padding))

        else:
            # 在目标图像的左边填充 0
            toim_t = np.hstack((np.zeros((toim.shape[0],padding)),toim))
            fromim_t = ndimage.geometric_transform(fromim,
                transf,(toim.shape[0],toim.shape[1]+padding))

            # 协调后返回(将 fromim 放置在 toim 上)
            if is_color:
                # 所有非黑色像素
                alpha = ((fromim_t[:,:,0] * fromim_t[:,:,1] * fromim_t[:,:,2] ) > 0)
                for col in range(3):
                    toim_t[:,:,col] = fromim_t[:,:,col]*alpha + toim_t[:,:,col]*(1-alpha)

            else:
                alpha = (fromim_t > 0)
                toim_t = fromim_t*alpha + toim_t*(1-alpha)

    return toim_t
# 扭曲图像
delta = 2000 # 用于填充和平移
im1 = np.array(cv2.cvtColor(cv2.imread(imname[1]), cv2.COLOR_BGR2RGB))
im2 = np.array(cv2.cvtColor(cv2.imread(imname[2]), cv2.COLOR_BGR2RGB))
im_12 = panorama(H_12,im1,im2,delta,delta)

im1 = np.array(cv2.cvtColor(cv2.imread(imname[0]), cv2.COLOR_BGR2RGB))
im_02 = panorama(dot(H_12,H_01),im1,im_12,delta,delta)

im1 = np.array(cv2.cvtColor(cv2.imread(imname[3]), cv2.COLOR_BGR2RGB))
im_32 = panorama(H_32,im1,im_02,delta,delta)

im1 = array(cv2.cvtColor(cv2.imread(imname[j+1]), cv2.COLOR_BGR2RGB))
im_42 = warp.panorama(dot(H_32,H_43),im1,im_32,delta,2*delta)

在这里插入图片描述

由于图像曝光不同,在单个图像的边界上存在边缘效应,但总体效果还行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pandas数据分析基础—pandas自带函数map()/apply()/applymap()

文章目录 前言一、Series数据处理1、map()方法2、apply()方法3、applymap()方法总结 二、DataFrame数据处理1、map()方法2、apply()方法3、applymap()方法总结 三、map、apply、applymap三个函数区别 前言 在进行数据处理时&#xff0c;经常会对一个DataFrame展开逐行、逐列、…

three.js(二):webpack + three.js + ts

用webpackts 开发 three.js 项目 webpack 依旧是主流的模块打包工具;ts和three.js 是绝配&#xff0c;three.js本身就是用ts写的&#xff0c;ts可以为three 项目提前做好规则约束&#xff0c;使项目的开发更加顺畅。 1.创建一个目录&#xff0c;初始化 npm mkdir demo cd de…

【项目 计网8】4.23 TCP状态转换 4.24半关闭、端口复用

文章目录 4.23 TCP状态转换关于三次握手四次挥手 4.24半关闭、端口复用端口复用 4.23 TCP状态转换 2MSL(Maximum Segment Lifetime) 主动断开连接的一方&#xff0c;最后进入一个TIME_WAIT状态&#xff0c;这个状态会持续&#xff1a;2msl msl&#xff1a;官方建议&#xff1a;…

Linux 多进程解决客户端与服务器端通信

写一个服务器端用多进程处理并发&#xff0c;使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程&#xff0c;让子进程去处理这个连接&#xff0c;父进程只用来接受连接。 与多线程相比的不同点&#xff1a;多线程如果其中一个线程操…

Kubernetes(K8s 1.28.x)部署---创建方式Docker(超详细)

目录 一、基础环境配置&#xff08;所有主机均要配置&#xff09; 1、配置IP地址和主机名、hosts解析 2、关闭防火墙、禁用SELinux 3、安装常用软件 4、配置时间同步 5、禁用Swap分区 6、修改linux的内核参数 7、配置ipvs功能 二、容器环境操作 1、定制软件源 2、安…

RocketMQ消息队列-@RocketMQMessageListener实现原理

使用Spring-RocketMQ时&#xff0c;只需要引入rocketmq-spring-boot-starter包&#xff0c;并且定义以下消费者&#xff0c;就可以很简单的实现消息消费 Component RocketMQMessageListener(topic "first-topic", consumerGroup "my-producer-group", s…

过滤器的应用-Filter

过滤器 1.工作原理 2.创建Filter 2.1通过注解的方式实现 //创建一个类&#xff0c;实现Filter接口 WebFilter(urlPatterns "/myfilter") //urlPatterns表示需要拦截的路径 public class MyFilter implements Filter {Overridepublic void doFilter(ServletReques…

深度解读NeuS代码(1):输入数据格式

准备训练数据 在preprocess_cutsom_data中&#xff0c;笔者采取第二个经过colmap的方法。但是这个方法其实需要一个前置结果&#xff0c;即colmap与LLFF的处理。接下来分别讨论&#xff1a; Colmap 一个非常好的操作流程在这里&#xff1a; https://zhuanlan.zhihu.com/p/57…

Kubernetes(k8s)安装NFS动态供给存储类并安装KubeSphere

Kubernetes安装NFS动态供给存储类并安装KubeSphere KubeSphere介绍环境准备KubeSphereNFS动态供给 安装NFS动态供给搭建NFS下载动态供给驱动修改驱动文件安装动态供给 安装KubeSphere下载KubeSphere的yaml资源清单文件安装KubeSphere 使用KubeSphere部署应用创建项目部署MySQL …

Android 13 - Media框架(9)- NuPlayer::Decoder

这一节我们将了解 NuPlayer::Decoder&#xff0c;学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容&#xff0c;如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单&#xff0c;越读越发现自己的无知&#xff0c;Android 源码真…

nginx-缓存

disk cache&#xff1a;磁盘缓存数据&#xff0c;有时间延迟&#xff0c;但是非常小&#xff0c;相对于直接请求服务器返回 对于用户来说基本无感知。 memory cache&#xff1a;磁盘缓存数据&#xff0c;基本上没有时间延迟 协商缓存&#xff08;nginx自带功能&#xff0c; 不…

机器人中的数值优化(五)——信赖域方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

18.kthread_worker:内核线程异步传输

目录 kthread_worker 驱动传输数据的方式 同步传输 异步传输 头文件 kthread_worker结构体 kthread_work结构体 kthread_flush_work结构体 init_kthread_worker()函数 为kthread_worker创建内核线程 init_kthread_work()函数 启动工作 刷新工作队列 停止内核线程…

3D点云测量:计算三个平面的交点

文章目录 0. 测试效果1. 基本内容文章目录:3D视觉测量目录微信:dhlddxB站: Non-Stop_0. 测试效果 1. 基本内容 计算三个平面的交点需要找到满足所有三个平面方程的点。三个平面通常由它们的法向量和通过它们的点(或参数形式的方程)来定义。以下是计算三个平面的交点的一般步…

在VScode中使用sftp传输本地文件到服务器端

安装SFTP 在VScode的扩展中安装sftp 注意这里需要在你没连接服务器的状态下安装&#xff0c;即本机需要有sftp 配置传输端口 安装成功后&#xff0c;使用快捷键"ctrlshiftp",输入sftp&#xff0c;选择Config 根据自己的实际情况修改配置文件&#xff0c;主要改h…

设计模式-6--装饰者模式(Decorator Pattern)

一、什么是装饰者模式&#xff08;Decorator Pattern&#xff09; 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你在不修改现有对象的情况下&#xff0c;动态地将新功能附加到对象上。这种模式通过创建一个包装类&#xff0c;…

排序之交换排序

文章目录 前言一、冒泡排序1、冒泡排序基本思想2、冒泡排序的效率 二、快速排序 -- hoare版本1、快速排序基本思想2、快速排序代码实现3、为什么最左边值做key时&#xff0c;右边先走 三、快速排序 -- 挖坑法1、快速排序 -- 挖坑法基本思想2、快速排序 -- 挖坑法代码实现3、为什…

stable diffusion实践操作-随机种子seed

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、seed是什么&#xff1f;二、使用步骤1.多批次随机生成多张图片2.提取图片seed3. 根据seed 再次培养4 seed使用4.1 复原别人图4.1 轻微修改 三、差异随机种子1. webUI位置2. 什么是差异随机种子3.使用差异…

找redis大key工具rdb_bigkeys

github官网 https://github.com/weiyanwei412/rdb_bigkeys 在centos下安装go [roothadoop102 rdb_bigkeys-master]# wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz [roothadoop102 rdb_bigkeys-master]# tar -zxf go1.13.5.linux-amd64.tar.gz -C /usr/local将g…

安装bpftrace和bcc的踩坑记录

最后在Ubuntu22.04使用Ubuntu提供的安装命令完成了安装。这里是记录尝试在Ubuntu18.04和Ubuntu22.04使用源码安装未果的过程。 文章目录 22版本安装bcc准备工具安装命令使用报错&#xff1a;iovisor封装的安装方式ubuntu的安装方式 For Bionic (18.04 LTS)官方提供的源码安装准…