Yolov3+C+++opencv+VS2015成功检测

版权声明:本文为博主原创文章,未经博主允许不得转载。https://mp.csdn.net/postedit/88533803

前言

     最近在用yolov3进行目标检测,也有一个多星期了,想把最近做出的一些成果记录下来,供大家参考下。我的运行环境是C+++opencv+VS2015+yolov3,下面将简单介绍下yolo的一些思想,这些也是我在看了很多博主介绍后,自己做的一些总结和笔记,后面也给大家附上了我写的一些关键代码和解释,关于整个工程由于一些私人原因不能整个附上,不过网上也有很多,大家可以自行搜索。

     关于环境的配置问题,我需要说一下就是opencv的版本最好是opencv 3.4.2版的,这个就不会有什么其他问题了。VS2015+opencv环境的配置可以查看我写的另一个博客。https://blog.csdn.net/qq_36417014/article/details/88539793

     最后,如果有什么地方写得不对的,希望大家不吝赐教,谢谢!

第一章 Yolo(Yon Only Look Once)

1.1 介绍

     关于它的详细介绍,在这里,我不想多说,网上有太多它的介绍了,大家可以自行查阅。我主要简单地介绍下,整个的核心想法,便于理解整个框架。要介绍yolov3,不得不从yolov1开始说起。

     yolov1:核心想法:(1)“分而治之”,输入图片:划分为7*7的网格,每个单元独立作检测。注意:网格只是物体中心点位置的划分之用,并不是对图片进行切片,不会让网格脱离整体的关系。输出:S*S*(B*5+C),S*S:表示网格数量,B表示每个网格生成框的个数,C表示能检测识别的种类(受输出的深度影响)。(2)训练,yolov是端到端的训练,意思就是我们只注意输入和输出就行了,整个网络也就一个损失函数搞定。具体关于损失函数介绍就不多说了。(3)总结,v1对于整个yolov系列的价值:1)leaky RelU y=\left\{\begin{matrix} x &x>0 \\ 0.1x & otherwise \end{matrix}\right.,保留负数输出。2)分而治之。3)端到端训练,损失函数的反向传播可以贯穿整个网络。

    yolov2:特点:“tradeoff”--折中,在速度和准确率上进行tradeoff。下面说说v2在v1上的提高,(1)batch normalization:BN显著提升模型收敛,去掉了dropout来避免过拟合,在mAP上提升了2%。(2)high resolution classifier(高分辨的分类器):mAP提高了4%。(3)Convolutional With Anchor Boxes:优化中尝试了anchor机制,通过全连接层之间预测Bounding Box的坐标值。anchor机制:anchor("锚"),第一步:把feature maps(256张map)拍扁,变成一张图,基于每个256d点开始“下锚”;第二步:“下锚”以后开始撒网,即以每一个锚点为中心生成大小不同的k个框,假设k=9,框的大小:长宽比:1:1,2:1,1:2三种,大小也有三种。这描述虽然不是专业的解释,但是通俗易懂,对anchor机制有了更好的理解。(4)Dimension clusters:当作者对yolov使用anchor机制时,遇到两个问题:1)模板框的大小是手动挑选的,作者并没有手动设定prior(框的大小),而是在训练集的b-box上用了k-means聚类来自动找到prior,找到的依据是:想要IOU得分更高的优选项,与box大小无关。所以对于距离的判断:d(box,centroid)=1-IOU(box,centroid)。(5)Direct location prediction:2)模型不稳定。不稳定因素主要来自于box预测(x,y)位置,yolov为每个bounding box预测出5个坐标(tx,ty,tw,th,to),

bx=\sigma(tx)+Cx                                     Pw和Ph都是k-means聚类之后的prior的宽和高,yolov直接预测出偏移量tw和th。使用聚类搭

by=\sigma(ty)+Cy                                     直接预测法的操作,使得模型上升了5个百分点。

bw=P_{w}e^{tw}

bh=P_{h}e^{th}

Pr(object)*IOU(b,object)=\sigma(to)

