Selective Search 算法

目标检测和图像分类

  • 图像分类:识别图像中的目标。输入图片,输出图像中存在的目标类别,以及对应类别的概率。
  • 目标检测:识别+定位图片中的目标。输入图片,输出图像中存在的目标类别,以及置信度,还输出边界框(x, y, width, height)表示目标在图片中的位置。

可见目标检测算法的核心之一也是图像分类。假设我们训练了一个图像分类模型,它可以识别图像中的目标,但是不知道目标的位置,要定位目标,我们需要选择图像的子区域(patches),然后将图像分类算法应用于这些 patches。目标的位置是由返回的类别概率较高的 patches 的位置给出。

因此,如何生成 patches? 生成较小的子区域最简单方法是滑动窗口 (Sliding Window) 算法。然而,滑动窗口方法有一些局限。“区域提名 (Region Proposal)” 算法可以克服这些。选择性搜索 (Selective Search) 算法是最流行的区域提名算法之一。


滑动窗口算法

在滑动窗口方法中,将一个窗口滑动到图像上以选择一个 patch,并使用分类模型对窗口覆盖的每个 patch 进行分类。为了尽可能框到图像中的目标。需要滑动搜索图像中所有可能的位置,相邻窗口会出现很多重叠地方,此外,由于图像中目标的大小,形状不确定,还必须用不同 size 的窗口依次遍历图像,例如改变窗口的大小再遍历,改变窗口的宽高比 (ratio) 再遍历。可见,滑动窗口简单易理解,但是利用各种大小的窗口在图像中全局搜索,导致分类模型要对数万个 patches 进行分类。

图像是三维到二维投影。根据拍摄角度,某些物体的长宽比和形状有很大的不同。当使用滑动窗口时,计算 very expensive。滑动窗口适用于宽高比变化相对稳定的目标,如脸部,行人。


区域提名算法

Region Proposal 的算法会选出图像中所有可能包含目标的框。这些区域方案可能会很嘈杂、重叠,也可能无法完美地包含目标,但在这些提名区域中,会有非常接近图像中的实际目标的 patch。然后利用分类,将概率分数高的区域输出。

Region Proposal 算法使用分割来识别目标中的预期对象。在分割中,我们根据颜色、纹理等标准对彼此相似的相邻区域进行分组。滑动窗口是通过所有像素的位置,各种大小框来遍历寻找图像中的目标,区域提名算法是通过将像素分组为较少数量的 segments 来工作。因此,最终生成的 proposals 比滑动窗口方法少很多倍。

另外,Region Proposal 算法一个重要特性是具有很高的召回率 (Recall = TP/(TP+FN))。这是由于真阳性 (TP) 较高,而假阴性 (FN) 较低。在提名的区域列表中尽可能包含我们正在查找的目标的区域。为了实现这一点,提名的区域列表中最终可能会包含许多不包含任何目标的区域。换句话说,Region Proposal 算法要想查找到所有的真阳性,就可以可能产生大量的假阳性 (FP)。这些假阳性区域大多会被目标识别算法 pass 掉。因此较多的假阳性对精度有只有轻微影响,只是检测所需的时间增加。可见高召回率确实是这类算法很好的一个特性,如果丢失了包含实际物体的区域 (FN),就会严重影响了检测率。

已经提出的几个 Region Proposal 算法

  1. [Objectness]

  2. [Constrained Parametric Min-Cuts for Automatic Object Segmentation]

    J. Carreira and C. Sminchisescu, “Constrained parametric min-cuts for automatic object segmentation,” in CVPR, 2010.

  3. [Category Independent Object Proposals]

    I. Endres and D. Hoiem, “Category independent object proposals,” in ECCV, 2010.

  4. Randomized Prim:

    S. Manén, M. Guillaumin, and L. Van Gool, “Prime object proposals with randomized prim’s algorithm,” in ICCV, 2013.

  5. Selective Search

目前,在所有这些区域提名算法中,选择性搜索算法 (Selective Search) 最常用,因为它速度快,召回率高。


选择性搜索算法

选择性搜索 (Selective Search) 算法基于颜色、纹理、大小和形状的兼容性来对相似区域层级分组 (Hierarchical grouping)。算法从图像 over-segment 后开始, over-segment 是基于像素强度,可以使用 Felzenszwalb 和 Huttenlocher 基于图形 (Graph-based) 的分割方法

那么可以将此图像中的分割部分用作提名区域吗?答案是否定的,两点原因:

  1. 原始图像中的大多数实际目标包含2个或更多分割部分 (segmented parts)

  2. 图像中可能有物体被遮挡,这样被遮挡的部分很可能没有被分割到该物体的区域中

