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

原文链接: https://blog.csdn.net/baolinq/article/details/78582309

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

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

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

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

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

【转载】目标检测之二(传统算法和深度学习的源码学习)_第1张图片

上图是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,每一种提高一点点,组合起来就很强大了

【转载】目标检测之二(传统算法和深度学习的源码学习)_第2张图片

    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窗口就可以运行了。输入图片路径即可进行检测。

运行截图:

【转载】目标检测之二(传统算法和深度学习的源码学习)_第3张图片

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

运行结果:

 【转载】目标检测之二(传统算法和深度学习的源码学习)_第4张图片

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

 【转载】目标检测之二(传统算法和深度学习的源码学习)_第5张图片

这样测试很不方便,每次只能测试一张图像,我想测试序列图像或者测试视频该怎么办呢?有两种方法,一种修改源代码,改写输入文件的类型;第二种更简单使用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等深度学习框架。我觉得这个功能真的很赞,以后训练好的模型,移植就非常方便了,用来做一些日常测试也是很不错的。

【转载】目标检测之二(传统算法和深度学习的源码学习)_第6张图片

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

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


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

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

 


   
   
   
   
  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. using namespace cv;
  7. using namespace cv::dnn;
  8. #include
  9. #include
  10. #include
  11. #include
  12. using namespace std;
  13. const size_t network_width = 416;
  14. const size_t network_height = 416;
  15. const char* about = "This sample uses You only look once (YOLO)-Detector "
  16. "(https://arxiv.org/abs/1612.08242)"
  17. "to detect objects on image\n"; // TODO: link
  18. const char* params
  19. = "{ help | false | print usage }"
  20. "{ cfg | yolo-voc.cfg | model configuration }" //写入训练好的网络参数文件cfg格式
  21. "{ model | yolo-voc.weights | model weights }" //写入训练好的模型的权重文件
  22. "{ image | | image for detection }" //测试的图片名,我没有使用
  23. "{ min_confidence | 0.24 | min confidence }"; //信心度阈值,低于该阈值的不输出,判断为没有检测到
  24. vector color = { Scalar( 0, 205, 150),Scalar( 255, 0, 0),Scalar( 0, 0, 255),Scalar( 200, 150, 0) };
  25. int main( int argc, char** argv)
  26. {
  27. cv::CommandLineParser parser(argc, argv, params); //读入params参数文件
  28. if (parser.get< bool>( "help"))
  29. {
  30. std::cout << about << std::endl;
  31. parser.printMessage();
  32. return 0;
  33. }
  34. //String modelConfiguration = "yolo.cfg"; // parser.get("cfg");
  35. //String modelBinary = "yolo.weights"; //parser.get("model");
  36. String modelConfiguration = parser.get( "cfg");
  37. String modelBinary =parser.get( "model");
  38. //! [Initialize network]
  39. dnn::Net net = readNetFromDarknet(modelConfiguration, modelBinary); //读取网络模型和参数,初始化
  40. //! [Initialize network]
  41. if (net.empty())
  42. {
  43. cerr << "Can't load network by using the following files: " << endl;
  44. cerr << "cfg-file: " << modelConfiguration << endl;
  45. cerr << "weights-file: " << modelBinary << endl;
  46. cerr << "Models can be downloaded here:" << endl;
  47. cerr << "https://pjreddie.com/darknet/yolo/" << endl;
  48. exit( -1);
  49. }
  50. // String imgpath =parser.get("image")
  51. //cv::Mat frame = cv::imread(imgpath);
  52. // 检测目标种类的名称在voc.txt,读入保存到tag_names向量中
  53. vector tag_names;
  54. ifstream fin;
  55. fin.open( "voc.txt");
  56. string buf;
  57. while(fin && getline(fin, buf)) {
  58. tag_names.push_back(buf);
  59. }
  60. fin.close();
  61. //打开默认摄像头,用于检测摄像头的图像
  62. //VideoCapture cap(0);
  63. //if (!cap.isOpened())
  64. // return -1;
  65. //图片的存放地址(相对路径),在当前目录的test_image文件夹里面
  66. String imgpath = "test_image/";
  67. vector imgvec;
  68. cv::glob(imgpath, imgvec); //保存文件名到imgvec向量中,便于批量处理
  69. Mat frame;
  70. bool stop = false;
  71. //while(!stop){
  72. int64 start = getTickCount(); //计算运行时间
  73. for ( int imgnum = 0; (imgnum < imgvec.size())&&(!stop); ++imgnum) {
  74. //if (!cap.read(frame))
  75. // break;
  76. frame = imread(imgvec[imgnum]);
  77. //imshow("原图",frame);
  78. //waitKey(5);
  79. //! [Resizing without keeping aspect ratio]
  80. cv::Mat resized;
  81. cv::resize(frame, resized, cv::Size(network_width, network_height));
  82. //! [Resizing without keeping aspect ratio]
  83. //! [Prepare blob]
  84. Mat inputBlob = blobFromImage(resized, 1 / 255.F); //Convert Mat to batch of images
  85. //! [Prepare blob]
  86. //! [Set input blob]输入归一化的图像
  87. net.setInput(inputBlob, "data"); //set the network input
  88. //! [Set input blob]
  89. //! [Make forward pass]前向传播进行计算
  90. cv::Mat detectionMat = net.forward( "detection_out"); //compute output
  91. //! [Make forward pass]
  92. float confidenceThreshold = parser.get< float>( "min_confidence");
  93. for ( int i = 0; i < detectionMat.rows; i++)
  94. {
  95. const int probability_index = 5; //前5个参数为每一个bounding box包含5个值:x,y,w,h和confidence(置信度),置信值代表box包含一个目标的置信度
  96. const int probability_size = detectionMat.cols - probability_index; //后20个为voc数据集中有20个类别,每个类的概率
  97. float *prob_array_ptr = &detectionMat.at< float>(i, probability_index);
  98. size_t objectClass = std::max_element(prob_array_ptr, prob_array_ptr + probability_size) - prob_array_ptr; //选择一个概率最大的作为检测结果
  99. float confidence = detectionMat.at< float>(i, ( int)objectClass + probability_index);
  100. if (confidence > confidenceThreshold) //大于信心度阈值才输出
  101. {
  102. float x = detectionMat.at< float>(i, 0); //检测的矩形框的计算,本来是中心点和长宽,这都是被归一化后的
  103. float y = detectionMat.at< float>(i, 1);
  104. float width = detectionMat.at< float>(i, 2);
  105. float height = detectionMat.at< float>(i, 3);
  106. float xLeftBottom = (x - width / 2) * frame.cols; //得到图像上的矩形框的左上角和右下角坐标,要反归一化
  107. float yLeftBottom = (y - height / 2) * frame.rows;
  108. float xRightTop = (x + width / 2) * frame.cols;
  109. float yRightTop = (y + height / 2) * frame.rows;
  110. //std::cout << "Class: " << tag_names[objectClass] << std::endl;
  111. //std::cout << "Confidence: " << confidence << std::endl;
  112. //std::cout << " " << xLeftBottom
  113. // << " " << yLeftBottom
  114. // << " " << xRightTop
  115. // << " " << yRightTop << std::endl;
  116. stringstream ss;
  117. ss << confidence;
  118. string str_text = tag_names[objectClass] + ": "+ss.str(); //输出类别和信心度
  119. Rect object(( int)xLeftBottom, ( int)yLeftBottom,
  120. ( int)(xRightTop - xLeftBottom),
  121. ( int)(yRightTop - yLeftBottom));
  122. rectangle(frame, object, color[objectClass % 4], 2); //显示矩形框,我给了一个颜色数组,避免都是一个色的不好看
  123. int baseline = 0;
  124. Size labelSize = getTextSize(str_text, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline); //设置字体
  125. //int x = xLeftBottom - labelSize.width >= 0 ? xLeftBottom - labelSize.width : xLeftBottom + labelSize.width;
  126. int rect_y = yLeftBottom - labelSize.height >= 0 ? yLeftBottom - labelSize.height : yLeftBottom ; //避免在边界时,显示的内容看不到
  127. rectangle(frame, Rect(Point(xLeftBottom, rect_y),Size(labelSize.width,labelSize.height+baseline)), Scalar( 0, 255, 0),CV_FILLED);
  128. //输出显示内容
  129. cv::putText(frame, str_text, Point(xLeftBottom, rect_y+labelSize.height), FONT_HERSHEY_SIMPLEX, 0.5, color[objectClass% 4]);
  130. }
  131. }
  132. //cout << "用时: "<<1.0*(getTickCount() - start) / getTickFrequency() << endl;
  133. imshow( "detections", frame); //显示图像
  134. //waitKey();
  135. if (waitKey( 5) == 'q')
  136. stop = true;
  137. }
  138. //cout << "用时: " << 1.0*(getTickCount() - start) / getTickFrequency() << endl;//计算所有图片共运行的时间
  139. return 0;
  140. } // main


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

【转载】目标检测之二(传统算法和深度学习的源码学习)_第7张图片

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

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

 【转载】目标检测之二(传统算法和深度学习的源码学习)_第8张图片

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

【转载】目标检测之二(传统算法和深度学习的源码学习)_第9张图片

 

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

 

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

参考文献

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

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


你可能感兴趣的:(【转载】目标检测之二(传统算法和深度学习的源码学习))