在日常使用中经常需要做模型转换,那tensorflow2.x的h5模型转onnx是必要的知识,如下代码实现了该转换,可以设置输入张量的形状,也可以选择opset。
# 读取h5模型转换为onnx模型,教程来自于https://github.com/onnx/tensorflow-onnx/blob/master/tutorials/keras-resnet50.ipynb
import tensorflow as tf
import numpy as np
import cv2
import tf2onnx
import onnxruntime as rt
######################################################################################################################
# 图片预处理,对输入图片进行等比例拉伸至指定尺寸,不足的地方填0,同时把像素值归一化到[-1, 1]
def image_preprocess(image, target_length, value=0.0, method=0):
image = image.astype("float32")
h, w, _ = image.shape # 获得原始尺寸
ih, iw = target_length, target_length # 获得目标尺寸
scale = min(iw/w, ih/h) # 实际拉伸比例
nw, nh = int(scale * w), int(scale * h) # 实际拉伸后的尺寸
image_resized = cv2.resize(image, (nw, nh)) # 实际拉伸图片
image_paded = np.full(shape=[ih, iw, 3], fill_value=value)
dw, dh = (iw - nw) // 2, (ih-nh) // 2
image_paded[dh:nh+dh, dw:nw+dw, :] = image_resized # 居中填充图片
if method == 0:
image_paded = image_paded / 255. # 图片归一化
elif method == 1:
image_paded = image_paded / 127.5 - 1.0 # 图片标准化
return image_paded
# 读取图片并预处理
image_path = "car.jpg" # 图片路径
image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB).astype("float32") # 读取图片
image = image_preprocess(image, 128, 0, 1) # 图片预处理
image = np.expand_dims(image, axis=0).astype(np.float32) # 图片维度扩展
######################################################################################################################
# 读取h5模型
model = tf.keras.models.load_model("/xxx.h5")
# 推理h5模型
preds = model.predict(image)
# 保存h5模型为tf的save_model格式
# model.save("./" + model.name))
######################################################################################################################
# 定义模型转onnx的参数
spec = (tf.TensorSpec((None, 128, 128, 3), tf.float32, name="input"),) # 输入签名参数,(None, 128, 128, 3)决定输入的size
output_path = model.name + "_13.onnx" # 输出路径
# 转换并保存onnx模型,opset决定选用的算子集合
model_proto, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13, output_path=output_path)
output_names = [n.name for n in model_proto.graph.output]
print(output_names) # 查看输出名称,后面推理用的到
######################################################################################################################
# 读取onnx模型,安装GPUonnx,并设置providers = ['GPUExecutionProvider'],可以实现GPU运行onnx
providers = ['CPUExecutionProvider']
m = rt.InferenceSession(output_path, providers=providers)
# 推理onnx模型
output_names = output_names
onnx_pred = m.run(output_names, {"input": image})
# 对比两种模型的推理结果
print('Keras Predicted:', preds)
print('ONNX Predicted:', onnx_pred[0])
# make sure ONNX and keras have the same results
np.testing.assert_allclose(preds, onnx_pred[0], rtol=1e-4)
针对onnx的x86端测评,该测评如果在GPU上进行,那单个进程就完全够用了,如果想要在GPU上运行需要:1. 安装GPU版本的onnxruntime;2. 代码选择GPU执行。如下代码是单进程版本的CPU上测评代码,如果要改为GPU版本只需要修改providers = ['CPUExecutionProvider']即可。
# 计算onnx模型的acc和混淆矩阵
import numpy as np
from tqdm import tqdm
import os
import cv2
import onnxruntime as rt
def image_preprocess(image, target_length, value=0.0, method=0):
image = image.astype("float32")
h, w, _ = image.shape # 获得原始尺寸
ih, iw = target_length, target_length # 获得目标尺寸
scale = min(iw/w, ih/h) # 实际拉伸比例
nw, nh = int(scale * w), int(scale * h) # 实际拉伸后的尺寸
image_resized = cv2.resize(image, (nw, nh)) # 实际拉伸图片
image_paded = np.full(shape=[ih, iw, 3], fill_value=value)
dw, dh = (iw - nw) // 2, (ih-nh) // 2
image_paded[dh:nh+dh, dw:nw+dw, :] = image_resized # 居中填充图片
if method == 0:
image_paded = image_paded / 255. # 图片归一化
elif method == 1:
image_paded = image_paded / 127.5 - 1.0 # 图片标准化
return image_paded
# 读取onnx模型
onnx_path = "./MobileNetV2.onnx"
output_names = ['Logits'] # 对应MobileNetV2.onnx进行修改,是生成模型时定义的名字
providers = ['CPUExecutionProvider']
model = rt.InferenceSession(onnx_path, providers=providers)
# 遍历识别每一幅图片并统计结果
path = "./test/"
label_name = ["label_1", "label_2", "label_3", "label_4", "label_5", "label_6", "label_7"] # 必须跟tflite模型的标签顺序匹配
num_prs = [0.000000001 for _ in range(len(label_name))]
num_rec = [0.000000001 for _ in range(len(label_name))]
num_mat = [[0] * len(label_name) for _ in range(len(label_name))]
label_true, label_pred = [], []
for i in range(len(label_name)): # 遍历每一个子文件夹
label_path = path + label_name[i] + "/"
image_list = os.listdir(label_path)
for j in tqdm(range(len(image_list)), desc='Processing %d' % i): # 遍历某文件夹下的所有图片
image_path = label_path + image_list[j]
image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)
image = image_preprocess(image, 128, 0, 1)
image = np.expand_dims(image, axis=0).astype("float32")
output = model.run(output_names, {"input": image})[0].tolist()
label_id = np.argmax(output[0])
pred_prob = output[0][label_id]
num_prs[int(label_id)] += 1
num_rec[i] += 1
num_mat[i][int(label_id)] += 1
label_true.append(i)
label_pred.append(int(label_id))
# 计算总的准确率accuracy
num1, num2, num3 = 0, 0, 0
for i in range(len(label_name)):
num1 += num_mat[i][i]
num2 += num_prs[i]
num3 += num_rec[i]
assert int(num2) == int(num3)
accuracy = (num1/num2*100*100//1)/100
print("total accuracy: "+ str(accuracy))
# 计算每个类别的precision
result_prs = {}
prs = [float(num_mat[i][i])/float(num_prs[i])*100 for i in range(len(label_name))]
for i in range(len(label_name)):
result_prs[label_name[i]] = (prs[i]*100//1)/100
print("every precision: "+ str(result_prs))
# 计算每个类别的recall
result_rec = {}
rec = [float(num_mat[i][i])/float(num_rec[i])*100 for i in range(len(label_name))]
for i in range(len(label_name)):
result_rec[label_name[i]] = (rec[i]*100//1)/100
print("every recall: "+ str(result_rec))
# 打印混淆矩阵
print(label_name)
print(np.array(num_mat))
当然如果没有GPU,需要在CPU上测评,那还是需要多进程的测评
# 计算onnx模型的acc和混淆矩阵,多进程
import numpy as np
import os
import cv2
import onnxruntime as rt
from multiprocessing import Process, Queue
# 定义图片预处理函数
def image_preprocess(image, target_length, value=0.0, method=0):
image = image.astype("float32")
h, w, _ = image.shape # 获得原始尺寸
ih, iw = target_length, target_length # 获得目标尺寸
scale = min(iw/w, ih/h) # 实际拉伸比例
nw, nh = int(scale * w), int(scale * h) # 实际拉伸后的尺寸
image_resized = cv2.resize(image, (nw, nh)) # 实际拉伸图片
image_paded = np.full(shape=[ih, iw, 3], fill_value=value)
dw, dh = (iw - nw) // 2, (ih-nh) // 2
image_paded[dh:nh+dh, dw:nw+dw, :] = image_resized # 居中填充图片
if method == 0:
image_paded = image_paded / 255. # 图片归一化
elif method == 1:
image_paded = image_paded / 127.5 - 1.0 # 图片标准化
return image_paded
# 读取onnx模型
onnx_path = "./MobileNetV2.onnx"
output_names = ['Logits'] # 生成MobileNetV2.onnx模型时自定义的名字
providers = ['CPUExecutionProvider']
model = rt.InferenceSession(onnx_path, providers=providers)
# 定义进程函数
def fun(q, model, image_list, label_name, n):
num_i = 0
num_total = len(image_list)
num_prs = np.array([0.000000001 for _ in range(len(label_name))])
num_rec = np.array([0.000000001 for _ in range(len(label_name))])
num_mat = np.array([[0] * len(label_name) for _ in range(len(label_name))])
for image_path in image_list:
i = label_name.index(image_path.split("/")[-2])
image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)
image = image_preprocess(image, 128, 0, 1)
image = np.expand_dims(image, axis=0).astype("float32")
output = model.run(output_names, {"input": image})[0].tolist()
label_id = np.argmax(output[0])
num_prs[int(label_id)] += 1
num_rec[i] += 1
num_mat[i, int(label_id)] += 1
num_i += 1
if num_i%100 == 0:
print("Process %d : finish %d | total %d | percentage %0.2f" % (n, num_i, num_total, num_i/num_total*100))
q.put([num_prs, num_rec, num_mat])
# 遍历识别每一幅图片并统计结果
num_core = 20 # cpu核心数
q = Queue()
path = "./test/"
label_name = ["label_1", "label_2", "label_3", "label_4", "label_5", "label_6", "label_7"] # 必须跟tflite模型的标签顺序匹配
num_prs = np.array([0.000000001 for _ in range(len(label_name))])
num_rec = np.array([0.000000001 for _ in range(len(label_name))])
num_mat = np.array([[0] * len(label_name) for _ in range(len(label_name))])
# 遍历每一个子文件夹,得到所有图片路径
image_list = []
for i in range(len(label_name)):
label_path = path + label_name[i] + "/"
for image_name in os.listdir(label_path):
image_list.append(label_path+image_name)
print("finish read image %d" % len(image_list))
# 按照进程分配任务
delta = int(len(image_list)/num_core)
image_index = []
for i in range(num_core):
image_index.append(i * delta)
image_index.append(len(image_list))
# 启动进程
process_list = []
for i in range(num_core):
p = Process(target=fun, args=(q, model, image_list[image_index[i]:image_index[i+1]], label_name, i))
p.start()
process_list.append(p)
# 保持进程
for p in process_list:
p.join()
# 整理结果
for i in range(num_core):
result = q.get()
num_prs += result[0]
num_rec += result[1]
num_mat += result[2]
# 计算总的准确率accuracy
num1, num2, num3 = 0, 0, 0
for i in range(len(label_name)):
num1 += num_mat[i, i]
num2 += num_prs[i]
num3 += num_rec[i]
assert int(num2) == int(num3)
accuracy = (num1/num2*100*100//1)/100
print("total accuracy: "+ str(accuracy))
# 计算每个类别的precision
result_prs = {}
prs = [float(num_mat[i, i])/float(num_prs[i])*100 for i in range(len(label_name))]
for i in range(len(label_name)):
result_prs[label_name[i]] = (prs[i]*100//1)/100
print("every precision: "+ str(result_prs))
# 计算每个类别的recall
result_rec = {}
rec = [float(num_mat[i, i])/float(num_rec[i])*100 for i in range(len(label_name))]
for i in range(len(label_name)):
result_rec[label_name[i]] = (rec[i]*100//1)/100
print("every recall: "+ str(result_rec))
# 打印混淆矩阵
print(label_name)
print(num_mat)