(6)Fine-Grained Feature(细粒度特征):调整后的yolo将在13*13的特征图上做检测任务,对小物体检测十分有帮助。(7)multi-scale training:用多种分辨率的输入图片进行训练。(8)darknet-19:用darknet-19作为yolov2的backbone分类网络,同时在darknet-19中使用batch normalization来加速收敛。

     yolov3:保留的东西:(1)“分而治之”。(2)采用“lerky RelU”作为激活函数。(3)端到端进行训练。一个loss function搞定训练,只需要关注输入端到输出端。(4)从yolov2开始,yolo就用batch normalization作为正则化,加速收敛和避免过拟合的方法。把BN层和leaky relu层接到每一层卷积层之后。(5)多尺度训练。在速度和准确率之间tradeoff。

    yolo每一代的提升很大一部分决定于backbone网络的提升,从v2的darknet-19到v3的darknet-53。yolov3还提供替换backbone----tiny darknet,想要性能好用Darknet-53,想要轻量高速用tiny-darknet。

    (1)backbone:没有池化层和全连接层,在forward中,张量的尺寸变换是通过改变卷积核的步长实现的。输入为416*416,则输出为13*13(416/32=13),stride=(2,2)。经历了5次缩小,1/2^5=1/32,所以通常要求输入图片是32的倍数。

    yolov2对于前向过程中张量尺寸的变换,都是通过Maxpool来进行的,一共5次,而yolov3是通过增大步长来进行的。

    (2)Output:最后输出是三个y1,y2,y3,深度都是255,边长规律是13:26:52。每个网格单元预测3个box,每个box有(x,y,w,h,confidence)五个基本参数,然后还要有80个类别的概率,所以3*(5+80)=255。v3用上采样的方法来实现这种多尺度的feature map。将backbone中间层的处理结果和后面网络层的上采样结果进行一个拼接之后的处理结果作为feature map。

    (3)Some tricks:1)Bounding Box Prediction。2)选用的b-box priors的k=9,对于tiny-yolo,k=6。priors都是在数据集上聚类得到的。3)v3对b-box进行预测的时候,采用了logistic regression。

    (4)Loss function:除了w,h的损失函数依然采用总误差之外,其他部分的损失函数用的是二值交叉熵。

1.2 关键代码解释

    整个代码,我是采用C++语言来写的,构造了一个Recognition的类。创建了yolov3.h和yolov3.cpp还有main.cpp三个文件。下面只针对关键代码进行解释,代码上也有注释。

(1)首先对yolov3进行配置,即把三个重要的文件分别加载进来,即coco.names,yolov3.cfg,yolov3.weights文件,注意这里的路径,写绝对路径,在这里我可是吃了亏的,刚开始就是由于路径问题,一直没有加载进来而报错。这三个文件,大家可以自行去网上下载,有很多。

/************************************************************************
函数功能:进行Yolov3的各种配置工作
输入:无
输出:返回配置好的网络
************************************************************************/
Net Recognition::InitYolov3()
{
	//加载类名文件
	std::string class_names_string = "F:\\VS\\20190309Yolov3\\coco.names";
	std::ifstream class_names_file(class_names_string.c_str());
	if (class_names_file.is_open())
	{
		std::string name = "";
		while (std::getline(class_names_file, name))
		{
			class_names.push_back(name);
		}
	}
	else
	{
		std::cout << "don't open class_names_file!" << endl;
	}

	//给出模型的配置和权重文件
	cv::String modelConfiguration = "F:\\VS\\20190309Yolov3\\yolov3.cfg";
	cv::String modelWeights = "F:\\VS\\20190309Yolov3\\yolov3.weights";

	// 装载网络
	cv::dnn::Net net = cv::dnn::readNetFromDarknet(modelConfiguration, modelWeights);//读取网络模型和参数,初始化网络
	std::cout << "Read Darknet..." << std::endl;
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

	return net;
}

  (2)得到输出层的名称,yolov3是端到端训练,所以需知道输出层的名称。

/************************************************************************
函数功能:得到输出层的名称
输入:需要遍历的网络Net
输出:返回输出层的名称
************************************************************************/
std::vector<cv::String> Recognition::getOutputsNames(const Net& net)
{
	static vector<String> names;
	if (names.empty())
	{
		//得到输出层的索引号
		std::vector<int> out_layer_indx = net.getUnconnectedOutLayers();

		//得到网络中所有层的名称
		std::vector<String> all_layers_names = net.getLayerNames();

		//在名称中获取输出层的名称
		names.resize(out_layer_indx.size());
		for (int i = 0; i < out_layer_indx.size(); i++)
		{
			names[i] = all_layers_names[out_layer_indx[i] - 1];
		}
	}
	return names;
}

       (3)得到最适合的边界框

