目标检测的主要任务有两步:1. 确定目标位置;2. 对目标位置的目标进行分类;前者属于定位问题,后者属于分类问题;
分类问题在前面的学习有了合理的解决办法,目前主要是定位问题怎么处理,这也是目标检测的重点;
边界框的表示
首先,目标定位一般使用矩形来作为边界框,边界框的表示方式有两种:一种是使用图像中左上角和右下角两点确定边界框,一种是根据矩形特定位置点以及宽和高确定边界框;plt.Rectangle
采取的方式是左上角以及宽和高;
# 两点式转化为中心式
def box_2p_to_center(box_2p):
left, up, right, dowm = box_2p[:, 0], box_2p[:, 1], box_2p[:, 2], box_2p[:, 3]
x = (left + right) / 2
y = (up + dowm) / 2
width = right - left
height = dowm - up
return np.c_[x, y, width , height]
# 中心式转化为两点式
def box_center_to_2p(box_center):
x, y, width, height = box_center[:,0], box_center[:,1], box_center[:,2], box_center[:,3]
left = x - width/2
right = x + width/2
up = y - height/2
dowm = y + height/2
return np.c_[left, up, right, dowm]
这里以这张猫狗图片为例子,可以直接保存测试;
使用 plt.Rectangle
在图片上标记
import numpy as np
import matplotlib.pyplot as plt
def bbox_to_rect(bbox, color):
return plt.Rectangle(
xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
fill=False, edgecolor=color, linewidth=2)
img = plt.imread('./cat-dog.jpg')
dog_bbox, cat_bbox,bak_bbox = [20.0, 5.0, 440.0, 480.0], [445.0, 86.0, 730.0, 490.0],[390.0, 17.0, 600.0, 71.0]
fig = plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'yellow'))
fig.axes.text(dog_bbox[0]+5,dog_bbox[1]+20,'dog',style='italic',bbox={
'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
fig.axes.add_patch(bbox_to_rect(bak_bbox, 'red'))
fig.axes.text(cat_bbox[0]+5,cat_bbox[1]+20,'cat',style='italic',bbox={
'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'blue'))
fig.axes.text(bak_bbox[0]+5,bak_bbox[1]+20,'background',style='italic',bbox={
'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
plt.axis('off')
plt.savefig('cat-dog_.jpg')
得到如下结果:
在实际操作中,我们可以使用 LabelImg
快速标记出目标位置并给出目标分类制作数据;LabelImg
的链接如下: labelImg
使用 选择性搜索 框定目标
选择性搜索(Selective Search, SS)方法是通过图像中的纹理,边缘,颜色等信息对图像进行自底向上的分割,然后对分割区域进行不同尺度的合并,在合并过程中,每生成一个新的区域就产生一个候选框,区域肯定是不规则,我们通过选取区域的最大外接矩阵作为候选框区域,这种方法速度较慢;
在这篇博客中尝试使用了颜色分割合并:[Initial Image Segmentation Generator]论文实现:Efficient Graph-Based Image Segmentation;该方法的一个重要特点是,它能够在低变异性图像区域保留细节,而忽略了高变异性图像区域的细节。也就是说在变化不大的区域可以对细节进行保留,在变化很大的区域对细节进行剔除,这个效果根据后面合并小区域得到的。
在这里我们可以利用 OpenCV
中 createSelectiveSearchSegmentation()
函数来获取候选框区域;该函数除了安装基本的 OpenCV
外,还需要安装拓展,代码: pip install opencv-contrib-python --user
import cv2 as cv
def get_ss_rects(img):
cv.setUseOptimized(True)
ss = cv.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(img)
ss.switchToSelectiveSearchFast()
rects = ss.process()
return rects
测试一下SS方法:
import tensorflow as tf
import cv2 as cv
import matplotlib.pyplot as plt
def get_ss_rects(img):
cv.setUseOptimized(True)
ss = cv.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(img)
ss.switchToSelectiveSearchFast()
rects = ss.process()
return rects
im = cv.imread('./cat-dog.jpg')
rects = get_ss_rects(im)
imOut = im.copy()
for i, rect in enumerate(rects):
x, y, w, h = rect
cv.rectangle(imOut, (x,y), (x+w,y+h), (0,255,0), 1, cv.LINE_AA)
plt.imshow(imOut[:,:,::-1])
plt.axis('off')
plt.show()
得到结果如图所示:
得到处理时间为:CPU times: total: 2.08 s Wall time: 2.36 s
使用RPN框定目标
锚框和锚点
首先要介绍的是锚框和锚点,其定义是根据原图和特征图来说明的,首先原图(origin map)采取池化的方式缩小尺寸变成特征图(feature map);这样导致特征图某一点与原图某一块相对应,特征图的那一点被称为锚点;其对应的那一块被称为锚框,锚框的中心点也可以看做是锚点;
将锚框按照比例(如 1:2, 1:1, 2:1)进行变换,然后再依次进行宽和高的缩放和放大(如8, 16, 32)就可以得到9个框;
其中池化缩小的倍数被称为 base_size
,变换比例被称为 ratios
,对宽和高放大的倍数被称为 scales
;
使用代码获取特征图上第一个点的锚框实现如下:
def generate_anchors(base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)):
"""
Generate anchor (reference) windows by enumerating aspect ratios X
scales wrt a reference (0, 0, 15, 15) window.
"""
base_anchor = np.array([1, 1, base_size, base_size]) - 1
ratio_anchors = _ratio_enum(base_anchor, ratios)
anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
for i in range(ratio_anchors.shape[0])])
return anchors
def _whctrs(anchor):
"""
Return width, height, x center, and y center for an anchor (window).
"""
w = anchor[2] - anchor[0] + 1
h = anchor[3] - anchor[1] + 1
x_ctr = anchor[0] + 0.5 * (w - 1)
y_ctr = anchor[1] + 0.5 * (h - 1)
return w, h, x_ctr, y_ctr
def _mkanchors(ws, hs, x_ctr, y_ctr):
"""
Given a vector of widths (ws) and heights (hs) around a center
(x_ctr, y_ctr), output a set of anchors (windows).
"""
ws = ws[:, np.newaxis]
hs = hs[:, np.newaxis]
anchors = np.hstack((x_ctr - 0.5 * (ws - 1),
y_ctr - 0.5 * (hs - 1),
x_ctr + 0.5 * (ws - 1),
y_ctr + 0.5 * (hs - 1)))
return anchors
def _ratio_enum(anchor, ratios):
"""
Enumerate a set of anchors for each aspect ratio wrt an anchor.
"""
w, h, x_ctr, y_ctr = _whctrs(anchor)
size = w * h
size_ratios = size / ratios
ws = np.round(np.sqrt(size_ratios))
hs = np.round(ws * ratios)
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
def _scale_enum(anchor, scales):
"""
Enumerate a set of anchors for each scale wrt an anchor.
"""
w, h, x_ctr, y_ctr = _whctrs(anchor)
ws = w * scales
hs = h * scales
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
获取特征图上所有点的锚框如下:
def get_anchors(height, width, base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)):
anchor_rect = []
base_anchors = generate_anchors(base_size, ratios, scales)
for i in range(width):
for j in range(height):
for item in base_anchors:
x1, y1, x2, y2 = item
x1, x2 = x1 + base_size*i, x2 + base_size*i
y1, y2 = y1 + base_size*j, y2 + base_size*j
item = [x1, y1, x2, y2]
anchor_rect.append(item)
anchor_rect = np.array(anchor_rect)
return anchor_rect
# 画出锚框
def bbox_to_rect(bbox, color):
return plt.Rectangle(
xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
fill=False, edgecolor=color, linewidth=0.5)
# 一步到位
def plot_anchors(anchors)