参考:
1、https://github.com/spmallick/learnopencv
2、https://github.com/opencv/
在本教程中,我们将了解在对象检测中称为“选择性搜索”的重要概念。 我们还将分享C ++和Python中的OpenCV代码。
物体识别算法识别图像中存在哪些物体。 它将整个图像作为输入并输出该图像中存在的对象的类标签和类概率。 例如,一个类标签可能是“狗”,相关的类概率可能是97%。
另一方面,物体检测算法不仅告诉您图像中存在哪些物体,还会输出边界框(x,y,宽度,高度)来指示图像内物体的位置。
所有对象检测算法的核心是一个对象识别算法。 假设我们训练了一个识别图像块中的狗的对象识别模型。 这个模型会告诉一个图像是否有一只狗在里面。 它不会告诉对象所在的位置。
为了本地化对象,我们必须选择图像的子区域(补丁),然后将对象识别算法应用于这些图像补丁。 物体的位置由物体识别算法返回的类概率高的图像块的位置给出。
生成较小子区域(补丁)的最直接的方法是滑动窗口方法。 但是,滑动窗口方法有几个限制。 这些限制被称为“区域提议”算法的一类算法所克服。 选择性搜索是最受欢迎的地区提议算法之一。
在滑动窗口方法中,我们在图像上滑动一个方框或窗口来选择一个补丁,并使用对象识别模型对窗口覆盖的每个图像补丁进行分类。 这是一个彻底的搜索整个图像的对象。 我们不仅需要搜索图像中所有可能的位置,还必须以不同的比例搜索。 这是因为对象识别模型通常是以特定的尺度(或尺度范围)进行训练的。 这导致对成千上万的图像块进行分类。
问题并没有在这里结束。 滑动窗口方法适用于固定宽高比的物体,如人脸或行人。 图像是3D对象的2D投影。 诸如纵横比和形状的对象特征根据拍摄图像的角度而显着变化。 滑动窗口的方法,因为当我们搜索多个纵横比时,计算上非常昂贵。
到目前为止我们讨论的问题可以通过使用区域提议算法来解决。 这些方法将图像作为与图像中最可能是对象的所有补丁对应的输入和输出边界框。 这些地区的建议可以是嘈杂的,重叠的,可能不完全包含对象,但在这些地区的建议中,会有一个与图像中的实际对象非常接近的建议。 然后,我们可以使用对象识别模型对这些建议进行分类。 具有高概率分数的区域提议是对象的位置。
Blue Boxes: False Positives; Green Boxes: True Positives
区域提议算法使用分割来识别图像中的预期对象。 在分割中,我们根据一些标准(如颜色,纹理等)将相邻的区域进行分组。不同于滑动窗口方法,我们在所有像素位置和所有尺度上查找对象,区域提议算法通过 将像素分组为较少的片段。 所以生成的提案的最终数量比滑动窗口方法少很多倍。 这减少了我们必须分类的图像补丁的数量。 这些生成的区域提案具有不同的比例和长宽比。
区域建议方法的一个重要特性是具有很高的召回率。 这只是一个奇特的说法,即包含我们所看到的对象的区域必须位于我们的区域提案列表中。 为了实现这个目标,我们的区域提案列表可能最终会包含很多不包含任何对象的区域。 换句话说,区域提议算法可以产生大量的误报,只要它能够捕捉到所有的真实的肯定。 这些误报大部分将被对象识别算法拒绝。 当我们有更多的误报,准确性受到一些影响时,检测所花费的时间就会增加。 但是高回忆率仍然是一个好主意,因为缺少包含实际对象的区域的选择会严重影响检测率。
已经提出了几种区域建议方法,例如
1、对象性
2、用于自动对象分割的约束参数Min-Cuts
3、类别独立对象建议
4、随机Prim
5、选择性搜索
在所有这些区域建议方法中,选择性搜索是最常用的,因为它是快速的并具有很高的召回率。
选择性搜索是用于对象检测的区域提议算法。 它被设计成具有非常高的召回速度。 它基于计算基于颜色,纹理,尺寸和形状兼容性的相似区域的分层分组。
选择性搜索通过使用Felzenszwalb和Huttenlocher的基于图的分割方法,基于像素的强度对图像进行过分割开始。 算法的输出如下所示。 右侧的图像包含使用纯色表示的分段区域。
这个图像中的部分分段作为区域建议? 答案是否定的,为什么我们不能这样做有两个原因:
1、原始图像中的大多数实际对象都包含2个或多个分段部分
2、用这种方法不能产生封闭物体的区域建议,例如被杯子覆盖的板或充满咖啡的杯子
如果我们试图通过进一步合并相邻区域来解决第一个问题,我们将最终得到一个覆盖两个物体的分割区域。
完美的细分不是我们的目标。 我们只是想预测很多地区的建议,其中一些建议应该与实际的对象有很高的重叠。
选择性搜索使用Felzenszwalb和Huttenlocher的方法作为最初的种子。 一个外向的图像看起来像这样。
选择性搜索算法将这些外生作为初始输入并执行以下步骤
1、将所有与分割部分对应的边界框添加到区域投标列表中
2、根据相似性对相邻的段进行分组
3、转到第1步
在每次迭代中,形成更大的片段并将其添加到区域提案列表中。 因此,我们使用自下而上的方法从较小的部分向较大的部分创建区域提案。 这就是我们所说的使用Felzenszwalb和Huttenlocher的规则来计算“等级”的分割。
该图显示了分层分割过程的初始,中间和最后一步。
让我们深入了解如何计算两个区域之间的相似度。
选择性搜索使用基于颜色,纹理,尺寸和形状兼容性的4种相似性度量。
为图像的每个通道计算25个区域的颜色直方图,并且连接所有通道的直方图以获得颜色描述符,得到25×3 = 75维颜色描述符。
两个区域的颜色相似度基于直方图交点,可以计算为:
cki 是颜色描述符中 kth bin的直方图值
纹理特征是通过提取每个通道8个方向的高斯导数来计算的。 对于每个方向和每个颜色通道,计算10个直方图,得到10×8×3 = 240维特征描述符。
两个区域的纹理相似性也使用直方图交点来计算。
tki 是纹理描述符中的第k个{bin}的直方图值
尺寸相似性鼓励较小的地区早期合并。 它确保了所有地区的地区建议都形成在图像的各个部分。 如果不考虑这种相似性度量,单个区域会一个接一个地吞噬所有较小的相邻区域,因此只能在这个位置生成多个尺度的区域提案。 大小相似性定义为:
size(im) 是图像的大小(以像素为单位)
形状兼容性测量两个区域( ri 和 rj )彼此适合的程度。 如果 ri 适合于 rj ,我们希望合并它们以弥补差距,如果它们甚至不相互接触,则不应该合并。
形状兼容性定义为:
size(BBij) 是围绕 ri 和 rj 的边界框
两个区域之间的最终相似性被定义为上述4个相似性的线性组合。
其中 ri 和 rj 是图像中的两个区域或片段, ai∈0,1 表示是否使用相似性度量。
OpenCV中的选择性搜索实现使成千上万个区域提案按照对象的顺序排列。 为了清楚起见,我们正在共享结果,在图像上绘制200-250个框。 一般而言,1000-1200个建议足以获得所有正确的地区建议。
我们来看看如何使用在OpenCV中实现的基于选择性搜索的分割。
下面的代码是使用OpenCV进行选择性搜索的C ++教程。 请通读注释以了解代码。
#include "opencv2/ximgproc/segmentation.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include
#include
using namespace cv;
using namespace cv::ximgproc::segmentation;
//程序执行命令:./ssearch input_image (f|q)
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 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
// 如果最后一个参数既不是f也不是q打印帮助信息
else {
help();
return -2;
}
// run selective search segmentation on input image
// 在输入图像上运行选择性搜索分割
std::vector 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
// m被按下
if (k == 109) {
// increase total number of rectangles to show by increment
// 增加矩形的总数以增量显示
numShowRects += increment;
}
// l is pressed
// l被按下
else if (k == 108 && numShowRects > increment) {
// decrease total number of rectangles to show by increment
// 减少总数的矩形显示增量
numShowRects -= increment;
}
// q is pressed
// 按下q 退出
else if (k == 113) {
break;
}
}
return 0;
}
注:
缺失的模块ximgproc可以去
https://github.com/opencv/opencv_contrib下载安装
编译参考:http://blog.csdn.net/wc781708249/article/details/78273241
下面的代码是使用OpenCV 3.3进行选择性搜索的Python教程。 注意在代码块之后提到的OpenCV 3.2的错误警报。 请通读注释以了解代码。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
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
# 如果参数既不是f也不是q打印帮助信息
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
# 为区域提议绘制矩形,直到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绑定中的一个错误。 所以Python代码可以用于OpenCV 3.3.0,但不适用于OpenCV 3.2.0。
如果你不想编译OpenCV 3.3.0,并且拥有之前编译的OpenCV 3.2.0的build文件夹,你也可以修复这个bug。
如果你看看Github提交,这只是一个小小的改变。 您必须更改文件中的第239行
opencv_contrib-3.2.0/modules/ximgproc/include/opencv2/ximgproc/segmentation.hpp
// from
CV_WRAP virtual void process(std::vector & rects) = 0;
// to
CV_WRAP virtual void process(CV_OUT std::vector & rects) = 0;
现在再次重新编译你的OpenCV 3.2.0。 如果你有一个早期编译OpenCV的生成文件夹,运行make命令将会编译这个模块。