OpenCV+yolov2-tiny实现目标检测(C++)
OpenCV+yolov2-tiny實現目標檢測(C++)
? ? 目標檢測算法主要分為兩類:一類是基于Region Proposal(候選區域)的算法,如R-CNN系算法(R-CNN,Fast R-CNN, Faster R-CNN),它們是two-stage(兩步法)的,需要先使用Selective search或者CNN網絡(RPN)產生Region Proposal,然后再在Region Proposal上做分類與回歸。而另一類是Yolo,SSD這類one-stage算法(一步法),其僅僅使用一個CNN網絡直接預測不同目標的類別與位置。第一類方法是準確度高一些,但是速度慢,而第二類算法是速度快,但是準確性要低一些。
? ? YOLO是一種比SSD還要快的目標檢測網絡模型,作者在其論文中說FPS是Fast R-CNN的100倍,這里首先簡單的介紹一下YOLO網絡基本結構,然后通過OpenCV C++調用Darknet的,實現目標檢測。OpenCV在3.3.1的版本中開始正式支持Darknet網絡框架并且支持YOLO1與YOLO2以及YOLO Tiny網絡模型的導入與使用。后面測試,OpenCV3.4.2也支持YOLO3。另外,OpenCV dnn模塊目前支持Caffe、TensorFlow、Torch、PyTorch等深度學習框架,關于《OpenCV調用TensorFlow預訓練模型》可參考鄙人的另一份博客:https://blog.csdn.net/guyuealian/article/details/80570120
? ? 關于《OpenCV+yolov3實現目標檢測(C++,Python)》請參考我的另一篇博客:https://blog.csdn.net/guyuealian/article/details/84098461
? ? 本博客源碼都放在Github上:https://github.com/PanJinquan/opencv-learning-tutorials/tree/master/dnn_tutorial,麻煩給個“Star”哈
參考資料:
《Deep Learning based Object Detection using YOLOv3 with OpenCV ( Python / C++ )》:
《YOLOv3 + OpenCV 實現目標檢測(Python / C ++)》:https://blog.csdn.net/haoqimao_hard/article/details/82081285
?Github參考源碼:https://github.com/spmallick/learnopencv/tree/master/ObjectDetection-YOLO
?darknt yolo官網:https://pjreddie.com/darknet/yolo/
目錄
目錄
OpenCV+yolov2-tiny實現目標檢測(C++)
1、YOLOv1網絡
(1)YOLOv1網絡結構
(2)YOLOv1損失函數
(3)tiny-YOLO網絡
2、OpenCV使用YOLO實現目標檢測
(1)代碼實現過程
(2)完整的代碼:??
3、YOLO的缺點
4、參考資料:
1、YOLOv1網絡
? ?YOLO全稱YOU ONLY ?Look Once表達的意思只要看一眼就能感知識別的物體了。YOLO的核心思想:就是利用整張圖作為網絡的輸入,直接在輸出層回歸物體的bounding box位置和所屬的類別。
(1)YOLOv1網絡結構
? ?實現過程:首先把輸入圖像448×448劃分成S×S的格子,然后對每個格子都預測B個Bounding Boxes(物體框),每個Bounding Boxes都包含5個預測值:x,y,w,h和confidence(置信度),另外每個格子都預測C個類別的概率分數,但是這個概率分數和物體框的confidence置信度分數是不相關的。這樣,每個單元格需要預測(B×5+C)個值。如果將輸入圖片劃分為S×S個網格,那么最終預測值為S×S×(B×5+C)大小的張量。整個模型的預測值結構如下圖所示。
?
- 1、將一幅圖像分成SxS個網格(grid cell),如果某個object的中心 落在這個網格中,則這個網格就負責預測這個object。
- 2、每個網格要預測B個bounding box,每個bounding box除了要回歸自身的位置(x,y,w,h)之外,還要附帶預測一個confidence值(每個bounding box要預測(x, y, w, h)和confidence共5個值)。這個confidence代表了所預測的box中含有object的置信度和這個box預測的有多準兩重信息,其值是這樣計算的:
confidence=
說明:如果有object落在一個grid cell里,第一項取1,否則取0。 第二項是預測的bounding box和實際的ground truth之間的IOU值。因此,confidence就是預測的bounding box和ground truth box的IOU值。?
- 3、每個網格還要預測一個類別概率信息,記為C類。這樣所有網格的類別概率就構成了class probability map
注意:class信息是針對每個網格的,confidence信息是針對每個bounding box的。
?
? ? ? 舉個栗子:在PASCAL VOC中,圖像輸入為448x448,取S=7(將圖像成7x7個網格(grid cell)),B=2(每個網格要預測2個bounding box),一共有C=20個類別(PASCAL VOC共有20類別)。則輸出就是S x S x (5*B+C)=7x7x30的一個張量tensor。整個網絡結構如下圖所示:
Yolo采用卷積網絡來提取特征,然后使用全連接層來得到預測值。網絡結構參考GooLeNet模型,包含24個卷積層和2個全連接層,如圖所示。對于卷積層,主要使用1x1卷積來做channle reduction,然后緊跟3x3卷積。對于卷積層和全連接層,采用Leaky ReLU激活函數:max(x,0.1x)。但是最后一層卻采用線性激活函數。除了上面這個結構,文章還提出了一個輕量級版本Fast Yolo,其僅使用9個卷積層,并且卷積層中使用更少的卷積核。
- 4、在test的時候,每個網格預測的class信息和bounding box預測的confidence信息相乘,就得到每個bounding box的class-specific confidence score:
等式左邊第一項就是每個網格預測的類別信息,第二三項就是每個bounding box預測的confidence。這個乘積即encode了預測的box屬于某一類的概率,也有該box準確度的信息。
- 5、得到每個box的class-specific confidence score以后,設置閾值,濾掉得分低的boxes,對保留的boxes進行NMS處理,就得到最終的檢測結果。
這部分的講解可以參考資料:https://blog.csdn.net/tangwei2014/article/details/50915317
可以看到網絡的最后輸出為7×7×30大小的張量。這和前面的討論是一致的。這個張量所代表的具體含義下圖所示。對于每一個單元格,前20個元素是類別概率值,然后2個元素是邊界框置信度,兩者相乘可以得到類別置信度,最后8個元素是邊界框的(x,y,w,h)(PS:預測2個BB,所以有2個置信度和8個位置邊界元素)。大家可能會感到奇怪,對于邊界框為什么把置信度c和(x,y,w,h)都分開排列,而不是按照(x,y,w,h,c)這樣排列,其實純粹是為了計算方便,因為實際上這30個元素都是對應一個單元格,其排列是可以任意的。但是分離排布,可以方便地提取每一個部分。這里來解釋一下,首先網絡的預測值是一個二維張量P,其shape為[batch,7×7×30]。采用切片,那么就是類別概率部分,而是置信度部分,最后剩余部分是邊界框的預測結果。這樣,提取每個部分是非常方便的,這會方面后面的訓練及預測時的計算。
(2)YOLOv1損失函數
YOLOv1算法將目標檢測看成回歸問題,所以采用的是均方差損失函數。YOLOv1的損失函數可簡單看成是坐標誤差+置信度誤差+類別誤差的組合之和。但是對不同的部分采用了不同的權重值。
坐標誤差(定位誤差),即邊界框坐標預測誤差,采用較大的權重λcoord=5。采用均方誤差,其同等對待大小不同的邊界框,但是實際上較小的邊界框的坐標誤差應該要比較大的邊界框要更敏感。為了保證這一點,將網絡的邊界框的寬與高預測改為對其平方根的預測,即預測值變為。
置信度誤差:對于不包含目標的邊界框的confidence誤差,采用較小的權重值λnoobj=0.5,而含有目標的邊界框的置信度confidence誤差權重值為1。
類別誤差:權重為1
其中:
第一項是邊界框中心坐標的誤差項,指的是第i個單元格存在目標,且該單元格中的第j個邊界框負責預測該目標。
第二項是邊界框的高與寬的誤差項。
第三項是包含目標的邊界框的置信度誤差項。
第四項是不包含目標的邊界框的置信度誤差項。
最后一項是包含目標的單元格的分類誤差項,指的是第ii個單元格存在目標。
(3)tiny-YOLO網絡
? ? tiny-YOLO網絡模型是更加輕量級的微型YOLO網絡,速度非常快,可以在移動端實時目標檢測,但檢測效果不算好。tiny-YOLO網絡的網絡結構如下:
與上面不同的是,tiny-YOLO網絡模型每個Cell需要檢測5個BOX,對每個BOX來說,包含如下數據
-
4個位置信息x、y、w、h
-
1個置信分數
-
基于VOC數據集的20個目標類別
? ? 所以對每個BOX來說,每個BOX有5+20=25個參數,5個BOX共有 5x25=125個參數。所以,tiny-YOLO網絡模型最后一層卷積層深度是125。
2、OpenCV使用YOLO實現目標檢測
? ?OpenCV使用YOLO實現目標檢測的代碼如下,注意?OpenCV只是前饋網絡,只支持預測,不能訓練。
(1)代碼實現過程
? ? 這里提供圖片測試image_detection()和視頻測試?video_detection()測試方法:
/** * @brief YOLO模型視頻測試. * @param cfgFile path to the .cfg file with text description of the network architecture. * @param weight path to the .weights file with learned network. * @param clsNames 種類標簽文件 * @param video_path 視頻文件 * @returns void */ void video_detection(string cfgFile, string weight, string clsNames, string video_path); /** * @brief YOLO模型圖像測試. * @param cfgFile path to the .cfg file with text description of the network architecture. * @param weight path to the .weights file with learned network. * @param clsNames 種類標簽文件 * @param image_path 圖像文件 * @returns void */ void image_detection(string cfgFile, string weight, string clsNames, string image_path);? ?1、需要調用OpenCV DNN模塊,所以頭文件必須添加:opencv2/dnn.hpp,頭文件和命名空間如下:
#include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp>#include <fstream> #include <iostream> #include <algorithm> #include <cstdlib> using namespace std; using namespace cv; using namespace cv::dnn;? ?2、Main函數以及全局的變量:如confidenceThreshold置信度的閾值,項目根目錄,這個參數根據自己的項目改改就好
float confidenceThreshold = 0.25; string pro_dir = "E:/opencv-learning-tutorials/"; //項目根目錄int main(int argc, char** argv) {String cfgFile = pro_dir + "data/models/yolov2-tiny-voc/yolov2-tiny-voc.cfg";String weight = pro_dir + "data/models/yolov2-tiny-voc/yolov2-tiny-voc.weights";string clsNames = pro_dir + "data/models/yolov2-tiny-voc/voc.names";string image_path = pro_dir + "data/images/1.jpg";image_detection(cfgFile, weight, clsNames, image_path);//圖片測試//string video_path = pro_dir + "data/images/lane.avi";//video_detection(cfgFile, weight, clsNames,video_path);//視頻測試 }? ? 3、image_detection函數加載網絡模型,需要調用DNN的readNetFromDarknet函數
// 加載網絡模型dnn::Net net = readNetFromDarknet(cfgFile, weight);if (net.empty()){printf("Could not load net...\n");return;}?? ? 4、加載分類信息
// 加載分類信息vector<string> classNamesVec;ifstream classNamesFile(clsNames);if (classNamesFile.is_open()){string className = "";while (std::getline(classNamesFile, className))classNamesVec.push_back(className);}? ? 5、加載被檢測的圖像
// 加載圖像Mat frame = imread(image_path);Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false);net.setInput(inputBlob, "data");? ? 6、進行目標檢測:
// 進行目標檢測Mat detectionMat = net.forward("detection_out");vector<double> layersTimings;double freq = getTickFrequency() / 1000;double time = net.getPerfProfile(layersTimings) / freq;ostringstream ss;ss << "detection time: " << time << " ms";putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));? ?7、檢測結果并顯示:
// 輸出結果for (int i = 0; i < detectionMat.rows; i++){const int probability_index = 5;const int probability_size = detectionMat.cols - probability_index;float *prob_array_ptr = &detectionMat.at<float>(i, probability_index);size_t objectClass = 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);int xLeftBottom = static_cast<int>((x - width / 2) * frame.cols);int yLeftBottom = static_cast<int>((y - height / 2) * frame.rows);int xRightTop = static_cast<int>((x + width / 2) * frame.cols);int yRightTop = static_cast<int>((y + height / 2) * frame.rows);Rect object(xLeftBottom, yLeftBottom,xRightTop - xLeftBottom,yRightTop - yLeftBottom);rectangle(frame, object, Scalar(0, 0, 255), 2, 8);if (objectClass < classNamesVec.size()){ss.str("");ss << confidence;String conf(ss.str());String label = String(classNamesVec[objectClass]) + ": " + conf;int baseLine = 0;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);rectangle(frame, Rect(Point(xLeftBottom, yLeftBottom),Size(labelSize.width, labelSize.height + baseLine)),Scalar(255, 255, 255), CV_FILLED);putText(frame, label, Point(xLeftBottom, yLeftBottom + labelSize.height),FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));}}}imshow("YOLO-Detections", frame);waitKey(0);return;(2)完整的代碼:??
? ?這里提供圖片測試image_detection()和視頻測試?video_detection()的方法,完整 是項目代碼如下:
#include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp>#include <fstream> #include <iostream> #include <algorithm> #include <cstdlib> using namespace std; using namespace cv; using namespace cv::dnn;float confidenceThreshold = 0.25; string pro_dir = "E:/opencv-learning-tutorials/"; //項目根目錄/** * @brief YOLO模型視頻測試. * @param cfgFile path to the .cfg file with text description of the network architecture. * @param weight path to the .weights file with learned network. * @param clsNames 種類標簽文件 * @param video_path 視頻文件 * @returns void */ void video_detection(string cfgFile, string weight, string clsNames, string video_path); /** * @brief YOLO模型圖像測試. * @param cfgFile path to the .cfg file with text description of the network architecture. * @param weight path to the .weights file with learned network. * @param clsNames 種類標簽文件 * @param image_path 圖像文件 * @returns void */ void image_detection(string cfgFile, string weight, string clsNames, string image_path);int main(int argc, char** argv) {String cfgFile = pro_dir + "data/models/yolov2-tiny-voc/yolov2-tiny-voc.cfg";String weight = pro_dir + "data/models/yolov2-tiny-voc/yolov2-tiny-voc.weights";string clsNames = pro_dir + "data/models/yolov2-tiny-voc/voc.names";string image_path = pro_dir + "data/images/1.jpg";image_detection(cfgFile, weight, clsNames, image_path);//圖片測試string video_path = pro_dir + "data/images/lane.avi";video_detection(cfgFile, weight, clsNames,video_path);//視頻測試 }void video_detection(string cfgFile, string weight,string clsNames, string video_path) {dnn::Net net = readNetFromDarknet(cfgFile, weight);if (net.empty()){printf("Could not load net...\n");return;}vector<string> classNamesVec;ifstream classNamesFile(clsNames);if (classNamesFile.is_open()){string className = "";while (std::getline(classNamesFile, className))classNamesVec.push_back(className);}// VideoCapture capture(0); VideoCapture capture;capture.open(video_path);if (!capture.isOpened()) {printf("could not open the camera...\n");return;}Mat frame;while (capture.read(frame)){if (frame.empty())if (frame.channels() == 4)cvtColor(frame, frame, COLOR_BGRA2BGR);Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false);net.setInput(inputBlob, "data");Mat detectionMat = net.forward("detection_out");vector<double> layersTimings;double freq = getTickFrequency() / 1000;double time = net.getPerfProfile(layersTimings) / freq;ostringstream ss;ss << "FPS: " << 1000 / time << " ; time: " << time << " ms";putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));for (int i = 0; i < detectionMat.rows; i++){const int probability_index = 5;const int probability_size = detectionMat.cols - probability_index;float *prob_array_ptr = &detectionMat.at<float>(i, probability_index);size_t objectClass = 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);int xLeftBottom = static_cast<int>((x - width / 2) * frame.cols);int yLeftBottom = static_cast<int>((y - height / 2) * frame.rows);int xRightTop = static_cast<int>((x + width / 2) * frame.cols);int yRightTop = static_cast<int>((y + height / 2) * frame.rows);Rect object(xLeftBottom, yLeftBottom,xRightTop - xLeftBottom,yRightTop - yLeftBottom);rectangle(frame, object, Scalar(0, 255, 0));if (objectClass < classNamesVec.size()){ss.str("");ss << confidence;String conf(ss.str());String label = String(classNamesVec[objectClass]) + ": " + conf;int baseLine = 0;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);rectangle(frame, Rect(Point(xLeftBottom, yLeftBottom),Size(labelSize.width, labelSize.height + baseLine)),Scalar(255, 255, 255), CV_FILLED);putText(frame, label, Point(xLeftBottom, yLeftBottom + labelSize.height),FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));}}}imshow("YOLOv3: Detections", frame);if (waitKey(1) >= 0) break;} }void image_detection(string cfgFile, string weight, string clsNames, string image_path) {// 加載網絡模型dnn::Net net = readNetFromDarknet(cfgFile, weight);if (net.empty()){printf("Could not load net...\n");return;}// 加載分類信息vector<string> classNamesVec;ifstream classNamesFile(clsNames);if (classNamesFile.is_open()){string className = "";while (std::getline(classNamesFile, className))classNamesVec.push_back(className);}// 加載圖像Mat frame = imread(image_path);Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false);net.setInput(inputBlob, "data");// 進行目標檢測Mat detectionMat = net.forward("detection_out");vector<double> layersTimings;double freq = getTickFrequency() / 1000;double time = net.getPerfProfile(layersTimings) / freq;ostringstream ss;ss << "detection time: " << time << " ms";putText(frame, ss.str(), Point(20, 20), 0, 0.5, Scalar(0, 0, 255));// 輸出結果for (int i = 0; i < detectionMat.rows; i++){const int probability_index = 5;const int probability_size = detectionMat.cols - probability_index;float *prob_array_ptr = &detectionMat.at<float>(i, probability_index);size_t objectClass = 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);int xLeftBottom = static_cast<int>((x - width / 2) * frame.cols);int yLeftBottom = static_cast<int>((y - height / 2) * frame.rows);int xRightTop = static_cast<int>((x + width / 2) * frame.cols);int yRightTop = static_cast<int>((y + height / 2) * frame.rows);Rect object(xLeftBottom, yLeftBottom,xRightTop - xLeftBottom,yRightTop - yLeftBottom);rectangle(frame, object, Scalar(0, 0, 255), 2, 8);if (objectClass < classNamesVec.size()){ss.str("");ss << confidence;String conf(ss.str());String label = String(classNamesVec[objectClass]) + ": " + conf;int baseLine = 0;Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);rectangle(frame, Rect(Point(xLeftBottom, yLeftBottom),Size(labelSize.width, labelSize.height + baseLine)),Scalar(255, 255, 255), CV_FILLED);putText(frame, label, Point(xLeftBottom, yLeftBottom + labelSize.height),FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 0));}}}imshow("YOLO-Detections", frame);waitKey(0);return; }圖片物體檢測:
?
?
3、YOLO的缺點
- YOLO對相互靠的很近的物體,還有很小的群體 檢測效果不好,這是因為一個網格中只預測了兩個框,并且只屬于一類。
- 對測試圖像中,同一類物體出現的新的不常見的長寬比和其他情況是。泛化能力偏弱。
- 由于損失函數的問題,定位誤差是影響檢測效果的主要原因。尤其是大小物體的處理上,還有待加強。
4、參考資料:
[1].《論文閱讀筆記:You Only Look Once: Unified, Real-Time Object Detection》https://blog.csdn.net/tangwei2014/article/details/50915317
[2].?https://blog.csdn.net/xiaohu2022/article/details/79211732?
[3].?https://blog.csdn.net/u014380165/article/details/72616238?
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的OpenCV+yolov2-tiny实现目标检测(C++)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bilateral filter双边滤波
- 下一篇: TensorFlow Lite学习笔记