OpenCV-Python 初尝试:图像特征提取及简单多图拼接

用 OpenCV-Python 实现图片的特征点提取(SIFT)、匹配筛选(FLANN)及简单的多图拼接为全景图。

准备工作

运行环境

  • Python 3.6.7
  • Jupyter Notebook
  • opencv-python 3.4.2.16

你可能需要装的库

1
2
3
4
5
pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.16
# 如果显示已安装并且出现版本不支持的error的话,需要先卸载一下原先的
pip uninstall opencv-python
pip uninstall opencv-contrib-python

Let’s Start

导入需要用到的库

1
2
3
4
5
%matplotlib inline #这行是为了让代码在jupyter运行的时候show的图片可以直接显示
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
import os

图片保存函数 / ImgSave( )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def ImgSave(i=0,**kwargs):
img, name, flag = kwargs['img'], kwargs['name'], kwargs['flag']
i = str(i)
if(img.any() != None):
if (name != None):
if(flag == 0):
cv.imwrite(name+'_pd_'+i+'.jpg', img)
elif(flag == 1):
cv.imwrite(name+'_jg_'+i+'.jpg', img)
else:
cv.imwrite(name+'_test_'+i+'.jpg', img)
else:
cv.imwrite('名前のない絵'+'_'+i+'.jpg', img)
else:
print("保存失败!")

图片尺寸调整函数 / ImgResize( )

因为图片太大会影响运行的速度,所以统一将读入的图片尺寸约束在1500*1500以内。

1
2
3
4
5
6
7
8
9
10
def ImgResize(**kwargs):
img = kwargs['img']
h, w = img.shape[:2]
for rt in range(10, 0, -1):
if h*rt<15000 and w*rt<15000:
rt = rt*0.1
break
imgr = cv.resize(img, (int(w*rt), int(h*rt)), interpolation=cv.INTER_AREA)
imgre = cv.cvtColor(imgr, cv.COLOR_BGR2RGB)
return imgre

图片黑边剪切函数 / ImgCrop( )

两张图拼接时会预留扩出border,所以输出的结果图有很厚的黑边,留着会影响到后续的匹配拼接,这里用了一个简单粗暴的方法去掉黑边。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def ImgCrop(img, lim = 10):
c = []
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
for i in range(4):
k = 0
if i == 0: #left
while gray[:,k].var() <= lim: k+=1
elif i == 1: #right
while gray[:,-k].var() <= lim: k+=1
elif i == 2: #top
while gray[k,:].var() <= lim: k+=1
elif i == 3: #bottom
while gray[-k,:].var() <= lim: k+=1
else: pass
c.append(k)
l,r,t,b = c
return img[t:-b,l:-r,:]

特征点提取及匹配函数 / ImgRegist( )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def ImgRegist(**kwargs):
srcImg = cv.copyMakeBorder(img1, 750,750,750,750,
cv.BORDER_CONSTANT, value=(0, 0, 0))
testImg = cv.copyMakeBorder(img2, top, bot, left, right,
cv.BORDER_CONSTANT, value=(0, 0, 0))
img1gray = cv.cvtColor(srcImg, cv.COLOR_BGR2GRAY)
img2gray = cv.cvtColor(testImg, cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d_SIFT().create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1gray, None)
kp2, des2 = sift.detectAndCompute(img2gray, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0, 0] for i in range(len(matches))]

good = []
pts1 = []
pts2 = []
# ratio test as per Lowe's paper
for i, (m, n) in enumerate(matches):
if m.distance < 0.7*n.distance:
good.append(m)
pts2.append(kp2[m.trainIdx].pt)
pts1.append(kp1[m.queryIdx].pt)
matchesMask[i] = [1, 0]

draw_params = dict(matchColor=(0, 255, 0),
singlePointColor=(255, 0, 0),
matchesMask=matchesMask,
flags=0)
img3 = cv.drawMatchesKnn(img1gray, kp1, img2gray,
kp2, matches, None, **draw_params)
return srcImg, testImg, good, m, kp1, kp2, img3

图片配准拼接函数 / ImgMosaic( )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def ImgMosaic(**kwargs):
rows, cols = srcImg.shape[:2]
MIN_MATCH_COUNT = 10
if len(good) > MIN_MATCH_COUNT:
src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
warpImg = cv.warpPerspective(testImg, np.array(M),
(testImg.shape[1], testImg.shape[0]),
flags=cv.WARP_INVERSE_MAP)

for col in range(0, cols):
if srcImg[:, col].any() and warpImg[:, col].any():
left = col
break
for col in range(cols-1, 0, -1):
if srcImg[:, col].any() and warpImg[:, col].any():
right = col
break

res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):
for col in range(0, cols):
if not srcImg[row, col].any():
res[row, col] = warpImg[row, col]
elif not warpImg[row, col].any():
res[row, col] = srcImg[row, col]
else:
srcImgLen = float(abs(col - left))
testImgLen = float(abs(col - right))
alpha = (srcImgLen / (srcImgLen + testImgLen))*0.95
res[row, col] = np.clip(srcImg[row, col] * (1-alpha)
+ warpImg[row, col] * alpha, 0, 255)
return res
else:
print("Not enough matches are found - {}/{}".format(len(good),
MIN_MATCH_COUNT))
matchesMask = None

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
if __name__ == '__main__':
root_path = "./data/"
dri_path = "HZ"
imgs_name = ["1-20180720","2-20180720","3-20180720","4-20180720","5-20180720"]
name = "HZ12345"

for i in range(len(imgs_name)-1):
if (i == 0):
im1 = cv.imread(os.path.join(root_path, dri_path, imgs_name[0]+".jpg"))
im2 = cv.imread(os.path.join(root_path, dri_path, imgs_name[1]+".jpg"))
img1 = ImgResize(img = im1)
img2 = ImgResize(img = im2)
top, bot, left, right = 750,750,750,750

else:
img1 = cv.cvtColor(res, cv.COLOR_BGR2RGB)
im2 = cv.imread(os.path.join(root_path, dri_path, imgs_name[i+1]+".jpg"))
img2 = ImgResize(img = im2)
h1 = img1.shape[0] +1500
w1 = img1.shape[1] + 1500
h2, w2 = img2.shape[:2]
top, bot, left, right = int((h1-h2)/2), h1-int((h1-h2)/2),
int((w1-w2)/2), w1-int((w1-w2)/2)

srcImg, testImg, good, m, kp1, kp2, img_pd = ImgRegist(img1=img1, img2=img2,
top=top, bot=bot,
left=left, right=right)
img_pd = cv.cvtColor(img_pd, cv.COLOR_BGR2RGB)
ImgSave(i+1, name=name, flag=0, img=img_pd)
print(str(i+1)+" Regist Finish!")

r = ImgMosaic(srcImg=srcImg, testImg=testImg, good=good,
m=m, kp1=kp1, kp2=kp2)
re = ImgCrop(img=r, lim=1)
res = cv.cvtColor(re, cv.COLOR_BGR2RGB)
ImgSave(i+1,name=name, flag=1, img=res)
print(str(i+1)+" Mosaic Finish!")

print("おめてどう~\(≧▽≦)/~")

See What Happens

横向三张拼接

outoflib

纵向五张拼接

HZ12345

横向三张&纵向三张拼接

HZ161123

Reference

https://www.cnblogs.com/ToBeDeveloper-Zhen/p/9314760.html

https://docs.opencv.org/3.4.1/d1/de0/tutorial_py_feature_homography.html

https://docs.opencv.org/3.4.1/dc/dc3/tutorial_py_matcher.html