計算機視覺--圖像的拼接融合

一、全景圖像拼接原理介紹

1.1 背景介紹

    圖片的全景拼接如今已不再稀奇,現在的智能攝像機和手機攝像頭基本都帶有圖片自動全景拼接的功能,但是一般都會要求拍攝者保持設備的平穩以及單方向的移動取景以實現較好的拼接結果。這是因爲拼接的圖片之間必須要有相似的區域以保證拼接結果的準確性和完整性。本文主要簡單描述如何用 Python 和 OpenCV 庫實現多張圖片的自動拼合。

1.2 基本原理

    要實現圖像的拼接融合,首先我們先看兩張圖片的簡單拼接,其實只需找出兩張圖片中相似的點 (至少四個,因爲 homography 矩陣的計算需要至少四個點), 計算一張圖片可以變換到另一張圖片的變換矩陣 (homography 單應性矩陣),用這個矩陣把那張圖片變換後放到另一張圖片相應的位置 ( 就是相當於把兩張圖片中定好的四個相似的點給重合在一起)。如此,就可以實現簡單的全景拼接。

    舉個例子,下面是書上原圖的全景拼接結果:
在這裏插入圖片描述
全景圖:
在這裏插入圖片描述
(一) RANSAC算法
    回顧上一篇博客內容,SIFT算法的描述子穩健性很強,比Harris角點要來得精確,但是它的匹配正確率也並不是百分百的,會受到一些噪聲點的干擾,有時就會因爲不同地方有類似的圖案導致匹配錯誤。那麼RANSAC算法便是用來找到正確模型來擬合帶有噪聲數據的迭代方法。
    RANSAC通過反覆取樣,也就是從整個觀測數據中隨機抽一些數據估算模型參數之後看和所有數據誤差有多大,然後取誤差最小視爲最好以及分離內羣與離羣數據。基本的思想是,數據中包含正確的點和噪聲點,合理的模型應該能夠在描述正確數據點的同時摒棄噪聲點。
    舉個簡單的例子,直線的擬合便是RANSAC的一個標準化體現:
在這裏插入圖片描述
    同理,RANSAC算法可以應用到其它模塊中,例如用於圖像變換的單應性矩陣的計算。
    在拼接的過程中,通過將響速和單應矩陣H相乘,然後對齊次座標進行歸一化來實現像素間的映射。通過查看H中的平移量,我們可以決定應該將該圖像填補到左邊還是右邊。

(二) 全景拼接
    SIFT特徵匹配前面寫過,這裏不多做介紹。這次將其應用到圖像拼接上,根據特徵點匹配的方式,則利用這些匹配的點來估算單應矩陣(使用上面的RANSAC算法),也就是把其中一張通過個關聯性和另一張匹配的方法。通過單應矩陣H,可以將原圖像中任意像素點座標轉換爲新座標點,轉換後的圖像即爲適合拼接的結果圖像。
(1)圖像配準
    圖像配準是對圖像進行變換,使變換後的圖像能夠在常見的座標系中對齊。爲了能夠進行圖像對比和更精細的圖像分析,圖像配準是一步非常重要的操作。
    圖像配準的方法有很多,這裏以APAP算法爲例:
    1.提取兩張圖片的sift特徵點。
    2.對兩張圖片的特徵點進行匹配。
    3.匹配後,仍有很多錯誤點,用RANSAC的改進算法進行特徵點對的篩選。篩選後的特徵點基本能夠異議一 一對應。
    4.使用DLT算法,將剩下的特徵點對進行透視變換矩陣的估計。
    5.因爲得到的透視變換矩陣是基於全局特徵點對進行的,即一個剛性的單應性矩陣完成配準。爲提高配準的精度,Apap將圖像切割成無數多個小方塊,對每個小方塊的變換矩陣逐一估計。

(2)圖割方法
1.關於最小割
    如以下圖1所示,這是一個有向帶權圖,共有4個頂點和5條邊。每條邊上的箭頭代表了邊的方向,每條邊上的數字代表了邊的權重。
在這裏插入圖片描述
什麼是最小割?
    現在要求剪短圖中的某幾條邊,使得不存在從s到t的路徑,並且保證所減的邊的權重和最小。那麼很明顯,剪掉邊”s->a”和邊”b->t”。我們就能得到上圖中的圖2.
    圖中已不存在從s到t的路徑,且所修剪的邊的權重和爲:2 + 3 = 5,爲所有修剪方式中權重和最小的。我們把這樣的修剪稱爲最小割。
