在《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进行应用实现的步骤,另一方面还可以看到进行透视变换处理时需要注意的点。
本案例输入图像为两辆带车牌的汽车图片,二者图像大小不一样、车牌展现的角度不一样,程序支持通过鼠标在图像上分别标记两个车牌左上角、右上角、左下角、右下角,然后进行两车车牌的交换。图片来源于网络,分别保存在f:\pic\Audi.JPG
和f:\pic\BMW.JPG
中。图像如下:
另外本节会使用到《https://blog.csdn.net/LaoYuanPython/article/details/111351901 OpenCV-Python图形图像处理:自用的一些工具函数功能及调用语法介绍》介绍的老猿自定义的常用函数constructRectFrom4Points、translation等,具体函数功能请参考原文的介绍。
在程序中:
上述步骤中,构建中间图像进行替换,是因为两个车牌大小不同、视角不同,对应车牌在图像矩阵中的内容不是一个矩形,不好进行替换操作,因此先统一转换成同大小、同视角的矩形再进行车牌替换,替换后需要恢复大小和视角,所以需要逆变换回去。
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, OnMouseEvent, 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')
本文介绍了实现两车交换车牌的详细过程,并提供了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 微信公号发消息咨询。
前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.csdn.net/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。
对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.csdn.net/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。
如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。