/************************************************************************
函数功能:使用非极大值抑制方法删除低置信度的边界框
输入:frame:网络的输入图像,out:网络输出层的输出图像
输出:无
************************************************************************/
void Recognition::postprocess(Mat& frame, const vector<Mat>& out)
{
	std::vector<float> confidences;
	std::vector<Rect> boxes;
	std::vector<int> classIds;

	for (int num = 0; num < out.size(); num++)
	{
		double value;
		Point Position;
		//得到每个输出的数据
		float *data = (float *)out[num].data;

		for (int i = 0; i < out[num].rows; i++, data += out[num].cols)
		{
			//得到每个输出的所有类别的分数
			Mat sorces = out[num].row(i).colRange(5, out[num].cols);

			//获取最大得分的值和位置
			minMaxLoc(sorces, 0, &value, 0, &Position);

			if (value > confThreshold)
			{
				//data[0],data[1],data[2],data[3]都是相对于原图像的比例
				int center_x = (int)(data[0] * frame.cols);
				int center_y = (int)(data[1] * frame.rows);
				int width = (int)(data[2] * frame.cols);
				int height = (int)(data[3] * frame.rows);
				int box_x = center_x - width / 2;
				int box_y = center_y - height / 2;

				classIds.push_back(Position.x);
				confidences.push_back((float)value);
				boxes.push_back(Rect(box_x,box_y,width,height));
			}
		}
	}

	//执行非极大值抑制,以消除具有较低置信度的冗余重叠框 
	std::vector<int> perfect_indx;
	NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, perfect_indx);
	for (int i = 0; i < perfect_indx.size(); i++)
	{
		int idx = perfect_indx[i];
		Rect box = boxes[idx];
		drawPred(box.x, box.y, box.x + box.width, box.y + box.height,frame);
	}

}

(4)yolov3的执行过程

/************************************************************************
函数功能:yolov3执行的过程
输入:yolov3的网络net,原始的图像frame
输出:无
************************************************************************/
void Recognition::runYolov3(Net& net,Mat& frame)
{
	// 从一个帧创建一个4D blob
	cv::Mat blob;

	// 1/255:将图像像素值缩放到0到1的目标范围
	// Scalar(0, 0, 0):我们不在此处执行任何均值减法,因此将[0,0,0]传递给函数的mean参数
	cv::dnn::blobFromImage(frame, blob, 1 / 255.0, cv::Size(inpWidth, inpHeight), cv::Scalar(0, 0, 0), true, false);

	// 设置网络的输入
	net.setInput(blob);

	// 运行向前传递以获得输出层的输出
	std::vector<cv::Mat> outs;
	net.forward(outs, getOutputsNames(net));//forward需要知道它的结束层

	// 以较低的置信度移除边界框
	postprocess(frame, outs);//端到端,输入和输出
	
	std::cout << "succeed!!!" << std::endl;

	cv::Mat detectedFrame;
	frame.convertTo(detectedFrame, CV_8U);
	// 显示detectedFrame
	cv::imshow("detectedFrame", detectedFrame);
	waitKey(1);

}

   以上即是yolov3的关键函数,在main函数中调用时,只需要先进行调用InitYolov3函数进行yolov3的配置,再调用runYolov3进行执行即可。关于代码有个地方需要解释下,我做的这个结果只是可以框出目标来,没有标上其他信息,这个在写绘制函数时可以自行修改,因为我后面主要是想做只检测一类物体,所以没必要标上每个物体的标签。

1.3 结果

如图1所示。

                                                                                             图1 

1.4 总结

     以上即是我上周一整周做的成果,有些理解可能还不够充分。以后会进一步完善,这周主要任务就是进行训练自己的数据集,这个可能在下周会给大家介绍详细的步骤。

未完,待续。。。

猜你喜欢

转载自blog.csdn.net/qq_36417014/article/details/88533803
今日推荐