您的位置:首页 > 编程语言 > C语言/C++

目标检测:选择性搜索策略(C++ / Python)

2017-09-21 14:31 936 查看
导读:通过本教程,我们将彻底理解一个重要的概念:目标检测中的常用方法“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命令就会编译这个模块。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息