2.關於最大流
什麼是最大流?
    如以上圖1,因爲s->a只能通過2,那麼s->a->t這條路的流量被限制在了2,同理,s->b->t被b->t限制,最多隻能通過3的流量,所以s->t的流量總和爲2+3=5,爲這條路的最大流。
(3)圖像融合-blending
    其實圖像拼接完會發現在拼接的交界處有明顯的銜接痕跡,存在邊緣效應,因爲光照色澤的原因使得圖片交界處的過渡很糟糕,所以需要特定的處理解決這種不自然。那麼這時候可以採用blending方法,在opencv內部已經實現了multi-band blending。

1.3 圖像拼接整體流程

  • 根據給定圖像/集,實現特徵匹配
  • 通過匹配特徵計算圖像之間的變換結構
  • 利用圖像變換結構,實現圖像映射
  • 針對疊加後的圖像,採用APAP之類的算法,對齊特徵點
  • 通過圖割方法,自動選取拼接縫
  • 根據multi-band bleding策略實現融合

二、全景圖像拼接實驗

2.1 代碼實現

# -*- coding: utf-8
from pylab import *
from numpy import *
from PIL import Image
import numpy as np
# If you have PCV installed, these imports should work
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift

"""
This is the panorama example from section 3.3.
"""
np.seterr(divide='ignore', invalid='ignore')
# set paths to data folder

featname = ['D:/Alike/python_dw/Code_a/admin_code/data/data_2/o' + str(i + 1) + '.sift' for i in range(5)]
imname = ['D:/Alike/python_dw/Code_a/admin_code/data/data_2/o' + str(i + 1) + '.jpg' for i in range(5)]

# extract features and match
l = {}
d = {}
for i in range(5):
    sift.process_image(imname[i], featname[i])
    l[i], d[i] = sift.read_features_from_file(featname[i])

matches = {}
for i in range(4):
    matches[i] = sift.match(d[i + 1], d[i])

# visualize the matches (Figure 3-11 in the book)
# sift匹配可視化
for i in range(4):
    im1 = array(Image.open(imname[i]))
    im2 = array(Image.open(imname[i + 1]))
    figure()
    sift.plot_matches(im2, im1, l[i + 1], l[i], matches[i], show_below=True)


# function to convert the matches to hom. points
# 將匹配轉換成齊次座標點的函數
def convert_points(j):
    ndx = matches[j].nonzero()[0]
    fp = homography.make_homog(l[j + 1][ndx, :2].T)
    ndx2 = [int(matches[j][i]) for i in ndx]
    tp = homography.make_homog(l[j][ndx2, :2].T)

    # switch x and y - TODO this should move elsewhere
    fp = vstack([fp[1], fp[0], fp[2]])
    tp = vstack([tp[1], tp[0], tp[2]])
    return fp, tp


# estimate the homographies
# 估計單應性矩陣
model = homography.RansacModel()

fp, tp = convert_points(1)
H_12 = homography.H_from_ransac(fp, tp, model)[0]  # im 1 to 2  # im1 到 im2 的單應性矩陣


fp, tp = convert_points(0)
H_01 = homography.H_from_ransac(fp, tp, model)[0]  # im 0 to 1

tp, fp = convert_points(2)  # NB: reverse order
H_32 = homography.H_from_ransac(fp, tp, model)[0]  # im 3 to 2

tp, fp = convert_points(3)  # NB: reverse order
H_43 = homography.H_from_ransac(fp, tp, model)[0]  # im 4 to 3

# warp the images
# 扭曲圖像

delta = 2000  # for padding and translation 用於填充和平移

im1 = array(Image.open(imname[1]), "uint8")
im2 = array(Image.open(imname[2]), "uint8")
im_12 = warp.panorama(H_12, im1, im2, delta, delta)

im1 = array(Image.open(imname[0]), "f")
im_02 = warp.panorama(dot(H_12, H_01), im1, im_12, delta, delta)

im1 = array(Image.open(imname[3]), "f")
im_32 = warp.panorama(H_32, im1, im_02, delta, delta)

