OpenCV-Python 實(shí)現(xiàn)兩張圖片自動(dòng)拼接成全景圖
背景介紹
圖片的全景拼接如今已不再稀奇,現(xiàn)在的智能攝像機(jī)和手機(jī)攝像頭基本都帶有圖片自動(dòng)全景拼接的功能,但是一般都會(huì)要求拍攝者保持設(shè)備的平穩(wěn)以及單方向的移動(dòng)取景以實(shí)現(xiàn)較好的拼接結(jié)果。這是因?yàn)槠唇拥膱D片之間必須要有相似的區(qū)域以保證拼接結(jié)果的準(zhǔn)確性和完整性。本文主要簡單描述如何用 Python 和 OpenCV 庫實(shí)現(xiàn)兩張圖片的自動(dòng)拼合,首先簡單介紹一下兩張圖片拼接的原理。
基本原理
要實(shí)現(xiàn)兩張圖片的簡單拼接,其實(shí)只需找出兩張圖片中相似的點(diǎn) (至少四個(gè),因?yàn)?homography 矩陣的計(jì)算需要至少四個(gè)點(diǎn)), 計(jì)算一張圖片可以變換到另一張圖片的變換矩陣 (homography 單應(yīng)性矩陣),用這個(gè)矩陣把那張圖片變換后放到另一張圖片相應(yīng)的位置 ( 就是相當(dāng)于把兩張圖片中定好的四個(gè)相似的點(diǎn)給重合在一起)。如此,就可以實(shí)現(xiàn)簡單的全景拼接。當(dāng)然,因?yàn)槠春现髨D片會(huì)重疊在一起,所以需要重新計(jì)算圖片重疊部分的像素值,否則結(jié)果會(huì)很難看。所以總結(jié)起來其實(shí)就兩個(gè)步驟:
1. 找兩張圖片中相似的點(diǎn),計(jì)算變換矩陣
2. 變換一張圖片放到另一張圖片合適的位置,并計(jì)算重疊區(qū)域新的像素值 (這里就是圖片融合所需要采取的策略)
具體實(shí)現(xiàn)
尋找相似點(diǎn)
當(dāng)然,我們可以手動(dòng)的尋找相似的點(diǎn),但是這樣比較麻煩。因?yàn)橄嗨泣c(diǎn)越多或者相似點(diǎn)對(duì)應(yīng)的位置越準(zhǔn)確,所得的結(jié)果就越好,但是人的肉眼所找的位置總是有誤差的,而且找出很多的點(diǎn)也不是一件容易的事。所以就有聰明的人設(shè)計(jì)了自動(dòng)尋找相似點(diǎn)的算法,這里我們就用了 SIFT 算法,而 OpenCV 也給我們提供 SIFT 算法的接口,所以我們就不需要自己費(fèi)力去實(shí)現(xiàn)了。如下是兩張測試圖片的原圖和找出相似點(diǎn)后的圖片。
其中紅色的點(diǎn)是 SIFT 算法找出的相似點(diǎn),而綠色的線表示的是在所有找出的相似的點(diǎn)中所篩選出的可信度更高的相似的點(diǎn)。因?yàn)樗惴ㄕ页龅南嗨泣c(diǎn)并不一定是百分百正確的。然后就可以根據(jù)這些篩選出的相似點(diǎn)計(jì)算變換矩陣,當(dāng)然 OpenCV 也提供了相應(yīng)的接口方便我們的計(jì)算,而具體的代碼實(shí)現(xiàn)也可以在 OpenCV 的 Python tutorial 中找到 [1]。
圖片拼接
計(jì)算出變換矩陣后,接下來就是第二步,用計(jì)算出的變換矩陣對(duì)其中一張圖做變換,然后把變換的圖片與另一張圖片重疊在一起,并重新計(jì)算重疊區(qū)域新的像素值。對(duì)于計(jì)算重疊區(qū)域的像素值,其實(shí)可以有多種方法去實(shí)現(xiàn)一個(gè)好的融合效果,這里就用最簡單粗暴的但效果也不錯(cuò)的方式。直白來說就是實(shí)現(xiàn)一個(gè)圖像的線性漸變,對(duì)于重疊的區(qū)域,靠近左邊的部分,讓左邊圖像內(nèi)容顯示的多一些,靠近右邊的部分,讓右邊圖像的內(nèi)容顯示的多一些。用公式表示就是,假設(shè) alpha 表示像素點(diǎn)橫坐標(biāo)到左右重疊區(qū)域邊界橫坐標(biāo)的距離,新的像素值就為 newpixel = 左圖像素值 × (1 - alpha) + 右圖像素值 × alpha 。這樣就可以實(shí)現(xiàn)一個(gè)簡單的融合效果,如果想實(shí)現(xiàn)更復(fù)雜或更好的效果,可以去搜索和嘗試一下 multi-band 融合,這里就不過多贅述了。最后附上實(shí)現(xiàn)的結(jié)果和代碼,可供參考。
Python 代碼如下:
import numpy as np import cv2 as cv from matplotlib import pyplot as plt if __name__ == '__main__': top, bot, left, right = 100, 100, 0, 500 img1 = cv.imread('test1.jpg') img2 = cv.imread('test2.jpg') srcImg = cv.copyMakeBorder(img1, top, bot, left, right, 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) plt.imshow(img3, ), plt.show() 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) res[row, col] = np.clip(srcImg[row, col] * (1-alpha) + warpImg[row, col] * alpha, 0, 255) # opencv is bgr, matplotlib is rgb res = cv.cvtColor(res, cv.COLOR_BGR2RGB) # show the result plt.figure() plt.imshow(res) plt.show() else: print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT)) matchesMask = None
Reference
[1] OpenCV tutorial: https://docs.opencv.org/3.4.1/d1/de0/tutorial_py_feature_homography.html
到此這篇關(guān)于OpenCV-Python 實(shí)現(xiàn)兩張圖片自動(dòng)拼接成全景圖的文章就介紹到這了,更多相關(guān)OpenCV 圖片自動(dòng)拼接成全景圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python+unittest+DDT實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測試
這篇文章主要介紹了Python+unittest+DDT實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測試,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Python 支持向量機(jī)分類器的實(shí)現(xiàn)
這篇文章主要介紹了Python 支持向量機(jī)分類器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01python判斷兩個(gè)序列的成員是否一樣的實(shí)例代碼
在本篇文章里小編給大家整理了關(guān)于python判斷兩個(gè)序列的成員是否一樣的實(shí)例代碼,需要的朋友們參考下。2020-03-03Scrapy爬蟲多線程導(dǎo)致抓取錯(cuò)亂的問題解決
本文針對(duì)Scrapy爬蟲多線程導(dǎo)致抓取錯(cuò)亂的問題進(jìn)行了深入分析,并提出了相應(yīng)的解決方案,具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11Python使用asyncio.Queue進(jìn)行任務(wù)調(diào)度的實(shí)現(xiàn)
本文主要介紹了Python使用asyncio.Queue進(jìn)行任務(wù)調(diào)度的實(shí)現(xiàn),它可以用于任務(wù)調(diào)度和數(shù)據(jù)交換,文中通過示例代碼介紹的非常詳細(xì),感興趣的可以了解一下2024-02-02