处理仿射变换任务需获取两张图像的目标区域的三个坐标点((x11,y11),(x21,y21),(x31,y31)及(x12,y12),(x22,y22),(x32,y32)),三点确定一个平面,通过解6个方程获得6个参数。
方程形式:
x1=x2*a+y2*b+k1;
y1=x2*c+y2*d+k2;
通过opencv自带的 cv2.getAffineTransform()函数获得参数矩阵M,以及函数cv2.warpAffine()获得仿射变换后的图像。
测试
ptsa.jpg
ptsb.jpg
res.jpg
实现将ptsa两个三角形颜色替换为ptsb的颜色,保持ptsa的形状不变。在不获取ptsb的颜色的情况下,用仿射变换将ptsb中的形状直接变为ptsa,再将变换后的ptsb中的各坐标与ptsa对应并替换即可。
处理方法有2种:
1、将三角区域外接矩形放缩到同等大小,经过仿射变换后的图像目标区域可以直接替换到原图。
2、不进行放缩,直接仿射变换获得参数矩阵M(a,b,k1,c,d,k2),需要对目标区域所有坐标经M进行映射变换(上述方程)到原图。
方法一实现代码:
import cv2
import numpy as np
imga=cv2.imread('img/pica.jpg')
imgb=cv2.imread('img/picb.jpg')
ptsa=np.float32([[100,200],[250,100],[300,250]])#ptsa的左边三角区域三点坐标
ptsb=np.float32([[50,120],[150,50],[120,130]])#ptsb的左边三角区域三点坐标
ptsa_=np.float32([[250,100],[300,250],[400,150]])#ptsa的右边三角区域三点坐标
ptsb_=np.float32([[150,50],[120,130],[220,80]])#ptsb的右边三角区域三点坐标
# np.float32([[0,70*rh],[100*rw,0],[70*rw,80*rh]])
def IsTrangleOrArea(x1, y1, x2, y2, x3, y3):
return abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2.0)
def IsInside(x1, y1, x2, y2, x3, y3, x, y):
# 三角形ABC的面积
ABC = IsTrangleOrArea(x1, y1, x2, y2, x3, y3)
# 三角形PBC的面积
PBC = IsTrangleOrArea(x, y, x2, y2, x3, y3)
# 三角形ABC的面积
PAC = IsTrangleOrArea(x1, y1, x, y, x3, y3)
# 三角形ABC的面积
PAB = IsTrangleOrArea(x1, y1, x2, y2, x, y)
return (ABC == PBC + PAC + PAB)
def warpTriangle(ptsa,ptsb,imga,imgb):
r1=cv2.boundingRect(ptsa)
r2=cv2.boundingRect(ptsb)
roia=imga[r1[1]:r1[1]+r1[3],r1[0]:r1[0]+r1[2],:]
roib=imgb[r2[1]:r2[1]+r2[3],r2[0]:r2[0]+r2[2],:]
rate_h=roia.shape[0]/roib.shape[0]
rate_w=roia.shape[1]/roib.shape[1]
roib=cv2.resize(roib,(0,0),fx=rate_w,fy=rate_h)
nptsa=[]
nptsb=[]
for i in range(3):
nptsa.append([ptsa[i,0]-r1[0],ptsa[i,1]-r1[1]])
nptsb.append([(ptsb[i, 0] - r2[0])*rate_w, (ptsb[i, 1] - r2[1])*rate_h])
# print(ptsa)
# print(ptsb)
# print(nptsa)
# print(nptsb)
nptsa=np.array(nptsa,dtype=np.float32)
nptsb=np.array(nptsb,dtype=np.float32)
M=cv2.getAffineTransform(nptsb,nptsa)
dst=cv2.warpAffine(roib,M,(roib.shape[1],roib.shape[0]))
res=imga.copy()
# print(ptsa[0,0], ptsa[0,1], ptsa[1,0], ptsa[1,1],ptsa[2,0], ptsa[2,1])
print(r1[0],r1[0]+r1[2],r1[1], r1[1] + r1[3])
for i in range(r1[1],r1[1]+r1[3]):
for j in range(r1[0], r1[0] + r1[2]):
if IsInside(ptsa[0,0], ptsa[0,1], ptsa[1,0], ptsa[1,1],ptsa[2,0], ptsa[2,1],j,i):
res[i,j,:]=dst[i-r1[1],j-r1[0],:]
return res
# cv2.imshow('roia',roia)
# cv2.imshow('dst',dst)
res=warpTriangle(ptsa,ptsb,imga,imgb)
res=warpTriangle(ptsa_,ptsb_,res,imgb)
cv2.imshow('res',res)
cv2.imshow('imgb',imgb)
cv2.imshow('imga', imga)
cv2.imwrite('res.jpg',res)
cv2.waitKey()
应用:
由于在处理图像过程中都是对矩形区域内像素进行遍历操作,而仿射变换需要3点坐标(即一个三角形),经变换后的图像部分区域不能直接替换到原图,而有效区域只是三角形内部,所以需要用到面积法进行有效区域的替换。如果处理的情况复杂,需要将目标区域拆分成多个三角区域(获取三点坐标)分别进行仿射变换。同时需要注意边界处理。
仿射变换原理参考:
何为仿射变换(Affine Transformation) - Alexander - 博客园
【OpenCV学习笔记】之仿射变换(Affine Transformation)_点滴成海~的博客-CSDN博客_仿射变换
图像的仿射变换原理及c++实现(旋转,平移,缩放,偏移,组合变换)_小武~~的博客-CSDN博客_c++仿射变换