OpenCV中使用YOLO對象檢測
點擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時間送達(dá)
本文轉(zhuǎn)自:opencv學(xué)堂
OpenCV在3.3.1的版本中開始正式支持Darknet網(wǎng)絡(luò)框架并且支持YOLO1與YOLO2以及YOLO Tiny網(wǎng)絡(luò)模型的導(dǎo)入與使用。YOLO是一種比SSD還要快的對象檢測網(wǎng)絡(luò)模型,算法作者在其論文中說FPS是Fast R-CNN的100倍,基于COCO數(shù)據(jù)集跟SSD網(wǎng)絡(luò)的各項指標(biāo)對比

在最新的OpenCV3.4上我也測試了YOLO3,發(fā)現(xiàn)不支持,因為YOLO3有個新層類型shortcut,OpenCV3.4的Darknet暫時還不支持。這里首先簡單的介紹一下YOLO網(wǎng)絡(luò)基本結(jié)構(gòu),然后在通過代碼演示Darknet支持的YOLO在OpenCV使用。
對象檢測網(wǎng)絡(luò)基本上可以分為兩種,一種稱為兩步法、另外一種稱為一步法,很顯然基于圖像分類加上滑動窗口的方式最早的R-CNN就是兩步法的代表之一,兩步法的前面基本上是一個卷積神經(jīng)網(wǎng)絡(luò),可以是VGGNet或者Inception之類的,然后再加上一個滑動窗口,但是這種方法太慢,所以就有了區(qū)域推薦(RP),預(yù)先推薦一些感興趣的區(qū)域,進(jìn)行預(yù)言,這些方法普遍有一個缺點,計算量比較大,導(dǎo)致性能低下無法實時,而YOLO采樣了一種完全不同的方法,達(dá)到對圖像每個區(qū)域只計算一次(You Only Look Once - YOLO),YOLO把圖像分為13x13的Cell(網(wǎng)格):

每個Cell預(yù)測5個BOX,同時YOLO也會生成一個置信分?jǐn)?shù),告訴每個BOX包含某個對象的可能性是多少,注意置信分?jǐn)?shù)不會直接說明BOX內(nèi)是檢測到何種對象,最終那些得分高的BOX被加粗顯示如下:

對于每個BOX來說,Cell會預(yù)測檢測對象類別,這部分的工作就像是一個分類器一樣,基于VOC數(shù)據(jù)集20中對象檢測,YOLO結(jié)合分?jǐn)?shù)與分類信息對每個BOX給出一個最終可能對象類型的可能性值,如下圖,黃色區(qū)域85%可能性是狗:

因為總數(shù)是13x13的網(wǎng)格,每個網(wǎng)格預(yù)言5個BOX,所以最終有854個BOX,證據(jù)表明絕大多數(shù)的BOX得分會很低,我們只要保留30%BOX即可(取決于你自己的閾值設(shè)置),最終輸出:

從上面可以看出整個圖像只是被計算了一次,真正做到了降低計算量,提高了檢測實時性。上述檢測使用的YOLO的網(wǎng)絡(luò)結(jié)構(gòu)如下:

發(fā)現(xiàn)只有CNN層,沒有FC層,是不是簡單到爆,最后說一下為什么最后一層卷積層深度是125, 因為每個Cell檢測5個BOX,對每個BOX來說,包含如下數(shù)據(jù)
BOX本身信息,x、y、w、h
置信分?jǐn)?shù)
基于VOC數(shù)據(jù)集的20個對象類別
所以對每個BOX來說有25個參數(shù),5個BOX= 5x25=125個參數(shù)。
上面是得到的網(wǎng)絡(luò)模型就是tiny-YOLO網(wǎng)絡(luò)模型,可以在移動端實時對象檢測。這個跟作者在論文中提到的稍微有點差異,論文中作者是輸入圖像為448x448,分為7x7的網(wǎng)格(Cell),結(jié)構(gòu)如下:

最終輸出是每個Cell預(yù)測兩個BOX,做20個分類,它得到最終是
BOX本身信息,x、y、w、h
置信分?jǐn)?shù)
深度 = SS(B5+20), 其中20個表示分類數(shù)目,S表示網(wǎng)絡(luò)分割,B表示BOX個數(shù)。S=7、B=2,最終輸出是77*30
OpenCV在3.3.1版本中開始支持Darknet,可能有人會問,Darknet是什么鬼,它是YOLO的作者自己搞出來的深度學(xué)習(xí)框架,支持C/C++/Python語言,支持YOLOv1、YOLOv2、YOLOv3等網(wǎng)絡(luò)模型訓(xùn)練與使用。但是在OpenCV只是前饋網(wǎng)絡(luò),只支持預(yù)測,不能訓(xùn)練。OpenCV中基于YOLO模型我使用的是tiny-YOLO網(wǎng)絡(luò)模型,支持20中對象檢測。代碼實現(xiàn)步驟如下:1.加載網(wǎng)絡(luò)模型
String modelConfiguration = "D:/vcprojects/images/dnn/yolov2-tiny-voc/yolov2-tiny-voc.cfg";
String modelBinary = "D:/vcprojects/images/dnn/yolov2-tiny-voc/yolov2-tiny-voc.weights";
dnn::Net net = readNetFromDarknet(modelConfiguration, modelBinary);
if (net.empty())
{
printf("Could not load net...\n");
return;
}
2.加載分類信息
vector<string> classNamesVec;
ifstream classNamesFile("D:/vcprojects/images/dnn/yolov2-tiny-voc/voc.names");
if (classNamesFile.is_open())
{
string className = "";
while (std::getline(classNamesFile, className))
classNamesVec.push_back(className);
}
3.加載測試圖像
// 加載圖像
Mat frame = imread("D:/vcprojects/images/fastrcnn.jpg");
Mat inputBlob = blobFromImage(frame, 1 / 255.F, Size(416, 416), Scalar(), true, false);
net.setInput(inputBlob, "data");
4.檢測
// 檢測
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));
5.顯示與運行效果
// 輸出結(jié)果
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);
運行結(jié)果如下:

文章中提到模型可以到本人的GITHUB上下載即可,地址如下
https://github.com/gloomyfish1998/opencv_tutorial
交流群
歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動駕駛、計算攝影、檢測、分割、識別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會逐漸細(xì)分),請掃描下面微信號加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進(jìn)入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~

