图像拼接

  • 单应性变换
    • 仿射变换
    • 图像扭曲实现
    • 图像嵌入(图中图)
  • RANSAC算法
    • 算法介绍
    • 图片收集
    • 无RANSAC优化和有RANSAC优化的代码实现
    • 差别
  • 总结

单应性变换

单应性变换是指一个平面上的点通过一个矩阵变换映射到另一个平面上的点,这个变换矩阵是一个 3 × 33 \times 33×3 的矩阵,称为单应性矩阵。单应性变换可以分为仿射变换和投影变换两种类型。

仿射变换

在单应性变换中,仿射变换是其中一种特殊的变换。仿射变换是指在变换前后,保持原来的平行线还是平行线,并且保持原来的比例关系不变。一个二维平面上的仿射变换可以表示为:

[ x′y′1 ]= [ a b c d e f 0 0 1 ] [ x y 1 ]\begin{bmatrix} x’ \\ y’ \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\ d & e & f \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} xy1 = ad0be0cf1 xy1

其中, ( x , y )(x,y)(x,y) 是原来平面上的一个点, ( x′, y′)(x’,y’)(x,y) 是变换后平面上的对应点。矩阵中的参数 a , b , c , d , e , fa,b,c,d,e,fa,b,c,d,e,f 决定了变换的效果。

具体地,仿射变换包括平移、旋转、缩放和错切四种基本变换。其中,平移可以通过将矩阵中的 ccc fff 分别设置为平移的距离 tx t_xtx ty t_yty 来实现;旋转可以通过将矩阵中的 aaa ddd 设置为 cos ⁡ θ\cos \thetacosθ sin ⁡ θ\sin \thetasinθ bbb eee 设置为 − sin ⁡ θ-\sin \thetasinθ cos ⁡ θ\cos \thetacosθ 来实现;缩放可以通过将矩阵中的 aaa eee 分别设置为 sx s_xsx sy s_ysy 来实现;错切可以通过将矩阵中的 bbb 设置为 kx k_xkx ddd 设置为 ky k_yky 来实现。其中, tx t_xtx ty t_yty 表示平移的距离, θ\thetaθ 表示旋转的角度, sx s_xsx sy s_ysy 表示缩放的比例, kx k_xkx ky k_yky 表示错切的程度。

图像扭曲实现

以下是原图:

import cv2import numpy as np# 读取图片img = cv2.imread('background/background.jpg')# 图像的高和宽rows, cols = img.shape[:2]# 设置扭曲前后的三个点的坐标pts1 = np.float32([[0, 0], [cols - 1, 0], [0, rows - 1]])pts2 = np.float32([[cols * 0.2, rows * 0.1], [cols * 0.9, rows * 0.2], [cols * 0.1, rows * 0.9]])# 生成变换矩阵M = cv2.getAffineTransform(pts1, pts2)# 进行仿射变换dst = cv2.warpAffine(img, M, (cols, rows))# 显示原图和扭曲后的图像cv2.imshow('image', img)cv2.imshow('affine', dst)cv2.waitKey(0)cv2.destroyAllWindows()

扭转后的图片:

图像嵌入(图中图)

被嵌入图像:
嵌入至以下图像中:

# -*- coding: utf-8 -*-import cv2from numpy import arrayfrom PCV.geometry import warpfrom PIL import Imagefrom pylab import *from scipy import ndimage# 读取两张灰度图像im1 = cv2.imread(r'background/in.jpg', cv2.IMREAD_GRAYSCALE)im2 = cv2.imread(r'background/background.jpg', cv2.IMREAD_GRAYSCALE)# 设置仿射变换的四个点tp = array([[164,538,540,264],[40,36,405,405],[1,1,1,1]])# 进行仿射变换im3 = warp.image_in_image(im1,im2,tp)# 显示三张图像figure()gray()# 显示第一张图像imshow(im1)show()# 显示第二张图像imshow(im2)show()# 显示第三张图像imshow(im3)show()

嵌入后的结果:

RANSAC算法

算法介绍

RANSAC(Random Sample Consensus)是一种基于随机采样的迭代算法,用于估计数据集中的模型参数。它主要用于从一组有噪声的数据中估计出一个最优的数学模型。

下面是 RANSAC 算法的基本流程:

  1. 随机采样:从原始数据中随机选择一定数量的样本来构造一个初始模型,这个初始模型可以用来进行后续的计算。

  2. 模型拟合:使用所选样本来拟合一个数学模型。

  3. 内点选择:计算每个数据点到所估计的模型的距离,如果距离小于给定的阈值,则将该数据点视为“内点”,否则将其视为“外点”。

  4. 判断收敛:判断是否有足够的内点,如果内点数目达到了一定的阈值,就认为模型已经收敛。

  5. 重复以上步骤:重复以上步骤,直到满足收敛条件或达到预先设定的最大迭代次数。

