本教程中,我们将了解目标检测中称为“选择性搜索”的重要概念。我们还将在OpenCV 中使用C ++和Python实现该算法。

1 背景

1.1 目标检测与目标识别

目标识别算法Target Recognition识别图像中存在哪些对象。它将整个图像作为输入,并输出该图像中存在的对象的类标签和类概率。例如,类标签可以是“狗”,相关的类概率可以是97%。另一方面,目标检测算法Target Detection不仅告诉您图像中存在哪些对象,还输出边界框(x,y,width,height)以表示图像内对象的位置。

所有目标检测算法的核心是物体识别算法。假设我们训练了一个目标识别模型,该模型识别图像中的狗。该模型将判断图像中是否有狗。它不会告诉对象的位置。

为了定位物体,我们必须要选择图片的次区域(子块)然后对这些图片子块应用目标识别算法。目标的位置由目标识别算法返回的类概率较高的图像块的位置给出。

最直接的生成较小的次区域的方法为滑动窗口方法。然而,滑动窗口方法有许多限制。这些限制叫做候选区域算法克服了。选择性搜索就是候选区域算法中最流行的一种。

https://s4.51cto.com/images/blog/202204/29105057_626b5291e26711683.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

1.2 滑动窗口算法

在滑动窗口方法中,我们在图像上滑动框或窗口以选择子块,并使用目标识别模型对窗口覆盖的每个图像子块进行分类。它是对整个图像上的对象的详尽搜索。我们不仅需要搜索图像中的所有可能位置,还必须搜索不同的比例。这是因为目标识别模型通常以特定尺度(或尺度范围)进行训练。这导致对数万个图像块进行分类。问题还不仅仅如此。滑动窗口方法对于固定的纵横比对象,如人脸或行人表现是好的。图像是三维物体的二维投影。根据图像拍摄的角度,诸如纵横比和形状等物体特征有很大的不同。因此当我们搜索多个纵横比时,滑动窗口方法的计算非常昂贵。

1.3 候选区域选择算法

到目前为止我们讨论的问题可以使用候选区域选择算法来解决。这种方法将图像作为输入,输出可能是包含目标对象子块的选择框。这些候选区域可能是嘈杂的、重叠的,并且可能不能完美地包含这个对象,但是在这些候选区域中,将会有一个非常接近于图像中的实际对象的候选区域。然后我们可以使用目标识别模型对这些候选区域进行分类。有分数最高的候选区域就是该物体的位置。

如下图所示,生成了多个候选区域,其中以绿色表示最后物体的位置框。

https://s4.51cto.com/images/blog/202204/29105058_626b52921761037089.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

候选区域选择算法使用分割来识别图像中的目标。在分割中,我们根据颜色、纹理等标准对相邻的相似区域进行分组。。与我们在所有像素位置和所有尺度上寻找对象的滑动窗口方法不同,候选区域选择算法通过在更细分的像素组下进行分割。因此,生成的最终候选区域数量比滑动窗口方法少很多倍。这减少了我们必须分类的图像子块的数量。这些生成的区域具有不同的比例和宽高比。

候选区域选择算法的一个重要特性是具有非常高的召回率。这只是一种花哨的说法,即包含我们所寻找的对象的区域必须出现在我们的候选区域清单中。为了实现这一点,我们的区域候选区域列表最终可能会包含许多不包含任何对象的区域。换句话说,候选区域算法可以产生大量的误报,只要它能够捕获所有真正的对象。多数这些误报将被目标识别算法否定。当我们有更多的误报并且精度受到轻微影响时,检测所需的时间就会增加。但是,召回率高仍然是一个好主意,因为会丢失包含实际对象的区域的其他方法会严重影响检测率。

已经提出了几种候选区域选择方法,例如

  1. Objectness
  2. Constrained Parametric Min-Cuts for Automaticobject Segmentation
  3. Category Independent Object Proposals
  4. Randomized Prim
  5. Selective Search

在所有这些区域提议方法中,选择性搜索是最常用的,因为它速度快且召回率很高。

2 选择性搜索算法

2.1 什么是选择性搜索?

选择性搜索是用于目标检测的候选区域选择算法。它快速并具有很高的召回率。它基于根据颜色,纹理,大小和形状兼容性计算相似区域的分层分组。它基于根据颜色,纹理,大小和形状等对相似区域进行分组。

选择性搜索首先使用Felzenszwalb和Huttenlocher 基于图形的分割方法,根据像素的强度对图像进行过度分割。算法的输出如下所示,图像分别为原图和包含了用纯色表示的分割区域。论文见

https://s4.51cto.com/images/blog/202204/29105057_626b5291f167a4020.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105057_626b5291e9ea731766.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

我们可以在这个图像中使用分割的部分作为候选区域吗?答案是否定的,我们不能这样做有两个原因:

1)原始图像中的大多数实际对象包含两个或多个分割区域;

2)对于被遮挡的物体,如杯子所覆盖的盘子或装满咖啡的杯子的候选区域不能用这种方法生成。

如果我们试图通过进一步合并相邻的区域来解决第一个问题,我们最终会得到一个包含两个对象的分割区域。完美的分割不是我们的目标。我们只是想获得许多候选区域,比如其中一些应该与实际的对象有非常高的重叠。选择性搜索使用 Felzenszwalb 和 huttenlocher 的方法中的过分割作为初始种子。一个过分割的图像是这样的。

https://s4.51cto.com/images/blog/202204/29105058_626b529215e3633303.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

选择性搜索算法将这些过分割作为初始输入,并执行以下步骤

1)将与分割部分对应的所有边界框添加到候选区域列表中

2)基于相似度合并相邻分割区域

