【python计算机视觉编程——3.图像到图像的映射】

python计算机视觉编程——3.图像到图像的映射

  • 3.图像到图像的映射
    • 3.1 单应性变换
      • 3.1.1 直接线性变换算法(DLT)
      • 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.图像到图像的映射

3.1 单应性变换

将一个图像平面上的点映射到另一个图像平面上的技术。它在图像配准、立体视觉、图像拼接等任务中非常重要。

单应性变换是一种线性变换,用于在不同视角或不同平面之间建立点的对应关系。它可以用一个3×3 的矩阵来表示,称为单应矩阵。这个矩阵描述了从一个图像平面到另一个图像平面的透视变换。

单应性矩阵 (H) 是一个 3×3 的矩阵,用来将一个图像中的点坐标(x,y)映射到另一个图像中的点坐标(x′,y′)。

  • 对点进行归一化处理
import numpy as np
def normalize(points):  #输入的点数组,通常是一个二维数组,其中每一行代表一个点的齐次坐标 ([x, y, w])points=points.astype(float)for row in points:row/=points[-1] #对每个点进行归一化操作。这里的 points[-1] 表示该点的最后一个坐标分量(齐次坐标的权重)。对于每个点,将其所有坐标分量除以该点的权重,以将点归一化到标准形式。return points
points = np.array([[2, 3, 2],[4, 6, 2],[8, 12, 4]])
normalized_points = normalize(points)
print(normalized_points)

在这里插入图片描述

  • 转换为齐次坐标

引入了一个额外的维度来处理点的变换和投影。

def make_homog(points):  return np.vstack((points,np.ones((1,points.shape[1]))))
  • ones((1,points.shape[1])):创建一个形状为 (1, N) 的数组,其中每个元素都是 1。这一行用于将所有点的齐次坐标的权重分量设置为 1。
  • np.vstack((points,ones((1,points.shape[1])))):将原始点数组和一行全 1 的数组垂直堆叠,形成一个新的数组,其中每列代表一个点的齐次坐标 [x, y, 1]。
points = np.array([[1, 2, 3],[4, 5, 6]])
homog_points = make_homog(points)
print(homog_points)

在这里插入图片描述

  • 库函数求解单应性矩阵 H
import cv2
import numpy as np
# src_pts 是源图像中的四个点的坐标。
src_pts = np.array([[100, 150], [200, 150], [100, 250], [200, 250]], dtype='float32')
# dst_pts 是目标图像中与 src_pts 对应的四个点的坐标。
dst_pts = np.array([[120, 170], [220, 170], [120, 270], [220, 270]], dtype='float32')
# 这些点用于计算从源图像到目标图像的变换关系。# 计算一个单应性矩阵 H,该矩阵描述了从源图像坐标系到目标图像坐标系的变换。
# status 是一个数组,指示每个点对是否被成功匹配
H, status = cv2.findHomography(src_pts, dst_pts) print("Homography Matrix:\n", H)
print(status)

在这里插入图片描述

验证如下:

在这里插入图片描述

3.1.1 直接线性变换算法(DLT)

用于计算单应性矩阵,基于给定的源点 (fp) 和目标点 (tp)。这个函数主要包含点的归一化、构建方程、求解矩阵以及反归一化的步骤。

from numpy import *
def H_from_points(fp,tp):if fp.shape!=tp.shape: # 确保源点 (fp) 和目标点 (tp) 的形状相同。如果形状不匹配,抛出异常。raise RuntimeError('number of points do not match')# 归一化源点:计算源点的均值m和标准差maxstd。创建归一化矩阵C1,用于将源点fp进行归一化处理,以减小计算中的数值误差。m=mean(fp[:2],axis=1)  # 计算源点的均值 m,对每个坐标分量进行均值计算maxstd=max(std(fp[:2],axis=1))+1e-9    # 计算源点的标准差 maxstd,加一个小偏移量以避免除零错误  C1=diag([1/maxstd,1/maxstd,1])   # 创建归一化矩阵 C1,用于缩放坐标C1[0][2]=-m[0]/maxstd  # 设置 C1 矩阵的平移部分C1[1][2]=-m[1]/maxstd  # 设置 C1 矩阵的平移部分fp=dot(C1,fp)       # 应用归一化矩阵 C1 到源点 fp# 归一化目标点:类似地,对目标点进行归一化处理,计算均值和标准差,并应用归一化矩阵 C2。m=mean(tp[:2],axis=1)maxstd=max(std(tp[:2],axis=1))+1e-9C2=diag([1/maxstd,1/maxstd,1])C2[0][2]=-m[0]/maxstdC2[1][2]=-m[1]/maxstdtp=dot(C2,tp)nbr_correspondences=fp.shape[1]A=zeros((2*nbr_correspondences,9)) #构建矩阵A,用于求解单应性矩阵。每对点提供两个方程,总共 2 * nbr_correspondences 行。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) # 使用奇异值分解 (SVD)求解矩阵A的最小特征值对应的向量。取V的最后一行(对应于最小特征值),重塑为3x3矩阵 HH=V[8].reshape((3,3))
#     反归一化H=dot(linalg.inv(C2),dot(H,C1))# 使用逆归一化矩阵将计算得到的单应性矩阵从归一化坐标系转换回原始坐标系,并进行归一化处理。
#     归一化,然后返回return H/H[2,2]
# 定义源点 fp 和目标点 tp
fp = array([[100,200,100,200],[150,150,250,250],[1,1,1,1]])
tp = array([[120,220,120,220],[170,170,270,270],[1,1,1,1]])# 计算单应性矩阵
H = H_from_points(fp, tp)
print(H)
print(dot(H,fp))  #结果为tp

在这里插入图片描述

3.1.2 仿射变换

