-
Notifications
You must be signed in to change notification settings - Fork 1
/
warp.py
153 lines (119 loc) · 4.51 KB
/
warp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
from PIL import Image
import numpy as np
import math
import scipy.spatial as spatial
def GetBilinearPixel(imArr, posX, posY, out):
#Get integer and fractional parts of numbers
modXi = int(posX)
modYi = int(posY)
modXf = posX - modXi
modYf = posY - modYi
#Get pixels in four corners
for chan in range(imArr.shape[2]):
bl = imArr[modYi, modXi, chan]
br = imArr[modYi, modXi+1, chan]
tl = imArr[modYi+1, modXi, chan]
tr = imArr[modYi+1, modXi+1, chan]
#Calculate interpolation
b = modXf * br + (1. - modXf) * bl
t = modXf * tr + (1. - modXf) * tl
pxf = modYf * t + (1. - modYf) * b
out[chan] = int(pxf+0.5) #Do fast rounding to integer
return None #Helps with profiling view
def WarpProcessing(inIm, inArr,
outArr,
inTriangle,
triAffines, shape):
#Ensure images are 3D arrays
px = np.empty((inArr.shape[2],), dtype=np.int32)
homogCoord = np.ones((3,), dtype=np.float32)
#Calculate ROI in target image
xmin = shape[:,0].min()
xmax = shape[:,0].max()
ymin = shape[:,1].min()
ymax = shape[:,1].max()
xmini = int(xmin)
xmaxi = int(xmax+1.)
ymini = int(ymin)
ymaxi = int(ymax+1.)
#print xmin, xmax, ymin, ymax
#Synthesis shape norm image
for i in range(xmini, xmaxi):
for j in range(ymini, ymaxi):
homogCoord[0] = i
homogCoord[1] = j
#Determine which tesselation triangle contains each pixel in the shape norm image
if i < 0 or i >= outArr.shape[1]: continue
if j < 0 or j >= outArr.shape[0]: continue
#Determine which triangle the destination pixel occupies
tri = inTriangle[i,j]
if tri == -1:
continue
#Calculate position in the input image
affine = triAffines[tri]
outImgCoord = np.dot(affine, homogCoord)
#Check destination pixel is within the image
if outImgCoord[0] < 0 or outImgCoord[0] >= inArr.shape[1]:
for chan in range(px.shape[0]): outArr[j,i,chan] = 0
continue
if outImgCoord[1] < 0 or outImgCoord[1] >= inArr.shape[0]:
for chan in range(px.shape[0]): outArr[j,i,chan] = 0
continue
#Nearest neighbour
#outImgL[i,j] = inImgL[int(round(inImgCoord[0])),int(round(inImgCoord[1]))]
#Copy pixel from source to destination by bilinear sampling
#print i,j,outImgCoord[0:2],im.size
GetBilinearPixel(inArr, outImgCoord[0], outImgCoord[1], px)
for chan in range(px.shape[0]):
outArr[j,i,chan] = px[chan]
#print outImgL[i,j]
return None
def PiecewiseAffineTransform(srcIm, srcPoints, dstIm, dstPoints):
#Convert input to correct types
srcArr = np.asarray(srcIm, dtype=np.float32)
dstPoints = np.array(dstPoints)
srcPoints = np.array(srcPoints)
#Split input shape into mesh
tess = spatial.Delaunay(dstPoints)
#Calculate ROI in target image
xmin, xmax = dstPoints[:,0].min(), dstPoints[:,0].max()
ymin, ymax = dstPoints[:,1].min(), dstPoints[:,1].max()
#print xmin, xmax, ymin, ymax
#Determine which tesselation triangle contains each pixel in the shape norm image
inTessTriangle = np.ones(dstIm.size, dtype=np.int) * -1
for i in range(int(xmin), int(xmax+1.)):
for j in range(int(ymin), int(ymax+1.)):
if i < 0 or i >= inTessTriangle.shape[0]: continue
if j < 0 or j >= inTessTriangle.shape[1]: continue
normSpaceCoord = (float(i),float(j))
simp = tess.find_simplex([normSpaceCoord])
inTessTriangle[i,j] = simp
#Find affine mapping from input positions to mean shape
triAffines = []
for i, tri in enumerate(tess.vertices):
meanVertPos = np.hstack((srcPoints[tri], np.ones((3,1)))).transpose()
shapeVertPos = np.hstack((dstPoints[tri,:], np.ones((3,1)))).transpose()
affine = np.dot(meanVertPos, np.linalg.inv(shapeVertPos))
triAffines.append(affine)
#Prepare arrays, check they are 3D
targetArr = np.copy(np.asarray(dstIm, dtype=np.uint8))
srcArr = srcArr.reshape(srcArr.shape[0], srcArr.shape[1], len(srcIm.mode))
targetArr = targetArr.reshape(targetArr.shape[0], targetArr.shape[1], len(dstIm.mode))
#Calculate pixel colours
WarpProcessing(srcIm, srcArr, targetArr, inTessTriangle, triAffines, dstPoints)
#Convert single channel images to 2D
if targetArr.shape[2] == 1:
targetArr = targetArr.reshape((targetArr.shape[0],targetArr.shape[1]))
dstIm.paste(Image.fromarray(targetArr))
if __name__ == "__main__":
#Load source image
srcIm = Image.open("lena.jpg")
#Create destination image
dstIm = Image.new(srcIm.mode,(500,500))
#Define control points for warp
srcCloud = [(100,100),(400,100),(400,400),(100,400)]
dstCloud = [(150,120),(374,105),(410,267),(105,390)]
#Perform transform
PiecewiseAffineTransform(srcIm, srcCloud, dstIm, dstCloud)
#Visualise result
dstIm.show()