选择性搜索算法将那些 over-segments 作为初始输入,执行以下步骤

  1. 将所有 segmented parts 对应的边界框添加到提名区域列表中

  2. 基于相似性对相邻段进行分组

  3. 转到步骤1

在每次迭代中,将形成更大的 segments,并添加到提名区域列表中。如下图,该算法是自下而上,从较小 segments 到较大 segments,来创建提名区域的。这就是上文提到的层级分组 (Hierarchical grouping) 的意思了。

​ Hierarchical Segmentation

相似度计算

如何计算两个区域之间的相似度。SS 算法根据颜色、纹理、尺寸和匹配程度这 4 种度量来计算相似度。

  • Color Similarity
  • Texture Similarity
  • Size similarity
  • Shape Compatibility

颜色相似度

对图像的每个通道计算 bins=25 的颜色直方图,所有通道的直方图构成 25×3=75 维的颜色描述符 (descriptor)。

两个区域 ( r i , r j ) (r_{i},r_{j}) (ri,rj) 的颜色相似度是基于直方图交叉,, c i k c_{i}^k cik 表示第 k t h b i n k^th bin kthbin 的直方图:
S c o l o r ( r i , r j ) = ∑ k = 1 n m i n ( c i k , c j k ) S_{color}(r_{i},r_{j})=\sum_{k=1}^nmin(c_{i}^k,c_{j}^k) Scolor(ri,rj)=k=1nmin(cik,cjk)

纹理相似度

通过提取每个通道 8 个方向上的高斯导数来计算纹理特征。对于每个方向和每个颜色通道,计算 bins=10 的直方图,因此生成一个10x8x3=240 维的特征描述符。

两个区域的纹理相似度也通过直方图交叉点来计算:
S t e x t u r e ( r i , r j ) = ∑ k = 1 n m i n ( t i k , t j k ) S_{texture}(r_{i},r_{j})=\sum_{k=1}^nmin(t_{i}^k,t_{j}^k) Stexture(ri,rj)=k=1nmin(tik,tjk)

尺寸相似度

尺寸相似度鼓励较小的区域尽早合并。它确保在图像的所有部分形成所有规模的 region proposals。这种相似性度量,避免单个区域一个接一个地吞没相邻较小的区域,举个不太恰当例子,很早之前的贪食蛇游戏,一条蛇不断吃,一个点一个点的吃,现在有那种大概叫蛇蛇大作战的游戏,多条蛇同时并存,不断壮大,可能独立存在,或者相互融合。尺寸相似性定义为:
S s i z e ( r i , r j ) = 1 − s i z e ( r i ) + s i z e ( r j ) s i z e ( i m ) S_{size}(r_{i},r_{j})=1-\frac{size(r_{i})+size(r_{j})}{size(im)} Ssize(ri,rj)=1size(im)size(ri)+size(rj)

形状匹配程度

测量两个区域 ( r i , r j ) (r_{i},r_{j}) (ri,rj) 彼此之间的匹配程度。希望合并它们以填补空白,如果 r i r_{i} ri r j r_{j} rj 之间几乎没有接触,很可能会形成一个奇怪的区域,不应该合并。将 B B i j BB_{ij} BBij 定义为紧紧围绕且包含 r i r_{i} ri r j r_{j} rj 的边界框。因此如下, S f i l l ( r i , r j ) S_{fill(r_{i},r_{j})} Sfill(ri,rj) B B i j BB_{ij} BBij 中不包含 r i r_{i} ri r j r_{j} rj 的区域。如果它们甚至没有相互接触,就不应该合并。

形状兼容性定义为:
S f i l l ( r i , r j ) = 1 − B B i j − s i z e ( r i ) − s i z e ( r j ) s i z e ( i m ) S_{fill(r_{i},r_{j})}=1-\frac{BB_{ij}-size(r_{i})-size(r_{j})}{size(im)} Sfill(ri,rj)=1size(im)BBijsize(ri)size(rj)

总的相似度
四个相似度的线性组合:
S ( r i , r j ) = a 1 S c o l o r + a 2 S t e x t u r e + a 3 S s i z e + a 4 S f i l l S(r_{i},r_{j})=a_{1}S_{color}+a_{2}S_{texture}+a_{3}S_{size}+a_{4}S_{fill} S(ri,rj)=a1Scolor+a2Stexture+a3Ssize+a4Sfill
其中 a i ∈ 0 , 1 a_{i} \in 0,1 ai0,1.

https://www.koen.me/research/pub/vandesande-iccv2011-poster.pdf

选择性搜索算法代码

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;
}

python 代码
opencv3.3

#!/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()
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值