它是将源点 (fp) 转换到目标点 (tp) 的一种线性变换矩阵。与单应性矩阵不同,仿射变换矩阵不包含透视变换的成分,因此它只适用于进行平移、旋转、缩放和剪切等变换。

def Haffine_from_points(fp,tp):if fp.shape!=tp.shape: # 确保源点 (fp) 和目标点 (tp) 的形状相同。如果形状不匹配,抛出异常。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 矩阵的平移部分C1[1][2]=-m[1]/maxstd  # 设置 C1 矩阵的平移部分fp_cond=dot(C1,fp)  # 应用归一化矩阵 C1 到源点 fpm=mean(tp[:2],axis=1)C2=C1.copy()  #两个点集,必须都进行相同的缩放C2[0][2]=-m[0]/maxstdC2[1][2]=-m[1]/maxstdtp_cond=dot(C2,tp)A=concatenate((fp_cond[:2],tp_cond[:2]),axis=0)  # 拼接源点和目标点的归一化坐标,形成矩阵 AU,S,V=linalg.svd(A.T)tmp=V[:2].T  # 取 V 的前两行,并转置B=tmp[:2]    # 取前两行作为矩阵 BC=tmp[2:4]   # 取接下来的两行作为矩阵 Ctmp2=concatenate((dot(C,linalg.pinv(B)),zeros((2,1))),axis=1) # 计算仿射矩阵的前两列,并添加一列零H=vstack((tmp2,[0,0,1]))   # 形成完整的 3x3 仿射变换矩阵 HH=dot(linalg.inv(C2),dot(H,C1))  # 将仿射矩阵从归一化坐标系转换回原始坐标系return H/H[2,2]
# 定义源点 fp 和目标点 tp
fp = array([[100,200,100,200],[150,150,250,250],[1,1,1,1]])
tp = array([[120,220,120,220],[170,170,270,270],[1,1,1,1]])# 计算仿射变换矩阵
H = Haffine_from_points(fp, tp)
print(H)
print(dot(H,fp))

在这里插入图片描述

3.2 图像扭曲

from scipy import ndimage
from PIL import Image
from numpy import *
from pylab import *
im=array(Image.open('sun.jpg').convert('L'))
H=array([[1.4,0.05,-100],[0.05,1.5,-100],[0,0,1]])#   ndimage.affine_transform()用于应用仿射变换到图像数据上
#   目标将图像im进行坐标变换
#   H[:2,:2]是仿射变换矩阵的前2×2部分,表示旋转和缩放部分,
#   (H[0, 2], H[1, 2])是平移部分,
im2=ndimage.affine_transform(im,H[:2,:2],(H[0,2],H[1,2]))figure(figsize=(10, 3))
gray()
subplot(121)
imshow(im)
subplot(122)
imshow(im2)
show()

在这里插入图片描述

3.2.1 图像中的图像

