图像拼接
- 单应性变换
- 仿射变换
- 图像扭曲实现
- 图像嵌入(图中图)
- RANSAC算法
- 算法介绍
- 图片收集
- 无RANSAC优化和有RANSAC优化的代码实现
- 差别
- 总结
单应性变换
单应性变换是指一个平面上的点通过一个矩阵变换映射到另一个平面上的点,这个变换矩阵是一个 3 × 3 3 \times 3 3×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} x′y′1 = ad0be0cf1 xy1
其中, ( x , y ) (x,y) (x,y) 是原来平面上的一个点, ( x ′ , y ′ ) (x',y') (x′,y′) 是变换后平面上的对应点。矩阵中的参数 a , b , c , d , e , f a,b,c,d,e,f a,b,c,d,e,f 决定了变换的效果。
具体地,仿射变换包括平移、旋转、缩放和错切四种基本变换。其中,平移可以通过将矩阵中的 c c c 和 f f f 分别设置为平移的距离 t x t_x tx 和 t y t_y ty 来实现;旋转可以通过将矩阵中的 a a a 和 d d d 设置为 cos θ \cos \theta cosθ 和 sin θ \sin \theta sinθ, b b b 和 e e e 设置为 − sin θ -\sin \theta −sinθ 和 cos θ \cos \theta cosθ 来实现;缩放可以通过将矩阵中的 a a a 和 e e e 分别设置为 s x s_x sx 和 s y s_y sy 来实现;错切可以通过将矩阵中的 b b b 设置为 k x k_x kx 或 d d d 设置为 k y k_y ky 来实现。其中, t x t_x tx 和 t y t_y ty 表示平移的距离, θ \theta θ 表示旋转的角度, s x s_x sx 和 s y s_y sy 表示缩放的比例, k x k_x kx 和 k y k_y ky 表示错切的程度。
图像扭曲实现
以下是原图:
import cv2
import 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 cv2
from numpy import array
from PCV.geometry import warp
from PIL import Image
from 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 算法的基本流程:
-
随机采样:从原始数据中随机选择一定数量的样本来构造一个初始模型,这个初始模型可以用来进行后续的计算。
-
模型拟合:使用所选样本来拟合一个数学模型。
-
内点选择:计算每个数据点到所估计的模型的距离,如果距离小于给定的阈值,则将该数据点视为“内点”,否则将其视为“外点”。
-
判断收敛:判断是否有足够的内点,如果内点数目达到了一定的阈值,就认为模型已经收敛。
-
重复以上步骤:重复以上步骤,直到满足收敛条件或达到预先设定的最大迭代次数。
图片收集
图片就地取材,在宿舍后方连续拍摄2张可拼接的图片:
无RANSAC优化和有RANSAC优化的代码实现
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
def 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进行图像拼接的基本技能。