im1 = array(Image.open(imname[4]), "f")
im_42 = warp.panorama(dot(H_32, H_43), im1, im_32, delta, 2 * delta)
imsave('ping3.jpg', array(im_42, "uint8"))
figure()
imshow(array(im_42, "uint8"))
axis('off')
show()

2.2 不同場景的實驗結果與分析

2.2.1 針對固定點拍攝圖片

針對固定點拍攝多張圖片,實現圖像的拼接融合
室外場景
原圖:
在這裏插入圖片描述
拼接結果:
在這裏插入圖片描述
在這裏插入圖片描述
室內場景
原圖:
在這裏插入圖片描述
拼接結果:
在這裏插入圖片描述
在這裏插入圖片描述
分析:

  • 觀察第一組室外場景得到的結果,發現檢測出的匹配特徵豐富,整體融合效果好。前兩張圖片融合幾乎沒有拼接痕跡,後面三張圖片在拼接的交界處有明顯的銜接痕跡,存在邊緣效應,出現參數曝光的問題。這種現象在圖像拼接中普遍存在,即使同一時間同一地點同樣拍攝設備,也會有些影響,要解決這個問題可以用multi-band bleding策略實現融合。
  • 觀察第二組室內場景得到的結果,發現全景圖融合不完整,只有原圖的k4.jpg和k5.jpg實現了融合,其他三張圖片沒有體現在全景圖中。觀察SIFT特徵匹配的結果圖,發現序號1與序號2的兩個匹配結果出現錯配,猜測是室內的窗戶、柱子等相似物體多,造成干擾。
  • 從一個固定點拍攝多張圖,實現融合,會發現室外場景的融合效果比室內好,可能原因是室外場景提取到的特徵點更豐富,有更大參考性,使得拼接效果更優。

2.2.2 移動拍攝位置

室外場景
原圖:
在這裏插入圖片描述
拼接結果;
在這裏插入圖片描述
在這裏插入圖片描述
分析:
    上面這個場景與前一組實驗中的室外場景取景相同,不同的是這組圖片在拍攝時我改變了位置,即不再站在一個固定點拍照,但是可以看到得到的全景圖雜亂無章。
    猜測這是由於鏡頭拍照的位置不同,會導致圖片雖然有相同的拍攝區域,但是並不能簡單的將五張圖片的重疊區域覆蓋進行拼接。因爲拍攝五張圖片時,相機的世界座標位置發生變化。試想一下,你首先在A位置看到物體一,然後換到B位置再看物體一,物體一雖然仍在你的視野裏,但是,它在你眼中與周圍參照物的距離會發生變化,位置不同,光線也可能不同,這都是拼接過程中存在的問題。

視差變化大的場景
原圖:
在這裏插入圖片描述
拼接結果:
在這裏插入圖片描述
在這裏插入圖片描述
分析:
    在該場景下,護欄以及靠在護欄上的拖把是近景目標,建築物是遠景目標,視差變化大。觀察近景目標拼接,可以看到右側護欄處出現了一些偏差,觀察遠景目標,看到後面的建築物出現鬼影現象;但是觀察SIFT特徵匹配結果,看到匹配結果基本正確。這是因爲景深變化大,這時即使都是正確的匹配點,但是不能用homography單應性矩陣表示,要解決這種問題可以使用APAP算法。
    全景圖出現像素失真的現象,可能與我改變了圖片的像素值大小有關。

三、總結

實驗內容總結

  • 室外場景的圖像拼接結果要好於室內,可能原因是室外特徵點豐富,進行拼接等操作時有更大的參考性。
  • 景深大的場景進行圖像融合易出現鬼影現象,原因是景深大時,即使匹配點是正確的,但無法用homography單應性矩陣表示。可以用APAP(As-Projective-As-Possible)算法解決該問題,此算法將圖像切割成無數多個小方塊,對每個小方塊的單應性矩陣逐一估計。
  • 經過多組實驗發現,拍攝時如果位置移動容易導致圖像無法正確融合,相對來說,固定位置拍攝得到的融合效果更好。

實驗過程總結

①問題一:出現報錯如下:
在這裏插入圖片描述
解決方案:
在代碼中添加——

import numpy as np
np.seterr(divide='ignore', invalid='ignore')

②問題二:得到的運行結果僅有非常窄的一個長條,無法看清內容。
解決方案:修改圖片像素值,原本圖像像素值是200×160,修改成1400×1120,即可得到較清晰的全景圖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章