導讀:通過本教程,我們將徹底理解一個重要的概念:目標檢測中的常用方法“Selective Search”。文末也會給出使用C++或者Python的Opencv代碼。
目標檢測 vs 目標識別
目標識別解決了是什麼的問題,目標檢測解決了在哪裏的問題。
所有目標檢測算法的核心是一種目標識別算法。假設我們訓練一個可以從區域中(pathes)識別狗的目標識別模型,這個模型可以告訴我們該patch中有無狗,它並不會給出狗的座標。
爲了對目標進行定位,我們必須選擇圖像的子區域(pathes),然後將目標識別算法應用於這些圖像塊。目標的位置是由目標識別算法返回的類概率高的圖像子區域的位置給出的。
生成較小子區域(pathes)的最直接的方法稱爲滑動窗口方法。然而,滑動窗口方法有幾個侷限性。這些侷限性被一類稱爲“區域建議”算法的算法所克服。選擇性搜索是最受歡迎的區域建議算法。
滑動窗口算法/Sliding Window Algorithm
在滑動窗口方法中,我們在圖像上滑動一個框或窗口來選擇一個區域,並使用目標識別模型對窗口覆蓋的每個圖像塊進行分類。這是一個窮盡搜索整個圖像的對象。我們不僅需要搜索圖像中所有可能的位置,還得在不同的尺度上搜索。這是因爲物體識別模型通常是在特定的尺度(或範圍)上進行訓練的。這將對成千上萬的圖像塊進行分類。
問題並沒有到此爲止。滑動窗口方法對於固定的縱橫比對象,例如人臉或行人是很好的。圖像是三維物體的二維投影,對象特徵,如縱橫比和形狀根據所拍攝圖像的角度而顯著變化。滑動窗口的方法因爲需要搜索多個縱橫比,因此變得非常昂貴。
區域建議的算法/Region Proposal Algorithms
我們目前已經討論過的問題可以用區域建議算法來解決。這些方法將圖像作爲輸入和輸出邊界框,對應於圖像中最可能成爲對象的所有子區域。這些區域建議可能是嘈雜的,重疊的,可能不完全包含對象,但在這些區域建議中,將有一個與圖像中的實際對象非常接近的建議。然後,我們可以使用對象識別模型對這些提議進行分類。具有高概率分數的區域建議是對象的位置。
區域建議算法利用分割的方法識別圖像中的前景物體。在分割時我們認爲相鄰的區域是彼此相似,基於一些標準,如顏色、紋理等不同的滑動窗口的方法,我們正在尋找所有的像素的位置和在所有尺度的對象,區域算法工作的分組像素到一個較小的段數。因此,提議的最終數量比滑動窗口方法少很多倍。這減少了我們必須分類的圖像塊的數量。這些生成的區域建議具有不同的尺度和長寬比。
目前提出了幾種區域建議方法,如
1. Objectness
2. Constrained Parametric Min-Cuts for Automatic Object Segmentation
3. Category Independent Object Proposals
4. Randomized Prim
5. Selective Search
在所有這些區域建議方法中,選擇性搜索是最常用的,因爲它速度快,召回率高。
目標識別的選擇性搜索
什麼是選擇性搜索?
選擇性搜索是一種用於目標檢測的區域推薦算法。它的設計速度快,召回率高。它是根據顏色、紋理、大小和形狀的兼容性,計算相似區域的層次分組。
選擇性搜索開始了基於利用圖由Felzenszwalb和Huttenlocher分割方法的像素的圖像分割。該算法的輸出如下所示。右邊的圖像包含用純色表示的分段區域。
我們可以在這個圖像中使用分段部分作爲區域建議嗎?答案是否定的,有兩個原因可以解釋爲什麼我們不能做到這一點:
1. 原始圖像中的大多數實際對象包含2個或多個分段部分。
2. 用這種方法不能爲被遮擋的物體提出建議,例如杯子覆蓋的盤子或裝滿咖啡的杯子。
如果我們試圖通過進一步合併相似的相鄰區域來解決第一個問題,我們將得到一個覆蓋兩個對象的分段區域。完美的分割不是我們的目標。我們只是想預測許多區域的建議,其中一些建議應該與實際對象有很高的重疊。選擇性搜索使用oversegments Felzenszwalb Huttenlocher的方法作爲初始種子。過分割圖像看起來像這樣:
選擇搜索算法將這些oversegments作爲初始輸入並執行以下步驟:
- 將分段部分對應的所有邊界框添加到區域建議列表中
- 基於相似性的羣鄰近段
轉到步驟1
在每次迭代中,都會生成較大的段,並添加到區域建議列表中。因此,我們用自下而上的方法從更小的部分到更大的部分創建區域建議。這就是我們所說的“層次”的基礎上計算分割使用Huttenlocher的oversegments。
該圖像顯示了分層分割過程的初始、中間和最後一個步驟。其中文章中涉及的各種相似性參考原文中:
http://www.learnopencv.com/selective-search-for-object-detection-cpp-python/
結果
在OpenCV實現了選擇性搜索區域建議的遞減順序排列對象名。爲清楚起見,我們與頂級200-250盒畫在圖像共享成果。一般在1000-1200建議是好的足以讓所有的正確區域的建議。
選擇性搜索代碼
讓我們來看看如何在opencv中實現基於選擇性搜索的分割。
Selective Search: C++
#include "opencv2/ximgproc/segmentation.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <ctime>
using namespace cv;
using namespace cv::ximgproc::segmentation;
static void help() {
std::cout << std::endl <<
"Usage:" << std::endl <<
"./ssearch input_image (f|q)" << std::endl <<
"f=fast, q=quality" << std::endl <<
"Use l to display less rects, m to display more rects, q to quit" << std::endl;
}
int main(int argc, char** argv) {
// If image path and f/q is not passed as command
// line arguments, quit and display help message
if (argc < 3) {
help();
return -1;
}
// speed-up using multithreads
setUseOptimized(true);
setNumThreads(4);
// read image
Mat im = imread(argv[1]);
// resize image
int newHeight = 200;
int newWidth = im.cols*newHeight/im.rows;
resize(im, im, Size(newWidth, newHeight));
// create Selective Search Segmentation Object using default parameters
Ptr<SelectiveSearchSegmentation> ss = createSelectiveSearchSegmentation();
// set input image on which we will run segmentation
ss->setBaseImage(im);
// Switch to fast but low recall Selective Search method
if (argv[2][0] == 'f') {
ss->switchToSelectiveSearchFast();
}
// Switch to high recall but slow Selective Search method
else if (argv[2][0] == 'q') {
ss->switchToSelectiveSearchQuality();
}
// if argument is neither f nor q print help message
else {
help();
return -2;
}
// run selective search segmentation on input image
std::vector<Rect> rects;
ss->process(rects);
std::cout << "Total Number of Region Proposals: " << rects.size() << std::endl;
// number of region proposals to show
int numShowRects = 100;
// increment to increase/decrease total number
// of reason proposals to be shown
int increment = 50;
while(1) {
// create a copy of original image
Mat imOut = im.clone();
// itereate over all the region proposals
for(int i = 0; i < rects.size(); i++) {
if (i < numShowRects) {
rectangle(imOut, rects[i], Scalar(0, 255, 0));
}
else {
break;
}
}
// show output
imshow("Output", imOut);
// record key press
int k = waitKey();
// m is pressed
if (k == 109) {
// increase total number of rectangles to show by increment
numShowRects += increment;
}
// l is pressed
else if (k == 108 && numShowRects > increment) {
// decrease total number of rectangles to show by increment
numShowRects -= increment;
}
// q is pressed
else if (k == 113) {
break;
}
}
return 0;
}
Selective Search: Python
#!/usr/bin/env python
'''
Usage:
./ssearch.py input_image (f|q)
f=fast, q=quality
Use "l" to display less rects, 'm' to display more rects, "q" to quit.
'''
import sys
import cv2
if __name__ == '__main__':
# If image path and f/q is not passed as command
# line arguments, quit and display help message
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
# speed-up using multithreads
cv2.setUseOptimized(True);
cv2.setNumThreads(4);
# read image
im = cv2.imread(sys.argv[1])
# resize image
newHeight = 200
newWidth = int(im.shape[1]*200/im.shape[0])
im = cv2.resize(im, (newWidth, newHeight))
# create Selective Search Segmentation Object using default parameters
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
# set input image on which we will run segmentation
ss.setBaseImage(im)
# Switch to fast but low recall Selective Search method
if (sys.argv[2] == 'f'):
ss.switchToSelectiveSearchFast()
# Switch to high recall but slow Selective Search method
elif (sys.argv[2] == 'q'):
ss.switchToSelectiveSearchQuality()
# if argument is neither f nor q print help message
else:
print(__doc__)
sys.exit(1)
# run selective search segmentation on input image
rects = ss.process()
print('Total Number of Region Proposals: {}'.format(len(rects)))
# number of region proposals to show
numShowRects = 100
# increment to increase/decrease total number
# of reason proposals to be shown
increment = 50
while True:
# create a copy of original image
imOut = im.copy()
# itereate over all the region proposals
for i, rect in enumerate(rects):
# draw rectangle for region proposal till numShowRects
if (i < numShowRects):
x, y, w, h = rect
cv2.rectangle(imOut, (x, y), (x+w, y+h), (0, 255, 0), 1, cv2.LINE_AA)
else:
break
# show output
cv2.imshow("Output", imOut)
# record key press
k = cv2.waitKey(0) & 0xFF
# m is pressed
if k == 109:
# increase total number of rectangles to show by increment
numShowRects += increment
# l is pressed
elif k == 108 and numShowRects > increment:
# decrease total number of rectangles to show by increment
numShowRects -= increment
# q is pressed
elif k == 113:
break
# close image show window
cv2.destroyAllWindows()
Bug:在上述代碼中,選擇性搜索的Python綁定中出現了一個bug。所以Python代碼使用OpenCV 3.3.0而不是OpenCV 3.2.0工作。如果你不想編譯OpenCV 3.3.0,構建OpenCV 3.2.0你編譯前的文件夾,你也可以修復這個bug。如果你看看GitHub上,這只是一個小小的改變。你必須在文件行# 239變化
opencv_contrib-3.2.0/modules/ximgproc/include/opencv2/ximgproc/segmentation.hpp
// from
CV_WRAP virtual void process(std::vector<Rect>& rects) = 0;
// to
CV_WRAP virtual void process(CV_OUT std::vector<Rect>& rects) = 0;
現在你又重新編譯OpenCV 3.2.0。如果您有一個在早期編譯過opencv的構建文件夾,那麼運行make命令就會編譯這個模塊。