特别声明:鉴于本人之前的文章在CSDN及博客园上被多次未署名抄袭及转载且附加广告,特此声明,本文禁止抄袭或未经作者许可下转载,请各位作者自重!!!!!!!!!!!
大致要求为从倒车时摄像头所拍摄的照片中寻找到车道并将其连接为完整的车道,以平面图形式呈现。
方案概述:先将原图片进行投影变化,再运用SURF算法匹配图片间的特征点,使用FLANN快速匹配图片间特征,计算一张图片可以变换到另一张图片的变换矩阵 (homography 单应性矩阵),用这个矩阵把那张图片变换后放到另一张图片相应的位置 ( 就是相当于把两张图片中定好的四个相似的点給重合在一起)。如此,就可以实现简单的全景拼接。这里我们主要利用opencv集成好的的stitching模块进行车道的拼接。
以下我会从原理及利用库两方面讲解此项目的实现方案
1.桶形变化
倒车摄像头与地面呈一定角度,所拍摄的图片如果直接进行全景拼接,会使图片产生较大畸变,及范围较大的黑边。这里考虑到倒车摄像头大都斜向下俯拍,且车道大都呈矩形,我们使用投影变化做图片的桶形变化。
#读取图片
img1 = cv2.imread('/home/yc/Pictures/zuo.jpg')
img2 = cv2.imread('/home/yc/Pictures/you.jpg')
img3 = cv2.imread('/home/yc/Pictures/zuo2.jpg')
#取图片的形态学坐标信息
h, w = img1.shape[:2]
h2, w2 = img2.shape[:2]
h3, w3 = img3.shape[:2]
#取出所选变化前图片的矩形角点
src=np.array([[380,7],[1432,320],[380,1904],[1432,1607]],np.float32)
#取出期望变化后图片的矩形角点
dst=np.array([[230,0],[1424,1],[230,1912],[1424,1912]],np.float32)
#计算投影矩阵
P=cv2.getPerspectiveTransform(src,dst)
#对图片做投影变化
img12=cv2.warpPerspective(img1,P,(w,h),borderValue=400)
src2=np.array([[0,0],[1432,320],[0,1904],[1432,1904]],np.float32)
dst2=np.array([[0,0],[1424,1],[0,1904],[1424,1912]],np.float32)
#计算投影矩阵
P2=cv2.getPerspectiveTransform(src2,dst2)
img22=cv2.warpPerspective(img2,P,(w2,h2),borderValue=400)
src3=np.array([[0,0],[1432,320],[0,1904],[1432,1904]],np.float32)
dst3=np.array([[0,0],[1424,1],[0,1904],[1424,1912]],np.float32)
P3=cv2.getPerspectiveTransform(src3,dst3)#计算投影矩阵
桶形变化的作用主要就是缩减了边缘图像匹配的发散,尽量让匹配点的纵坐标的差值减小,缩减透视变换后的变形
2.特征检测及特征匹配
这里我们主要采用hessian角点检测及surf特征点检测算法,检测到关键点使用FLANN快速匹配器进行特征匹配,要注意,surf算法有版权限制,请下载旧版opencv依赖库。
hessian=10
surf=cv2.cv2.xfeatures2d.SURF_create(hessian)
#将Hessian Threshold设置为400,阈值越大能检测的特征就越少
kp1,des1=surf.detectAndCompute(leftgray,None) #查找关键点和描述符
kp2,des2=surf.detectAndCompute(rightgray,None)
FLANN_INDEX_KDTREE=0#建立FLANN匹配器的参数
indexParams=dict(algorithm=FLANN_INDEX_KDTREE,trees=5) #配置索引,密度树的数量为5
searchParams=dict(checks=50)#指定递归次数
#FlannBasedMatcher:是目前最快的特征匹配算法(最近邻搜索)
flann=cv2.FlannBasedMatcher(indexParams,searchParams) #建立匹配器
matches=flann.knnMatch(des1,des2,k=2) #得出匹配的关键点
good=[]#提取优秀的特征点
for m,n in matches:
if m.distance < 0.7*n.distance: #如果第一个邻近距离比第二个邻近距离的0.7倍小,则保留
good.append(m)
有关特征匹配算法有未曾了解的,可以参考我之前的文章https://www.jianshu.com/p/bad83a08b1a8
3.利用匹配得到的变换矩阵,合并两幅图
H=cv2.findHomography(src_pts,dst_pts)#生成变换矩阵
h,w=leftgray.shape[:2]
h1,w1=rightgray.shape[:2]
shft=np.array([[1.0,0,w],[0,1.0,0],[0,0,1.0]])
M=np.dot(shft,H[0]) #获取左边图像到右边图像的投影映射关系
dst_corners=cv2.warpPerspective(leftgray,M,(w*2,h))#透视变换,新图像可容纳完整的两幅图
cv2.namedWindow("tiledImg1", cv2.WINDOW_NORMAL)
cv2.imshow('tiledImg1',dst_corners) #显示,第一幅图已在标准位置
dst_corners[0:h,w:w*2]=rightgray #将第二幅图放在右侧
4.stitching模块的应用
Image Stitching 模块下共包含七个子模块,分别为:
.Features Finding and Images Matching 功能查找和图像匹配
.Rotation Estimation 轮换估计
.Autocalibration 自动校准
.Images Warping 图像变形
.Seam Estimation 接缝估计
.Exposure Compensation 曝光补偿
.Image Blenders 图像搅拌机
借用一下stitching的官方引导图。
基本上是之前三个步骤的完善版代码,也很好的克服了合成图片产生黑边的问题,但合成速度较慢,运用在普通全景图像拼接时略有小题大做。
这里讲解一下使用python调用opencv中的stitching模块的具体应用方法。
因为项目所用图片的特殊性,我们依然需要进行第一步的投影变化,纠正拍摄图形的视角。此步骤参考第一步。
接着我们调用两个stitching库中的函数cv2.createStitcher
以及.stitch
,第一个函数仅一个参数决定调用打开stitching库的方式有try_gpu以及False,鉴于opencv对GPU的支持很有限,这里我们建议采用False,第二个函数即直接调用stitching拼接图片,有以下四个参数:
OK = 0:图像拼接成功。
ERR_NEED_MORE_IMGS = 1:如果您收到此状态代码,则需要更多输入图像来构建全景图。通常,如果输入图像中检测不到足够的关键点,则会发生此错误。
ERR_HOMOGRAPHY_EST_FAIL = 2:当RANSAC单应性估计失败时,会发生此错误。同样,您可能需要更多图像,或者您的图像没有足够的区别,独特的纹理/对象,以便准确匹配关键点。
ERR_CAMERA_PARAMS_ADJUST_FAIL = 3:我之前从未遇到过这个错误,所以我对它没有多少了解,但要点是它与未能从输入图像中正确估计相机内参/外参有关。如果遇到此错误,您可能需要参考OpenCV文档,甚至可以深入了解OpenCV C ++代码。
#-*- coding:utf-8 -*-
import numpy as np
import cv2
from cv2 import Stitcher
import matplotlib.pyplot as plt
if __name__ == "__main__":
img1 = cv2.imread('/home/yc/Pictures/zuo.jpg')
img2 = cv2.imread('/home/yc/Pictures/you.jpg')
img3 = cv2.imread('/home/yc/Pictures/zuo2.jpg')
h, w = img1.shape[:2]
h2, w2 = img2.shape[:2]
h3, w3 = img3.shape[:2]
src=np.array([[380,7],[1432,320],[380,1904],[1432,1607]],np.float32)
dst=np.array([[230,0],[1424,1],[230,1912],[1424,1912]],np.float32)
P=cv2.getPerspectiveTransform(src,dst)#计算投影矩阵
img12=cv2.warpPerspective(img1,P,(w,h),borderValue=400)
src2=np.array([[0,0],[1432,320],[0,1904],[1432,1904]],np.float32)
dst2=np.array([[0,0],[1424,1],[0,1904],[1424,1912]],np.float32)
P2=cv2.getPerspectiveTransform(src2,dst2)#计算投影矩阵
img22=cv2.warpPerspective(img2,P,(w2,h2),borderValue=400)
src3=np.array([[0,0],[1432,320],[0,1904],[1432,1904]],np.float32)
dst3=np.array([[0,0],[1424,1],[0,1904],[1424,1912]],np.float32)
P3=cv2.getPerspectiveTransform(src3,dst3)#计算投影矩阵
img23=cv2.warpPerspective(img3,P,(w3,h3),borderValue=400)
stitcher = cv2.createStitcher(False)
#stitcher = cv2.Stitcher.create(cv2.Stitcher_PANORAMA)根据不同的OpenCV版本来调用
(_result, out) = stitcher.stitch((img12, img22, img23))
rows3, cols3 = out.shape[:2]
#旋转图片
M1 = cv2.getRotationMatrix2D((w / 2, h / 2), 90, 1)
M2 = cv2.getRotationMatrix2D((w2 / 2, h2 / 2), 90, 1)
M3 = cv2.getRotationMatrix2D((w3 / 2, h3 / 2), 90, 1)
#M4 = cv2.getRotationMatrix2D((cols3 / 2, rows3 / 2), 90, 1)
img31 = cv2.warpAffine(img1, M1, (w, h))
img32 = cv2.warpAffine(img2, M2, (w2, h2))
img33 = cv2.warpAffine(img3, M3, (w3, h3))
#imgout = cv2.warpAffine(out, M4, (cols3, rows3))
#拼接三张原图
img4=np.vstack([img33,img32,img31])
titles = ['orgin', 'result']
imgs = [img4,out]
#画出图形
for i in range(2):
plt.subplot(1,2,i+1)
#plt.plot([0,10],[0,5])
#plt.subplots_adjust(left=0.09,right=1,wspace=0.25,hspace=0.25,bottom=0.13,top=0.91)
plt.imshow(imgs[i])
plt.title(titles[i])
plt.show()
以下是样图所呈现的效果,左边为拍摄的原图,右边为所拼接的车道图片
希望我的教程可以给一起学习opencv的同学一点帮助