目录
使用对象检测算法时,基本方法是尝试通过在目标对象周围绘制边界框来定位目标对象。由于可能存在多个感兴趣的对象,并且事先不知道它们的出现次数,因此会产生可变长度的输出层,这意味着无法通过构建由完全连接的层组成的标准深度神经网络来解决对象检测问题。解决此问题的一种方法是从图像中获取不同的感兴趣区域,并使用神经网络检测每个特定区域内是否存在所需对象。由于所需对象在图像中可能具有不同的纵横比和位置,从而导致大量区域并最终在计算上爆炸,因此该方法似乎也失败了。
为了解决该问题,已经开发了诸如R-CNN,Fast R-CNN和YOLO之类的算法。在本文中,我们将实现R-CNN以检测给定图像中的人。
神经网络
带有CNN的区域(R-CNN)由Ross,Jeff和Jitendra于2014年提出。我们的想法是,我们无需对大量区域进行检测,而是通过选择性搜索传递图像,从而从图像中仅提取2000个区域地区提案。现在,我们可以与这2000个提议的区域合作,而不必尝试对大量区域进行分类。接下来,我们在提议的区域上计算联合交叉点(IOU),并使用地面真实数据添加标签。为了理解所有内容,我们将在这里使用Keras从零开始实现R-CNN,但是在本系列的后面部分,我们一定会更详细地介绍R-CNN。
准备用于对象检测的数据集
我们将使用可在kaggle上轻松获得的INRIAPerson数据集。提到的数据集有2个包含“测试”和“训练”数据的子目录,并且两个子目录都具有图像及其关联的注释。图像注释基本上标记了图像上的数据,并使对象可以被AI和ML模型感知。这些图像可能包含人类、车辆或任何其他类型的物体,以使其可被机器识别。但是,专门创建了INRIAPerson数据集以检测图像文件中的人,因此仅包含人注释。看下面的文件可以使思路清晰。
<?xml version="1.0" ?>
<annotation>
<folder>VOC2007</folder>
<filename>crop_000010.png</filename>
<source>
<database>PASperson Database</database>
<annotation>PASperson</annotation>
</source>
<size>
<width>594</width>
<height>720</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>person</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>194</xmin>
<ymin>127</ymin>
<xmax>413</xmax>
<ymax>647</ymax>
</bndbox>
</object>
</annotation>
我们将使用这些注释使模型可识别对象。但是,在继续前进之前,我们首先需要将这些批注解析为csv文件并提取所需的数据。Python提供了用于解析xml文件的ElementTree API。下面是一个函数,可用于轻松加载和解析xml批注文件。
def parse_xml_to_csv(path):
xml_list = []
#iterate over all files to extract the bounding box for person present in the corresponding image
for xml_annot in glob.glob(path + '/*.xml'):
#load and parse file
tree = ET.parse(xml_annot)
doc = etree.parse(xml_annot)
count = doc.xpath("count(//object)")
#getting root of the document
root = tree.getroot()
with open(str(xml_annot)[0:-4]+".csv","w+") as file:
file.write(str(int(count)))
for person in root.findall('object'):
value = (
person[4][0].text,
person[4][1].text,
person[4][2].text,
person[4][3].text
)
coors = " ".join(value)
with open(str(xml_annot)[0:-4]+".csv","a") as file:
file.write("\n")
file.write(coors)
调用上面的函数,将路径传递到注释文件作为参数:
annot_path ="./Annotations"
xml_df = parse_xml_to_csv(annot_path)
功能完成后,您可以查看所有转换后的csv文件。
使用Keras实现R-CNN
有了数据,我们就可以继续实现R-CNN。首先,让我们导入将要使用的所有库。
import os # to interact with OS
import cv2 # to perform selective search on images
import keras # to implement neural net
import numpy as np # to work with arrays
import pandas as pd # for data manipulation
import tensorflow as tf # for deep learning models
import matplotlib.pyplot as plt # for plotting
正如我们之前提到的,搜索感兴趣的区域在计算上已耗费大量力气,因此我们将在此处尝试实现有效的解决方案。选择性搜索根据颜色、纹理、大小或形状计算相似度,并按层次将最相似的区域分组。继续该过程,直到整个图像变为单个区域。OpenCV提供使用该createSelectiveSearchSegmentation函数来实现选择性搜索。将优化和选择性搜索添加到您的解决方案中,如下所示:
# OpenCV optimization
cv2.setUseOptimized(True);
# selective search
selective_search = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
现在,如果我们在测试图像上应用选择性搜索,则会导致所需对象周围的边界框。
在这一点上,我们对当前边界框的精确度感兴趣。为此,我们可以简单地使用“联合交叉点”(IOU),这是一种评估指标,用于测量对象检测器的准确性。可以通过计算预测边界框和地面真相边界框之间的重叠面积(相交面积)除以两者所限定的总面积(并集面积)来计算:
def compute_iou(box1, box2):
x_left = max(box1['x1'], box2['x1'])
y_top = max(box1['y1'], box2['y1'])
x_right = min(box1['x2'], box2['x2'])
y_bottom = min(box1['y2'], box2['y2'])
intersection_area = (x_right - x_left) * (y_bottom - y_top)
box1_area = (box1['x2'] - box1['x1']) * (box1['y2'] - box1['y1'])
box2_area = (box2['x2'] - box2['x1']) * (box2['y2'] - box2['y1'])
union_area = box1_area + box2_area - intersection_area
iou = intersection_area / union_area
return iou
现在,我们需要对数据进行预处理,以创建可以传递给我们的模型的数据集。我们将遍历所有图像并将它们设置为选择性搜索的基础。然后,我们将对通过选择性搜索得出的前2000个建议区域进行迭代,并计算IOU,以便可以注释所需对象(人类)的区域。这些图像将根据对象的存在进行标记,并将附加到我们的training_images数组中。
training_images=[]
training_labels=[]
for e,i in enumerate(os.listdir(annot)):
try:
filename = i.split(".")[0]+".png"
img = cv2.imread(os.path.join(path,filename))
dataframe = pd.read_csv(os.path.join(annot,i))
ground_truth_values=[]
for row in dataframe.iterrows():
x1 = int(row[1][0].split(" ")[0])
y1 = int(row[1][0].split(" ")[1])
x2 = int(row[1][0].split(" ")[2])
y2 = int(row[1][0].split(" ")[3])
ground_truth_values.append({"x1":x1,"x2":x2,"y1":y1,"y2":y2})
# setting the image as base image for selective search
selective_search.setBaseImage(img)
# initializing fast selective search
selective_search.switchToSelectiveSearchFast()
# getting proposed regions
ssresults = selective_search.process()
imout = img.copy()
counter = 0
f_counter = 0
flag = 0
fflag = 0
bflag = 0
for e,result in enumerate(ssresults):
# iterating over the first 2000 results from selective search to colculate IOU
if e < 2000 and flag == 0:
for val in ground_truth_values:
x,y,w,h = result
iou = compute_iou(val,{"x1":x,"x2":x+w,"y1":y,"y2":y+h})
# limiting the maximum positive samples to 20
if counter < 20:
# setting IOU > 0.70 as goodness measure for positive i.e. person detected
if iou > 0.70:
image = imout[y:y+h,x:x+w]
resized = cv2.resize(image, (224,224), interpolation = cv2.INTER_AREA)
training_images.append(resized)
training_labels.append(1)
counter += 1
else :
fflag =1
# limiting the maximum negative samples to 20
if f_counter <20:
if iou < 0.3:
image = imout[y:y+h,x:x+w]
resized = cv2.resizetimage, (224,224), interpolation = cv2.INTER_AREA)
training_images.append(resized)
training_labels.append(0)
f_counter += 1
else :
bflag = 1
if fflag == 1 and bflag == 1:
flag = 1
except Exception as e:
print(e)
continue
现在,training_images和training_labels包含我们模型的新x和y坐标。让我们从模型的导入开始。
from keras.layers import Dense
from keras import Model
from keras import optimizers
from keras.optimizers import Adam
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint, EarlyStopping
从技术上讲,R-CNN模型可以从头开始拟合,但是它花费了太多时间,并且导致性能不佳。在这里,我们将使用迁移学习来节省时间并获得更好的性能。您可以根据自己的喜好使用imagenet或coco权重进行转移学习。
vggmodel = VGG16(weights='imagenet', include_top=True)
vggmodel.summary()
上面的代码片段导致以下输出:
接下来,我们将设置trainable为false冻结模型的前十层。
for layers in (vggmodel.layers)[:10]:
layers.trainable = False
我们只对人类的存在与否感兴趣,这意味着我们只有两个类别可以预测,因此我们将添加两个具有softmax激活的单位密集层。使用softmax激活的原因是它确保输出的总和为1(即,输出是概率)。
X= vggmodel.layers[-2].output
predictions = Dense(2, activation="softmax")(X)
最后,我们将使用Adam优化器来编译模型。
model_final = Model(vggmodel.input, predictions)
model_final.compile(loss = keras.losses.categorical_crossentropy, optimizer = Adam(lr=0.001), metrics=["accuracy"])
我们已经创建了模型。在继续之前,我们需要对数据集进行编码。我们可以使用LabelBinarizer编码。
class Label_Binarizer(LabelBinarizer):
def transform(self, y_old):
Y = super().transform(y_old)
if self.y_type_ == 'binary':
return np.hstack((Y, 1-Y))
else:
return Y
def inverse(self, Y):
if self.y_type_ == 'binary':
return super().inverse(Y[:, 0])
else:
return super().inverse(Y)
encoded = Label_Binarizer()
Y = encoded.fit_transform(y_new)
我们还需要将数据集分为训练集和测试集,可以使用来自sklearn的train_test_split来完成。在这里,我们将数据分为80%的训练和20%的测试比率。
X_train, X_test , y_train, y_test = train_test_split(X_new,Y,test_size=0.20)
Keras提供ImageDataGenerator来将数据集传递给模型。您还可以应用水平或垂直翻转来增加数据集。
train_data_prep = ImageDataGenerator(horizontal_flip=True, vertical_flip=True, rotation_range=90)
trainingdata = train_data_prep.flow(x=X_train, y=y_train)
test_data_prep = ImageDataGenerator(horizontal_flip=True, vertical_flip=True, rotation_range=90)
testingdata = test_data_prep.flow(x=X_test, y=y_test)
添加Keras回调
训练深度神经网络需要花费大量时间,而且我们总是有浪费计算资源的风险。为避免此问题,Keras提供了两个回调:EarlyStopping和ModelCheckPoint。EarlyStopping在一个时代结束时被调用。一旦不再改善训练流程,它将终止训练过程,从而使您可以配置任意数量的时代。ModelCheckpoint在每个时代之后也会调用,并自动保存效果最佳的模型。在使用fit_generator训练模型时,我们可以同时使用两个回调:
checkpoint = ModelCheckpoint("rcnn.h5", monitor='val_loss', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', save_freq=1)
early = EarlyStopping(monitor='val_loss', min_delta=0, patience=100, verbose=1, mode='auto')
hist = model_final.fit_generator(generator= traindata, steps_per_epoch= 10, epochs= 500, validation_data= testdata, validation_steps=2, callbacks=[checkpoint,early])
测试我们的模型
现在将创建我们的模型并将其保存为rcnn.h5,我们处于良好的位置对模型进行预测。我们将按照与之前相同的步骤进行操作:遍历所有图像并将它们设置为基本图像以进行选择性搜索。稍后,我们会将选择性搜索结果传递给我们的模型进行预测,并且模型在图像中遇到人类时会创建边界框。
count=0
for e,i in enumerate(os.listdir(path)):
count += 1
image = cv2.imread(os.path.join(path,i))
selective_search.setBaseImage(image)
selective_search.switchToSelectiveSearchFast()
ssresults = selective_search.process()
imout = image.copy()
for e, res in enumerate(ssresults):
if e < 2000:
x,y,w,h = res
test_image = imout[y:y+h,x:x+w]
resized = cv2.resize(test_image, (224,224), interpolation = cv2.INTER_AREA)
image = np.expand_dims(resized, axis=0)
out= model_final.predict(image)
if out[0][0] > 0.65:
cv2.rectangle(imout, (x, y), (x+w, y+h), (0, 255, 0), 1, cv2.LINE_AA)
plt.figure()
plt.imshow(imout)
注意:结果来自提前终止
R-CNN的局限性
R-CNN具有一些缺点。它仍然在其根部实现滑动窗口。唯一的区别是,它实际上是作为卷积实现的,这使其比传统的滑动窗口技术更有效。但是,对于2000个区域提案中的每一个提案,它仍然需要对CNN进行全面的前瞻,并且具有复杂的多阶段培训流程,从而导致性能问题。同样,由于测试时间长,R-CNN在实时或拥挤的区域变得不可行。
下一步是什么?
在本文中,我们学习了在Keras中使用深度神经网络实现第一个自定义对象检测器。我们还讨论了该方法的一些局限性。在该系列的下一篇文章中,我们将尝试克服R-CNN带来的限制,并获得对该区域中存在的人数的估计。