这篇博客算是写论文过程中记录实验结果及学习过程的系列博客吧,这是第一篇。
0.前言
论文做的是变形文本矫正,得到的变形文本结果分为三种阶段,依次进行矫正。第一阶段是:纸张水平放置在桌面,变换手机拍摄的角度和焦距使文本发生透视变换;第二阶段是:纸张已然变形,手机平行于水平面,固定焦距进行拍摄,得到透视变换的结果;第三个阶段是:变形文本+手机拍摄姿态变形的透视变换结果;
目前进行的是第一阶段的变形文本矫正。
1.正文
先通过透视变换原理生成变形后的文本坐标,再使用神经网络预测其变形参数的值,最后即可通过透视投影变换公式矫正变形文本坐标。
1.1透视投影变换原理简介
根据之前学习的透视变换矩阵知识,编写程序, 生成变形后的文本的坐标。
在这里简要的介绍一下透视变换矩阵的相关知识。
透视投影变换分为:世界坐标系转摄像机坐标系、摄像机坐标系转图像物理坐标系、图像物理坐标系转图像像素坐标系。
1.1.1世界坐标系转摄像机坐标系
公式如下:
其中,R为旋转矩阵,T为平移矩阵,右侧矩阵是世界坐标系坐标点,左侧是摄像机坐标系坐标点。R旋转矩阵表示的是摄像机在世界坐标系中绕X,Y,Z旋转的程度,平移矩阵表示的是摄像机在世界坐标系中里世界坐标原点的程度。R矩阵为3*3的正交矩阵,T为3*1的矩阵。
绕x,y, z轴旋转的矩阵分别如下:
1.1.2 摄像机坐标系转图像物理坐标系
公式如下:
1.1.3图像物理坐标系转图像像素坐标系
公式如下:
1.2 代码
根据以上公式编写代码如下:
#mathType:0-->sin;1-->cos; def getTrigonometric(angle, mathType): if mathType == 0: return math.sin(angle) elif mathType == 1: return math.cos(angle) else: print("Trigonometric ERROR!") #some define """ 得到旋转矩阵 世界坐标系,x,y轴为物体平面,z轴为视线垂直xy平面 pitchAngle:俯仰角,绕轴ox旋转的角度,范围为:[0,90) rollAngle:横滚角,绕轴oy旋转的角度,范围为:[0,90) HeadingAngle:航向角,绕轴oz旋转的角度,此处定义航向角为0; 先绕着z轴转,然后是y轴,最后是x轴,依次左乘 isRotation:true or false;为真则旋转,为假则不旋转 """ def getRotationMatrix(pitchAngle, rollAngle, headingAngle, isRotation): #Generate rotation matrix if isRotation: RMatrixX = np.array([[1, 0, 0], [0, getTrigonometric(pitchAngle, 1), getTrigonometric(pitchAngle, 0)], [0, -getTrigonometric(pitchAngle, 0), getTrigonometric(pitchAngle, 1)]]) RMatrixY = np.array([[getTrigonometric(rollAngle, 1), 0, -getTrigonometric(rollAngle, 0)], [0, 1, 0], [getTrigonometric(rollAngle, 0), 0, getTrigonometric(rollAngle, 1)]]) RMatrixZ = np.array([[getTrigonometric(headingAngle, 1), getTrigonometric(headingAngle, 0), 0], [-getTrigonometric(headingAngle, 0), getTrigonometric(headingAngle, 1), 0], [0, 0, 1]]) RmatrixTemp = np.dot(RMatrixX, RMatrixY) Rmatrix = np.dot(RmatrixTemp, RMatrixZ) return Rmatrix else: Rmatrix = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) return Rmatrix """ getTranslationMatrix 平移矩阵 如果两个坐标系之间不共原点 ,就是说明这两个坐标系之间除了旋转向量,还存在平移向量; translationValue,传入的平移值,若为0则说明没有发生平移 """ def getTranslationMatrix(tx,ty,tz): translationMatrix = np.array([[tx], [ty], [tz]]) return np.mat(translationMatrix) """ worldPositionMatrix:三维世界坐标点,以4*1的矩阵表示,每次传入一个值; 输出是对应于摄像机坐标系的三维坐标点坐标,以4*1的矩阵表示 """ def worldMapCameraCoording(worldPositionMatrix, pitch, roll, heading, isRotation, tx,ty,tz): R = getRotationMatrix(pitch, roll, heading, isRotation) T = getTranslationMatrix(tx,ty,tz) RT = np.hstack((R,T)) #print("RT= %s" % RT) #logFile.write("in worldMapCameraCoording\n") #logFile.write("RT=%s\n" % RT) #print("RT dot= %s" % RT.dot(worldPositionMatrix)) RdotResult = RT.dot(worldPositionMatrix) #print("vstack rt= %s" % np.vstack((RdotResult, 1))) #logFile.write("vstack rt= %s\n" % np.vstack((RdotResult, 1))) return np.vstack((RdotResult, 1)) """ cameraPosition:传入的参数,是相机坐标系的一组点,以4*1的矩阵表示 focalLength:焦距 """ def cameraMapImagePhysicalCoording(cameraPosition, focalLength): Zc = cameraPosition[2][0] ZcArray = Zc.getA() ZcValue = ZcArray[0][0] #print("zc=%s" %Zc) #logFile.write("zc=%s\n" %Zc) focalMatrix = np.array([[focalLength/ZcValue, 0, 0, 0], [0, focalLength/ZcValue, 0, 0], [0, 0, 1/ZcValue, 0]]) #print("focalmatrix = %s" %focalMatrix) #print("focalMatrix.dot(cameraPosition)= %s " % focalMatrix.dot(cameraPosition)) #logFile.write("focalMatrix.dot(cameraPosition)= %s \n" % focalMatrix.dot(cameraPosition)) return focalMatrix.dot(cameraPosition) """ 现在只考虑简单情况,即两坐标轴为直角坐标系 imagePositon:二维坐标点,以3*1的矩阵表示 输出是3*1的矩阵,表示得到的以像素为单位的坐标点 """ def imageMapPixelCoording(imagePositon, dx, dy, u0, v0): internalMatrix = np.array([[1/dx, 0, u0], [0, 1/dy, v0], [0, 0, 1]]) #logFile.write("imgaePosition = %s\n" % imagePositon) #logFile.write("internalMatrix=%s\n" % internalMatrix) #print("imgaePosition = %s" % imagePositon) #print("internalMatrix = %s" % internalMatrix) return internalMatrix.dot(imagePositon)
2.1 神经网络
以上工作生成了变形后文本的坐标值,接下来,采用Keras搭建神经网络,实现对于映射关系因子旋转矩阵三个角度和平移矩阵三个参数的预测。
Keras的相关知识可直接参考Keras中文文档,非常详细。
本文搭建一个3层的全连接网络,代码如下:
print("construct net begin...") model = Sequential() #input layer #model.add(Dense(units=312, input_shape=(rowNum,colNum,2))) #model.add(Dense(units=160, input_dim = colNum*rowNum*2)) model.add(Dense(units=firstLayerOutput, input_shape=(firstLayerInputDim,))) model.add(Activation('relu')) #hidden layer model.add(Dense(secondLayerOutput)) model.add(Activation('relu')) """ model.add(Dense(43)) model.add(Activation('relu')) model.add(Dense(16)) model.add(Activation('relu')) """ #output 6 #model.add(Flatten()) model.add(Dense(thirdLayerOutput)) model.add(Activation('tanh')) #compile,fit,until convergence #batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步。 #epochs:整数,训练终止时的epoch值,训练将在达到该epoch值时停止,当没有设置initial_epoch时, #..它就是训练的总轮数,否则训练的总轮数为epochs - inital_epoch #shuffle:表示是否在训练过程中随机打乱输入样本的顺序 #verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录 #validation_split:0~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。验证集将不参与训练, #并在每个epoch结束后测试的模型的指标,如损失函数、精确度等。注意,validation_split的划分在shuffle之前, #因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀。 model.compile(loss='mse', optimizer='sgd', metrics=['accuracy']) #model.fit(data, label, batch_size=batch_size, epochs=epochs, shuffle=True, verbose=1, validation_split=0.2) print("Training...") his = model.fit(data_train, label_train, batch_size= batch_size, epochs=epochs,verbose=1, callbacks=[history, EarlyStopping(monitor='loss', patience=4, verbose=1, mode='min'), lrate]) print('\nTesting ------------') cost = model.evaluate(data_test, label_test, batch_size=200) print('test cost:', cost)
数据方面,由于模拟的是11行39列的文本,所以数据格式为:
np.empty((samplesNum, 11*39*2))
或者为:
np.empty((samplesNum, 2, 11*39))
接下来就是训练网络,生成网络,使用网络进行预测。
3. 说明
本文模拟的是11行39列的文本,假定每一行每一列都有文字, 其原始坐标以图像最中心为原点,字与字之间的距离是10;生成变形文本的时候,根据角度不同,范围分别是:
angle = random.randint(0, 45)
angle = random.uniform(-45, 45)
angle = random.uniform(-15, 15)
一度一度的随机,代码如下:
while(len(rollAngleList) < 90): angle = random.uniform(-45, 45) if angle not in rollAngleList: rollAngleList.append(angle*(math.pi/180))
根据三个角度的所有情况生成变形文本坐标值,共有133650种组合。样本库还是很大的。而且在这个基础上,每一个输出都对对应其一定的缩放因子的变换坐标值。
scaleRatioList = [0.5, 0.6, 0.7, 0.8, 0.9, 1.1, 1.2, 1.3, 1.4, 1.5]
#get scale position form transformResult for scaleRatio in scaleRatioList: lstIndex = scaleRatioList.index(scaleRatio) transformResult_scale[lstIndex] = scaleRatio*transformResult
所以,共有样本为1336500个。
由于数据巨大,全部进网络太耗时,不过最后肯定要试试这种情况。
下一篇记录实验过程。