“ 此篇介绍如何使用C++加载OpenCV DNN模块库来调用深度神经网络模型以及一些应用”
目前,深度神经网络广泛应用于用于图像处理的各个领域,OpenCV4丰富了DNN模块,增加了与深度神经网络相关的函数及比较前沿的算法,以下就此展开描述:
>注意:此篇不对如何训练出深度神经网络模型做出概述!后续会单独出文说明。
以下,围绕对深度神经网络模型的应用。
软硬件环境:
- Ubuntu20.04
- OpenCV4.5.1
- Cmake3.18.5
01
—
加载深度神经网络模型
1、函数说明
读取深度学习模型函数:
Net cv::dnn::readNet(const String & model,const String & config="",const String & framework="")
model:模型文件名;
config:配置文件名;
framework:框架种类;
框架种类 | 模型文件格式 | 配置文件格式 |
Caffe | *.caffemodel | *.prototxt |
TensorFlow | *.pb | *.pbtxt |
Torch | *.t7 | *.net | -- |
Darknet | *.weights | *.cfg |
DLDT | *.bin | *.xml |
深度学习模型参数:
empty():判断模型是否为空,不需要输入参数,模型为空返回true,否则false;
getLayerNames():得到每层网络的名称,不需要输入参数,返回值为vector类型变量;
getLayerId():得到每层网络的ID,输入参数为网络名称,返回值为int类型变量;
getLayer():得到指向指定ID或名称的网络层的指针,输入参数为网络层ID,返回值为Ptr类型变量;
forward():执行前向传输,输入参数为需要输出的网络层的名称,返回值为Mat类型数据;
setInput():设置网络新的输入数据;
向网络层中添加数据:
void cv::dnn::Net::setInput(InputArray blob,const String & name="",double scalefactor=1.0,const Scalar & mean=Scalar())
blob:新的输入数据,类型为CV_32F或CV_8U;
name:输入网络层的名称;
scalefactor:可选的标准化比例;
mean:可选的减数数值;
2、编写代码
1、编写main.cpp:
#include #include using namespace cv;using namespace std;using namespace cv::dnn;int main(){ string model="/home/zja/Downloads/bvlc_googlenet.caffemodel"; string config="/home/zja/Downloads/bvlc_googlenet.prototxt"; Net net=dnn::readNet(model,config); if(net.empty()) { cout<<"请确认输入了模型文件"<<endl; return -1; } //获取各层信息 vector layerNames=net.getLayerNames(); for(unsigned long i=0;i { //读取每层网络的ID int ID=net.getLayerId(layerNames[i]); //读取每层网络信息 Ptr layer=net.getLayer(ID); //输出网络信息 cout<<"网络层数:"<"网络名称:"<endl<<"网络层数类型:"<type.c_str()<<endl; }return 0;}
#include
#include
using namespace cv;
using namespace std;
using namespace cv::dnn;
int main()
{
string model="/home/zja/Downloads/bvlc_googlenet.caffemodel";
string config="/home/zja/Downloads/bvlc_googlenet.prototxt";
Net net=dnn::readNet(model,config);
if(net.empty())
{
cout<<"请确认输入了模型文件"<<endl;
return -1;
}
//获取各层信息
vector layerNames=net.getLayerNames();
for(unsigned long i=0;i
{
//读取每层网络的ID
int ID=net.getLayerId(layerNames[i]);
//读取每层网络信息
Ptr layer=net.getLayer(ID);
//输出网络信息
cout<<"网络层数:"<"网络名称:"<endl<<"网络层数类型:"<type.c_str()<<endl;
}
return 0;
}
2、编写CMakeLists.txt:
# 声明要求的 cmake 最低版本cmake_minimum_required(VERSION 2.8)# 声明一个 myCV 工程project( myCV )# 设置编译模式 (Debug/Release/...)set(CMAKE_BUILD_TYPE "Debug")# 寻找OpenCV库find_package( OpenCV REQUIRED )# 添加头文件include_directories( ${OpenCV_INCLUDE_DIRS} )# 生成可执行二进制文件add_executable( MyCV main.cpp )# 链接OpenCV库target_link_libraries( MyCV ${OpenCV_LIBS} )
3、文件目录结构:
(base) zja@zja:~$ tree -L 2
.
├── bvlc_googlenet.caffemodel
├── bvlc_googlenet.prototxt
├── CMakeLists.txt
└── main.cpp
3、运行
Cmake编译以上代码:
(base) zja@zja:~$ mkdir build && cd build
(base) zja@zja:~/build$ cmake ..
(base) zja@zja:~/build$ make
(base) zja@zja:~/build$ ./Mycv
4、运行结果
以下为加载的深度神经网络模型网络信息,见百度云盘:
链接: https://pan.baidu.com/s/1ogk7qLsV2-sVmFT9RRZ-_A 提取码: 99qf
网络层数:1网络名称:conv1/7x7_s2网络层数类型:Convolution网络层数:2网络名称:conv1/relu_7x7网络层数类型:ReLU网络层数:3网络名称:pool1/3x3_s2网络层数类型:Pooling......网络层数:141网络名称:loss3/classifier网络层数类型:InnerProduct网络层数:142网络名称:prob网络层数类型:Softmax
02
—
深度神经网络模型使用
1、函数说明
输入数据尺寸转换函数:
Mat cv::dnn::blobFromImages(InputArrayOfArrays images,double scalefactor= 1.0,Size size=Size(),const Scalar & Scalar(),bool swapRB=false,bool crop=false,int ddepth=CV_32F)
images:输入图像,图像可以是单通道、三通道或者四通道;
scalefactor:图像像素缩放系数;
size:输出图像的尺寸;
mean:像素值去均值化的数值;
swapRB:是否交换三通道图像的第一个通道和最后一个通道的标志;
size:调整大小后是否对图像进行剪切的标志;
size:输出图像的数据类型,可选参数为CV_32F或CV_8U;
>注意:训练模型时规定了输入数据尺寸!
2、编写代码
以下用到的四种深度神经网络模型见百度云盘:
链接: https://pan.baidu.com/s/1BdplyQDfqwRBwXsSFb9aOg 提取码: fbti
1、编写main1.cpp:
使用目标识别深度神经网络模型:
tensorflow_inception_graph.pb && imagenet_comp_graph_label_strings.txt
功能:目标识别。
#include #include #include using namespace cv;using namespace std;using namespace cv::dnn;int main(){ Mat AirLine=imread("/home/zja/Pictures/TianTianDnn/TianTianDnn1.jpg"); if(AirLine.empty()) { cout<<"Please Input your Image Correctly"<<endl; return -1; } //读取分类种类名称 String typeListFile="/home/zja/Downloads/imagenet_comp_graph_label_strings.txt"; vector typeList;//字符串向量 ifstream file(typeListFile); if(!file.is_open()) { printf("请确认分类种类名称是否正确"); return -1; } std::string type; while(!file.eof()) { //读取名称 getline(file,type); if(type.length()) typeList.push_back(type); } file.close(); //加载网络 String tf_pb_file="/home/zja/Downloads/tensorflow_inception_graph.pb"; Net net=readNet(tf_pb_file); if(net.empty()) { printf("请确认模型文件是否正确"); return -1; } //对输入图像数据进行处理 Mat blob=blobFromImage(AirLine,1.0f,Size(244,244),Scalar(),true,false); //进行图像种类预测 Mat prob; net.setInput(blob,"input");//向模型中添加数据 prob=net.forward("softmax2"); //得到最可能分类输出 Mat probMat=prob.reshape(1,1); Point classNumber; double classProb;//最大可能性 minMaxLoc(probMat,NULL,&classProb,NULL,&classNumber); string typeName=typeList.at(classNumber.x).c_str(); cout<<"图像中物体最有可能为:"<"可能性为:"< //检测内容 string str=typeName+" possibility is: "+to_string(classProb); putText(AirLine,str,Point(50,50),FONT_HERSHEY_SIMPLEX,1.0,Scalar(0,0,255),2,8); imshow("图像判断结果",AirLine);waitKey(0);return 0;}
#include
#include
#include
using namespace cv;
using namespace std;
using namespace cv::dnn;
int main()
{
Mat AirLine=imread("/home/zja/Pictures/TianTianDnn/TianTianDnn1.jpg");
if(AirLine.empty())
{
cout<<"Please Input your Image Correctly"<<endl;
return -1;
}
//读取分类种类名称
String typeListFile="/home/zja/Downloads/imagenet_comp_graph_label_strings.txt";
vector typeList;//字符串向量
ifstream file(typeListFile);
if(!file.is_open())
{
printf("请确认分类种类名称是否正确");
return -1;
}
std::string type;
while(!file.eof())
{
//读取名称
getline(file,type);
if(type.length())
typeList.push_back(type);
}
file.close();
//加载网络
String tf_pb_file="/home/zja/Downloads/tensorflow_inception_graph.pb";
Net net=readNet(tf_pb_file);
if(net.empty())
{
printf("请确认模型文件是否正确");
return -1;
}
//对输入图像数据进行处理
Mat blob=blobFromImage(AirLine,1.0f,Size(244,244),Scalar(),true,false);
//进行图像种类预测
Mat prob;
net.setInput(blob,"input");//向模型中添加数据
prob=net.forward("softmax2");
//得到最可能分类输出
Mat probMat=prob.reshape(1,1);
Point classNumber;
double classProb;//最大可能性
minMaxLoc(probMat,NULL,&classProb,NULL,&classNumber);
string typeName=typeList.at(classNumber.x).c_str();
cout<<"图像中物体最有可能为:"<"可能性为:"<
//检测内容
string str=typeName+" possibility is: "+to_string(classProb);
putText(AirLine,str,Point(50,50),FONT_HERSHEY_SIMPLEX,1.0,Scalar(0,0,255),2,8);
imshow("图像判断结果",AirLine);
waitKey(0);
return 0;
}
2、编写main2.cpp:
使用风格迁移深度神经网络模型:
"the_wave.t7","mosaic.t7","feathers.t7","candy.t7","udnie.t7","composition_vii.t7","la_muse.t7","starry_night.t7","the_scream.t7"
功能:风格迁移变换。
#include #include using namespace cv;using namespace std;using namespace cv::dnn;int main(){ Mat image=imread("/home/zja/Pictures/TianTian.jpg"); //下载地址:https://cs.stanford.edu/people/jcjohns/fast-neural-style/models//instance_norm/candy.t7 String models[9]={"the_wave.t7","mosaic.t7","feathers.t7","candy.t7","udnie.t7","composition_vii.t7","la_muse.t7","starry_night.t7","the_scream.t7"}; for(int i=0;i<9;i++) { Net net=readNet("/home/zja/Downloads/fast_style/"+models[i]); imshow("原始图像",image); //计算图像每个通道的均值 Scalar imageMean=mean(image); //调整图像尺寸和格式 Mat blobImage=blobFromImage(image,1.0,Size(256,256),imageMean,false,false); //计算网络对原图像处理结果 net.setInput(blobImage); Mat output=net.forward(); //输出结果的尺寸和通道数 int outputChannels=output.size[1]; int outputRows=output.size[2]; int outputCols=output.size[3]; //将输出结果存放到图像中 Mat result=Mat::zeros(Size(outputCols,outputRows),CV_32FC3); float* data=output.ptr<float>(); for(int channel=0;channel { for(int row=0;row { for(int col=0;col { result.at(row,col)[channel]=*data++; } } } //对迁移结果进行进一步操作处理 //恢复图像减掉的均值 result=result+imageMean; //对图像进行归一化,便于图像显示 result=result/255.0; //调整图像尺寸,使得与原图像尺寸相同 resize(result,result,image.size()); //显示结果 imshow("第"+to_string(i)+"风格迁移结果",result); }waitKey(0);return 0;}
3、编写main3.cpp:
使用人脸识别深度神经网络模型:
opencv_face_detector_uint8.pb && opencv_face_detector.pbtxt
使用性别识别深度神经网络模型:
gender_net.caffemodel && gender_deploy.prototxt
功能:人脸识别及性别判断。
注意:第一个网络输出结果=第二个网络的输入
#include #include using namespace cv;using namespace std;using namespace cv::dnn;int main(){ Mat image=imread("/home/zja/Pictures/face_age/1.jpg"); if(image.empty()) { cout<<"请确认是否输入正确的图像文件"<<endl; return -1; } //读取人脸识别模型 String model_bin="/home/zja/Downloads/face_age/opencv_face_detector_uint8.pb"; String config_text="/home/zja/Downloads/face_age/opencv_face_detector.pbtxt"; Net faceNet=readNet(model_bin,config_text); //读取性别检测模型 String genderProto="/home/zja/Downloads/face_age/gender_deploy.prototxt"; String genderModel="/home/zja/Downloads/face_age/gender_net.caffemodel"; String genderList[]={"Male","Female"}; Net genderNet=readNet(genderModel,genderProto); if(faceNet.empty() && genderNet.empty()) { cout<<"请正确输入模型文件"<<endl; return -1; } //对整副图像进行人脸检测 Mat blobImage=blobFromImage(image,1.0,Size(300,300),Scalar(),false,false); faceNet.setInput(blobImage,"data"); Mat detect=faceNet.forward("detection_out"); //人脸概率、人脸矩形区域的位置 Mat detectionMat(detect.size[2],detect.size[3],CV_32F,detect.ptr<float>()); //对每个人脸区域进行性别检测 int exBoundray=25;//每个人脸区域四个方向扩充的尺寸 float confidenceThreshold=0.5;//判定为人脸的概率阈值,阈值越大准确性越高 for(int i=0;i { float confidence=detectionMat.at<float>(i,2);//检测为人脸的概率 //只检测概率大于阈值区域的性别 if(confidence>confidenceThreshold) { //网络检测人脸区域大小 int topLx=detectionMat.at<float>(i,3)*image.cols;//398 int topLy=detectionMat.at<float>(i,4)*image.rows;//195 int bottomRx=detectionMat.at<float>(i,5)*image.cols;//573 int bottomRy=detectionMat.at<float>(i,6)*image.rows;//421 Rect faceRect(topLx,topLy,bottomRx-topLx,bottomRy-topLy); //将网络检测出的区域尺寸进行扩充,要注意防止尺寸在图像真实尺寸之外 Rect faceTextRect; faceTextRect.x=max(0,faceRect.x-exBoundray);//373 faceTextRect.y=max(0,faceRect.y-exBoundray);//170 faceTextRect.width=min(faceRect.width+exBoundray,image.cols-1);//200 faceTextRect.height=min(faceRect.height+exBoundray,image.rows-1);//251 Mat face=image(faceTextRect);//扩充后的人脸图像 //调整面部图像尺寸 Mat faceblob=blobFromImage(face,1.0,Size(227,227),Scalar(),false,false); //调整后的面部图像输入到性别检测网络 genderNet.setInput(faceblob); //计算检测结果 Mat genderPreds=genderNet.forward();//两个性别的可能性 //性别检测结果 float male,female; male=genderPreds.at<float>(0,0); female=genderPreds.at<float>(0,1); int classID=male>female?0:1; String gender=genderList[classID]; //在原图像中绘制面部轮廓和性别 rectangle(image,faceRect,Scalar(0,0,255),2,8,0); putText(image,gender.c_str(),faceRect.tl(),FONT_HERSHEY_SIMPLEX,0.8,Scalar(0,0,255),2,8); } } imshow("性别检测结果",image);waitKey(0);return 0;}
4、编写CMakeLists.txt:
# 声明要求的 cmake 最低版本cmake_minimum_required(VERSION 2.8)# 声明一个 myCV 工程project( myCV )# 设置编译模式 (Debug/Release/...)set(CMAKE_BUILD_TYPE "Release")# 寻找OpenCV库find_package( OpenCV REQUIRED )# 添加头文件include_directories( ${OpenCV_INCLUDE_DIRS} )# 生成可执行二进制文件add_executable( MyCV main1.cpp main2.cpp main3.cpp )# 链接OpenCV库target_link_libraries( MyCV ${OpenCV_LIBS} )
5、文件目录结构:
(base) zja@zja:~$ tree -L 2
.
├── main1.cpp
├── main2.cpp
├── main3.cpp
└── CMakeLists.txt
3、运行
Cmake编译以上代码:
(base) zja@zja:~$ mkdir build && cd build
(base) zja@zja:~/build$ cmake ..
(base) zja@zja:~/build$ make
(base) zja@zja:~/build$ ./Mycv
4、运行结果
4.1、运行main1.cpp的结果:
终端输出:
(base) zja@zja:~/build$
图像中物体最有可能为:Bernese mountain dog可能性为:0.967678
4.2、运行main2.cpp的结果:
4.3、运行main3.cpp的结果:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.....END