使用tensorflow训练模型,C++通过opencv的dnn模块调用模型,并进行推理.
(如果对延时要求不是特别高的话,推荐直接用ros)
一. tensorflow训练模型,生成pb文件
这里训练了一个很简单的bp网络作为示例,输入是一维的6个数据
1. 读取数据/数据预处理
1.1读取数据
根据自己的数据存储方式编写,这里的数据是存在txt中的
#这段代码从txt中读取数据,一行为一个样本,相邻数据间由逗号隔开
def Load_Voice_Data(path):
"""
这是导入数据的函数
:param path: 数据文件的路径
:return: 数据集
"""
data = []
label = []
with open(path) as f:
for line in f.readlines():
str = line.strip().split(",")
tmp = []
for i in range(1,len(str)-1):
tmp.append(float(str[i]))
data.append(tmp)
if 1 == int(str[0]): #第一个参数为类别
label.append([1.,0.])
else:
label.append([0.,1.])
#data = np.array(data,dtype=np.float64)
#label = np.array(label,dtype=np.float364)
return data,label
path = './lamps.txt'
Data,Label = Load_Voice_Data(path) #读取数据集
1.2 正则化
训练前先将数据正则化,并分成训练集与验证集
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Normalizer
#正则化
Data = Normalizer().fit_transform(Data)
#生成训练集与验证集
Train_Data,Test_Data,Train_Label,Test_Label = train_test_split(Data,Label,test_size=1/10,random_state=10)
可指定验证集的比例与选取batch的随机数种子
1.3 生成batch(删了,有空补全)
介绍一种方法:用多线程去实现batch
image_batch, label_batch =
tf.train.shuffle_batch([Test_Data,Test_Label],batch_size=4,num_threads=16,capacity=160,min_after_dequeue=50)
本文数据量很小,最后其实没有分batch,直接将整个数据集一起喂入训练了.
2. 训练
2.1 构建网络
with tf.variable_scope('conv1'):
X = tf.placeholder(tf.float32, shape = [None, input_size],name="x")
Y = tf.placeholder(tf.float32, shape = [None, num_classes])
W1 = tf.Variable(tf.random_normal ([input_size, hidden_size1], stddev = 0.1)) #初始化,给一个正态分布随机值
B1 = tf.Variable(tf.random_normal ([ hidden_size1], stddev = 0.1))
W2 = tf.Variable(tf.random_normal ([hidden_size1, hidden_size2], stddev = 0.1))
B2 = tf.Variable(tf.random_normal ([ hidden_size2], stddev = 0.1))
W3 = tf.Variable(tf.random_normal ([hidden_size2, num_classes], stddev = 0.1))
B3 = tf.Variable(tf.random_normal ([ num_classes], stddev = 0.1))
W4 = tf.Variable(tf.random_normal ([num_classes, num_classes], stddev = 0.1))
hidden_opt = tf.matmul(X, W1)+B1 # 输入层到隐藏层正向传播
hidden_opt = tf.nn.relu(hidden_opt) # 激活函数,用于计算节点输出值
hidden_opt2 = tf.matmul(hidden_opt, W2)+B2 # 输入层到隐藏层正向传播
hidden_opt2 = tf.nn.relu(hidden_opt2) # 激活函数,用于计算节点输出值
temp=tf.matmul(hidden_opt2, W3)+B3
final_opt=tf.matmul(temp, W4)
softmax_Y=tf.nn.softmax(final_opt,name="y")
这一步有个大坑记录一下,python训练一切正常但opencv调用会报错
报错信息:
xxxxxxxxxx
原因仍旧是版本兼容问题,由于python训练并没有报错,也谷歌不到类似问题,解决的过程真是一把辛酸泪啊…
解决方法:需要使用随机数去初始化偏置项bias,不要用tf.constant去定义,也不能用zero、one等方式
B1 = tf.Variable(tf.random_normal ([ hidden_size1], stddev = 0.1))
2.2 设置训练器
#设置训练器
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=final_opt)) # 对输出层计算交叉熵损失
opt = tf.compat.v1.train.GradientDescentOptimizer(0.001).minimize(loss) # 梯度下降算法,这里使用了反向传播算法用于修改权重,减小损失
init = tf.compat.v1.global_variables_initializer() # 初始化变量
correct_prediction =tf.equal (tf.argmax (Y, 1)tf.argmax(softmax_Y, 1)) # 计算准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction,'float'))
2.3开始训练
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range (training_iterations) : #设置epoch数
#训练
training_loss = sess.run ([opt, loss], feed_dict = {X: Train_Data, Y: Train_Label})
if i % 1000 == 0 : #每1000轮计算一次准确率与loss值
train_accuracy = accuracy.eval (session = sess, feed_dict = {X: Test_Data,Y: Test_Label})
step_loss = sess.run([loss],feed_dict={X:Test_Data,Y:Test_Label})
train_accuracy2 = accuracy.eval (session = sess, feed_dict = {X: Train_Data,Y: Train_Label})
print ("step: %d --- accuracy= %g, loss= %g" % (i, train_accuracy2,step_loss[0]))
3. 保存模型
3.1 保存pb模型(供opencv调用)
保存时注意指定下输出的Tensor.name
(输入也可以指定)
def save_pb(sess):
constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['conv1/y']) #需要注意下 'conv/y',事先定义好输出Tensor的name后在这里填好就行
with tf.gfile.FastGFile("./graph.pb", mode='wb') as f:
f.write(constant_graph.SerializeToString())
"""
训练过程...
"""
# 训练结束后
save_pb(sess)
3.2 保存ckpt
基本操作
import tensorflow as tf
"""
训练过程...
"""
saver = tf.train.Saver()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess, './checkpoint_dir/MyModel')
至此,完成了tensorflow模型的训练与保存,接下来就可以用opencv去调用了.
二. opencv-dnn模块调用模型
1. 加载pb模型
std::string pbfile = ros::package::getPath("opcv_tf")+"/src/graph.pb"; //改为自己pb文件的路径
cv::dnn::Net net = cv::dnn::readNetFromTensorflow(pbfile); //加载模型
一般来说,除了pb文件还需要一个pbtxt文件(由opencv-samples转化pb文件生成,具体可以百度.若网络结构很简单,则只需pb文件即可)
2. 喂数据进网络
使用Net对象的setInput
函数即可将数据喂入网络. 但注意,需要先转化下数据的格式
Mat blob = (Mat_ <float>(6, 1) << 21.8658, 205.5499, 205.5449, 0.0463, 7.6381, 0.7756);
blob = cv::dnn::blobFromImage(blob, 1.0, Size(6,1), Scalar(), false, false); //转化格式
net.setInput(blob,"conv1/x"); //将数据喂给网络(可指定Tensor名)
按实际情况修改scale
与size
注意: 大多数网络使用blobFromImage
函数处理下Mat即可,但有些特殊的网络结构需要额外进行一些处理.
3. 模型推理,数据解析
调用Net对象的forward
函数即可进行前向推理并得到预测结果
cv::Mat pred = net.forward("conv1/y"); //如果网络输出层是softmax的话,pred即为各类别的概率
三. 完整代码
tensorflow训练
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Normalizer
import tensorflow as tf
from tensorflow.python.framework import graph_util
# # from BPNN import BPNN
def Load_Voice_Data(path):
"""
这是导入数据的函数
:param path: 数据文件的路径
:return: 数据集
"""
data = []
label = []
with open(path) as f:
for line in f.readlines():
str = line.strip().split(",")
tmp = []
for i in range(1,len(str)-1):
tmp.append(float(str[i]))
data.append(tmp)
if 1 == int(float(str[0])): #第一个参数为类别
label.append([0.,1.])
else:
label.append([1.,0.])
return data,label
def save_pb(sess):
constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['conv1/y'])
with tf.gfile.FastGFile("./graph.pb", mode='wb') as f:
f.write(constant_graph.SerializeToString())
def main():
num_classes = 2 # 输出大小
input_size = 5 # 输入大小
hidden_size1 = 6 # 隐藏层节点数量
training_iterations=100000
path = './lamps.txt'
Data,Label = Load_Voice_Data(path) #读取数据集
# 分割数据集,并对数据集进行标准化
Train_Data,Test_Data,Train_Label,Test_Label = train_test_split(Data,Label,test_size=1/10,random_state=10)
# Train_Data = Normalizer().fit_transform(Train_Data)
# Test_Data = Normalizer().fit_transform(Test_Data)
with tf.variable_scope('conv1'):
X = tf.placeholder(tf.float32, shape = [None, input_size],name="x")
Y = tf.placeholder(tf.float32, shape = [None, num_classes])
W1 = tf.Variable(tf.random_normal ([input_size, hidden_size1], stddev = 0.1)) #初始化,给一个正态分布随机值
B1 = tf.Variable(tf.random_normal ([ hidden_size1], stddev = 0.1))
W2 = tf.Variable(tf.random_normal ([hidden_size1, num_classes], stddev = 0.1))
B2 = tf.Variable(tf.random_normal ([ num_classes], stddev = 0.1))
hidden_opt = tf.matmul(X, W1)+B1 # 输入层到隐藏层正向传播
hidden_opt = tf.nn.relu(hidden_opt) # 激活函数,用于计算节点输出值
final_opt=tf.matmul(hidden_opt, W2)+B2
#final_opt=tf.matmul(temp, W4)
softmax_Y=tf.nn.softmax(final_opt,name="y")
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=final_opt)) # 对输出层计算交叉熵损失
opt = tf.compat.v1.train.GradientDescentOptimizer(0.001).minimize(loss) # 梯度下降算法,这里使用了反向传播算法用于修改权重,减小损失
init = tf.compat.v1.global_variables_initializer() # 初始化变量
# 计算准确率
correct_prediction =tf.equal (tf.argmax (Y, 1), tf.argmax(softmax_Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
saver = tf.train.Saver()
with tf.Session() as sess:
tf.initialize_all_variables().run()
for i in range (training_iterations) :
#训练
training_loss = sess.run ([opt, loss], feed_dict = {X: Train_Data, Y: Train_Label})
if i % 1000 == 0 :
train_accuracy = accuracy.eval (session = sess, feed_dict = {X: Test_Data,Y: Test_Label})
step_loss = sess.run([loss],feed_dict={X:Test_Data,Y:Test_Label})
train_accuracy2 = accuracy.eval (session = sess, feed_dict = {X: Train_Data,Y: Train_Label})
#pro=tf.nn.softmax(step_loss = sess.run([final_opt],feed_dict={X:Test_Data,Y:Test_Label}))
print ("step: %d --- accuracy= %g, loss= %g, pro:%g" % (i, train_accuracy,sum(step_loss),step_loss[0]))
#训练
#training_loss = sess.run ([opt, loss], feed_dict batch= {X: [[9,7]], Y: [[0,1]]})
#print(sess.run(final_opt, feed_dict={X:[[7.4, 9.1]]}))
save_pb(sess)
saver.save(sess,"./model")
main()
C++调用
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>
#include <vector>
#include <string>
#include "ros/ros.h"
#include "ros/package.h"
using namespace cv;
using namespace std;
//多分类问题用这个函数判断类别,二分类的话不用也行
std::vector<int> Argmax(cv::Mat x)
{
std::vector<int> res;
for (int i = 0; i < x.rows; i++)
{
int maxIdx = 0;
float maxNum = 0.0;
for (int j = 0; j < x.cols; j++)
{
float tmp = x.at<float>(i, j);
if (tmp > maxNum)
{
maxIdx = j; //更新最优值序号
maxNum = tmp; //更新最优值
}
}
res.push_back(maxIdx); //最优预测值的序号
}
return res;
}
int main(){
std::string config_path = ros::package::getPath("armor_detect_uestc");
std::string pbfile = config_path+"/src/graph.pb";
cv::dnn::Net net = cv::dnn::readNetFromTensorflow(pbfile);
//用Mat存储数据
Mat blob = (Mat_ <float>(6, 1) << 21.8658, 205.5499, 205.5449, 0.0463, 7.6381, 0.7756);
blob = cv::dnn::blobFromImage(blob, 1.0, Size(6,1), Scalar(), false, false); //转化格式
//将数据喂给网络(可指定Tensor名)
net.setInput(blob,"conv1/x");
//前向传播,得到推理结果
cv::Mat pred = net.forward("conv1/y"); //如果网络输出层是softmax的话,pred即为各类别的概率
//分类结果
vector<int> res = Argmax(pred);
}