? ? 老猿Python博文目錄:https://blog.csdn.net/LaoYuanPython ?
一、引言
在《https://blog.csdn.net/LaoYuanPython/article/details/114003964 OpenCV-Python影像處理:透視變換概念、矩陣及實作案例詳解》介紹了透視變換的概念、原理、OpenCV-Python相關的函式,并提供了一個簡單案例說明了OpenCV-Python進行透視變換的實作方法,
在《https://blog.csdn.net/LaoYuanPython/article/details/113958088 狗狗變形記:任選4點的投影變換warpPerspective OpenCV-Python案例》介紹了一個在影像中自行選擇4個點進行透視變換的稍微復雜案例,
本節將介紹一個更復雜的案例,通過該案例我們一方面可以進一步深入理解透視變換及應用OpenCV-Python進行應用實作的步驟,另一方面還可以看到進行透視變換處理時需要注意的點,
二、案例說明
2.1、案例背景
本案例輸入影像為兩輛帶車牌的汽車圖片,二者影像大小不一樣、車牌展現的角度不一樣,程式支持通過滑鼠在影像上分別標記兩個車牌左上角、右上角、左下角、右下角,然后進行兩車車牌的交換,圖片來源于網路,分別保存在f:\pic\Audi.JPG和f:\pic\BMW.JPG中,影像如下:

另外本節會使用到《https://blog.csdn.net/LaoYuanPython/article/details/111351901 OpenCV-Python圖形影像處理:自用的一些工具函式功能及呼叫語法介紹》介紹的老猿自定義的常用函式constructRectFrom4Points、translation等,具體函式功能請參考原文的介紹,
2.2、實作思路
在程式中:
- 首先支持將兩個車牌位置使用滑鼠進行標記,就是用滑鼠點擊車牌四角;
- 以寶馬車牌四角構造的四邊形為依據,構建一個和坐標軸平行的矩形,構建原則是取四邊形左上角的點為矩形的左上角點,四邊形上邊和下邊x、y坐標的最大差距作為矩形橫邊的邊長,以及側邊的邊長
- 根據奧迪車車牌左上角、寶馬車車牌構建的矩形邊長構建一個和坐標軸平行的矩形
- 以源車牌選定4個點和對應新構建的矩形4個點對作為透視變換矩陣的映射點對,通過getPerspectiveTransform獲取透視變換矩陣
- 對兩個原影像進行透視變換,然后將2個矩形位置的影像相互替換,獲得中間影像;
- 以前面獲得的透視矩陣的逆矩陣對兩個中間頭像進行透視變換的逆變換,
上述步驟中,構建中間影像進行替換,是因為兩個車牌大小不同、視角不同,對應車牌在影像矩陣中的內容不是一個矩形,不好進行替換操作,因此先統一轉換成同大小、同視角的矩形再進行車牌替換,替換后需要恢復大小和視角,所以需要逆變換回去,
2.3、示例代碼
import cv2
import numpy as np
from opencvPublic import replaceImgRegionBySpecImg,constructRectFrom4Points,translation
def getRect4Point(rect):
"""
根據矩形的左上角坐標及長和高回傳矩形的左上、右上、左下、右下四個點的坐標
:param rect:為代表矩形的串列,三個元素,分別為左上角坐標、寬和高
:return:回傳矩形的四個頂點,分別是左上、右上、左下、右下四個點的坐標
"""
luPoint, w, h = rect
x0,y0 = luPoint
return (luPoint,(x0+w,y0),(x0,y0+h),(x0+w,y0+h))
def OnMouseEvent( event, x, y, flags, param):
winName,lbtDownPos,pointList,img= param
if event == cv2.EVENT_LBUTTONUP:
pos = (x,y)
print("OnMouseEvent EVENT_LBUTTONUP:",pos)
n = len(pointList)
if pos==lbtDownPos:
n += 1
if n <= 4:
pointList.append(pos)
cv2.putText(img, '.', (x-15, y+3), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=3, color=(255, 0, 0)) #坐標調整是因為點放大了占用了不止一個像素
cv2.circle(img,(x , y),10,(0,0,255))
cv2.putText(img, f'{n}', (x + 20, y), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.3, color=(255, 0, 0))
cv2.imshow(winName,img)
lbtDownPos = None
elif event == cv2.EVENT_LBUTTONDOWN:
param[1] = (x,y)
print("OnMouseEvent EVENT_LBUTTONDOWN:", lbtDownPos)
else:lbtDownPos = None
def getselectRangePoint(img,winName,bWaitEscKey=False):
"""
:param img: 輸入影像
:param winName: 影像視窗展示名
:param bWaitEscKey: 是否需要設定回圈等待
:return: 一個包含4個元素的串列,4個元素分別是winName、當前最后一個選擇點的坐標、選擇的4個點的坐標串列、輸入影像img
"""
cv2.putText(img, 'https://blog.csdn.net/LaoYuanPython', (100, 120), fontFace=cv2.FONT_HERSHEY_SIMPLEX,fontScale=0.5, color=(255, 0, 0))
rows,cols = img.shape[:2]
cv2.namedWindow(winName)
param=[winName,None,[],img]
cv2.setMouseCallback(winName, onm ouseEvent, param)
print("請在圖中用滑鼠左鍵分別點擊ROI區域左上角、右上角、左下角、右下角4個點,選擇完成后按ESC退出")
cv2.imshow(winName, img)
if bWaitEscKey:
while True: # 通過滑鼠左鍵點擊選擇4個點,分別代表要映射到左上角、右上角、左下角、右下角4個點
ch = cv2.waitKey(100)
if ch == 27: break
return param
def chanePlate(srcCarImgFile,destCarImgFile):
#加載兩車影像
srcImg = cv2.imread(srcCarImgFile)
destImg = cv2.imread(destCarImgFile)
r1,c1 = srcImg.shape[:2]
r2,c2 = destImg.shape[:2]
srcImg = translation(srcImg,100,100,(c1+100,r1+100))
destImg = translation(destImg, 100, 100, (c2 + 100, r2 + 100))
#選擇兩車的車牌四角點(左上角、右上角、左下角、右下角)
srcInf = getselectRangePoint(np.array(srcImg),"select the plate range of source car")
destInf = getselectRangePoint(np.array(destImg),'select the plate range of target car',True)
srcPointsSelected = srcInf[2]
destPointsSelected = destInf[2]
if len(srcPointsSelected)<4:
print("源影像沒有選擇足夠的點,請點擊源影像用于選擇ROI區域左上角、右上角、左下角、右下角4個點")
elif len(destPointsSelected)<4:
print("目標影像沒有選擇足夠的點,請點擊目標影像替換區域左上角、右上角、左下角、右下角4個點")
else:
destRect = constructRectFrom4Points(destPointsSelected) #根據目標車牌的左上角、右上角、左下角、右下角4四角頂點以左上角構建矩形
srcRect = (srcPointsSelected[0], destRect[1], destRect[2]) #根據目標車牌的矩形大小以源影像車牌左上角構建一個相同大小的矩形
#根據車牌的四頂點將車牌區域映射到對應矩形進行透視變換得到統一車牌大小的中間影像,確保兩車車牌大小和形狀相同
destRectPoints = np.float32(getRect4Point(destRect))
srcRectPoints = np.float32(getRect4Point(srcRect))
srcPoints = np.float32(srcPointsSelected)
destPoints = np.float32(destPointsSelected)
srcM = cv2.getPerspectiveTransform(srcPoints, srcRectPoints)
destM = cv2.getPerspectiveTransform(destPoints,destRectPoints)
dstSrc = cv2.warpPerspective(srcImg, srcM, (srcImg.shape[1]*2,srcImg.shape[0]*2))
dstDst = cv2.warpPerspective(destImg, destM, (destImg.shape[1] * 2, destImg.shape[0] * 2))
cv2.imshow("mid img-src", dstSrc)
cv2.imshow("mid img-dst", dstDst)
#將中間影像的目標車輛牌換成源車車牌
srcX0,srcY0 = srcPointsSelected[0] #源車牌左上角點
destX0,destY0 = destPointsSelected[0]#目標車牌左上角點
w = destRect[1]
h = destRect[2]
srcPlate = np.array(dstSrc[srcY0:srcY0+h,srcX0:srcX0+w])#取源車牌對應的影像
destPlate = np.array(dstDst[destY0:destY0+h,destX0:destX0+w]) #取目標車牌對應的影像
replaceImgRegionBySpecImg(dstDst,destPointsSelected[0],srcPlate) #將目標車牌的影像換成源車牌影像
replaceImgRegionBySpecImg(dstSrc, srcPointsSelected[0], destPlate) # 將源車牌的影像換成目標車牌影像
#將中間影像映射回原角度
resultImgDest = cv2.warpPerspective(dstDst, destM, (destImg.shape[1], destImg.shape[0]),flags= cv2.INTER_LINEAR | cv2.WARP_INVERSE_MAP)
resultImgSrc= cv2.warpPerspective(dstSrc, srcM, (srcImg.shape[1], srcImg.shape[0]),flags=cv2.INTER_LINEAR | cv2.WARP_INVERSE_MAP)
cv2.destroyAllWindows()
cv2.imshow("result img-dest", resultImgDest)
cv2.imshow("result img-src", resultImgSrc)
ch = cv2.waitKey(0)
chanePlate(r'f:\pic\Audi.JPG',r'f:\pic\BMW.JPG')
2.4、運行截圖
選擇車牌四角頂點的截圖:


替換后的影像:


三、小結
本文介紹了實作兩車交換車牌的詳細程序,并提供了OpenCV-Python示例代碼,輸入處理的兩車影像大小及車牌視角朝向不同,只能通過透視變換先統一視角和車牌大小后才能進行車牌替換,替換后需要還原原影像各自的大小和視角,根據老猿的操作經驗,利用透視變換操作程序中需要注意:
-
變換時選擇的四個點,從視角來看盡量是在兩條平行線上(平行線在無窮遠處相交),否則影像變換程序中可能產生畸變,即使逆變換也不一定能變換回來;
下圖是首次透視變換沒有逆變回來的中間影像截圖:

可以看到影像左下角已經超出范圍, -
變換時最好給影像四周預留一定空間,用為變換后影像大小范圍有可能超出原影像大小范文,這樣才能保證影像變換后沒有影像丟失,本案例中使用了擴大目標影像大小、變換前先進行平移來預留空間,
更多影像處理的介紹請參考專欄《OpenCV-Python圖形影像處理 https://blog.csdn.net/laoyuanpython/category_9979286.html》和《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初學者疑難問題集》相關文章,
更多影像處理的數學基礎知識請參考專欄《人工智能數學基礎 https://blog.csdn.net/laoyuanpython/category_10382948.html》
寫博不易,敬請支持:
如果閱讀本文于您有所獲,敬請點贊、評論、收藏,謝謝大家的支持!
如果對文章內容存在疑問,可以在博客評論區留言,或關注:老猿Python 微信公號發訊息咨詢,
關于老猿的付費專欄
- 付費專欄《https://blog.csdn.net/laoyuanpython/category_9607725.html 使用PyQt開發圖形界面Python應用》專門介紹基于Python的PyQt圖形界面開發基礎教程,對應文章目錄為《 https://blog.csdn.net/LaoYuanPython/article/details/107580932 使用PyQt開發圖形界面Python應用專欄目錄》;
- 付費專欄《https://blog.csdn.net/laoyuanpython/category_10232926.html moviepy音視頻開發專欄 )詳細介紹moviepy音視頻剪輯合成處理的類相關方法及使用相關方法進行相關剪輯合成場景的處理,對應文章目錄為《https://blog.csdn.net/LaoYuanPython/article/details/107574583 moviepy音視頻開發專欄文章目錄》;
- 付費專欄《https://blog.csdn.net/laoyuanpython/category_10581071.html OpenCV-Python初學者疑難問題集》為《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python圖形影像處理 》的伴生專欄,是筆者對OpenCV-Python圖形影像處理學習中遇到的一些問題個人感悟的整合,相關資料基本上都是老猿反復研究的成果,有助于OpenCV-Python初學者比較深入地理解OpenCV,對應文章目錄為《https://blog.csdn.net/LaoYuanPython/article/details/109713407 OpenCV-Python初學者疑難問題集專欄目錄 》
- 付費專欄《https://blog.csdn.net/laoyuanpython/category_10762553.html Python爬蟲入門 》站在一個互聯網前端開發小白的角度介紹爬蟲開發應知應會內容,包括爬蟲入門的基礎知識,以及爬取CSDN文章資訊、博主資訊、給文章點贊、評論等實戰內容,
前兩個專欄都適合有一定Python基礎但無相關知識的小白讀者學習,第三個專欄請大家結合《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python圖形影像處理 》的學習使用,
對于缺乏Python基礎的同仁,可以通過老猿的免費專欄《https://blog.csdn.net/laoyuanpython/category_9831699.html 專欄:Python基礎教程目錄)從零開始學習Python,
如果有興趣也愿意支持老猿的讀者,歡迎購買付費專欄,

跟老猿學Python!
? ? 前往老猿Python博文目錄 https://blog.csdn.net/LaoYuanPython ?
CSDN認證博客專家
Python專家
CSDN博客專家
博客之星季軍
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/265838.html
標籤:AI
