Image-Stitching
学习源码来自Github-pavanpn/Image-Stitching
主要函数:
1. cv2.cvtColor()
颜色空间转换函数
img_yuv=cv2.cvtColor(img,k) # img是需要转换对图片,k是转换成何种格式
# cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式
# cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片
2. cv2.equalizeHist()
直方图均衡化函数
cv2.equalizeHist(img) # 将要均衡化的原图像[要求是灰度图像]作为参数传入,则返回值即为均衡化后的图像
3. bf=cv2.BFMatcher()、matches=bf.knnMatch()
K-最邻近匹配
# 暴力匹配BFMatcher,遍历描述符,确定描述符是否匹配,然后计算匹配距离并排序
# BFMatcher函数参数:
# normType:NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2。
# NORM_L1和NORM_L2是SIFT和SURF描述符的优先选择
# NORM_HAMMING和NORM_HAMMING2是用于ORB算法
bf = cv2.BFMatcher(normType=cv2.NORM_HAMMING, crossCheck=True)
matches = bf.knnMatch(d1, d2, k=1) #获得knn检测器,d1,d2为描述子
# knn匹配可以返回k个最佳的匹配项,bf返回所有的匹配项
4. cv2.findHomography()
单应矩阵发现函数
M,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)
# img1_pts 目标图像的特征点集合
# 第三个参数 配准方法的选择
# 第四个参数 每次抽取样本的个数,仅RANSAC和RHO有
5. cv2.perspectiveTransform()
透视变换函数
cv2.perspectiveTransform(src, m[, dst]) → dst
# src:输入的2通道或者3通道的图片
# m:变换矩阵
6. np.concatenate()
result_dims=np.concatenate((img1_dims,img2_dims),axis=0)
# 前两个参数为要拼接对数组
# axis参数为指定按照哪个维度进行拼接,axis=0则代表着按照第一维度进行拼接
上述两个数组不能按行拼接(axis不能为0),因为第二维度尺寸不同。
7. cv2.warpPerspective()
cv2.warpPespective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) # 获得根据单应性矩阵变化后的图像
# image表示输入图像,H表示单应性的矩阵,(imageA.shape[1] + imageB.shape[1], imageA.shape[0])表示矩阵变化后的维度
8. sift.detectAndComputer()
sift.detectAndComputer(gray, None) # 计算出图像的关键点和sift特征向量,gray表示输入的图片
9. np.reshape()
numpy.reshape(a, newshape, order='C')
# a 需要reshape的array,可以看成是对数组的扩展,但是不影响原始数组
# 关于newshape参数
np.reshape(m,n) # 返回一个m行n列的数组
np.reshape(-1) # 返回一个一维数组
np.reshape(-1,n) # 返回一个n列的数组,行数自动给定
np.reshape(k,n,m) # 创建一个三维 (k个n*m)的数组
np.reshape(p,k,n,m) # 四维 p个(k,n,m)
# order: 可选为(C, F, A)
C: 按照行来填充
F: 按照列的顺序来填充
A: 按任意方向,默认,这里相当于行
arr=np.array([1,2,3,4,5,6,7,8,9,10,11,12])
m=arr.reshape(3,4)
print(m)
>>
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
m=arr.reshape(3,2,2)
>>
[[[ 1 2]
[ 3 4]]
[[ 5 6]
[ 7 8]]
[[ 9 10]
[11 12]]]
m=arr.reshape(2,3,1,2)
>>
[[[[ 1 2]]
[[ 3 4]]
[[ 5 6]]]
[[[ 7 8]]
[[ 9 10]]
[[11 12]]]]
当newshape参数大于2个的时候,pylint就开始报参数过多的错 ?
步骤:
- 读入图像,
- 用SIFT/SURF/FAST/ORB提取每张图的特征点 和 每个特征点对应的描述子
- 通过匹配特征点描述子,找到两张图中匹配的特征点对(这里可能存在错误匹配)
- 使用RANSAC算法剔除错误匹配
- 求解方程组,计算Homograph单应性变换矩阵
- 通过Homograph单应性变换矩阵对其中一张图片进行仿射变换
- 拼接图片
1.彩色图像的直方图均衡化
直方图均衡化
就是对图像进行非线性拉伸
,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。
中心思想:
原始图像的灰度直方图从比较集中的某个
灰度区间变成在全部灰度范围内
的均匀
分布
def equalize_histogram_color(img):
img_yuv=cv2.cvtColor(img,cv2.COLOR_BGR2YUV)
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
img = cv2.cvtColor(img_yuv,cv2.COLOR_YUV2BGR)
return img
为什么要做直方图均衡化?
一副高质量的图像的像素值分布应该很广泛
,所以应该把它的直方图做一个横向拉伸,这就是直方图均衡化要做的事情。通常情况下这种操作会改善图像的对比度
。
2.特征点匹配和计算单应矩阵
def get_sift_homography(img1, img2):
#特征提取
# 初始化SIFT
sift=cv2.xfeatures2d.SIFT_create() # 修改点 版本原因
#提取关键点和描述子
k1,d1=sift.detectAndCompute(img1,None) # k1存储提取的特征点,d1存储对应的描述子
k2,d2=sift.detectAndCompute(img2,None)
#图片匹配
# 描述子计算:k-最邻近匹配
bf=cv2.BFMatcher()
matches=bf.knnMatch(d1,d2, k=2)
# 获得理想匹配
verify_ratio=0.8 # Source: stackoverflow
verified_matches=[]
for m1,m2 in matches:
# 只加入理想匹配
if m1.distance<0.8*m2.distance:
verified_matches.append(m1)
# 最少匹配数
min_matches=8
if len(verified_matches)>min_matches:
# 存储匹配点
img1_pts=[]
img2_pts=[]
# 把匹配点加进去
for match in verified_matches:
img1_pts.append(k1[match.queryIdx].pt)
img2_pts.append(k2[match.trainIdx].pt)
img1_pts=np.float32(img1_pts).reshape(-1,1,2)
img2_pts=np.float32(img2_pts).reshape(-1,1,2)
# 计算单应矩阵M,掩模 mark,使用了RANSAC算法剔初错误匹配
M,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)
return M
else: # 如果不够最小匹配数
print('Error: Not enough matches')
exit()
3.拼接图片
# 使用关键点来拼接图片
def get_stitched_image(img1,img2,M):
# 获得输入图片的宽、高
w1,h1=img1.shape[:2]
w2,h2=img2.shape[:2]
# 获得图像维度
img1_dims=np.float32([[0,0],[0,w1],[h1,w1],[h1,0]]).reshape(-1,1,2)
img2_dims_temp=np.float32([[0,0],[0,w2],[h2,w2],[h2,0]]).reshape(-1,1,2)
# 获取第二幅图像的相对透视图
img2_dims=cv2.perspectiveTransform(img2_dims_temp,M) # 执行矢量的透视矩阵变换
# 得到结果图片的维度
result_dims=np.concatenate((img1_dims,img2_dims),axis=0)
# 拼接图像
# 计算匹配点的维度
[x_min,y_min]=np.int32(result_dims.min(axis=0).ravel()-0.5)
[x_max,y_max]=np.int32(result_dims.max(axis=0).ravel()+0.5)
# 仿射变换后创建输出数组
transform_dist=[-x_min,-y_min]
transform_array=np.array([[1, 0, transform_dist[0]],
[0, 1, transform_dist[1]],
[0,0,1]])
# 扭曲图像以用于拼接
result_img=cv2.warpPerspective(img2,transform_array.dot(M),
(x_max-x_min,y_max-y_min))
result_img[transform_dist[1]:w1+transform_dist[1],
transform_dist[0]:h1+transform_dist[0]]=img1
# Return the result
return result_img
几个修改点
1. >83 print
后需要带括号输出 版本原因
2. >45 module ‘cv2’ has no attribute ‘SIFT’ 版本原因
3.4.2
版本中,openCV将SIFT等算法整合到xfeatures2d集合里面了
把sift=cv2.SIFT()
变成sift=cv2.xfeatures2d.SIFT_create()
3. >97 98 关于读入图像cv2.imread()
函数的使用
# 一直越界报错 (可能是我环境下的命令行参数没有配置好)
img1 = cv2.imread(sys.argv[1])
img2 = cv2.imread(sys.argv[2])
需要修改成路径形式
注意:
1.路径形式不支持 单右斜线
形式,可以支持 双右斜线、双左斜线、单左斜线形式、混合形式
#include<opencv2\opencv.hpp>
using namespace cv;
int main(int argc,char* argv[])
{
Mat img;
//-- 1 --双右斜线法
//string imgpath = "C:\\Users\\bingbuyu\\Pictures\\photo\\miao1.jpg";
//-- 2 --双左斜线法
//string imgpath = "C://Users//bingbuyu//Pictures//photo//miao1.jpg";
//-- 3 --单左斜线法
//string imgpath = "C:/Users/bingbuyu/Pictures/photo/miao1.jpg";
//-- 4 --以上三种混合法
//string imgpath = "C:/Users//bingbuyu\\Pictures//photo//miao1.jpg";
//-- 5 --相对路径法
//string imgpath = "miao.jpg";
//-- 6 --命令行参数法
string imgpath = argv[1];
img = imread(imgpath, 1);
imshow("img", img);
waitKey(0);
return 0;
}
2.命令行参数法
Python中 sys.argv[]的用法简明解释
4. >115 写入时,同样是由sys.argv[]
引起的越界错误,写入时需要带后缀名
写入
不能为imwrite()找到特定扩展名的writer,因为result_image_name
必须是.png
或.jpg
或.bmg
,改成带后缀的即可。
虽然能跑出来,但是有个报错没整明白 :是因为多于了2个newshape参数,变三维、四维数组的时候就会报这个,但还是可以用
方法调用的位置参数过多?reshape()
?
完整代码:
import sys
import cv2
import numpy as np
# Use the keypoints to stitch the images
def get_stitched_image(img1,img2,M):
# Get width and height of input images
w1,h1=img1.shape[:2]
w2,h2=img2.shape[:2]
# Get the canvas dimesions
img1_dims=np.float32([[0,0],[0,w1],[h1,w1],[h1,0]]).reshape(-1,1,2)
img2_dims_temp=np.float32([[0,0],[0,w2],[h2,w2],[h2,0]]).reshape(-1,1,2)
# Get relative perspective of second image
img2_dims=cv2.perspectiveTransform(img2_dims_temp,M)
# Resulting dimensions
result_dims=np.concatenate((img1_dims,img2_dims),axis=0)
# Getting images together
# Calculate dimensions of match points
[x_min,y_min]=np.int32(result_dims.min(axis=0).ravel()-0.5)
[x_max,y_max]=np.int32(result_dims.max(axis=0).ravel()+0.5)
# Create output array after affine transformation
transform_dist=[-x_min,-y_min]
transform_array=np.array([[1, 0, transform_dist[0]],
[0, 1, transform_dist[1]],
[0,0,1]])
# Warp images to get the resulting image
result_img=cv2.warpPerspective(img2,transform_array.dot(M),
(x_max-x_min,y_max-y_min))
result_img[transform_dist[1]:w1+transform_dist[1],
transform_dist[0]:h1+transform_dist[0]]=img1
# Return the result
return result_img
# Find SIFT and return Homography Matrix
def get_sift_homography(img1, img2):
# Initialize SIFT
sift=cv2.xfeatures2d.SIFT_create() #修改点 版本原因
# Extract keypoints and descriptors
k1,d1=sift.detectAndCompute(img1,None)
k2,d2=sift.detectAndCompute(img2,None)
# Bruteforce matcher on the descriptors
bf=cv2.BFMatcher()
matches=bf.knnMatch(d1,d2, k=2)
# Make sure that the matches are good
verify_ratio=0.8 # Source: stackoverflow
verified_matches=[]
for m1,m2 in matches:
# Add to array only if it's a good match
if m1.distance<0.8*m2.distance:
verified_matches.append(m1)
# Mimnum number of matches
min_matches=8
if len(verified_matches)>min_matches:
# Array to store matching points
img1_pts=[]
img2_pts=[]
# Add matching points to array
for match in verified_matches:
img1_pts.append(k1[match.queryIdx].pt)
img2_pts.append(k2[match.trainIdx].pt)
img1_pts=np.float32(img1_pts).reshape(-1,1,2)
img2_pts=np.float32(img2_pts).reshape(-1,1,2)
# Compute homography matrix
M,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)
return M
else:
print('Error: Not enough matches')
exit()
# Equalize Histogram of Color Images
def equalize_histogram_color(img):
img_yuv=cv2.cvtColor(img,cv2.COLOR_BGR2YUV)
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
img = cv2.cvtColor(img_yuv,cv2.COLOR_YUV2BGR)
return img
# Main function definition
def main():
# Get input set of images
img1=cv2.imread("/Applications/code/test/01_suburbA.jpg") #修改点
img2=cv2.imread("/Applications/code/test/01_suburbB.jpg")
# Equalize histogram
img1=equalize_histogram_color(img1)
img2=equalize_histogram_color(img2)
# Show input images
input_images=np.hstack((img1,img2))
cv2.imshow('Input Images',input_images)
# Use SIFT to find keypoints and return homography matrix
M=get_sift_homography(img1, img2)
# Stitch the images together using homography matrix
result_image=get_stitched_image(img2,img1,M)
# Write the result to the same directory
result_image_name='result.jpg' #需要带后缀写入
cv2.imwrite(result_image_name,result_image)
# Show the resulting image
cv2.imshow('Result',result_image)
cv2.waitKey()
# Call main function
if __name__=='__main__':
main()