Faster-RCNN进行对象检测
Faster-RCNN模型
下载权重文件及描述文件
1. 进入网络
https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API
2. 下载weights文件及raw中下载config文件
注意下载config文件时,要在raw中下载原文件,最好不要自己新建文件将内容直接复制下来,很有可能出现格式不匹配,导致后续加载网络模型时无法加载的问题。
3. 解决无法打开RAW的问题(无此问题可跳过)
Windows上可以这样办:
上https://www.ipaddress.com查一下raw.githubusercontent.com的ipv4地址,比如我现在查到的是199.232.68.133。
使用管理员权限打开C:\Windows\System32\drivers\etc\hosts文件,添加到最后一行
199.232.68.133 raw.githubusercontent.com
或者管理员权限开或者给个用户写入权限。然后差不多就行了,可能要重启。不过本人使用editplus将其打开并输入后再打开raw.githubusercontent.com就成功了。
4. 进入raw后,右击下载
5. 下载成功后的权重文件
6. 将下载的描述文件复制到此文件目录下
7. 找到此模型对应的标签文件
其中有80个类别,此标签文件在前面的章节googlenet分类中下载的文件里有
标签内容如下,因其内的类名并不是一行一个,而是多行一个,有规律的分布,故在后面的读取标签文件函数的定义时也会和之前的读取标签文件函数有所不同。
具体操作步骤
1.加载模型(读取网络信息)
string bin_model = "D:/OpenCV/project/opencv_tutorial-master/data/models/TensorFlow/faster_rcnn_resnet50_coco_2018_01_28/frozen_inference_graph.pb"; //定义模型权重文件的加载路径
string pbtxt = "D:/OpenCV/project/opencv_tutorial-master/data/models/TensorFlow/faster_rcnn_resnet50_coco_2018_01_28/faster_rcnn_resnet50_coco_2018_01_28.pbtxt"; //定义模型描述文件的加载路径
//load DNN model
Net net = readNetFromTensorflow(bin_model, pbtxt); //TensorFlow和之前的caffe不同之处在于,参数1::模型文件,参数2:描述文件
//设置计算后台(OpenCVdnn模块支持设置不同的计算后台和在不同的设备上进行)
net.setPreferableBackend(DNN_BACKEND_OPENCV); //setPreferableBackend实际计算后台,default默认是DNN_BACKEND_OPENCV作为计算后台,使用此就行(也有加速后台ENGINE)
net.setPreferableTarget(DNN_TARGET_CPU); //设置在什么设备上进行计算(opencl(需要有interl图形卡)、FPGA、CPU)
//获取各层信息
vector<string> layer_names = net.getLayerNames(); //此时我们就可以获取所有层的名称了,有了这些可以将其ID取出
for (int i = 0; i < layer_names.size(); i++) {
int id = net.getLayerId(layer_names[i]); //通过name获取其id
auto layer = net.getLayer(id); //通过id获取layer
printf("layer id:%d,type:%s,name:%s\n", id, layer->type.c_str(), layer->name.c_str()); //将每一层的id,类型,姓名打印出来(可以明白此网络有哪些结构信息了)
}
输出层网络信息如下
2.构建输入
- 在浏览器输入网址https://github.com/opencv/opencv/blob/master/samples/dnn/models.yml,可以查看关于模型的配置参数
Mat src = imread("G:/OpenCV/opencv笔记所用图片/magou.jpg"); //plane
if (src.empty()) {
cout << "could not load image.." << endl;
getchar();
return -1;
}
imshow("src", src);
//构建输入(根据models.yml)
Mat blob = blobFromImage(src, 1.0, Size(800, 600), Scalar(), true, false); //交换通道false,是否剪切false
//上方得到的输入blob变量为4维数据,无法在imagewatch查看其内容
//将输入变量输入到网络中
net.setInput(blob); //输入层名称可以不用写,默认输入第一层,TensorFlow输入名称与caffe不同
- 原始图片如下
3.推测得到输出,并对其降维变形
其输出结果和SSD输出结果相同,都是每个对象对应7个数据
• 输出结果[1x1xNx7], 其中输出的七个维度浮点数如下:
• [image_id, label, conf, x_min, y_min, x_max, y_max]
推测得到单个输出层结果,默认最后输出层结果
//推测结果(下面的操作步骤和SSD检测对象步骤类似,网络输出都为1*1*N*7)
Mat detection = net.forward(); //不输入名称默认是返回最后一层,TensorFlow最后一层的名称可以查看描述文件
//上方得到的detection宽高为0(使用imagewatch查看,原因是其为tensor,它的宽高维度等信息都存在size结构中,所以要在下方通过size获取)
上方经网络推测得到的detection 是4维的变量,在imagewatch中无法查看其内容,需要对结果进行降维,才能在imagewatch中查看
并且4维的变量结果不好利用进行解析,需要将其变为单通道N行7列的数组,再对此数组进行后续的结果解析
方法一,使用Mat构造函数进行降维
直接根据此处4位变量中size[2]、size[3]存储的数组的行数、列数,使用Mat的构造函数构造一个新的二维变量,并通过detection.ptr()将4维变量中的数据也拷贝过来,此处生成了一个100行7列的二维数组,里面存储着推测得到的图片信息:
//cout << detection.size[2] << " " << detection.size[3]; //detection.size[2] 是其行数,detection.size[3]表示列数
//重新定义一个Mat对象接收
Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>()); //此处是初始化一个行为size[2],列为size[3]大小,深度为浮点型,数据从detection中获取
//使用imagewatch可以看到7*100的数组,单通道32F深度
使用imagewatch查看结果
方法二,使用reshape对其降维变形
Mat detection1 = detection.reshape(1, 1); //参数1,改变为单通道,参数2,变形成1行n列的
//可以先转成单通道,1行的数据,查看其总共数量,再根据输出的特点,再reshape一下
//在imagewatch中可以查看到,输出数组detection1为700*1,此数组共700个数据,根据输出应是7列的,计算要再次将此数组变形成7列,即应变形成100行
Mat detectionMat = detection1.reshape(1, 100); //此处使用imagewatch查看就变成100行7列的数组了,后续就可以对此结果进行解析了
reshape输出结果可达到要求
可以发现上述两种方式得到的输出结果相同,下面就可以对其中的内容进行解析,然后将预测的结果输出或者打印到图片上了
4.解析预测内容并打印
读取标签文件,此标签文件中的类名需要按一定顺序得到,在后续的打印相关类名时会用到。此处读取标签文件的方式与之前googlenet分类读取标签文件方式不同。
std::map<int, string> readLabelMaps(); //函数声明
string label_map = "D:/OpenCV/project/opencv_tutorial-master/data/models/mscoco_label_map.pbtxt";
//定义一个函数专门读取这80个类的文件mscoco_label_map.pbtxt(将里面的类名提取出来)
std::map<int, string> readLabelMaps()
{
std::map<int, string> labelNames;
std::ifstream fp(label_map);
if (!fp.is_open())
{
printf("could not open file...\n");
exit(-1);
}
string one_line;
string display_name;
while (!fp.eof())
{
std::getline(fp, one_line);
std::size_t found = one_line.find("id:");
if (found != std::string::npos) {
int index = found;
string id = one_line.substr(index + 4, one_line.length() - index);
std::getline(fp, display_name);
std::size_t found = display_name.find("display_name:");
index = found + 15;
string name = display_name.substr(index, display_name.length() - index);
name = name.replace(name.length() - 1, name.length(), "");
// printf("id : %d, name: %s \n", stoi(id.c_str()), name.c_str());
labelNames[stoi(id)] = name;
}
}
fp.close();
return labelNames;
}
解析结果预测的内容,并打印输出
map<int, string> names = readLabelMaps(); //将标签文件中的类名读入,此类名和index一一对应
//获取浮点型数据后就可以进行数据的解析了
float confidence_threshold = 0.8; //在后面通过此判断合适的结果进行保存(有时某些检测不出,将其调低一点可以显示很多,如0.3)
//解析输出数据
//上方获取的detectionMat是100行,7列的,每行对应一个对象
//输出结果[1x1xNx7], 其中输出的七个维度浮点数如下:
//[image_id, label, conf, x_min, y_min, x_max, y_max]
for (int i = 0; i < detectionMat.rows; i++) {
float score = detectionMat.at<float>(i, 2); //此处获得score分数
if (score > confidence_threshold) { //如果获取的score大于0.8,就将box保存,不要的扔掉
size_t objIndex = (size_t)(detectionMat.at<float>(i, 1))+1; //此处是从1开始的,还要加1,就是索引
//得到四个点的矩形框位置
float tl_x = detectionMat.at<float>(i, 3)*src.cols; //x_min(此处detectionMat.at<float>(i, 3)得到的是一个图像的比例值不是真实值,需要变换)
float tl_y = detectionMat.at<float>(i, 4)*src.rows; //y_min
float br_x = detectionMat.at<float>(i, 5)*src.cols; //x_max
float br_y = detectionMat.at<float>(i, 6)*src.rows; //y_max
//得到box
Rect box((int)tl_x, (int)tl_y, (int)br_x - tl_x, (int)br_y - tl_y);
//画矩形
rectangle(src, box, Scalar(0, 0, 255), 2, 8, 0);
//找到索引对应的类名
map<int, string>::iterator it = names.find(objIndex);
printf("id : %d, display name : %s \n", objIndex, (it->second).c_str());
//写文字(在box的做上角写文字)
putText(src, format("score:%2f,%s", score, (it->second).c_str()), box.tl(), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(255, 0, 0), 2, 8);
}
}
imshow("faster-rcnn-detection-demo", src);
结果图像如下:
完整代码
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp> //包含dnn模块的头文件
#include <iostream>
#include <fstream> //文件流进行txt文件读取
using namespace cv;
using namespace cv::dnn; //包含dnn的命名空间
using namespace std;
std::map<int, string> readLabelMaps();
string label_map = "D:/OpenCV/project/opencv_tutorial-master/data/models/mscoco_label_map.pbtxt";
int main() {
string bin_model = "D:/OpenCV/project/opencv_tutorial-master/data/models/TensorFlow/faster_rcnn_resnet50_coco_2018_01_28/frozen_inference_graph.pb"; //定义模型权重文件的加载路径
string pbtxt = "D:/OpenCV/project/opencv_tutorial-master/data/models/TensorFlow/faster_rcnn_resnet50_coco_2018_01_28/faster_rcnn_resnet50_coco_2018_01_28.pbtxt"; //定义模型描述文件的加载路径
map<int, string> names = readLabelMaps(); //将标签文件中的类名读入,此类名和index一一对应
//load DNN model
Net net = readNetFromTensorflow(bin_model, pbtxt); //TensorFlow和之前的caffe不同之处在于,参数1::模型文件,参数2:描述文件
//设置计算后台(OpenCVdnn模块支持设置不同的计算后台和在不同的设备上进行)
net.setPreferableBackend(DNN_BACKEND_OPENCV); //setPreferableBackend实际计算后台,default默认是DNN_BACKEND_OPENCV作为计算后台,使用此就行(也有加速后台ENGINE)
net.setPreferableTarget(DNN_TARGET_CPU); //设置在什么设备上进行计算(opencl(需要有interl图形卡)、FPGA、CPU)
//获取各层信息
vector<string> layer_names = net.getLayerNames(); //此时我们就可以获取所有层的名称了,有了这些可以将其ID取出
for (int i = 0; i < layer_names.size(); i++) {
int id = net.getLayerId(layer_names[i]); //通过name获取其id
auto layer = net.getLayer(id); //通过id获取layer
printf("layer id:%d,type:%s,name:%s\n", id, layer->type.c_str(), layer->name.c_str()); //将每一层的id,类型,姓名打印出来(可以明白此网络有哪些结构信息了)
}
Mat src = imread("G:/OpenCV/opencv笔记所用图片/magou.jpg"); //plane
if (src.empty()) {
cout << "could not load image.." << endl;
getchar();
return -1;
}
imshow("src", src);
//构建输入(根据models.yml)
Mat blob = blobFromImage(src, 1.0, Size(800, 600), Scalar(), true, false); //交换通道false,是否剪切false,此处的300*300与models.yml文件有所不同
net.setInput(blob); //输入层名称可以不用写,默认输入第一层,TensorFlow输入名称与caffe不同
//推测结果(下面的操作步骤和SSD检测对象步骤类似,网络输出都为1*1*N*7)
Mat detection = net.forward(); //不输入名称默认是返回最后一层,TensorFlow最后一层的名称可以查看描述文件
//上方得到的detection宽高为0(使用imagewatch查看,原因是其为tensor,它的宽高维度等信息都存在size结构中,所以要在下方通过size获取)
//cout << detection.size[2] << " " << detection.size[3]; //detection.size[2] 是其行数,detection.size[3]表示列数
//重新定义一个Mat对象接收
Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>()); //此处是初始化一个行为size[2],列为size[3]大小,深度为浮点型,数据从detection中获取
//使用imagewatch可以看到7*100的数组,单通道32F深度
//Mat detection1 = detection.reshape(1, 1); //参数1,改变为单通道,参数2,变形成1行n列的
//可以先转成单通道,1行的数据,查看其总共数量,再根据输出的特点,再reshape一下
//在imagewatch中可以查看到,输出数组detection1为700*1,此数组共700个数据,根据输出应是7列的,计算要再次将此数组变形成7列,即应变形成100行
//Mat detectionMat = detection1.reshape(1, 100); //此处使用imagewatch查看就变成100行7列的数组了,后需就可以对此结果进行解析了
//获取浮点型数据后就可以进行数据的解析了
float confidence_threshold = 0.8; //在后面通过此判断合适的结果进行保存(有时某些检测不出,将其调低一点可以显示很多,如0.3)
//解析输出数据
//上方获取的detectionMat是100行,7列的,每行对应一个对象
//输出结果[1x1xNx7], 其中输出的七个维度浮点数如下:
//[image_id, label, conf, x_min, y_min, x_max, y_max]
for (int i = 0; i < detectionMat.rows; i++) {
float score = detectionMat.at<float>(i, 2); //此处获得score分数
if (score > confidence_threshold) { //如果获取的score大于0.5,就将box保存,不要扔掉
size_t objIndex = (size_t)(detectionMat.at<float>(i, 1))+1; //此处是从1开始的,还要加1,就是索引
//得到四个点的矩形框位置
float tl_x = detectionMat.at<float>(i, 3)*src.cols; //x_min(此处detectionMat.at<float>(i, 3)得到的是一个图像的比例值不是真实值,需要变换)
float tl_y = detectionMat.at<float>(i, 4)*src.rows; //y_min
float br_x = detectionMat.at<float>(i, 5)*src.cols; //x_max
float br_y = detectionMat.at<float>(i, 6)*src.rows; //y_max
//得到box
Rect box((int)tl_x, (int)tl_y, (int)br_x - tl_x, (int)br_y - tl_y);
//画矩形
rectangle(src, box, Scalar(0, 0, 255), 2, 8, 0);
//找到索引对应的类名
map<int, string>::iterator it = names.find(objIndex);
printf("id : %d, display name : %s \n", objIndex, (it->second).c_str());
//写文字(在box的做上角写文字)
putText(src, format("score:%2f,%s", score, (it->second).c_str()), box.tl(), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(255, 0, 0), 2, 8);
}
}
imshow("faster-rcnn-detection-demo", src);
waitKey(0);
return 0;
}
//定义一个函数专门读取这80个类的文件mscoco_label_map.pbtxt(将里面的类名提取出来)
std::map<int, string> readLabelMaps()
{
std::map<int, string> labelNames;
std::ifstream fp(label_map);
if (!fp.is_open())
{
printf("could not open file...\n");
exit(-1);
}
string one_line;
string display_name;
while (!fp.eof())
{
std::getline(fp, one_line);
std::size_t found = one_line.find("id:");
if (found != std::string::npos) {
int index = found;
string id = one_line.substr(index + 4, one_line.length() - index);
std::getline(fp, display_name);
std::size_t found = display_name.find("display_name:");
index = found + 15;
string name = display_name.substr(index, display_name.length() - index);
name = name.replace(name.length() - 1, name.length(), "");
// printf("id : %d, name: %s \n", stoi(id.c_str()), name.c_str());
labelNames[stoi(id)] = name;
}
}
fp.close();
return labelNames;
}