from numpy import *
from pylab import *
from scipy import ndimage
def image_in_image(im1,im2,tp):m,n=im1.shape[:2] # 获取图像 im1 的高度 (m) 和宽度 (n)fp=array([[0,m,m,0],[0,0,n,n],[1,1,1,1]]) # 定义源图像 im1 的四个角点的齐次坐标
# 创建一个 3x4 的矩阵 fp,它表示源图像 im1 四个角点的齐次坐标。
# 这些点包括左上角 (0,0),右上角 (m,0),右下角 (m,n) 和左下角 (0,n)。H=Haffine_from_points(tp,fp)  # 计算从源点 fp 到目标点 tp 的仿射变换矩阵 H。#   ndimage.affine_transform()在原来基础上加上了一个参数
#   im2.shape[:2]:指定了变换后图像的大小。    im1_t=ndimage.affine_transform(im1,H[:2,:2],(H[0,2],H[1,2]),im2.shape[:2])alpha=(im1_t>0)  # 生成一个布尔掩码 alpha,它指示图像 im1_t 中哪些像素值大于 0。这有助于确定哪些区域的图像内容有效。return (1-alpha)*im2+alpha*im1_t
#   将图像 im1_t 和 im2 根据掩码 alpha 进行融合。
#   (1 - alpha) 用于选择目标图像 im2 中的像素,alpha 用于选择变换后的图像 im1_t 中的像素。
#   这样可以将 im1_t 中的有效像素覆盖到 im2 上,并保持 im2 中原有的像素不变。
im1=array(Image.open('beatles.jpg').convert('L'))
im2=array(Image.open('billboard_for_rent.jpg').convert('L'))
figure()
subplot(121)
imshow(im1)
subplot(122)
imshow(im2)# 定义目标点 tp 的坐标,并调用 image_in_image 函数将图像 im2 映射到图像 im1 中,生成变换后的图像 im3
tp=array([[120,260,260,120],   #目标图像中四个点的 y 坐标。[16,16,305,305],    #目标图像中四个点的 x 坐标。[1,1,1,1]])
im3=image_in_image(im1,im2,tp)
# tp 矩阵定义了目标图像的四个角点的位置。仿射变换的目的是将源图像 im1 中的四个角点变换到目标图像中的这些点位置。通过计算这些点之间的仿射变换矩阵,可以将 im1 映射到 im2 中,确保 im1 的四个角点在 im2 中对应到 tp 中指定的位置。figure()
gray()
imshow(im3)
# plot([16,16,305,305],[120,260,260,120],'*')
# axis('equal')
axis('off')
show()

在这里插入图片描述

在这里插入图片描述

3.2.2 分段仿射扭曲

from scipy.spatial import Delaunay
from numpy import *
from PIL import Image
def triangulate_points(x, y):""" 二维点的Delaunay三角剖分 """ tri = Delaunay(np.c_[x, y]).simplicesreturn tri
x,y=array(random.standard_normal((2,100)))
# centers,edges,tri,neighbors=Delaunay(x,y)tri = triangulate_points(x,y)figure()
for t in tri:t_ext=[t[0],t[1],t[2],t[0]]plot(x[t_ext],y[t_ext],'r')
plot(x,y,'*')
axis('off')
show()

在这里插入图片描述

def pw_affine(fromim,toim,fp,tp,tri):"""从一幅图像中扭曲矩形图像块fromim = 将要扭曲的图像toim =目标图像fp = 齐次坐标下,扭曲前的点tp =齐次坐标下,扭曲后的点tri = 三角剖分"""im = toim.copy()# 检查图像是灰色图像还是彩色图像is_color = len(fromim.shape) == 3 #创建扭曲后的图像(如果需要对彩色图像的每个颜色通道进行迭代操作,那么有必要这么做)im_t = zeros(im.shape,'uint8')for t in tri:#计算仿射变换H = Haffine_from_points(tp[:,t],fp[:,t])if is_color:for col in range(fromim.shape[2]):im_t[:,:,col]=ndimage.affine_transform(fromim[:,:,col],H[:2,:2],(H[0,2],H[1,2]),im.shape[:2])else:im_t = ndimage.affine_transform(fromim,H[:2,:2],(H[0,2],H[1,2]),im.shape[:2])#三角形的alphaalpha = alpha_for_triangle(tp[:,t],im.shape[0],im.shape[1])#将三角形加入到图像中im[alpha>0] = im_t[alpha>0]return im
def plot_mesh(x,y,tri):for t in tri:t_ext=[t[0],t[1],t[2],t[0]]plot(x[t_ext],y[t_ext],'r')
fromim=array(Image.open('sunset_tree.jpg'))
x,y=meshgrid(range(5),range(6))
x=(fromim.shape[1]/4)*x.flatten()
y=(fromim.shape[0]/5)*y.flatten()tri=triangulate_points(x,y)im=array(Image.open('turningtorso1.jpg'))
tp=loadtxt('turningtorso1_points.txt')
figure()
subplot(121)
imshow(im)
axis('off')fp=vstack((y,x,ones((1,len(x)))))
tp=vstack((tp[:,1],tp[:,0],ones((1,len(tp)))))im=pw_affine(fromim,im,fp,tp,tri)subplot(122)
imshow(fromim)
for t in tri:t_ext=[t[0],t[1],t[2],t[0]]plot(x[t_ext],y[t_ext],'r')
axis('off')figure()
subplot(121)
imshow(im)
axis('off')subplot(122)
imshow(im)
plot_mesh(tp[1],tp[0],tri)
axis('off')
show()

在这里插入图片描述

在这里插入图片描述

3.2.3 图像配准

from xml.dom import minidom
from scipy import linalg
from scipy import ndimage
import os
import imageio
imsave = imageio.imsave
def read_points_from_xml(xmlFileName):""" 读取用于人脸对齐的控制点 """xmldoc = minidom.parse(xmlFileName)facelist = xmldoc.getElementsByTagName('face')faces = {}for xmlFace in facelist:fileName = xmlFace.attributes['file'].valuexf = int(xmlFace.attributes['xf'].value)yf = int(xmlFace.attributes['yf'].value)xs = int(xmlFace.attributes['xs'].value)ys = int(xmlFace.attributes['ys'].value)xm = int(xmlFace.attributes['xm'].value)ym = int(xmlFace.attributes['ym'].value)faces[fileName] = array([xf, yf, xs, ys, xm, ym])return faces
def compute_rigid_transform(refpoints,points):""" 计算用于将点对齐到参考点的旋转、尺度和平移量 """A = array([ [points[0], -points[1], 1, 0],[points[1],  points[0], 0, 1],[points[2], -points[3], 1, 0],[points[3],  points[2], 0, 1],[points[4], -points[5], 1, 0],[points[5],  points[4], 0, 1]])y = array([ refpoints[0],refpoints[1],refpoints[2],refpoints[3],refpoints[4],refpoints[5]])# 计算最小化 ||Ax-y|| 的最小二乘解a,b,tx,ty = linalg.lstsq(A,y)[0]R = array([[a, -b], [b, a]]) # 包含尺度的旋转矩阵return R,tx,ty
def rigid_alignment(faces,path,plotflag=False):""" 严格对齐图像,并将其保存为新的图像path 决定对齐后图像保存的位置设置 plotflag=True,以绘制图像"""# 将第一幅图像中的点作为参考点 
#     print(faces.values())refpoints = list(faces.values())[0]# 使用仿射变换扭曲每幅图像for face in faces:
#         print(os.path.join(path,face))points = faces[face]R,tx,ty = compute_rigid_transform(refpoints, points)T = array([[R[1][1], R[1][0]], [R[0][1], R[0][0]]])im = array(Image.open(os.path.join(path,face)))im2 = zeros(im.shape,'uint8')# 对每个颜色通道进行扭曲for i in range(len(im.shape)):im2[:,:,i] = ndimage.affine_transform(im[:,:,i],linalg.inv(T),offset=[-ty,-tx])if plotflag:imshow(im2)show()# 裁剪边界,并保存对齐后的图像h,w = im2.shape[:2]border = (w+h)/20# 裁剪边界imsave(os.path.join(path,'aligned/'+face),im2[int(border):int(h-border),int(border):int(w-border),:])
# 载入控制点的位置
xmlFileName = r'D:\pyFile\Python计算机视觉编程\data\jkfaces.xml'
points = read_points_from_xml(xmlFileName)
# print(points)
# 注册
rigid_alignment(points,r'D:\pyFile\Python计算机视觉编程\data\jkfaces\\')
figure()
for i in range(1,7):subplot(1,6,i)im=array(Image.open(r'D:\pyFile\Python计算机视觉编程\data\jkfaces\2008010%d.jpg'%i))imshow(im)axis('off')
figure()
for i in range(1,7):subplot(1,6,i)im=array(Image.open(r'D:\pyFile\Python计算机视觉编程\data\jkfaces\aligned\2008010%d.jpg'%i))imshow(im)axis('off')

在这里插入图片描述

在这里插入图片描述

3.3 创建全景图

3.3.1 RANSAC

RANSAC是一种用于估计数学模型参数的鲁棒算法,特别是在数据中存在大量异常值时。它最常用于计算机视觉和图像处理中的模型拟合任务。

  • RANSAC 的工作原理
    1. 随机抽样: 从数据集中随机选择一小部分数据点,用于计算模型的初始估计。这个小子集通常是模型参数的最小样本量。
    2. 模型估计: 使用这些随机选择的数据点来拟合模型,计算模型参数。
    3. 验证模型: 通过将拟合得到的模型应用于所有数据点,确定哪些数据点与模型一致(内点)或不一致(外点)。
    4. 评估模型: 计算模型的内点数目或模型的其他度量指标。记录模型的内点数目,找出最好的模型。
    5. 迭代: 重复上述过程多次,每次使用不同的随机样本。选择内点数最多的模型作为最终结果。
    6. 优化: 一旦确定了最佳模型,使用所有内点来重新估计模型参数,得到更精确的模型。
import numpy
import scipy
import scipy.linalg
def ransac(data,model,n,k,t,d,debug=False,return_all=False):iterations = 0bestfit = Nonebesterr = numpy.infbest_inlier_idxs = Nonewhile iterations < k:maybe_idxs, test_idxs = random_partition(n,data.shape[0])maybeinliers = data[maybe_idxs,:]test_points = data[test_idxs]maybemodel = model.fit(maybeinliers)test_err = model.get_error( test_points, maybemodel)also_idxs = test_idxs[test_err < t] # select indices of rows with accepted pointsalsoinliers = data[also_idxs,:]if debug:print('test_err.min()',test_err.min())print('test_err.max()',test_err.max())print('numpy.mean(test_err)',numpy.mean(test_err))print('iteration %d:len(alsoinliers) = %d'%(iterations,len(alsoinliers)))if len(alsoinliers) > d:betterdata = numpy.concatenate( (maybeinliers, alsoinliers) )bettermodel = model.fit(betterdata)better_errs = model.get_error( betterdata, bettermodel)thiserr = numpy.mean( better_errs )if thiserr < besterr:bestfit = bettermodelbesterr = thiserrbest_inlier_idxs = numpy.concatenate( (maybe_idxs, also_idxs) )iterations+=1if bestfit is None:raise ValueError("did not meet fit acceptance criteria")if return_all:return bestfit, {'inliers':best_inlier_idxs}else:return bestfitdef random_partition(n,n_data):"""return n random rows of data (and also the other len(data)-n rows)"""all_idxs = numpy.arange( n_data )numpy.random.shuffle(all_idxs)idxs1 = all_idxs[:n]idxs2 = all_idxs[n:]return idxs1, idxs2class LinearLeastSquaresModel:"""linear system solved using linear least squaresThis class serves as an example that fulfills the model interfaceneeded by the ransac() function."""def __init__(self,input_columns,output_columns,debug=False):self.input_columns = input_columnsself.output_columns = output_columnsself.debug = debugdef fit(self, data):A = numpy.vstack([data[:,i] for i in self.input_columns]).TB = numpy.vstack([data[:,i] for i in self.output_columns]).Tx,resids,rank,s = numpy.linalg.lstsq(A,B)return xdef get_error( self, data, model):A = numpy.vstack([data[:,i] for i in self.input_columns]).TB = numpy.vstack([data[:,i] for i in self.output_columns]).TB_fit = scipy.dot(A,model)err_per_point = numpy.sum((B-B_fit)**2,axis=1) # sum squared error per rowreturn err_per_pointdef test():# generate perfect input datan_samples = 500n_inputs = 1n_outputs = 1A_exact = 20*numpy.random.random((n_samples,n_inputs) )perfect_fit = 60*numpy.random.normal(size=(n_inputs,n_outputs) ) # the modelB_exact = scipy.dot(A_exact,perfect_fit)assert B_exact.shape == (n_samples,n_outputs)# add a little gaussian noise (linear least squares alone should handle this well)A_noisy = A_exact + numpy.random.normal(size=A_exact.shape )B_noisy = B_exact + numpy.random.normal(size=B_exact.shape )if 1:# add some outliersn_outliers = 100all_idxs = numpy.arange( A_noisy.shape[0] )numpy.random.shuffle(all_idxs)outlier_idxs = all_idxs[:n_outliers]non_outlier_idxs = all_idxs[n_outliers:]A_noisy[outlier_idxs] =  20*numpy.random.random((n_outliers,n_inputs) )B_noisy[outlier_idxs] = 50*numpy.random.normal(size=(n_outliers,n_outputs) )# setup modelall_data = numpy.hstack( (A_noisy,B_noisy) )input_columns = range(n_inputs) # the first columns of the arrayoutput_columns = [n_inputs+i for i in range(n_outputs)] # the last columns of the arraydebug = Truemodel = LinearLeastSquaresModel(input_columns,output_columns,debug=debug)linear_fit,resids,rank,s = numpy.linalg.lstsq(all_data[:,input_columns],all_data[:,output_columns])# run RANSAC algorithmransac_fit, ransac_data = ransac(all_data,model,5, 5000, 7e4, 50, # misc. parametersdebug=debug,return_all=True)if 1:import pylabsort_idxs = numpy.argsort(A_exact[:,0])A_col0_sorted = A_exact[sort_idxs] # maintain as rank-2 arrayif 1:pylab.plot( A_noisy[:,0], B_noisy[:,0], 'k.', label='data' )pylab.plot( A_noisy[ransac_data['inliers'],0], B_noisy[ransac_data['inliers'],0], 'bx', label='RANSAC data' )else:pylab.plot( A_noisy[non_outlier_idxs,0], B_noisy[non_outlier_idxs,0], 'k.', label='noisy data' )pylab.plot( A_noisy[outlier_idxs,0], B_noisy[outlier_idxs,0], 'r.', label='outlier data' )pylab.plot( A_col0_sorted[:,0],numpy.dot(A_col0_sorted,ransac_fit)[:,0],label='RANSAC fit' )pylab.plot( A_col0_sorted[:,0],numpy.dot(A_col0_sorted,perfect_fit)[:,0],label='exact system' )pylab.plot( A_col0_sorted[:,0],numpy.dot(A_col0_sorted,linear_fit)[:,0],label='linear fit' )pylab.legend()pylab.show()if __name__=='__main__':test()
  • ransac函数:实现了 RANSAC 算法,包含参数设置、模型估计、内点检测等步骤。
  • LinearLeastSquaresModel类:一个示例模型,使用线性最小二乘法来拟合数据。
  • test函数:生成测试数据、添加噪声和异常值、运行 RANSAC 算法并可视化结果。

在这里插入图片描述

3.3.2 稳健的单应性矩阵估计

from PIL import Image
from numpy import *
from pylab import *
import os
import subprocess
matplotlib.rcParams['font.family'] = 'sans-serif'
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 黑体字体

以下是第二章SIFT特征匹配所应用的函数,因为本节学习需要,我拷贝了过来,方便学习。(后期是可以封装进行使用的)

def process_image(imagename, resultname, params="--edge-thresh 10 --peak-thresh 5"):if imagename[-3:] != 'pgm':im = Image.open(imagename).convert('L')im.save('tmp.pgm')imagename = 'tmp.pgm'cmmd = str(".\sift.exe " + imagename + " --output=" + resultname + " " + params)os.system(cmmd)print('processed', imagename, 'to', resultname)
def read_features_from_file(filename):f=loadtxt(filename)return f[:,:4],f[:,4:]
def match(desc1,desc2):desc1=array([d/linalg.norm(d) for d in desc1])desc2=array([d/linalg.norm(d) for d in desc2])dist_ratio=0.6desc1_size=desc1.shapematchscores=zeros((desc1_size[0],1),'int')desc2t=desc2.Tfor i in range(desc1_size[0]):dotprods=dot(desc1[i,:],desc2t)dotprods=0.9999*dotprodsindx=argsort(arccos(dotprods))if arccos(dotprods)[indx[0]]<dist_ratio*arccos(dotprods)[indx[1]]:matchscores[i]=int(indx[0])return matchscores
def appendimages(im1,im2):rows1=im1.shape[0]rows2=im2.shape[0]if rows1<rows2:im1=concatenate((im1,zeros((rows2-rows1,im1.shape[1]))),axis=0)elif rows1>rows2:im2=concatenate((im2,zeros((rows1-rows2,im2.shape[1]))),axis=0)return concatenate((im1,im2),axis=1)
def plot_matches(im1,im2,locs1,locs2,matchscores,show_below=True):print(locs1.shape,locs2.shape)im3=appendimages(im1,im2)  # 将两张图像水平拼接成一张新图像if show_below:im3=vstack((im3,im3))  # 如果 show_below 为 True,将拼接后的图像在垂直方向上再拼接一次figure(figsize=(20, 10))imshow(im3)cols1=im1.shape[1]  # 存储im1的宽度,用于计算绘制线条时的水平偏移量。
#     print(matchscores)for i,m in enumerate(matchscores):  # 会返回一个由索引和值组成的元组value=m[0]if value>0:plot([locs1[i][0],locs2[value][0]+cols1],[locs1[i][1],locs2[value][1]],'c')axis('off')

将这次需要用于全景的五张图片进行处理,提取每张图像的特征,并且匹配相邻图像之间的特征。

featname = ['Univ'+str(i+1)+'.sift' for i in range(5)]
imname = ['Univ'+str(i+1)+'.jpg' for i in range(5)]
l = {}
d = {}for i in range(5):process_image(imname[i],featname[i])l[i],d[i] = read_features_from_file(featname[i])matches = {}
for i in range(4):matches[i] = match(d[i+1],d[i])

查看图片1和图片2以及匹配结果的可视化。

gray()
im1 = array(Image.open(imname[0]).convert('L'))
im2 = array(Image.open(imname[1]).convert('L'))
plot_matches(im2,im1,l[1],l[0],matches[0])

在这里插入图片描述

查看图片2和图片3以及匹配结果的可视化。

gray()
im1 = array(Image.open(imname[1]).convert('L'))
im2 = array(Image.open(imname[2]).convert('L'))
plot_matches(im2,im1,l[2],l[1],matches[1])

在这里插入图片描述

查看图片3和图片4以及匹配结果的可视化。

gray()
im1 = array(Image.open(imname[3]).convert('L'))
im2 = array(Image.open(imname[2]).convert('L'))
plot_matches(im1,im2,l[3],l[2],matches[2])

在这里插入图片描述

查看图片4和图片5以及匹配结果的可视化。

gray()
im1 = array(Image.open(imname[4]).convert('L'))
im2 = array(Image.open(imname[3]).convert('L'))
plot_matches(im1,im2,l[4],l[3],matches[3])

在这里插入图片描述

接下来使用RANSAC算法求解单应性矩阵H

import numpy as np
import scipy
import scipy.linalg
def H_from_points(fp,tp):if fp.shape!=tp.shape: # 确保源点 (fp) 和目标点 (tp) 的形状相同。如果形状不匹配,抛出异常。raise RuntimeError('number of points do not match')# 归一化源点:计算源点的均值m和标准差maxstd。创建归一化矩阵C1,用于将源点fp进行归一化处理,以减小计算中的数值误差。m=np.mean(fp[:2],axis=1)  # 计算源点的均值 m,对每个坐标分量进行均值计算maxstd=max(np.std(fp[:2],axis=1))+1e-9    # 计算源点的标准差 maxstd,加一个小偏移量以避免除零错误  C1=np.diag([1/maxstd,1/maxstd,1])   # 创建归一化矩阵 C1,用于缩放坐标C1[0][2]=-m[0]/maxstd  # 设置 C1 矩阵的平移部分C1[1][2]=-m[1]/maxstd  # 设置 C1 矩阵的平移部分fp=np.dot(C1,fp)       # 应用归一化矩阵 C1 到源点 fp# 归一化目标点:类似地,对目标点进行归一化处理,计算均值和标准差,并应用归一化矩阵 C2。m=np.mean(tp[:2],axis=1)maxstd=max(np.std(tp[:2],axis=1))+1e-9C2=np.diag([1/maxstd,1/maxstd,1])C2[0][2]=-m[0]/maxstdC2[1][2]=-m[1]/maxstdtp=np.dot(C2,tp)nbr_correspondences=fp.shape[1]A=np.zeros((2*nbr_correspondences,9)) #构建矩阵A,用于求解单应性矩阵。每对点提供两个方程,总共 2 * nbr_correspondences 行。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) # 使用奇异值分解 (SVD)求解矩阵A的最小特征值对应的向量。取V的最后一行(对应于最小特征值),重塑为3x3矩阵 HH=V[8].reshape((3,3))
#     反归一化H=np.dot(np.linalg.inv(C2),np.dot(H,C1))# 使用逆归一化矩阵将计算得到的单应性矩阵从归一化坐标系转换回原始坐标系,并进行归一化处理。
#     归一化,然后返回return H/H[2,2]
class RansacModel(object):def __init__(self,debug=False):self.debug=debug def fit(self,data):""" 计算选取的 4 个对应的单应性矩阵 """# 将其转置,来调用 H_from_points() 计算单应性矩阵data = data.T# 映射的起始点fp = data[:3,:4]# 映射的目标点tp = data[3:,:4]# 计算单应性矩阵,然后返回return H_from_points(fp,tp)def get_error(self, data, H):""" 对所有的对应计算单应性矩阵,然后对每个变换后的点,返回相应的误差 """data = data.T# 映射的起始点fp = data[:3]# 映射的目标点tp = data[3:]# 变换fpfp_transformed = dot(H,fp)# 归一化齐次坐标for i in range(3):fp_transformed[i]/=fp_transformed[2]# 返回每个点的误差return sqrt( sum((tp-fp_transformed)**2,axis=0) )
def random_partition(n,n_data):"""return n random rows of data (and also the other len(data)-n rows)"""all_idxs = np.arange( n_data )np.random.shuffle(all_idxs)idxs1 = all_idxs[:n]idxs2 = all_idxs[n:]return idxs1, idxs2
def ransac(data, model, n, k, t, d, debug=False, return_all=False):iterations = 0bestfit = Nonebesterr = np.infbest_inlier_idxs = Nonewhile iterations < k:maybe_idxs, test_idxs = random_partition(n, data.shape[0])maybeinliers = data[maybe_idxs, :]test_points = data[test_idxs]maybemodel = model.fit(maybeinliers)test_err = model.get_error(test_points, maybemodel)also_idxs = test_idxs[test_err < t]  # select indices of rows with accepted pointsalsoinliers = data[also_idxs, :]if debug:print('test_err.min()', test_err.min())print('test_err.max()', test_err.max())print('numpy.mean(test_err)', np.mean(test_err))print('iteration %d:len(alsoinliers) = %d' %(iterations, len(alsoinliers)))if len(alsoinliers) > d:betterdata = np.concatenate((maybeinliers, alsoinliers))bettermodel = model.fit(betterdata)better_errs = model.get_error(betterdata, bettermodel)#重新计算总的errorthiserr = np.mean(better_errs)if thiserr < besterr:bestfit = bettermodelbesterr = thiserrbest_inlier_idxs = np.concatenate((maybe_idxs, also_idxs))iterations += 1if bestfit is None:raise ValueError("did not meet fit acceptance criteria")if return_all:return bestfit, {'inliers': best_inlier_idxs}else:return bestfit
def H_from_ransac(fp,tp,model,maxiter=1000,match_theshold=10):# 对应点组data = vstack((fp,tp))# 计算 H,并返回H,ransac_data = ransac(data.T,model,4,maxiter,match_theshold,10,return_all=True)
#     print(H,ransac_data)return H,ransac_data['inliers']
def make_homog(points):  # 输入的二维点数组,通常形状为(2,N),其中N是点的数量。每一列代表一个二维点的坐标[x,y]return np.vstack((points,np.ones((1,points.shape[1]))))
# 将匹配转换成齐次坐标点的函数
def convert_points(j):ndx = matches[j].nonzero()[0]fp = make_homog(l[j+1][ndx,:2].T)ndx2 = [int(matches[j][i]) for i in ndx]tp = make_homog(l[j][ndx2,:2].T)return fp,tp# 估计单应性矩阵
model = RansacModel()fp,tp = convert_points(1)
H_12 = H_from_ransac(fp,tp,model)[0] # im1 到 im2 的单应性矩阵
# print(tp)
# print(dot(H_12,fp))
# print('---')fp,tp = convert_points(0)
H_01 = H_from_ransac(fp,tp,model)[0] # im0 到 im1 的单应性矩阵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 拼接图像

from scipy import ndimage

接下来是panorama全景图的主要函数,这里需要注意的是,或许是因为我使用的是jupyter,所有x和y的坐标有些许相反,我按照课本上的函数执行是会发生位置对调(也是调了半天),所以我把三处和课本不一致的地方标了出来

def panorama(H,fromim,toim,padding=2400,delta=2400):is_color = len(fromim.shape) == 3  #用于检查图像是灰度图像还是彩色图像(3通道)# 用于geometric_transform()的单应性变换def transf(p):  # 输入点的坐标,通常是一个二维坐标 (x, y),表示图像上的一个点。p2 = dot(H,[p[1],p[0],1])  # p[1]为x坐标,p[0]为y坐标,跟课本上的第一处不一样) 
#         p2 = dot(H,[p[0],p[1],1])
#         print(p)# 与上面相同,进行归一化后,p2[1]/p2[2]为x坐标,p2[0]/p2[2]为y坐标,跟课本上的第二处不一样) return (p2[1]/p2[2],p2[0]/p2[2])
#         return (p2[1]/p2[2],p2[0]/p2[2])if H[0,2]<0: # fromim在右边,H[0,2]为y轴的偏移量(跟课本上的第三处不一样) 
#   if H[1,2]<0:print('warp - right')# 变换fromimif is_color:# 在目标图像的右边填充0# hstack:水平拼接数组toim_t=hstack((toim,zeros((toim.shape[0],padding,3))))#  fromim_t用于存储变换后的 fromim 图像。这个数组的宽度是目标图像宽度加上 padding,以容纳图像拼接后的结果。fromim_t=zeros((toim.shape[0],toim.shape[1]+padding,toim.shape[2]))for col in range(3):
#               ndimage.geometric_transform 会对图像中的每一个像素应用变换函数 transf。fromim_t[:,:,col]=ndimage.geometric_transform(fromim[:,:,col],transf,(toim.shape[0],toim.shape[1]+padding))else:# 在目标图像的右边填充0toim_t = hstack((toim,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_delta = array([[1,0,-delta],[0,1,0],[0,0,1]])H = dot(H,H_delta)# fromim变换if is_color:# 在目标图像的左边填充0toim_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:# 在目标图像的左边填充0toim_t = hstack((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

以第3张图为中心,图1和图2拼接在右边,图4和图5拼接在左边

# 扭曲图像
delta = 2000 # 用于填充和平移
im1 = array(Image.open(imname[1]))
im2 = array(Image.open(imname[2]))
im_12 = panorama(H_12,im1,im2,delta,delta)im1 = array(Image.open(imname[0]))
im_02 = panorama(dot(H_12,H_01),im1,im_12,delta,delta)im1 = array(Image.open(imname[3]))
im_32 = panorama(H_32,im1,im_02,delta,delta)im1 = array(Image.open(imname[4]))
im_42 = panorama(dot(H_32,H_43),im1,im_32,delta,2*delta)imshow(array(im_42, "uint8"))
axis('off')
show()

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

机器学习/数据分析--通俗语言带你入门决策树(结合分类和回归案例)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 机器学习是深度学习和数据分析的基础&#xff0c;接下来将更新常见的机器学习算法注意&#xff1a;在打数学建模比赛中&#xff0c;机器学习用的也很多&a…

从零开始:渗透测试环境安装详细教程

一、引言 在进行渗透测试之前&#xff0c;搭建一个合适的渗透测试环境是至关重要的。一个良好的渗透测试环境可以帮助我们更好地学习和实践渗透测试技术&#xff0c;同时也可以降低对实际生产环境造成的风险。本文将详细介绍如何安装渗透测试环境&#xff0c;包括选择虚拟机软…

李沐--动手学深度学习 批量规范化

1.理论 2.从零开始实现批量规范化 import torch from torch import nn from d2l import torch as d2l from torch.utils.hooks import RemovableHandle #从零开始实现批量规范化 def batch_norm(X,gamma,beta,moving_mean,moving_var,eps,momentum):#通过is_grad_enabled来判断…

PyCharm汉化:简单一步到胃!PyCharm怎么设置中文简体

最近在弄python的项目 一起加油哦 步骤&#xff1a; PyCharm的汉化可以通过两种主要方法完成&#xff1a; 方法一&#xff1a;通过PyCharm内置的插件市场安装中文语言包 1. 打开PyCharm&#xff0c;点击File -> Settings&#xff08;在Mac上是PyCharm -> Preferences…

java一键生成数据库说明文档html格式

要验收项目了&#xff0c;要写数据库文档&#xff0c;一大堆表太费劲了&#xff0c;直接生成一个吧&#xff0c;本来想用个别人的轮子&#xff0c;网上看了几个&#xff0c;感觉效果不怎么好&#xff0c;自己动手写一个吧。抽空再把字典表补充进去就OK了 先看效果&#xff1a; …

Python3:多行文本内容转换为标准的cURL请求参数值

背景 在最近的工作中&#xff0c;经常需要处理一些接口请求的参数&#xff0c;参数来源形式很多&#xff0c;可能是Excel、知识库文档等&#xff0c;有些数据形式比较复杂&#xff0c;比如多行或者包含很多不同的字符&#xff0c;示例如下&#xff1a; **客服质检分析指引** …

【精选】分享9款AI毕业论文生成初稿题目网站

在当今学术研究领域&#xff0c;AI技术的应用日益广泛&#xff0c;尤其是在学术论文的撰写过程中。AI论文生成器的出现&#xff0c;极大地简化了学术写作流程&#xff0c;提高了写作效率。以下是9款推荐的AI毕业论文生成初稿的网站&#xff0c;它们各有特色&#xff0c;能够满足…

MFC工控项目实例之七点击下拉菜单弹出对话框

承接专栏《MFC工控项目实例之六CFile添加菜单栏》 1、在SEAL_PRESSUREDlg.h文件中添加代码 class CSEAL_PRESSUREDlg : public CDialog { ...afx_msg void OnTypeManage(); ... } 2、在SEAL_PRESSUREDlg.cpp文件中添加代码 BEGIN_MESSAGE_MAP(CSEAL_PRESSUREDlg, CDialog)//…

MySQL的源码安装及基本部署(基于RHEL7.9)

这里源码安装mysql的5.7.44版本 一、源码安装 1.下载并解压mysql , 进入目录: wget https://downloads.mysql.com/archives/get/p/23/file/mysql-boost-5.7.44.tar.gz tar xf mysql-boost-5.7.44.tar.gz cd mysql-5.7.44/ 2.准备好mysql编译安装依赖: yum install cmake g…

Python爬虫——简单网页抓取(实战案例)小白篇

Python 爬虫是一种强大的工具&#xff0c;用于从网页中提取数据。这里&#xff0c;我将通过一个简单的实战案例来展示如何使用 Python 和一些流行的库&#xff08;如 requests 和 BeautifulSoup&#xff09;来抓取网页数据。 实战案例&#xff1a;抓取一个新闻网站的头条新闻标…

Windows上传Linux文件行尾符转换

Windows上传Linux文件行尾符转换 1、Windows与Linux文件行尾符2、Windows与Linux文件格式转换 1、Windows与Linux文件行尾符 众所周知&#xff0c;Windows、Mac与Linux三种系统的文件行尾符不同&#xff0c;其中 Windows文件行尾符&#xff08;\r\n&#xff09;&#xff1a; L…

使用kafka改造分布式事务

文章目录 1、kafka确保消息不丢失&#xff1f;1.1、生产者端确保消息不丢失1.2、kafka服务端确保消息不丢失1.3、消费者确保正确无误的消费 2、生产者发送消息 KafkaService3、UserInfoServiceImpl -> login()4、service-account - > AccountListener.java 1、kafka确保消…

day31-测试之性能测试工具JMeter的功能概要、元件作用域和执行顺序

目录 一、JMeter的功能概要 1.1.文件目录介绍 1).bin目录 2).docs目录 3).printable_docs目录 4).lib目录 1.2.基本配置 1).汉化 2).主题修改 1.3.基本使用流程 二、JMeter元件作用域和执行顺序 2.1.名称解释 2.2.基本元件 2.3.元件作用域 1).核心 2).提示 3).作用域的原则 2.…

