目标检测之二(传统算法和深度学习的源码学习)

2023-11-19

目标检测之二(传统算法和深度学习的源码学习)

本系列写一写关于目标检测的东西,包括传统算法和深度学习的方法都会涉及到,注重实验而不着重理论,理论相关的看论文去哈,主要依赖opencv。

  本文主要内容:简单分析下yolo9000的原理,然后使用opencv的dnn模块进行目标检测.

    接着上一篇提到的车辆检测(http://blog.csdn.net/baolinq/article/details/78579317),使用了Haar+Adaboost算法进行车辆检测,对于简单场景的检测效果还不错,但是对于稍微复杂点的场景或者由于光照等原因的影响,检测效果不尽人意,但是我们看到深度学习的检测方法,比如YOLO的检测效果仍然很好。

    YOLO9000秉承着“奥运理念”, Better, Faster, Stronger,翻译出来的意思就是检测精度更高,检测速度更快,性能更强(更鲁棒),主要原理和YOLO v1一样,YOLO9000增加了更多的深度学习的训练tricks和借鉴了其他算法的优点,


上图是YOLOv1论文(https://arxiv.org/abs/1506.02640)里面的截图,我们简单看一下它的原理:

(1) 给个一个输入图像,首先将图像划分成7 * 7的网格。

(2) 对于每个网格,每个网格预测2个boudingbox(每个box包含5个预测量)以及20个类别概率,总共输出7×7×(2*5+20)=1470个tensor

(3) 根据上一步可以预测出7 * 7 * 2 = 98个目标窗口,然后根据阈值去除可能性比较低的目标窗口,再由NMS去除冗余窗口即可。

YOLOv1使用了end-to-end的回归方法,没有regionproposal步骤,直接回归便完成了位置和类别的判定。种种原因使得YOLOv1在目标定位上不那么精准,直接导致YOLO的检测精度并不是很高。

YOLO9000用了这么多tricks,每一种提高一点点,组合起来就很强大了


    YOLO9000略有不同YOLOv2最后输出的是S * S * (B * (5 + C))维,它直接使用每个boxes进行预测,而yolov1不是这样的,它是每个cell只负责一个object。其中参数S,B,C的取值也不同:

S=13,YOLOv2对输入图像(416*416,yolov1是448*448)进行了卷积和池化等,最终输出的特征图尺寸是13 * 13。

B=5,这是由Dimension Clusters得到的。Fast-RCNN中使用3种scales和3中aspectratios(1:1,1:2,2:1)在每个位置产生了9个anchor boxes。作者认为这种手动选取的anchor不够好,虽然网络最终可以学出来,但如果我们可以给出更好的anchor,那么网络肯定更加容易训练而且效果更好。作者使用kmeans算法进行训练,使用基于IOU的度量方式d(box,centroid)=1−IOU(box,centroid)代替一般场景下的欧氏距离。作者的实验表明这种做法比手动选取的效果更好

C=20,表示object的种类,如果是用voc数据集的话,C=20。如果是coco数据集,C=80。

 

详细的原理自己去网上搜一下有一大堆,不过还是建议看下原文(https://arxiv.org/abs/1612.08242),项目主页(https://pjreddie.com/darknet/yolo/)上面有关于YOLO的详细介绍以及很多训练好的model可以使用以及yolo的源代码,作者可以说是非常良心了,点个赞。

    直接先去github上面下载YOLO的源代码(https://github.com/pjreddie/darknet),代码写的非常nice,使用darknet网络结构,几乎不用环境就可以独立运行的,当然如果你要显示窗口的话,最好还是要有一个opencv,他也会自动把运行结果保存到当前目录,不过看起来不方便。根据自己的环境编译一下,使用命令行cmd窗口就可以运行了。输入图片路径即可进行检测。

运行截图:


电脑显卡很渣,暂时使用的是no gpu版本的。

运行结果:

 

按任意键关闭窗口,即可继续输入图片路径继续进行目标检测

 

这样测试很不方便,每次只能测试一张图像,我想测试序列图像或者测试视频该怎么办呢?有两种方法,一种修改源代码,改写输入文件的类型;第二种更简单使用opencv的dnn模块(Depth neural network)。详情见opencv官方文档https://docs.opencv.org/master/d6/d0f/group__dnn.html

我以第二种方法为例,我使用的是opencv3.3。Dnn模块是opencv新推出的用于深度学习的一个模块,目前支持Caffe、TensorFlow、Torch、PyTorch、darknet等深度学习框架。我觉得这个功能真的很赞,以后训练好的模型,移植就非常方便了,用来做一些日常测试也是很不错的。


dnn::Net net=readNetFromDarknet()就可以从参数中读取darknet网络参数以及训练好的模型权重。

先把输入图像归一化一下,然后输入图像到网络中,再进行前向传播,就OK了,是不是非常的easy啊。剩下的只是怎么显示和保存数据的问题了,处理结果已经保存到detectionMat里面去了。

       //! [Prepare blob]
       Mat inputBlob = blobFromImage(resized, 1 / 255.F); //Convert Mat to batch of images
       //! [Prepare blob]
       //! [Set input blob]
       net.setInput(inputBlob, "data");               //set the network input
       //! [Set input blob]
 
       //! [Make forward pass]
       cv::Mat detectionMat = net.forward("detection_out");    //compute output
       //! [Make forward pass]

Talk is cheap,show me thecode。给出测试源码,其实也就是稍微改了一下opencv的samples。

 

#include <opencv2/dnn.hpp>
#include <opencv2/dnn/shape_utils.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace cv::dnn;

#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstdlib>
using namespace std;

const size_t network_width = 416;
const size_t network_height = 416;

const char* about = "This sample uses You only look once (YOLO)-Detector "
"(https://arxiv.org/abs/1612.08242)"
"to detect objects on image\n"; // TODO: link

const char* params
= "{ help           | false | print usage         }"
"{ cfg            |    yolo-voc.cfg   | model configuration }"	//写入训练好的网络参数文件cfg格式
"{ model          |   yolo-voc.weights    | model weights       }"//写入训练好的模型的权重文件
"{ image          |       | image for detection }"				//测试的图片名,我没有使用
"{ min_confidence | 0.24  | min confidence      }";		//信心度阈值,低于该阈值的不输出,判断为没有检测到

vector<cv::Scalar> color = { Scalar(0,205,150),Scalar(255,0,0),Scalar(0,0,255),Scalar(200,150,0) };
int main(int argc, char** argv)
{
    cv::CommandLineParser parser(argc, argv, params);	//读入params参数文件
    
    if (parser.get<bool>("help"))
    {
        std::cout << about << std::endl;
        parser.printMessage();
        return 0;
    }
    
    //String modelConfiguration = "yolo.cfg"; // parser.get<string>("cfg");
    //String modelBinary = "yolo.weights"; //parser.get<string>("model");
	String modelConfiguration = parser.get<string>("cfg");
	String modelBinary =parser.get<string>("model");

    //! [Initialize network]
    dnn::Net net = readNetFromDarknet(modelConfiguration, modelBinary);//读取网络模型和参数,初始化
    //! [Initialize network]
    
    if (net.empty())
    {
        cerr << "Can't load network by using the following files: " << endl;
        cerr << "cfg-file:     " << modelConfiguration << endl;
        cerr << "weights-file: " << modelBinary << endl;
        cerr << "Models can be downloaded here:" << endl;
        cerr << "https://pjreddie.com/darknet/yolo/" << endl;
        exit(-1);
    }

	//  String imgpath =parser.get<string>("image")
    //cv::Mat frame = cv::imread(imgpath);

    // 检测目标种类的名称在voc.txt,读入保存到tag_names向量中
    vector<string> tag_names;
    ifstream fin;
    fin.open("voc.txt");
    string buf;
    while(fin && getline(fin, buf)) {
        tag_names.push_back(buf);
    }
	fin.close();

	//打开默认摄像头,用于检测摄像头的图像
	//VideoCapture cap(0);
	//if (!cap.isOpened())
	//	return -1;

	//图片的存放地址(相对路径),在当前目录的test_image文件夹里面
	String imgpath = "test_image/";
	vector<cv::String> imgvec;
	cv::glob(imgpath, imgvec);//保存文件名到imgvec向量中,便于批量处理
	Mat frame;
	bool stop = false;
	//while(!stop){

	int64 start = getTickCount();	//计算运行时间
	for (int imgnum = 0; (imgnum < imgvec.size())&&(!stop); ++imgnum) {
		
		//if (!cap.read(frame))
		//	break;
		frame = imread(imgvec[imgnum]);
		//imshow("原图",frame);
		//waitKey(5);
		//! [Resizing without keeping aspect ratio]
		cv::Mat resized;
		cv::resize(frame, resized, cv::Size(network_width, network_height));
		//! [Resizing without keeping aspect ratio]

		//! [Prepare blob]
		Mat inputBlob = blobFromImage(resized, 1 / 255.F); //Convert Mat to batch of images
		//! [Prepare blob]

		//! [Set input blob]输入归一化的图像
		net.setInput(inputBlob, "data");                //set the network input
		//! [Set input blob]

		//! [Make forward pass]前向传播进行计算
		cv::Mat detectionMat = net.forward("detection_out");    //compute output
		//! [Make forward pass]


		float confidenceThreshold = parser.get<float>("min_confidence");
		for (int i = 0; i < detectionMat.rows; i++)
		{
			const int probability_index = 5; //前5个参数为每一个bounding box包含5个值:x,y,w,h和confidence(置信度),置信值代表box包含一个目标的置信度
			const int probability_size = detectionMat.cols - probability_index;//后20个为voc数据集中有20个类别,每个类的概率
			float *prob_array_ptr = &detectionMat.at<float>(i, probability_index);

			size_t objectClass = std::max_element(prob_array_ptr, prob_array_ptr + probability_size) - prob_array_ptr;//选择一个概率最大的作为检测结果
			float confidence = detectionMat.at<float>(i, (int)objectClass + probability_index);

			if (confidence > confidenceThreshold)//大于信心度阈值才输出
			{
				float x = detectionMat.at<float>(i, 0);//检测的矩形框的计算,本来是中心点和长宽,这都是被归一化后的
				float y = detectionMat.at<float>(i, 1);
				float width = detectionMat.at<float>(i, 2);
				float height = detectionMat.at<float>(i, 3);
				float xLeftBottom = (x - width / 2) * frame.cols;//得到图像上的矩形框的左上角和右下角坐标,要反归一化
				float yLeftBottom = (y - height / 2) * frame.rows;
				float xRightTop = (x + width / 2) * frame.cols;
				float yRightTop = (y + height / 2) * frame.rows;

				//std::cout << "Class: " << tag_names[objectClass] << std::endl;
				//std::cout << "Confidence: " << confidence << std::endl;

				//std::cout << " " << xLeftBottom
				//	<< " " << yLeftBottom
				//	<< " " << xRightTop
				//	<< " " << yRightTop << std::endl;
				stringstream ss;
				ss << confidence;
				string str_text = tag_names[objectClass] + ": "+ss.str();//输出类别和信心度

				Rect object((int)xLeftBottom, (int)yLeftBottom,
					(int)(xRightTop - xLeftBottom),
					(int)(yRightTop - yLeftBottom));

				rectangle(frame, object, color[objectClass % 4],2);	//显示矩形框,我给了一个颜色数组,避免都是一个色的不好看
				int baseline = 0;
				Size labelSize = getTextSize(str_text, FONT_HERSHEY_SIMPLEX, 0.5,1, &baseline);//设置字体
				//int x = xLeftBottom - labelSize.width >= 0 ? xLeftBottom - labelSize.width : xLeftBottom + labelSize.width;
				int rect_y = yLeftBottom - labelSize.height >= 0 ? yLeftBottom - labelSize.height : yLeftBottom ;//避免在边界时,显示的内容看不到

				rectangle(frame, Rect(Point(xLeftBottom, rect_y),Size(labelSize.width,labelSize.height+baseline)), Scalar(0, 255, 0),CV_FILLED);
				//输出显示内容
				cv::putText(frame, str_text, Point(xLeftBottom, rect_y+labelSize.height), FONT_HERSHEY_SIMPLEX, 0.5, color[objectClass%4]);

			}
		}
		//cout << "用时: "<<1.0*(getTickCount() - start) / getTickFrequency() << endl;
		imshow("detections", frame);//显示图像
		//waitKey();
		if (waitKey(5) == 'q')
			stop = true;
	}
	//cout << "用时: " << 1.0*(getTickCount() - start) / getTickFrequency() << endl;//计算所有图片共运行的时间
    return 0;
} // main


代码上都有比较详细的注释,稍微看过论文或者看过其他人的写的论文笔记的,了解yolo的原理的都应该能看懂。我们简单分析一下网络的输出矩阵detectionMat,根据上面的分析。我们知道YOLO9000的输出是13*13*5*(5+20)维 的向量。


果然像我们分析的一样,调试时可以看到detectionMat是一个845*25维的矩阵,相当于一共845个boxes,每行表示一个boxes,每行有25列,分别表示矩形框的定位(4),该boxes的置信值(confidence score)。置信值代表box包含一个目标的置信度,以及20个类别的概率值。

知道了网络输出矩阵的意义,我们只需要分别取出来即可,每次只取类别概率最大的那一类,作为预测结果,然后跟confidenceThreshold作比较,超过阈值才认为是正确的预测,否则认为没有预测到。

 

最后就是预测框的计算了,因为作者模仿了faster-rcnn的做法,使用偏移量表示位置,而不是直接预测,实验表明这样做更容易训练,且可以提高mAP。作者就没有采用预测直接的offset的方法,而使用了预测相对于grid cell的坐标位置的办法,作者又把ground truth限制在了0到1之间,利用logistic回归函数来进行这一限制。所以最后计算出来的坐标需要去归一化,乘以图像的宽或者高。

 

最后部分的显示,我就不多说了,自己看看opencv的函数就可以了。

 

搞定收工。写的有点啰嗦,本来想和第一篇写在一起,结果写了这么多,如果你看到了这里,我只能说给你真有耐心,给你点个赞。下篇见~~

参考文献

[1] Redmon J, Farhadi A. YOLO9000: Better, Faster, Stronger[J]. 2016.

[2]http://blog.csdn.net/jesse_mx/article/details/53925356


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

目标检测之二(传统算法和深度学习的源码学习) 的相关文章

  • C++实现FFT频谱分析

    Update 9 10 2022 鸽了太久 增补了一些新的表述和简单推导 以及FFT在算法竞赛中的应用部分 帖子里的代码已经分别在2021全国大学生电子设计竞赛 洛谷OJ和课程设计中实战过 可靠性有保障 Origin 10 23 2021

随机推荐

  • web前端技术笔记(九)JavaScript介绍、变量、操作元素属性

    JavaScript JavaScript介绍 变量 变量类型 变量 函数 属性 函数参数命名规范 获取元素方法一 操作元素属性 通过 操作属性 通过 操作属性 innerHTML JavaScript介绍 JavaScript是运行在浏览
  • Ant-Maven-Gradle

    make Makefile学习 peterYong 博客园 ant ant 工具 milkty 博客园 maven 学习Maven这一篇就够了 轻松的小希的博客 CSDN博客 学Maven 这篇万余字的教程 真的够用了 江南一点雨 博客园
  • CSS 样式书写规范,css样式书写规范

    在工作当中css样式是非常重要的 但是咋样书写css样式更重要 一 css书写规范 1 定位属性 position display float left top right bottom overflow clear z index 2 自
  • 千与千寻 中日歌词与罗马音译(最准确啦)

    千与千寻 国语和日语版 Cover 木村 弓 作曲 木村 弓 作词 觉 和歌子 张 就此告别吧水上的列车就快到站 粥 呼 胸 奥 yo n de i ru mu ne no do ko ka o ku de 张 开往未来的路上没有人会再回返
  • MySQL 触发器入门 (转载)

    博客迁移 时空蚂蚁http cui zhbor com MySQL 5 1包含对触发器的支持 触发器是一种与表操作有关的数据库对象 当触发器所在表上出现指定事件时 将调用该对象 即表的操作事件触发表上的触发器的执行 创建触发器 在MySQL
  • Android 本地更新APK(无需添加运行时权限)

    很多APP都会有自动更新APP然后本地安装的功能 之前一直是用AsnycTask来做的 最近发现AsyncTask被标记为过时 那么就换一种方式来写吧 我自己是做在Dialog里面 使用okhttp进行文件下载 配合自定义View的进度条进
  • python大规模数据处理技巧之一:数据常用操作

    面对读取上G的数据 python不能像做简单代码验证那样随意 必须考虑到相应的代码的实现形式将对效率的影响 如下所示 对pandas对象的行计数实现方式不同 运行的效率差别非常大 虽然时间看起来都微不足道 但一旦运行次数达到百万级别时 其运
  • 线程的创建及性能

    目录 1 多线程 VS 单线程性能 2 线程3中创建方式 2 1 创建方式一 继承Thread 1种写法 2 2 创建方式二 实现Runnable及变种 4种写法 2 3 创建方式三 带返回值的Callable 2种写法 线程休眠演示打印电
  • momentJS 时间差计算

    momentJS时间差计算 最近在使用JavaScript计算时间差的时候 发现很多问题需要处理 在查看momentJS之后 发现非常容易 console log moment format YYYY MM DD HH mm ss 当前时间
  • 完全二叉树与满二叉树

    去笔试了很多次 每次都有有关于二叉树的题目 而且其中最多的是关于完全二叉树 然而完全二叉树在哥心中的形态一直很模糊 究其原因是我把完全二叉树和满二叉树搞混了 其实满二叉树是完全二叉树的特例 因为满二叉树已经满了 而完全并不代表满 所以形态你
  • 提升应用性能的关键步骤——UniApp性能优化策略与技巧详解

    前言 作者主页 雪碧有白泡泡 个人网站 雪碧的个人网站 推荐专栏 java一站式服务 前端炫酷代码分享 uniapp 从构建到提升 从0到英雄 vue成神之路 解决算法 一个专栏就够了 架构咱们从0说 数据流通的精妙之道 文章目录 前言 代
  • JAVA,异常

    异常概念 通常大家认为异常就是错误 但这个错误有很多种 1 语法错误 2 JVM虚拟机错误 3 平台错误 4 程序运行错误 平台或者资源或者逻辑 数值等错误 常见的异常 1 java lang NullPointerException 空指
  • C++ 模板简介(一)—— SFINAE

    SFINAE 类型检查 Concepts SFINAE 机制是组成 C 模板机制及类型安全的相当重要的基础 全称是 Substitution failure is not an error 大概的意思就是只要找到了可用的原型 比如函数模板
  • Video Device Class Codes

    ifndef LINUX USB VIDEO H define LINUX USB VIDEO H include
  • 解决导入keras.engine 问题

    导入 keras engine 可能会产生No module named tensorflow keras engine 我们采用下列方式导入时 from tensorflow keras engine topology import La
  • 2019-面向小白的微信小程序-视频教学-基础

    看掘金链接 https juejin im post 5dd739a1e51d4523053c4282
  • react和react jsx基础

    本文是个人学习笔记 例子都是来自React Native官网 之前不是做前端的 没有使用过react 要学习react native做混合开发 react 包括react jsx还是得补补 react和react jsx react是一个j
  • RabbitMQ访问Web端口报错User can only log in via localhost

    RabbitMQ访问Web端口报错User can only log in via localhost 一 错误信息 在项目开发过程中 通过浏览器访问Web端口时报错 User can only log in via localhost 外
  • 图像识别小车(电机部分)——电赛学习笔记(2)

    图片来源 B站唐老师讲电赛 目录 一 电机部分结构 二 步进电机示例 三 伺服电机示例 四 我们的方案 一 电机部分结构 二 步进电机示例 1 驱动器 L298N CSDN搜索使用方法 控制器 stm32 电源暂时用12V直流源 2 控制
  • 目标检测之二(传统算法和深度学习的源码学习)

    目标检测之二 传统算法和深度学习的源码学习 本系列写一写关于目标检测的东西 包括传统算法和深度学习的方法都会涉及到 注重实验而不着重理论 理论相关的看论文去哈 主要依赖opencv 本文主要内容 简单分析下yolo9000的原理 然后使用o