OpenCV的DNN模块,提供了对其他框架训练模型的加载,目前支持框架有:
1. *Caffe, http://caffe.berkeleyvision.org/
2. *TensorFlow, https://www.tensorflow.org/
3. *Torch, http://torch.ch/
4. *Darknet, https://pjreddie.com/darknet/
5. *DLDT, https://software.intel.com/openvino-toolkit
6. *ONNX, https://onnx.ai/
本主题按照OpenCV官方的例子撸了两个模型加载的Demo:Darknet与Caffe. 其中使用的预训练模型可以从下面两个URL获取下载地址:
- Caffe模型:
https://docs.opencv.org/4.1.2/d5/de7/tutorial_dnn_googlenet.html
- Darknet模型:
https://pjreddie.com/darknet/yolo/
Caffe模型加载
#include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
int main(int argc, char const *argv[]){
/*
加载别人训练好的模型,用来识别图片的类别;
- bvlc_googlenet.caffemodel : 训练好的模型
- bvlc_googlenet.prototxt : 训练使用的深度网络结构
- classification_classes_ILSVRC2012.txt :类别与类别名称
*/
/*
框架名:
*Caffe, http://caffe.berkeleyvision.org/
*TensorFlow, https://www.tensorflow.org/
*Torch, http://torch.ch/
*Darknet, https://pjreddie.com/darknet/
*DLDT, https://software.intel.com/openvino-toolkit
*ONNX, https://onnx.ai/
框架的模型扩展名:
*.caffemodel (Caffe
*.pb (TensorFlow)
*.t7 | *.net (Torch)
*.weights (Darknet)
*.bin (DLDT)
*.onnx (ONNX)
框架的结构配置扩展名:
*.prototxt (Caffe)
*.pbtxt (TensorFlow)
*.cfg (Darknet)
*.xml (DLDT)
*/
// 加载模型
cv::String model = "bvlc_googlenet.caffemodel";
cv::String config = "bvlc_googlenet.prototxt";
cv::String framework = "Caffe"; // 显式指定框架名(可以使用"",会根据扩展名自动确定)
cv::dnn::Net net = cv::dnn::readNet(model, config, framework);
// 设置使用的计算推理库
/*
:
cv::dnn::DNN_BACKEND_DEFAULT, = DNN_BACKEND_INFERENCE_ENGINE
cv::dnn::DNN_BACKEND_HALIDE, = DNN_BACKEND_OPENCV
cv::dnn::DNN_BACKEND_INFERENCE_ENGINE,
cv::dnn::DNN_BACKEND_OPENCV,
cv::dnn::DNN_BACKEND_VKCOM = DNN_BACKEND_OPENCV
}
*/
net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT); // 采用默认值
// 设置运算的目标设备
/*
cv::dnn::DNN_TARGET_CPU,
cv::dnn::DNN_TARGET_OPENCL,
cv::dnn::DNN_TARGET_OPENCL_FP16,
cv::dnn::DNN_TARGET_MYRIAD,
cv::dnn::DNN_TARGET_VULKAN,
cv::dnn::DNN_TARGET_FPGA
*/
net.setPreferableBackend(cv::dnn::DNN_TARGET_CPU); // 采用默认值
// net.setPreferableBackend(cv::dnn::DNN_BACKEND_HALIDE); // 采用OpenGL的管道图像处理
// 加载图片(或者直接从视频抓取图片识别)
cv::String imgfile = "space_shuttle.jpg";
cv::Mat img = cv::imread(imgfile);
// 图像的预处理(去均值,缩放),并返回服务训练模型的图像格式:NCHW矩阵
cv::Mat std_img;
// 注意:bvlc_googlenet.prototxt中的深度网络模型需要的图像必须是224 * 224大小的。
cv::dnn::blobFromImage(img, std_img, 1.0, cv::Size(224, 224), cv::Scalar(), false, false, CV_32F);
// 预测
net.setInput(std_img); // 输入数据
// 返回的结果是一个数组(一维矩阵),包含是每类可能的概率,结果当然取概率最高的。
// 类别的对应清单,从classification_classes_ILSVRC2012.txt获取
cv::Mat prob = net.forward(); // 返回预测结果
// 取最大的概率数据与位置
double max_prob;
cv::Point max_pos;
cv::minMaxLoc(prob, 0, &max_prob, 0, &max_pos); // 最小值不取,就直接使用空指针(0)。
std::cout << "识别类别是:" << max_pos.x << "概率是:" << max_prob << std::endl; // 0行,所以取x列数
// 从classification_classes_ILSVRC2012.txt文件获取max_pos.x位置的类别名称:space shuttle
// 注意:矩阵下标从0开始,文件的行数也要按照从0开始计。
std::ifstream ifs("classification_classes_ILSVRC2012.txt");
int idx = 0;
char buf[256] = {0};
while(ifs.good()){
bzero(buf, sizeof(buf));
ifs.getline(buf, sizeof(buf)-1);
if(idx == max_pos.x)
break;
idx++;
}
std::cout << "类别名:"<< buf << std::endl;
return 0;
}
Darknet模型加载
#include <fstream>
#include <iostream>
#include <opencv2/opencv.hpp>
// 调用预先训练好的yolo模型,用来侦测识别目标。
/**
* 深度网络模型 :yolov3.cfg
* 预先训练好的权重:yolov3.weights
* 分类的类别文件:coco.names
* 用来测试的侦测识别图像:dog.jpg
*/
int main(int argc, char** argv){
// 加载深度网络模型
cv::String model = "../yolov3.weights"; // 使用darknet框架训练出来的模型
cv::String config = "../yolov3.cfg";
cv::dnn::Net net = cv::dnn::readNet(model, config, "Darknet"); // 如果不知道使用的什么框架训练,最后参数使用空字符串,函数自动识别;
// 设置运算方式
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); // cv::dnn::DNN_BACKEND_INFERENCE_ENGINE
net.setPreferableBackend(cv::dnn::DNN_TARGET_CPU); // 使用CPU计算
// 获取输出层名字
std::vector<cv::String> outNames = net.getUnconnectedOutLayersNames();
// 打印输出层名字
// for(cv::String name : outNames){
// std::cout<< name << std::endl;
// }
// 打开需要测试的图片(可以使用VideoCapture打开视频,来动态抓取图像)
cv::Mat img = cv::imread("../road.jpg");
// 对图像进行预处理
cv::Mat blob;
cv::dnn::blobFromImage(
img, // 输入
blob, // 输出
1.0, // 图像的像素的控制系数
// cv::Size(img.rows, img.cols), // 测试过,图片太大,会产生错误,1008测试也会出错,768宽可以
cv::Size(416, 416),
cv::Scalar(), // 均值处理(如果不指定,策使用图像的均值)
true, // 第1通道与第3通道交换(就是RGB与BGR的区别)
false, // 图像resize后是否裁剪
CV_8U); // 输出图像的深度
net.setInput(
blob, // 输入的数据
"", // 输入层的名字,采用默认
0.00392, // 对输入的控制系数
cv::Scalar()); // 对输入的均值处理(上面已经处理,这个函数只需要第一个参数即可)
// 处理Faster-RCNN与R-FCN的情况
if (net.getLayer(0)->outputNameToIndex("im_info") != -1){
cv::Mat imInfo = (cv::Mat_<float>(1, 3) << img.rows, img.cols, 1.6f);
net.setInput(imInfo, "im_info");
}
// 输出预测
std::vector<cv::Mat> outs; // 输出层可能多个
net.forward(outs, outNames);
// 对预测的结果处理:类别,概率,图像区域
// 得到输出层数量
std::vector<int> outLayers = net.getUnconnectedOutLayers();
// 得到第一个输出层类型
std::string outLayerType = net.getLayer(outLayers[2])->type;
// 定义解析的侦测类别结果
std::vector<int> classIds; // 类别id
std::vector<float> confidences; // 类别的概率(置信度)
std::vector<cv::Rect> boxes; // 侦测对象的图像区域
// 输出层类型两类:DetectionOutput或者Region,表示两种不通的输出格式
if(outLayerType == "Region"){
// 格式:N * C,N表示侦测的对象数,C表示总得类别数 + 4,其中最前面4个参数是识别的对象区域[center-x, center-y, w, h]
//循环解析数据
for(cv::Mat an_out: outs){
// 每行表示一个侦测到的对象
for(int row = 0; row < an_out.rows; row++){
// 取坐标
cv::Mat rect = an_out.row(row).colRange(0,4);
// 取侦测的概率
cv::Mat prob = an_out.row(row).colRange(5, an_out.cols);
// 取概率最大值
cv::Point classIdPoint;
double confidence;
cv::minMaxLoc(prob, 0, &confidence, 0, &classIdPoint);
if(confidence > 0.5){
// 类别
classIds.push_back(classIdPoint.x);
// 概率
confidences.push_back((float)confidence);
// 区域
int centerX = (int)(rect.at<float>(0,0) * img.cols);
int centerY = (int)(rect.at<float>(0,1) * img.rows);
int width = (int)(rect.at<float>(0,2) * img.cols);
int height = (int)(rect.at<float>(0,3) * img.rows);
int left = centerX - width / 2;
int top = centerY - height / 2;
boxes.push_back(cv::Rect(left, top, width, height));
}
}
}
}
else if(outLayerType == "DetectionOutput"){
//
std::cout << "DetectionOutput" << std::endl;
}
std::vector<int> indices;
cv::dnn::NMSBoxes(boxes, confidences, 0.5, 0.4, indices);
for(cv::Rect b : boxes){
cv::rectangle(
img,
cv::Point(b.x, b.y),
cv::Point(b.x + b.width, b.y + b.height),
cv::Scalar(0, 0, 255),
4);
}
cv::imwrite("../road_out.png", img);
return 0;
}
-
图片效果
目标侦测
附录:
- 有OpenCV的dnn模型加载,Python训练的模型可以很轻易的使用到任何设备与平台。
网友评论