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

导读:通过本教程,我们将彻底理解一个重要的概念:目标检测中的常用方法“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. 将分段部分对应的所有边界框添加到区域建议列表中
  2. 基于相似性的群邻近段
  3. 转到步骤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命令就会编译这个模块。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏郭耀华‘s Blog

Batch Normalization&Dropout浅析

一. Batch Normalization 对于深度神经网络,训练起来有时很难拟合,可以使用更先进的优化算法,例如:SGD+momentum、RMSProp、...

2646
来自专栏杨熹的专栏

TensorFlow-1: 如何识别数字

识别数字在机器学习任务中的地位和 Hello World 在编程中是一样的。 主要步骤: 获得数据:from Yann LeCun's website 建立模型...

3547
来自专栏达观数据

技术干货 | XGBoost原理解析

作者简介 刘英涛:达观数据推荐算法工程师,负责达观数据个性化推荐系统的研发与优化。 XGBoost的全称是 eXtremeGradient Boosting,...

31510
来自专栏PPV课数据科学社区

【V课堂】R语言十八讲(八)—简单运算

这节我们将会讲解R语言基础的最后一节,数据的计算,包含了一些简单的统计数字特征和简单的四则运算,逻辑运算等等,也涉及到了矩阵方面的知识,由于数字特征,矩...

2334
来自专栏Java与Android技术栈

高斯反向投影实现检测图像中的特定物

在图像处理中,我们通常需要设置感兴趣的区域(ROI,region of interest),来简化我们的工作。也就是从图像中选择的一个图像区域,这个区域是我们图...

641
来自专栏PPV课数据科学社区

机器学习系列:(四)从线性回归到逻辑回归---续篇

欢迎小伙伴们回来继续学习,本篇内容是连着上一篇 “机器学习系列:(四)从线性回归到逻辑回归”文章。 多类分类 现实中有很多问题不只是分成两类,许多问题都需要分成...

2796
来自专栏磐创AI技术团队的专栏

实用 | 分享一个决策树可视化工具

【磐创AI导读】:这篇文章希望跟大家分享一个可视化决策树或者随机森林的工具。这可以帮助我们更好的去理解或解释我们的模型。想要获取更多的机器学习、深度学习资源。欢...

801
来自专栏AI-Master

TensorFlow实现XOR

tf.train.GradientDescentOptimizer是实现梯度下降算法的优化器。

321
来自专栏专知

理解多层CNN中转置卷积的反向传播(附代码)

6802
来自专栏Petrichor的专栏

深度学习: Faster R-CNN 网络

网络结构有两种,一种是将ZFNet(扔掉了尾端的全连接层)拿来用,另一种则是将VGG拿来用(扔掉了尾端的全连接层)。论文中给出的是第一种(绿框内为拿来用的那部分...

1724

扫码关注云+社区