Redis 实现哨兵模式

目录 1 哨兵模式介绍 1.1 什么是哨兵模式 1.2 sentinel中的三个定时任务 2 配置哨兵 2.1 实验环境 2.2 实现哨兵的三条参数&#xff1a; 2.3 修改配置文件 2.3.1 MASTER 2.3.2 SLAVE 2.4 将 sentinel 进行备份 2.5 开启哨兵模式 2.6 故障模拟 3 在整个架构中可能会出现的问题 …

go中 panicrecoverdefer机制

go的defer机制-CSDN博客 常见panic场景 数组或切片越界&#xff0c;例如 s : make([]int, 3); fmt.Println(s[5]) 会引发 panic: runtime error: index out of range空指针调用&#xff0c;例如 var p *Person; fmt.Println(p.Name) 会引发 panic: runtime error: invalid m…

网络通信tcp

一、udp案例 二、基于tcp: tcp //c/s tcp 客户端: 1.建立连接 socket bind connect 2.通信过程 read write close tcp服务器: 1.建立连接 socket bind listen accept 2.通信过程 read write close connect函数 int connect(int sockfd, con…

Git克隆仓库太大导致拉不下来的解决方法 fatal: fetch-pack: invalid index-pack output

一般这种问题是因为某个文件/某个文件夹/某些文件夹过大导致整个项目超过1G了导致的 试过其他教程里的设置depth为1,也改过git的postBuffer,都不管用 最后还是靠克隆指定文件夹这种方式成功把项目拉下来 1. Git Bash 输入命令 git clone --filterblob:none --sparse 项目路径…

探索Unity3D URP后处理在UI控件Image上的应用

探索Unity3D URP后处理在UI控件Image上的应用 前言初识URP配置后处理效果将后处理应用于UI控件方法一&#xff1a;自定义Shader方法二&#xff1a;RenderTexture的使用 实践操作步骤一&#xff1a;创建RenderTexture步骤二&#xff1a;UI渲染至RenderTexture步骤三&#xff1a;…

视频如何转gif?分享这几款软件!

在这个快节奏、高创意的互联网时代&#xff0c;动图&#xff08;GIF&#xff09;以其独特的魅力成为了社交媒体、聊天软件中的宠儿。它们不仅能瞬间抓住眼球&#xff0c;还能让信息传递更加生动有趣。然而&#xff0c;你是否曾为如何将精彩瞬间从视频中精准截取并转换成GIF而苦…

​北斗终端:无人驾驶领域的导航新星

一、北斗终端在无人驾驶领域的应用 北斗终端&#xff0c;作为我国自主研发的北斗卫星导航系统的重要组成部分&#xff0c;其在无人驾驶领域中的应用正逐步显现其独特魅力。北斗系统的高精度、高可靠性和良好的抗干扰性能&#xff0c;为无人驾驶车辆提供了精确的定位和导航服务…