不用PS 摳圖,Python + OpenCV 自動實現海報場景替換!
有一個問題,就下面圖片中的兩本書而言,怎樣快速讓中間邊的書本與左邊書本對齊(最終效果能實現兩張圖片重疊(最終結果在最右邊)),進行的影象轉變可旋轉、平移、縮放、形變。
本文介紹 利用Opencv 如何解決這個問題,解決這個問題一般需要三步:
-
確定至少四組對應點座標
-
找到一個轉換矩陣;
-
把找到的轉換矩陣應用到 Moving Image 上,實現影象對齊;
上面提到的圖片旋轉、平移、縮放等操作的主要目的,就是要最終實現兩影象中點對點一一對映關係,點的對映本質上就是畫素點的空間轉換
圖中標記了其中四組對應點,分別標為不同的顏色,分別標為紅、橙、黃和綠四種顏色;比如這裡的$A(x_1,y_1)$ 和 $A_1 (x_2,y_2)$ 是就是一組對應點,圖片經過轉換之後 $A_1 $ 點 必須對映到 $A$ 點位置。
涉及圖片中點座標變換,都需要藉助於 矩陣 運算,這裡探究的影象維度都屬於二維,座標只需要$x,y$ 即可
面向此類轉換問題,Homography 轉換 ( 3 × 3 矩陣) 可用於解決此類轉化問題,用來解決點對點對映問題,Homography 矩陣可寫作下列方式:
$$
H = \left[
\begin{matrix}
h_{00}& h_{01} & h_{02}\
h_{10} & h_{11}&h_{12}\
h_{20} & h_{21}&h_{22}\
\end{matrix}
\right]
$$
則 $A(x_1 ,y_1)$ 、$A_1(x_2,y_2)$ 作為對應點,則 Homography $ H $ 的的應用 如下:
$$
\left[
\begin{matrix}
x_1\
y_1\
1\
\end{matrix}
\right] = H\left[
\begin{matrix}
x_2\
y_2\
1\
\end{matrix}
\right] = \left[
\begin{matrix}
h_{00} & h_{01} & h_{02}\
h_{10} & h_{11} & h_{12}\
h_{20} & h_{21} & h_{22}\
\end{matrix}
\right]\left[
\begin{matrix}
x_2\
y_2\
1\
\end{matrix}
\right]
$$
而矩陣 H 引數的確定至少需要 4 組對應點,因此在計算 H 時至少要找到 4 組對應點;找到的對應點組數越多,計算得到的 H 會越精確,最終的轉換效果也就會越好。
下面用 Opencv + Python 來實現上面圖片中的書籍的對齊,
import cv2 import numpy as np if __name__ =='__main__': #圖片讀取 img_src = cv2.imread("D:/book2.jpg") position_src = np.array([[141,131],[480,159],[493,630],[64,601]],dtype = float) img_dst = cv2.imread("D:/book1.jpg") position_dst = np.array([[318,256],[543,372],[316,670],[73,473]],dtype = float) #計算轉換矩陣 h,status = cv2.findHomography(position_src,position_dst) #對圖片進行仿射變換 out_img = cv2.warpPerspective(img_src,h,(img_dst.shape[1],img_dst.shape[0])) #Display images; cv2.imshow("Source image",img_src) cv2.imshow("Destination Image",img_dst) cv2.imshow("Warped Source Image",out_img) cv2.waitKey(0)
這裡事先已經確定好對應的四個點的座標,然後把這四個點的座標帶入 cv2.findHomography() 計算出轉換矩陣,最後把矩陣應用到兩影象中,得到最終的轉換結果,
這裡提醒一點,warpPerspective 函式進行對影象畫素進行矩陣變換時,隱藏了一個引數 Interpolator ,預設為線性插值,功能是防止畫素點畫素值缺失
上面小案例不方便的一點需要確定對應四個點的座標,這個步驟是比較繁瑣的,下面案例將在程式中加入互動功能,實現某個圖片的自動標記點收集、標記點點轉換:
首先需要準備兩張圖片,其中一張為海報,一張為需要替換的海報;關於確定點的座標時,被替換的圖片的座標非常好確定,只需知道圖片的長寬即可;
但的海報影象區域四個點是不好確定的, 這裡利用 Opencv 的滑鼠回撥函式,監視滑鼠響應,根據使用者點選來收集 PIck 得到的座標;
def mouse_handler(event,x,y,flags,data):
if event ==cv2.EVENT_LBUTTONDOWN:
cv2.circle(data['im'],(x,y),3,(0,0,255),5,16)
cv2.namedWindow("Image",0)
cv2.imshow("Image",data['im'])
if len(data['points']) <4:
data['points'].append([x,y])
def get_four_points(im):
data = {}
data['im'] = im.copy()
data['points'] = []
# Set the callback function for any mouse event
cv2.namedWindow("Image", 0)
cv2.imshow('Image',im)
#請注意你標記點的資料,是順時針,需要與pst_src 方向一致
cv2.setMouseCallback("Image",mouse_handler,data)
cv2.waitKey(0)
# Convert array to np.array
#豎直方向堆疊起來;;;
points = np.vstack(data['points']).astype(float)
return points
座標確定以後,接下來就很簡單了,跟上個案例一樣,計算變換矩陣,矩陣應用到影象旋轉,最終更換海報內容也就輕鬆完成啦,
需要注意一點,座標 Pick 點的順序須與記錄替換影象頂點順序一致,否則轉換圖會有偏差,案例完整程式碼如下:
if __name__ =='__main__':
img_src = cv2.imread("D:/first-image.jpg")
size = img_src.shape
# 取得四個座標
pst_src = np.array(
[
[0,0],[size[1]-1,0],
[size[1]-1,size[0]-1],
[0,size[0]-1]
],dtype=float
)
#Read the destination image
img_dst = cv2.imread("D:/times-square.jpg")
print("Click on four corners of bllboard and the press ENTER")
four_point = get_four_points(img_dst)
# Calculate Homography between source and destination points
h,status = cv2.findHomography(pst_src,four_point)
im_temp = cv2.warpPerspective(img_src,h,(img_dst.shape[1],img_dst.shape[0]))
cv2.fillConvexPoly(img_dst,four_point.astype(int),0,16)
#add wraped source image to destination image
img_dst = img_dst + im_temp
cv2.namedWindow("Image", 0)
cv2.imshow("Image",img_dst)
cv2.waitKey(0)