3)回到第一步

在每次迭代中,形成更大的部分并添加到区域候选区域列表中。因此,我们通过自下而上的方法从较小的部分到更大的部分获得候选区域。:这就是我们所说的使用 Felzenszwalb 和 huttenlocher 的过分割来计算“分层”分割的意思。下图显示了分层分割过程的初始,中间和最后一步。

详细见:

https://s4.51cto.com/images/blog/202204/29105058_626b529209d7132173.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

2.2 选择性搜索相似性度量

让我们深入探讨如何计算两个区域之间的相似性。选择性搜索使用基于颜色,纹理,大小和形状兼容性的4种相似性度量方法。具体见:

(1)颜色相似性

针对图像的每个通道计算25个区间的颜色直方图,并且连接所有通道的直方图以获得颜色描述符,得到25×3=75维颜色描述符。

两个区域的颜色相似性基于直方图交集,可以计算为:

https://s4.51cto.com/images/blog/202204/29105058_626b52920ff8f50127.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105058_626b52921fab936002.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

是在颜色描述符直方图中

https://s4.51cto.com/images/blog/202204/29105058_626b52921469258230.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

组的值

(2)纹理相似性

纹理特征是通过在每个通道的8个方向提取高斯导数来计算的。对于每一个方向和每个颜色通道,计算一个10组直方图,形成一个10× 8×3=240维的特征描述符。

利用直方图相交法计算两个区域的纹理相似度。

https://s4.51cto.com/images/blog/202204/29105058_626b52922d39a57479.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105057_626b5291f263949115.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

是在纹理描述符直方图中

https://s4.51cto.com/images/blog/202204/29105058_626b5292209624776.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

组的值

(3)大小相似度

大小相似促使较小的区域尽早合并。它确保了在所有尺度上的区域建议都是在图像的所有部分形成的。如果没有考虑到这种相似性度量,单个区域将会一个接一个地吞噬所有较小的相邻区域,因此在这个位置只会产生多个尺度的候选区域。大小相似性被定义为:

https://s4.51cto.com/images/blog/202204/29105058_626b5292093a767116.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

其中

https://s4.51cto.com/images/blog/202204/29105058_626b529206acc77558.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

是图像的大小,以像素为单位。

(4)形状兼容性

形状兼容性测量两个区域(

https://s4.51cto.com/images/blog/202204/29105058_626b52922cf1431567.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105058_626b529238b7723531.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

)之间的匹配程度。如果

https://s4.51cto.com/images/blog/202204/29105058_626b52921905c41396.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105057_626b5291f263949115.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

形状匹配,我们会合并它们以填补空白,否则,它们就不应该合并。 形状兼容性定义如下:

https://s4.51cto.com/images/blog/202204/29105058_626b529216ad122804.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

这里

https://s4.51cto.com/images/blog/202204/29105058_626b52921634349585.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

是在

https://s4.51cto.com/images/blog/202204/29105058_626b5292149ac17487.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105058_626b5292174bd6730.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

周围的边界框。

(5)最终相似度

两个区域之间的最终相似性被定义为前述4个相似性的线性组合。

https://s4.51cto.com/images/blog/202204/29105058_626b52920d0e679353.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

其中

https://s4.51cto.com/images/blog/202204/29105058_626b52921ed6c79744.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105058_626b52923479b28831.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

是图像中的两个区域或区段,并

https://s4.51cto.com/images/blog/202204/29105058_626b52921bf6978529.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

表示是否使用该相似性度量。

2.3 结果

OpenCV中的选择性搜索实现提供了数千个按对象有效程度递减顺序排列的区域方案。为了清晰起见,我们将与图像上绘制的前200-250个框共享结果。一般来说,1000-1200个候选区域足以获得所有正确的目标区域。下图为250个候选框结果和200候选框结果图。

https://s4.51cto.com/images/blog/202204/29105100_626b52942ecff32002.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

https://s4.51cto.com/images/blog/202204/29105058_626b52925b14c88511.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

3 代码

OpenCV有自带的SelectiveSearchSegmentation类,使用起来很容易。需要包含ximgproc.hpp和其命名空间。但是速度很慢。所有代码见:

C++:

#include "pch.h"
#include <opencv2/opencv.hpp>
#include <opencv2/ximgproc.hpp>
#include <iostream>
#include <ctime>

using namespace cv;
using namespace cv::ximgproc::segmentation;

int main()
{
    // speed-up using multithreads 使用多线程
    //开启CPU的硬件指令优化功能
    setUseOptimized(true);
    setNumThreads(4);

    // read image 读图
    Mat im = imread("./image/dogs.jpg");
    if (im.empty())
    {
        return 0;
    }
    // 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 快速搜索(速度快,召回率低)
    //ss->switchToSelectiveSearchFast();
    //精准搜索(速度慢,召回率高)
    ss->switchToSelectiveSearchQuality();

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

    while (1)
    {
        // create a copy of original image 做一份图像图像拷贝
        Mat imOut = im.clone();

        // itereate over all the region proposals 画框前numShowRects个
        for (int i = 0; i < numShowRects; i++)
        {
            rectangle(imOut, rects[i], Scalar(0, 255, 0));
        }

        // show output
        imshow("Output", imOut);

        waitKey(0);
    }
    return 0;
}

Python:


import sys
import cv2

if __name__ == '__main__':

    # speed-up using multithreads
    cv2.setUseOptimized(True)
    cv2.setNumThreads(4);

    # read image
    im = cv2.imread('image/dogs.jpg')
    # 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)

    #ss.switchToSelectiveSearchFast()

    ss.switchToSelectiveSearchQuality()

    # 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()