affine.py
import numpy as np
class Affine(object):
@classmethod
def transPntForward(cls, pt, T):
newPt = np.zeros(2, dtype=pt.dtype)
newPt[0] = T[0, 0] * pt[0] + T[1, 0] * pt[1] + T[2, 0]
newPt[1] = T[0, 1] * pt[0] + T[1, 1] * pt[1] + T[2, 1]
return newPt
@classmethod
def transPntsForwardWithSameT(cls, pts, T):
if pts.ndim != 2:
raise Exception("Must 2-D array")
newPts = np.zeros(pts.shape)
newPts[:, 0] = T[0, 0] * pts[:, 0] + T[1, 0] * pts[:, 1] + T[2, 0]
newPts[:, 1] = T[0, 1] * pts[:, 0] + T[1, 1] * pts[:, 1] + T[2, 1]
return newPts
@classmethod
def transPntsForwardWithDiffT(cls, pts, Ts):
if pts.ndim != 2:
raise Exception("Must 2-D array")
nPts = np.zeros(pts.shape)
pntNum = pts.shape[0]
for i in range(pntNum):
T = Ts[i]
nPts[i, 0] = T[0, 0] * pts[i, 0] + T[1, 0] * pts[i, 1] + T[2, 0]
nPts[i, 1] = T[0, 1] * pts[i, 0] + T[1, 1] * pts[i, 1] + T[2, 1]
return nPts
@classmethod
def fitGeoTrans(cls, src, dst,
mode="NonreflectiveSimilarity"):
"""
This function is the same as matlab fitgeotrans
"""
### Subtract the mean
src0 = np.subtract(src, np.mean(src, axis=0))
dst0 = np.subtract(dst, np.mean(dst, axis=0))
if "NonreflectiveSimilarity" == mode:
return cls.findNonreflectiveSimilarity(src0, dst0)
else:
raise Exception("Unsupported transformation")
@classmethod
def findNonreflectiveSimilarity(cls, uv, xy):
uv, normMatSrc = cls.normalizeControlPoints(uv)
xy, normMatDst = cls.normalizeControlPoints(xy)
ptNum = uv.shape[0]
minNonCollinearPairs = 2
x = xy[:, 0].reshape(ptNum, 1)
y = xy[:, 1].reshape(ptNum, 1)
X = np.concatenate((
np.concatenate((x, y,
np.ones((ptNum, 1)),
np.zeros((ptNum, 1))), axis=1),
np.concatenate((y, -x,
np.zeros((ptNum, 1)),
np.ones((ptNum, 1))), axis=1)
))
u = uv[:, 0].reshape(ptNum, 1)
v = uv[:, 1].reshape(ptNum, 1)
U = np.concatenate((u, v), axis=0,)
### X*r = U, Solve the r by least squared error
if np.linalg.matrix_rank(X) >= 2 * minNonCollinearPairs:
U = np.array(U, dtype='float')
r = np.linalg.lstsq(X, U, rcond=None)[0]
else:
raise Exception("At least 2 noncollinear Pts")
sc, ss, tx, ty = r
Tinv = np.array(((sc, -ss, 0.),
(ss, sc, 0.),
(tx, ty, 1.)))
Tinv = np.array(Tinv, dtype='float')
Tinv = np.linalg.lstsq(normMatDst,
np.dot(Tinv, normMatSrc), rcond=None)[0]
T = np.linalg.inv(Tinv)
T[:, 2] = [0, 0, 1]
return T
@classmethod
def normalizeControlPoints(cls, pts):
ptNum = pts.shape[0]
cent = np.mean(pts, axis=0)
ptsNorm = np.subtract(pts, cent)
distSum = np.sum(np.power(ptsNorm, 2))
if distSum > 0:
scaleFactor = np.sqrt(2 * ptNum) / np.sqrt(distSum)
else:
scaleFactor = 1
ptsNorm = scaleFactor * ptsNorm
normMatInv = np.array(((1 / scaleFactor, 0, 0),
(0, 1 / scaleFactor, 0),
(cent[0], cent[1], 1)))
return ptsNorm, normMatInv
cascade.py
import sys
import os
import numpy as np
import pickle
from data_load import DataWrapper
from affine import Affine
from regressorWrapper import RegressorWrapper
from shape import Shape
class Cascador(object):
"""
Cascade regression for landmark
"""
def __init__(self):
self.name = None
# self.version = None
self.stageNum = None
self.dataWrapper = None
self.regWrapper = None
self.regressors = []
self.meanShape = None
def printParas(self):
print('------------------------------------------')
print('---------- Configuration ------------')
print('Name = %s' % (self.name))
# print('Version = %s' % (self.version))
print('Stage Num = %s' % (self.stageNum))
print('\n-- Data Config --')
self.dataWrapper.printParas()
print('\n-- Regressor Config --')
self.regWrapper.printParas()
print('--------- End of Configuration -------')
print('------------------------------------------\n')
def config(self, paras):
self.name = paras['name']
# self.version = paras['version']
self.stageNum = paras['stageNum']
# Construct the regressor wrapper
regPara = paras['regressorPara']
self.regWrapper = RegressorWrapper(regPara)
# Construct the data wrapper
dataPara = paras['dataPara']
if 'dataset' in paras:
dataPara['dataset'] = paras['dataset']
self.dataWrapper = DataWrapper(dataPara)
def train(self, save_path):
# mkdir model folder for train model
if not os.path.exists('%s/model' % (save_path)):
os.mkdir('%s/model' % (save_path))
# read data first
trainSet = self.dataWrapper.read()
dataNum = trainSet.initShapes.shape[0]
self.meanShape = trainSet.meanShape
print("\tData Number : %d" % (dataNum))
trainSet.calResiduals()
sumR = np.mean(np.abs(trainSet.residuals))
print("\tManhattan Distance in MeanShape : %f\n" % sumR)
for idx in range(self.stageNum):
print("\t%drd stage begin ..." % idx)
# train one stage
reg = self.regWrapper.getClassInstance(idx)
reg.train(trainSet)
self.regressors.append(reg)
# calculate the residuals
trainSet.calResiduals()
sumR = np.mean(np.abs(trainSet.residuals))
print("\tManhattan Distance in MeanShape : %f" % sumR)
self.saveModel(save_path)
def detect(self, img, bndbox, initShape):
mShape = Shape.shapeNorm2Real(self.meanShape,
bndbox)
for reg in self.regressors:
affineT = Affine.fitGeoTrans(mShape,
initShape)
reg.detect(img, bndbox, initShape, affineT)
def loadModel(self, model):
path_obj = open(model, 'r').readline().strip()
folder = os.path.split(model)[0]
objFile = open("%s/%s" % (folder, path_obj), 'rb')
self = pickle.load(objFile)
objFile.close()
return self
def saveModel(self, save_path):
name = self.name.lower()
model_path = "%s/model/train.model" % (save_path)
model = open(model_path, 'w')
model.write("%s.pyobj" % (name))
obj_path = "%s/model/%s.pyobj" % (save_path, name)
objFile = open(obj_path, 'wb')
pickle.dump(self, objFile)
objFile.close()
model.close()
if __name__ == '__main__':
config = {
'name': "face",
# Different dataset using different reading method
'dataset': "I",
'version': "1.0",
'stageNum': 4,
'regressorPara':
{
'name': 'lbf_reg',
'para':
{
'maxTreeNums': [100],
'treeDepths': [4],
'feaNums': [1000, 750, 500, 375, 250],
'radiuses': [0.4, 0.3, 0.2, 0.15, 0.12],
# Following para is used to quantize the feature
'binNums': [511],
'feaRanges': [[-255, 255]],
}
},
'dataPara':
{
'path': "./data/I/",
# augNum < 1 means don't do augmenting
'augNum': 0
}
}
cascade = Cascador()
cascade.config(config)
save_path = './'
cascade.train(save_path)
data_load.py
from PIL import Image
import numpy as np
import re
import math
import copy
from shape import Shape
from affine import Affine
import cv2
class TrainSet(object):
def __init__(self):
self.imgDatas = []
self.gtShapes = []
self.bndBoxs = []
self.initShapes = []
self.ms2reals = []
self.real2mss = []
self.meanShape = None
self.augNum = 1
def getBBoxByPts(self, pts):
maxV = np.max(pts, axis=0)
# print('maxV: {}'.format(maxV))
minV = np.min(pts, axis=0)
# print('minV: {}'.format(minV))
return (minV[0], minV[1],
maxV[0] - minV[0] + 1,
maxV[1] - minV[1] + 1)
def read(self, line, folder):
gtShape = []
# Load the ground truth of shape
line = re.split(r' ', line)
#rect = line[1:5]
x = line[5::2]
y = line[6::2]
for i in range(len(x)):
gtShape.append((x[i], y[i]))
gtShape = np.asarray(gtShape, dtype=np.float32)
# gtShape = gtShape[0, :, :]
#print(gtShape.shape) # (1, 21, 2) --> (21, 2)
# Load the image data
img_name = line[0]
img_path = folder + img_name
img = Image.open(img_path)
# gray image
img = img.convert('L')
img = np.asarray(img, dtype=np.uint8)
# print(img)
# Crop the image
bndBox = self.getBBoxByPts(gtShape)
#print('bndBox: {}'.format(bndBox))
return img, gtShape, bndBox
def cropRegion(self, bbox, scale, img):
height, width = img.shape
w = math.floor(scale * bbox[2])
h = math.floor(scale * bbox[3])
x = max(0, math.floor(bbox[0] - (w - bbox[2]) / 2))
y = max(0, math.floor(bbox[1] - (h - bbox[3]) / 2))
w = min(width - x, w)
h = min(height - y, h)
### If not use deepcopy, the subImg will hold the whole img's memory
subImg = copy.deepcopy(img[y:y + h, x:x + w])
return (x, y, w, h), subImg
def add(self, img, gtShape, bndBox):
self.imgDatas.append(img)
self.gtShapes.append(gtShape)
self.bndBoxs.append(bndBox)
def calMeanShape(self):
meanShape = np.zeros(self.gtShapes[0].shape)
for i, s in enumerate(self.gtShapes):
normS = Shape.shapeReal2Norm(s, self.bndBoxs[i])
meanShape = np.add(meanShape, normS)
self.meanShape = meanShape / len(self.gtShapes)
def genTrainData(self, augNum):
# Step1 : Compute the mean shape
self.calMeanShape()
# Set meanshape as the initshape
for bb in self.bndBoxs:
initShape = Shape.shapeNorm2Real(self.meanShape,
bb)
self.initShapes.append(initShape)
# Translate list into numpy's array
self.initShapes = np.asarray(self.initShapes,
dtype=np.float32)
self.gtShapes = np.asarray(self.gtShapes,
dtype=np.float32)
self.bndBoxs = np.asarray(self.bndBoxs,
dtype=np.float32)
# Shape augment
if augNum > 1:
self.augNum = augNum
self.initShapes = np.repeat(self.initShapes,
augNum,
axis=0)
self.gtShapes = np.repeat(self.gtShapes,
augNum,
axis=0)
self.bndBoxs = np.repeat(self.bndBoxs,
augNum,
axis=0)
# Permutate the augmented shape
sampleNum = self.initShapes.shape[0]
for i in range(sampleNum):
if 0 == i % sampleNum:
continue
shape = self.initShapes[i]
self.initShapes[i] = Shape.augment(shape)
return
def getAffineT(self):
num = self.gtShapes.shape[0]
self.ms2real = []
self.real2ms = []
for i in range(num):
### Project to meanshape coordinary
bndBox = self.bndBoxs[i]
initShape = self.initShapes[i]
mShape = Shape.shapeNorm2Real(self.meanShape,
bndBox)
T = Affine.fitGeoTrans(initShape, mShape)
self.real2mss.append(T)
T = Affine.fitGeoTrans(mShape, initShape)
self.ms2reals.append(T)
def calResiduals(self):
### Compute the affine matrix
self.getAffineT()
self.residuals = np.zeros(self.gtShapes.shape)
num = self.gtShapes.shape[0]
for i in range(num):
# Project to meanshape coordinary
T = self.real2mss[i]
bndBox = self.bndBoxs[i]
err = self.gtShapes[i] - self.initShapes[i]
err = np.divide(err, (bndBox[2], bndBox[3]))
err = Affine.transPntsForwardWithSameT(err, T)
self.residuals[i, :] = err
class DataWrapper(object):
def __init__(self, para):
self.path = para['path'] # './data/I/'
self.augNum = para['augNum'] # 1
def read(self):
trainSet = TrainSet()
# folders = ['data/I/', 'data/II/']
folder = self.path
ann_path = folder + 'label.txt'
lines = open(ann_path, 'r').readlines()
# print((lines[0].strip()))
for line in lines:
img, gtShape, bndBox = trainSet.read(line, folder)
scale = 2
cropB, img = trainSet.cropRegion(bndBox, scale, img)
gtShape = np.subtract(gtShape,
(cropB[0], cropB[1]))
# Get the bndBox.
bndBox = trainSet.getBBoxByPts(gtShape)
trainSet.add(img, gtShape, bndBox)
# Generate the meanShape
trainSet.genTrainData(self.augNum)
return trainSet
def printParas(self):
print('\tDataset = %s' % (self.path))
print('\tAugment Num = %d' % (self.augNum))
if __name__ == '__main__':
config = {
'name': "face",
# Different dataset using different reading method
'dataset': "I",
'version': "1.0",
'stageNum': 4,
'regressorPara':
{
'name': 'lbf_reg',
'para':
{
'maxTreeNums': [100],
'treeDepths': [4],
'feaNums': [1000, 750, 500, 375, 250],
'radiuses': [0.4, 0.3, 0.2, 0.15, 0.12],
# Following para is used to quantize the feature
'binNums': [511],
'feaRanges': [[-255, 255]],
}
},
'dataPara':
{
'path': "./data/I/",
# augNum < 1 means don't do augmenting
'augNum': 1
}
}
dataloder = DataWrapper(config['dataPara'])
trainSet = dataloder.read()
cv2.imshow('img',trainSet.imgDatas[100])
key = cv2.waitKey()
print(len(trainSet.imgDatas))
/data/2020-CV-FaceLandmark-训练营-datasets # 数据集路径
demo_train.py
from cascade import Cascador
config = {
'name': "face",
# Different dataset using different reading method
'dataset': "I",
'version': "1.0",
'stageNum': 4,
'regressorPara':
{
'name': 'lbf_reg',
'para':
{
'maxTreeNums': [100],
'treeDepths': [4],
'feaNums': [1000, 750, 500, 375, 250],
'radiuses': [0.4, 0.3, 0.2, 0.15, 0.12],
# Following para is used to quantize the feature
'binNums': [511],
'feaRanges': [[-255, 255]],
}
},
'dataPara':
{
'path': "./data/I/",
# augNum < 1 means don't do augmenting
'augNum': 0
}
}
cascade = Cascador()
cascade.config(config)
save_path = './'
cascade.train(save_path)
lbfRegressor.py
import numpy as np
from scipy.sparse import lil_matrix
from sklearn.svm import LinearSVR
from randForest import RandForest
from affine import Affine
class LBFRegressor(object):
"""
Face Alignment at 3000 FPS via Regressing LBF
"""
def __init__(self, paras):
self.maxTreeNum = paras["maxTreeNum"]
self.treeDepth = paras["treeDepth"]
self.feaNum = paras["feaNum"]
self.radius = paras["radius"]
self.binNum = paras["binNum"]
self.feaRange = paras["feaRange"]
self.rfs = []
self.regs = []
def train(self, trainSet):
pntNum = trainSet.meanShape.shape[0]
treeNum = int(self.maxTreeNum / pntNum)
# Train the random forests
for i in range(pntNum):
rf = RandForest(treeDepth=self.treeDepth,
treeNum=treeNum,
feaNum=self.feaNum,
radius=self.radius,
binNum=self.binNum,
feaRange=self.feaRange)
rf.train(trainSet, i)
self.rfs.append(rf)
# Extract the local binary features
feas = self.genFeaOnTrainset(trainSet)
# Global regression
y = trainSet.residuals
y = y.reshape(y.shape[0], y.shape[1] * y.shape[2])
for i in range(pntNum * 2):
# TODO Show the training result
reg = LinearSVR(epsilon=0.0,
C=1.0 / feas.shape[0],
loss='squared_epsilon_insensitive',
fit_intercept=True)
reg.fit(feas, y[:, i])
self.regs.append(reg)
# Update the initshapes
for i in range(pntNum):
regX = self.regs[2 * i]
regY = self.regs[2 * i + 1]
x = regX.predict(feas)
y = regY.predict(feas)
delta = np.squeeze(np.dstack((x, y)))
delta = Affine.transPntsForwardWithDiffT(delta,
trainSet.ms2reals)
delta = np.multiply(delta,
trainSet.bndBoxs[:, [2, 3]])
trainSet.initShapes[:, i, :] = trainSet.initShapes[:, i, :] + delta
def detect(self, img, bndbox, initShape, affineT):
# Extract features
fea = self.extractFea(img, bndbox,
initShape, affineT)
pntNum = initShape.shape[0]
# Get the residules
for i in range(pntNum):
regX = self.regs[2 * i]
regY = self.regs[2 * i + 1]
x = regX.predict(fea)
y = regY.predict(fea)
delta = np.squeeze(np.dstack((x, y)))
delta = Affine.transPntForward(delta, affineT)
delta = np.multiply(delta, (bndbox[2], bndbox[3]))
initShape[i, :] = initShape[i, :] + delta
def getFeaDim(self):
feaDim = 0
for rf in self.rfs:
for tree in rf.trees:
feaDim = feaDim + tree.leafNum
return feaDim
def extractFea(self, img, bndbox, initShape, affineT):
feaDim = self.getFeaDim()
fea = lil_matrix((1, feaDim),
dtype=np.int8)
offset = 0
for j, rf in enumerate(self.rfs):
point = initShape[j]
for t in rf.trees:
# TODO judge the empty tree
leafIdx, dim = t.genBinaryFea(img,
bndbox,
affineT,
point)
fea[0, offset + leafIdx] = 1
offset = offset + dim
return fea
def genFeaOnTrainset(self, trainSet):
feaDim = self.getFeaDim()
sampleNum = trainSet.initShapes.shape[0]
feas = lil_matrix((sampleNum, feaDim),
dtype=np.int8)
augNum = trainSet.augNum
for i in range(sampleNum):
imgData = trainSet.imgDatas[int(i / augNum)]
bndBox = trainSet.bndBoxs[i]
affineT = trainSet.ms2reals[i]
shape = trainSet.initShapes[i]
offset = 0
for j, rf in enumerate(self.rfs):
point = shape[j]
for t in rf.trees:
# TODO judge the empty tree
leafIdx, dim = t.genBinaryFea(imgData,
bndBox,
affineT,
point)
feas[i, offset + leafIdx] = 1
offset = offset + dim
return feas
p1.py
Jupyter Notebook
p1.py
2020年7月28日
Python
File
Edit
View
Language
1
#cfrom my_cascade import Cascador
2
from cascade import Cascador
3
from data_load import TrainSet
4
from shape import Shape
5
import numpy as np
6
import cv2
7
8
9
def display(img, gtPnts, resPnts):
10
gtPnts = np.round(gtPnts).astype(np.int32)
11
resPnts = np.round(resPnts).astype(np.int32)
12
13
showImg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
14
for i in range(gtPnts.shape[0]):
15
cv2.circle(showImg, (gtPnts[i, 0], gtPnts[i, 1]),
16
3, (0, 0, 255), -1)
17
cv2.circle(showImg, (resPnts[i, 0], resPnts[i, 1]),
18
3, (255, 0, 0), -1)
19
return showImg
20
21
model = './model/train.model'
22
cas = Cascador()
23
cas = cas.loadModel(model)
24
25
folders = ['data/I/', 'data/II/']
26
imgListPath = folders[1] + 'label.txt'
27
pathList = open(imgListPath, 'r').readlines()
28
reader = TrainSet()
29
30
for imgPath in pathList:
predict.py
#from my_cascade import Cascador
from cascade import Cascador
from data_load import TrainSet
from shape import Shape
import numpy as np
import cv2
def display(img, gtPnts, resPnts):
gtPnts = np.round(gtPnts).astype(np.int32)
resPnts = np.round(resPnts).astype(np.int32)
showImg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for i in range(gtPnts.shape[0]):
cv2.circle(showImg, (gtPnts[i, 0], gtPnts[i, 1]),
3, (0, 0, 255), -1)
cv2.circle(showImg, (resPnts[i, 0], resPnts[i, 1]),
3, (255, 0, 0), -1)
return showImg
model = './model/train.model'
cas = Cascador()
cas = cas.loadModel(model)
folders = ['data/I/', 'data/II/']
imgListPath = folders[1] + 'label.txt'
pathList = open(imgListPath, 'r').readlines()
reader = TrainSet()
for imgPath in pathList:
img, gtShape, bndBox = reader.read(imgPath, folders[1])
scale = 3
cropB, img = reader.cropRegion(bndBox, scale, img)
gtShape = np.subtract(gtShape, (cropB[0], cropB[1]))
# TODO try face detector.
bndbox = Shape.getBBoxByPts(gtShape)
# Set the initial shape
# print('bndbox: {}'.format(bndbox))
# print('type bndbox: {}'.format(type(bndbox)))
initShape = Shape.shapeNorm2Real(cas.meanShape, bndbox)
# Detect the landmark
cas.detect(img, bndbox, initShape)
showImg = display(img, gtShape, initShape)
cv2.imshow("Landmark", showImg)
key = cv2.waitKey(1000)
if key in [ord("q"), 27]:
break
cv2.destroyAllWindows()
randForest.py
from lbfRegressor import *
class RegressorWrapper(object):
def __init__(self, paras):
self.name = paras['name'].upper()
self.para = paras['para']
def printParas(self):
print('\t%-20s= %s'%('name', self.name))
for key in self.para:
print('\t%-20s= %s'%(key, str(self.para[key])))
def getParaLBF(self, idx):
regPara = dict()
length = len(self.para['maxTreeNums'])
_idx = min(idx, length-1)
regPara['maxTreeNum'] = self.para['maxTreeNums'][_idx]
length = len(self.para['treeDepths'])
_idx = min(idx, length-1)
regPara['treeDepth'] = self.para['treeDepths'][_idx]
length = len(self.para['feaNums'])
_idx = min(idx, length-1)
regPara['feaNum'] = self.para['feaNums'][_idx]
length = len(self.para['radiuses'])
_idx = min(idx, length-1)
regPara['radius'] = self.para['radiuses'][_idx]
length = len(self.para['binNums'])
_idx = min(idx, length-1)
regPara['binNum'] = self.para['binNums'][_idx]
length = len(self.para['feaRanges'])
_idx = min(idx, length-1)
regPara['feaRange'] = self.para['feaRanges'][_idx]
return regPara
def getClassInstance(self, idx):
if "LBF_REG" == self.name:
regPara = self.getParaLBF(idx)
regClass = LBFRegressor
else:
raise Exception("Unsupport: %s " %(self.name))
return regClass(regPara)
regressorWrapper.py
from lbfRegressor import *
class RegressorWrapper(object):
def __init__(self, paras):
self.name = paras['name'].upper()
self.para = paras['para']
def printParas(self):
print('\t%-20s= %s'%('name', self.name))
for key in self.para:
print('\t%-20s= %s'%(key, str(self.para[key])))
def getParaLBF(self, idx):
regPara = dict()
length = len(self.para['maxTreeNums'])
_idx = min(idx, length-1)
regPara['maxTreeNum'] = self.para['maxTreeNums'][_idx]
length = len(self.para['treeDepths'])
_idx = min(idx, length-1)
regPara['treeDepth'] = self.para['treeDepths'][_idx]
length = len(self.para['feaNums'])
_idx = min(idx, length-1)
regPara['feaNum'] = self.para['feaNums'][_idx]
length = len(self.para['radiuses'])
_idx = min(idx, length-1)
regPara['radius'] = self.para['radiuses'][_idx]
length = len(self.para['binNums'])
_idx = min(idx, length-1)
regPara['binNum'] = self.para['binNums'][_idx]
length = len(self.para['feaRanges'])
_idx = min(idx, length-1)
regPara['feaRange'] = self.para['feaRanges'][_idx]
return regPara
def getClassInstance(self, idx):
if "LBF_REG" == self.name:
regPara = self.getParaLBF(idx)
regClass = LBFRegressor
else:
raise Exception("Unsupport: %s " %(self.name))
return regClass(regPara)
shape.py
import numpy as np
import math
import random
# API for shape operation
class Shape(object):
@classmethod
def getBBoxByPts(cls, pts):
maxV = np.max(pts, axis=0)
minV = np.min(pts, axis=0)
return (minV[0], minV[1],
maxV[0] - minV[0] + 1,
maxV[1] - minV[1] + 1)
@classmethod
def shapeReal2Norm(cls, realShape, bndBox):
normShape = np.subtract(realShape,
(bndBox[0], bndBox[1]))
normShape = np.divide(normShape,
(bndBox[2] - 1, bndBox[3] - 1))
return normShape
@classmethod
def shapeNorm2Real(cls, normShape, bndBox):
realShape = np.multiply(normShape,
(bndBox[2] - 1, bndBox[3] - 1))
realShape = np.add(realShape,
(bndBox[0], bndBox[1]))
return realShape
@classmethod
def augment(cls, shape):
shape = cls.scale(shape)
shape = cls.rotate(shape)
shape = cls.shift(shape)
return shape
@classmethod
def scale(cls, shape):
### scale in [0.9, 1.1]
scale = 1 + 0.2 * (random() - 0.5)
cent = np.mean(shape, axis=0)
newShape = scale * (shape - cent) + cent
return newShape
@classmethod
def rotate(cls, shape):
### Rotate in [-30,30]
angle = (random() - 0.5) * math.pi / 3
### TODO try the middle point instead of mean point
# cent = (np.max(shape, axis=0)-
# np.min(shape, axis=0))/2.0
cent = np.mean(shape, axis=0)
rotateT = np.array(((math.cos(angle),
-math.sin(angle)),
(math.sin(angle),
math.cos(angle))),
dtype=np.float32)
newShape = np.dot(shape - cent, rotateT) + cent
return newShape
@classmethod
def shift(cls, shape):
### shift in [-0.1, 0.1]
shiftX = 0.2 * (random() - 0.5)
shiftY = 0.2 * (random() - 0.5)
shift = np.max(shape, axis=0) - np.min(shape, axis=0)
shift = shift * (shiftX, shiftY)
newShape = shape + shift
return newShape