图片收集

图片就地取材,在宿舍后方连续拍摄2张可拼接的图片:

无RANSAC优化和有RANSAC优化的代码实现

import numpy as npimport cv2 as cvfrom matplotlib import pyplot as pltdef blend_images(s, t):# 给图片加边框s = cv.copyMakeBorder(s, 50, 50, 0, 250, cv.BORDER_CONSTANT, value=(0,0,0))t = cv.copyMakeBorder(t, 50, 50, 0, 250, cv.BORDER_CONSTANT, value=(0,0,0))# 获取图片大小w,h = s.shape[:2]# 转换为灰度图g1 = cv.cvtColor(s, cv.COLOR_BGR2GRAY)g2 = cv.cvtColor(t, cv.COLOR_BGR2GRAY)# 创建SIFT对象sift = cv.SIFT_create()# 检测关键点和计算描述符k1, d1 = sift.detectAndCompute(g1, None)k2, d2 = sift.detectAndCompute(g2, None)# 创建FLANN匹配器F = cv.FlannBasedMatcher(dict(algorithm=1, trees=5), dict(checks=50))# 匹配关键点m = F.knnMatch(d1, d2, k=2)mask = [[0, 0] for _ in range(len(m))]# 根据阈值筛选匹配点l1 = [j for i, (j, k) in enumerate(m) if j.distance < 0.7 * k.distance]mask = [[1, 0] if j.distance < 0.7 * k.distance else [0, 0] for i, (j, k) in enumerate(m)]# 获取图片大小r, c = s.shape[:2]# 如果匹配点大于10个,计算单应性矩阵if len(l1) > 10:sc = np.float32([k1[m.queryIdx].pt for m in l1]).reshape(-1, 1, 2)ds = np.float32([k2[m.trainIdx].pt for m in l1]).reshape(-1, 1, 2)M, mask = cv.findHomography(sc, ds, cv.RANSAC, 4.0)MM = np.array(M)# 透视变换wimg = cv.warpPerspective(t, np.array(MM), (t.shape[1], t.shape[0]), flags=cv.WARP_INVERSE_MAP)# 获取左右边界left = next(col for col in range(0, c) if s[:, col].any() and wimg[:, col].any())right = next(col for col in range(c-1, 0, -1) if s[:, col].any() and wimg[:, col].any())# 创建结果图像res = np.zeros([r, c, 3], np.uint8)for row in range(0, r):for col in range(0, c):if not s[row, col].any():res[row, col] = wimg[row, col]elif not wimg[row, col].any():res[row, col] = s[row, col]else:slen = float(abs(col - left))tlen = float(abs(col - right))alpha = slen / (slen + tlen)# 混合图像res[row, col] = np.clip(s[row, col] * (1-alpha) + wimg[row, col] * alpha, 0, 255)# 以下为不做ransac处理的结果结果M0, mask0 = cv.findHomography(sc, ds, cv.RANSAC, 0)MM0 = np.array(M0)wimg0 = cv.warpPerspective(t, np.array(MM0), (t.shape[1], t.shape[0]), flags=cv.WARP_INVERSE_MAP)res0 = np.zeros([r, c, 3], np.uint8)for row in range(0, r):for col in range(0, c):if not s[row, col].any():res0[row, col] = wimg0[row, col]elif not wimg0[row, col].any():res0[row, col] = s[row, col]else:slen = float(abs(col - left))tlen = float(abs(col - right))alpha = slen / (slen + tlen)# 混合图像res0[row, col] = np.clip(s[row, col] * (1-alpha) + wimg0[row, col] * alpha, 0, 255)return res, res0# 读取左右两张图片i1 = cv.imread(r'left.jpg')i2 = cv.imread(r'right.jpg')# 调整图片大小i1 = cv.resize(i1,(756,1008))i2 = cv.resize(i2,(756,1008))res, res0 = blend_images(i1, i2)# 展示结果plt.subplot(1, 2, 1)plt.imshow(cv.cvtColor(res0, cv.COLOR_BGR2RGB))plt.title('res0')plt.subplot(1, 2, 2)plt.imshow(cv.cvtColor(res, cv.COLOR_BGR2RGB))plt.title('res')plt.show()# 保存结果cv.imwrite('res0.jpg', res0)cv.imwrite('res.jpg', res)

生成的全景结果:

差别

理论上这两种方法应该会存在些许差别,但是通过这次实验我们很难看出,当然我在本机上也运行过其他图片,拼接的结果都几乎相似。

总结

本次实验主要涉及到计算机视觉领域中图像特征提取、匹配、单应性矩阵估计以及图像融合等方面的内容。这一过程中,我们使用了一些常用的库和工具,例如numpy、cv2和matplotlib等。通过学习本实验,我对图像拼接的原理、方法和实现方式有了更深入的了解和认识,同时也掌握了使用OpenCV进行图像拼接的基本技能。