解决Opencv dnn模块无法使用onnx模型的问题(将onnx的动态输入改成静态)

一、问题来源

最近做人脸识别项目,想只用OpenCV自带的人脸检测和识别模块实现,使用OpenCV传统方法:Haar级联分类器人脸检测+LBPH算法人脸识别的教程已经有了,于是想着用OpenCV中的dnn模块来实现,dnn实现人脸检测也有(详细教程可见我的这篇博客https://blog.csdn.net/weixin_42149550/article/details/131474284),问题就是基于cnn的人脸识别咋用opencv的dnn模块实现?一番搜索,发现OpenCV的dnn模块在加载YuNet模型时会报错

从官网下载的模型文件:

python 复制代码
# 加载人脸检测模型
faceDetector = cv2.FaceDetectorYN.create('./opencvmodels/yunet.onnx', '', (320, 320))
# 加载人脸识别模型
faceRecognizer = cv2.FaceRecognizerSF_create(model='./opencvmodels/face_recognizer_fast.onnx', config='')

yunet模型加载会报错,face_recognizer_fast加载没有问题

报错提示:

重点看最后一句话:

error: (-215:Assertion failed) !isDynamicShape in function 'cv::dnn::dnn4_v20221220::ONNXImporter::parseShape'

这句报错是说加载的onnx模型是动态的,但是OpenCV的cv2.FaceDetectorYN.create()不支持加载动态的模型,这里的动态指的是模型没有指定固定的输入参数。

我们通过netron(https://netron.app/)来查看yunet.onnx的模型结构(部分)

从图中可以看到,官网上下载的模型结构batch_size,height,width都是未知的,这在OpenCV中是不支持的,虽然问题知道了,但是在网上查了几天都没找到解决办法!!!

PS:遇到bug真的不能陷进去,放一放,平静一下,第二天说不定就能解决了

二、解决办法

非常感谢这几篇文章,让我对onnx模型有了更多的了解,最后终于摸索出来(把自己给感动了)
如何修改已有的ONNX模型 https://tool.4xseo.com/a/15892.html
模型部署入门教程(五):ONNX 模型的修改与调试

https://zhuanlan.zhihu.com/p/516920606?utm_medium=social\&utm_oi=800114666985648128\&utm_id=0
官网讨论

https://github.com/opencv/opencv/issues/23288


先看onnx解释:

ONNX 在底层是用 Protobuf 定义的。Protobuf,全称 Protocol Buffer,是 Google 提出的一套表示和序列化数据的机制。使用 Protobuf 时,用户需要先写一份数据定义文件,再根据这份定义文件把数据存储进一份二进制文件。可以说,数据定义文件就是数据类,二进制文件就是数据类的实例。

神经网络本质上是一个计算图。计算图的节点是算子,边是参与运算的张量。而通过可视化 ONNX 模型,我们知道 ONNX 记录了所有算子节点的属性信息,并把参与运算的张量信息存储在算子节点的输入输出信息中。事实上,ONNX 模型的结构可以用类图大致表示如下:

我们把yunet.onnx模型先加载进来

python 复制代码
# 使用onnx模块
model = onnx.load('./opencvmodels/yunet.onnx')
onnx.checker.check_model(model) # 验证Onnx模型是否准确

通过验证说明OpenCV提供的onnx模型是没有问题的。

接着我们打印出yunet的计算图

python 复制代码
# 计算图太大了
print(model.graph)

可以看到initializer中储存了一个节点的数据信息,其中的名字name都是跟netron中看到的一一对应,因为数据太多了,看不全,我们只把计算图的ninput输入部分打印出来,看看是怎么样的

python 复制代码
print(model.graph.input)

[name: "input"

type {

tensor_type {

elem_type: 1

shape {

dim {

dim_param: "batch_size"

}

dim {

dim_value: 3

}

dim {

dim_param: "height"

}

dim {

dim_param: "width"

}

}

}

}

]

可以看到输入结构在onnx中是一个可迭代的列表容器,包含了name名字,type类型,type中又定义了输入的维度,现在的思路就是要把shape中的dim_param: "height"和dim_param: "width"换成固定的值即dim_value,我们一层一层来打印:

python 复制代码
for input_node in model.graph.input:
    print(input_node)

name: "input"

type {

tensor_type {

elem_type: 1

shape {

dim {

dim_param: "batch_size"

}

dim {

dim_value: 3

}

dim {

dim_param: "height"

}

dim {

dim_param: "width"

}

}

}

}

我们遍历输入结构,可以看到外层的括号去掉了,我们接着向下层索引:

python 复制代码
for input_node in model.graph.input:
    print(input_node.type)

tensor_type {

elem_type: 1

shape {

dim {

dim_param: "batch_size"

}

dim {

dim_value: 3

}

dim {

dim_param: "height"

}

dim {

dim_param: "width"

}

}

}

python 复制代码
for input_node in model.graph.input:
    print(input_node.type.tensor_type.shape)

dim {

dim_param: "batch_size"

}

dim {

dim_value: 3

}

dim {

dim_param: "height"

}

dim {

dim_param: "width"

}

那么思路就有了,我们只要索引到dim这一层,然后把其中的dim_param: "height"和dim_param: "width"替换成固定的dim_value: 320就可以了

python 复制代码
for input_node in model.graph.input:
    # print(input_node.type.tensor_type.shape)
    input_node.type.tensor_type.shape.dim[2].dim_value = 320
    input_node.type.tensor_type.shape.dim[3].dim_value = 320
    print(input_node.type.tensor_type.shape)

想要的结果出现了!!!

dim {

dim_param: "batch_size"

}

dim {

dim_value: 3

}

dim {

dim_value: 320

}

dim {

dim_value: 320

}

接下来我们重新验证模型有没有问题,然后将模型保存成新的模型

python 复制代码
onnx.checker.check_model(model)
onnx.save(model, 'opencvmodels/new_yunet.onnx')

我们重新在netron中查看一下模型结构

新的yunet模型的输入已经改成固定输入尺寸,下面我们来测试一下模型能不能用

python 复制代码
recognize_net = cv2.dnn.readNetFromONNX('./opencvmodels/new_yunet.onnx')
dnn_faceDetector = cv2.FaceDetectorYN_create(model="./opencvmodels/new_yunet.onnx", config="", input_size=(320, 320))

没有报错!!! 呜呜呜~~~~

来看一下人脸识别效果

(借用彭于晏哥哥的照片)

人脸对齐

cv2.FaceDetectorYN还封装了人脸对齐,超级方便有木有

python 复制代码
face1_align = faceRecognizer.alignCrop(image1, face1_detect[1])

人脸检测

检测的准确率很高!!

人脸匹配结果,判断出这是同一个人,这里使用了两种距离匹配方法:余弦距离和L2距离,结果一样。

大功告成!!!

相关推荐
SQingL几秒前
用OPenCV分割视频
人工智能·opencv·音视频
洋葱土豆和香菜6 分钟前
图像处理与分析
图像处理·人工智能
神奇的代码在哪里8 分钟前
MiniCPM3-4B | 笔记本电脑运行端侧大模型OpenBMB/MiniCPM3-4B-GPTQ-Int4量化版 | PyCharm环境
人工智能·大模型·gptq·minicpm·端侧大模型
audyxiao00111 分钟前
KDD 2024论文分享┆STAMP:一种基于时空图神经网络的微服务工作负载预测方法
人工智能·微服务·资源管理·工作负载预测
Hiweir ·34 分钟前
机器翻译之数据处理
前端·人工智能·python·rnn·自然语言处理·nlp·机器翻译
黑色叉腰丶大魔王36 分钟前
ChatGPT:强大的人工智能聊天机器人
人工智能·chatgpt·机器人
十有久诚42 分钟前
NEMESIS: NORMALIZING THE SOFT-PROMPT VECTORS OF VISION-LANGUAGE MODELS
人工智能·深度学习·提示学习·视觉语言模型
绕灵儿1 小时前
YOLOV8& OpenCV + usb 相机 实时识别
数码相机·opencv·yolo
吉小雨1 小时前
PyTorch 教程
人工智能·pytorch·python
陪学2 小时前
自动驾驶,被逼着上市?
大数据·人工智能·物联网·机器学习·自动驾驶·产品运营·产品经理