来源:深度学习与计算机视觉
作者:磐怼怼
在1957年以前,地球上只有一颗天然卫星:月球。1957年10月4日,苏联发射了世界上第一颗人造卫星,从那时起,来自40多个国家大约有8900颗卫星发射升空。
这些卫星可以帮助我们进行监视、通信、导航等等。国家可以利用卫星监视另一个国家的土地及其动向,估计其经济和实力,然而所有的国家都互相隐瞒着他们的信息。
同理,全球石油市场也并非完全透明,几乎所有的产油国都在努力隐藏着自己的总产量、消费量和储存量,各国这样做是为了间接地向外界隐瞒其实际经济,并增强其国防系统的能力,这种做法可能会对其他国家造成威胁。
出于这个原因,许多初创公司,如Planet和Orbital Insight,都通过卫星图像来关注各国的此类活动。通过收集储油罐的卫星图像来估算石油储量。
但问题是,如何仅凭卫星图像来估计储油罐的体积呢?首先第一个条件是储油罐为浮顶油罐,因为只有这样,卫星才能检测到。这种特殊类型的油罐是专门为储存大量石油产品而设计的,如原油或凝析油,它由顶盖组成,直接位于油的顶部,随着油箱中油量的增加或下降,并在其周围形成两个阴影。如下图所示,阴影位于北侧
(外部阴影)是指储罐的总高度,而储罐内的阴影(内部阴影)表示浮顶的深度,体积可估计计算为1-(内部阴影区域/外部阴影区域)。
在本文,我们将使用Tensorflow2.x框架,在卫星图像的帮助下,使用python从零开始实现一个完整的模型来估计储油罐的占用量。
GitHub仓库
本文的所有内容和整个代码都可以在这个github存储库中找到
以下是本文目录。我们会逐一探索。
利用卫星图像进行浮顶油罐的检测和储油量的估算,然后将图像块重新组合成具有储油量估计的全图像。
数据集链接:https://www.kaggle.com/toward...
该数据集包含一个带注释的边界框,卫星图像是从谷歌地球(google earth)拍摄的,它包含有世界各地的工业区。数据集中有2个文件夹和3个文件,让我们逐一看看。
对于储油罐的检测,我们将使用每种储油罐的平均精度(Average Precision,AP)和各种储油罐的mAP(Mean Average Precision,平均精度)来作为评估指标。浮顶罐的估计容积没有度量标准。
mAP 是目标检测模型的标准评估指标。mAP 的详细说明可以在下面的youtube播放列表中找到
https://www.youtube.com/watch?list=PL1GQaVhO4f/_jE5pnXU/_Q4MSrIQx4wpFLM&v=e4G9H18VYmA
Karl Keyer [1]在他的存储库中使用RetinaNet来完成储油罐探测任务。他从头开始创建模型,并将生成的锚框应用于该数据集,他的研究使得浮顶罐的平均精度(AP)达到76.3%,然后他应用阴影增强和像素阈值法来计算它的体积。
据我所知,这是互联网上唯一可用的方法。
这篇文章提出了一种基于卫星图像的油罐容量/容积估算方法。为了计算一个储油罐的总容积,他们需要储油罐的高度和半径。为了计算高度,他们使用了与投影阴影长度的几何关系,但是计算阴影的长度并不容易,为了突出阴影使用了HSV(即色调饱和度值)颜色空间,因为通常阴影在HSV颜色空间中具有高饱和度,然后采用基于亚像素细分定位(sub-pixel subdivision positioning)的中值法来计算阴影长度,最后利用Hough变换算法得到油罐半径。
在本文的相关工作中,提出了基于卫星图像的建筑物高度计算方法。
本博客作者为TankerTracker.com,其中的一项工作是利用卫星图像跟踪几个感兴趣的地理位置的原油储存情况。
在这篇博客中,他们详细描述了储油罐的外部和内部阴影如何帮助我们估计其中的石油含量,还比较了卫星在特定时间和一个月后拍摄的图像,显示了一个月来储油罐的变化。这个博客给了我们一个直观的知识,即如何估计量。
本文会介绍对象检测初学者头脑中出现的最令人困惑的概念。首先,描述了目标分类、目标定位、目标识别和目标检测之间的区别,然后讨论了一些最新的深度学习算法来展开目标识别任务。
对象分类是指将标签分配给包含单个对象的图像,而对象定位是指在图像中的一个或多个对象周围绘制一个边界框,目标检测任务结合了目标分类和定位。这意味着这是一个更具挑战性/复杂的任务,首先通过本地化技术在感兴趣对象(OI)周围绘制一个边界框,然后借助分类为每个OI分配一个标签。目标识别是上述所有任务的集合(即分类、定位和检测)。
最后,本文还讨论了两种主要的目标检测算法/模型:Region-Based Convolutional Neural Networks (R-CNN)和You Only Look Once (YOLO)。
在目标检测任务中,最关键的部分是目标定位,因为目标分类是在此基础上进行的,它依赖于定位所输出的目标区域(简称区域建议)。更完美的定位可以实现更完美的目标检测。选择性搜索是一种新兴的算法,在一些物体识别模型中被用于物体定位,如R-CNN和Fast-R-CNN。
该算法首先使用高效的基于图的图像分割方法生成输入图像的子段,然后使用贪婪算法将较小的相似区域合并为较大的相似区域。分段相似性基于颜色、纹理、大小和填充四个属性。
RPN(Region-proposition Network)由于其比传统选择性搜索算法更快而被广泛地应用于目标定位,它从特征图中学习目标的最佳位置,就像CNN从特征图中学习分类一样。
它负责三个主要任务,首先生成锚定框(每个特征映射点生成9个不同形状的锚定框),然后将每个锚定框分类为前景或背景(即是否包含对象),最后学习锚定框的形状偏移量以使其适合对象。
Faster R-CNN模型解决了前两个相关模型(R-CNN和Fast R-CNN)的所有问题,并使用RPN作为区域建议生成器。它的架构与Fast R-CNN完全相同,只是它使用了RPN而不是选择性搜索,这使得它比Fast R-CNN快34倍。
在介绍Yolo系列模型之前,让我们先看一下首席研究员约瑟夫·雷德曼在Ted演讲上的演讲。
这个模型在对象检测模型列表中占据首位的原因有很多,然而,最主要的原因是它的牢固性,它的推理时间非常短,这是为什么它很容易匹配视频的正常速度(即25fps)并应用于实时数据的原因。
与其他对象检测模型不同,Yolo模型具有以下特性。
是什么让YoloV3比Yolov2更好。
我们的问题陈述包括两个任务,第一个是浮顶罐的检测,另一个是阴影的提取和已识别罐容积的估计。第一个任务是基于目标检测,第二个任务是基于计算机视觉技术。让我们描述一下解决每个任务的方法。
我们的目标是估算浮顶罐的容积。我们可以为一个类建立目标检测模型,但是为了减少一个模型与另一种储油罐(即其他类型储油罐)的混淆,并使其具有鲁棒性,我们提出了三个类别的目标检测模型。使用带有转移学习的YoloV3进行目标检测是因为它更容易在机器上训练,此外为了提高度量分值,还采用了数据增强的方法。
阴影提取涉及许多计算机视觉技术,由于RGB颜色方案对阴影不敏感,必须先将其转换成HSV和LAB颜色空间,我们使用(l1+l3)/(V+1) (其中l1是LAB颜色空间的第一个通道值)的比值图像来增强阴影部分。
然后,通过阈值0.5×t1+0.4×t2(其中t1是最小像素值,t2是平均值)来过滤增强图像,再对阈值图像进行形态学处理(即去除噪声、清晰轮廓等)。
最后,提取出两个储油罐的阴影轮廓,然后根据上述公式估算出所占用的体积。这些想法摘自以下Notebook。
https://www.kaggle.com/towardsentropy/oil-tank-volume-estimation
遵循整个流程来解决这个案例研究如下所示。
让我们从数据集的探索性数据分析EDA开始吧!!
json/_labels = json.load(open(os.path.join('data','labels.json')))
print('Number of Images: ',len(json/_labels))
json/_labels/[25:30/]
所有的标签都存储在字典列表中,总共有10万张图片。不包含任何储罐的图像将标记为Skip,而包含储罐的图像将标记为tank、tank Cluster或Floating Head tank,每个tank对象都有字典格式的四个角点的边界框坐标。
在10K个图像中,8187个图像没有标签(即它们不包含任何储油罐对象,此外有81个图像包含至少一个储油罐簇对象,1595个图像包含至少一个浮顶储油罐。
在条形图中,可以观察到,在包含图像的1595个浮顶罐中,26.45%的图像仅包含一个浮顶罐对象,单个图像中浮顶储罐对象的最高数量为34。
json/_labels/_coco = json.load(open(os.path.join('data','labels/_coco.json')))
print('Number of Floating tanks: ',len(json/_labels/_coco/['annotations'/]))
no/_unique/_img/_id = set()
for ann in json/_labels/_coco/['annotations'/]:
no/_unique/_img/_id.add(ann/['image/_id'/])
print('Number of Images that contains Floating head tank: ', len(no/_unique/_img/_id))
json/_labels/_coco/['annotations'/]/[:8/]
此文件仅包含浮顶罐的边界框及其在字典格式列表中的_image/_id_
储油罐有三种:
- Tank(T 油罐)
- Tank Cluster(TC 油罐组),
- Floating Head Tank(FHT,浮顶罐)
在EDA中,人们观察到10000幅图像中有8171幅是无用的,因为它们不包含任何对象,此外1595个图像包含至少一个浮顶罐对象。众所周知,所有的深度学习模型都需要大量的数据,没有足够的数据会导致性能的下降。
因此,我们先进行数据扩充,然后将获得的扩充数据拟合到Yolov3目标检测模型中。
对象的注释是以Jason格式给出的,其中有4个角点,首先,从这些角点提取左上角点和右下角点,然后属于单个图像的所有注释及其对应的标签都保存在CSV文件的一行列表中。
从角点提取左上角点和右下角点的代码
def conv/_bbox(box/_dict):
"""
input: box/_dict-> 字典中有4个角点
Function: 获取左上方和右下方的点
output: tuple(ymin, xmin, ymax, xmax)
"""
xs = np.array(list(set(/[i/['x'/] for i in box/_dict/])))
ys = np.array(list(set(/[i/['y'/] for i in box/_dict/])))
x/_min = xs.min()
x/_max = xs.max()
y/_min = ys.min()
y/_max = ys.max()
return y/_min, x/_min, y/_max, x/_max
CSV文件将如下所示
为了评估模型,我们将保留10%的图像作为测试集。
# 训练和测试划分
df/_train, df/_test= model/_selection.train/_test/_split(
df, #CSV文件注释
test/_size=0.1,
random/_state=42,
shuffle=True,
)
df/_train.shape, df/_test.shape
我们知道目标检测需要大量的数据,但是我们只有1645幅图像用于训练,这是非常少的,为了增加数据,我们必须执行数据扩充。我们通过翻转和旋转原始图像来生成新图像。我们转到下面的GitHub存储库,从中提取代码进行扩充
https://blog.paperspace.com/data-augmentation-for-bounding-boxes/
通过执行以下操作从单个原始图像生成7个新图像:
示例如下所示
TFRecords是TensorFlow自己的二进制存储格式。当数据集太大时,它通常很有用。它以二进制格式存储数据,并对训练模型的性能产生显著影响。二进制数据复制所需的时间更少,而且由于在训练时只加载了一个batch数据,所以占用的空间也更少。你可以在下面的博客中找到它的详细描述。
https://medium.com/mostly-ai/tensorflow-records-what-they-are-and-how-to-use-them-c46bc4bbb564
也可以查看下面的Tensorflow文档。
https://www.tensorflow.org/tutorials/load/_data/tfrecord
我们的数据集已转换成RFRecords格式,但是我们没有必要执行此任务,因为我们的数据集不是很大,如果你感兴趣,可以在我的GitHub存储库中找到代码。
为了训练yolov3模型,采用了迁移学习。第一步包括加载DarkNet网络的权重,并在训练期间冻结它以保持权重不变。
def create/_model():
tf.keras.backend.clear/_session()
pret/_model = YoloV3(size, channels, classes=80)
load/_darknet/_weights(pret/_model, 'Pretrained/_Model/yolov3.weights')
print('//nPretrained Weight Loaded')
model = YoloV3(size, channels, classes=3)
model.get/_layer('yolo/_darknet').set/_weights(
pret/_model.get/_layer('yolo/_darknet').get/_weights())
print('Yolo DarkNet weight loaded')
freeze/_all(model.get/_layer('yolo/_darknet'))
print('Frozen DarkNet layers')
return model
model = create/_model()
model.summary()
我们使用adam优化器(初始学习率=0.001)来训练我们的模型,并根据epoch应用余弦衰减来降低学习速率。在训练过程中使用模型检查点保存最佳权重,训练结束后保存最后一个权重。
tf.keras.backend.clear/_session()
epochs = 100
learning/_rate=1e-3
optimizer = get/_optimizer(
optim/_type = 'adam',
learning/_rate=1e-3,
decay/_type='cosine',
decay/_steps=10/*600
)
loss = /[YoloLoss(yolo/_anchors/[mask/], classes=3) for mask in yolo/_anchor/_masks/]
model = create/_model()
model.compile(optimizer=optimizer, loss=loss)
# Tensorbaord
! rm -rf ./logs/
logdir = os.path.join("logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
%tensorboard --logdir $logdir
tensorboard/_callback = tf.keras.callbacks.TensorBoard(logdir, histogram/_freq=1)
callbacks = /[
EarlyStopping(monitor='val/_loss', min/_delta=0, patience=15, verbose=1),
ModelCheckpoint('Weights/Best/_weight.hdf5', verbose=1, save/_best/_only=True),
tensorboard/_callback,
/]
history = model.fit(train/_dataset,
epochs=epochs,
callbacks=callbacks,
validation/_data=valid/_dataset)
model.save('Weights/Last/_weight.hdf5')
损失函数:
Yolov3模型训练中所用的损失函数相当复杂。Yolo在三个不同的尺度上计算三个不同的损失,并对反向传播进行总结(正如你在上面的代码单元中看到的,最终损失是三个不同损失的列表),每个loss都通过4个子函数来计算检测损失和分类损失。
让我们看看Yolov2中使用的损失公式
Yolov2中的最后三项是平方误差,而在Yolov3中,它们被交叉熵误差项所取代,换句话说,Yolov3中的对象置信度和类预测现在通过logistic回归来进行预测。
看看Yolov3损失函数的实现
def YoloLoss(anchors, classes=3, ignore/_thresh=0.5):
def yolo/_loss(y/_true, y/_pred):
# 1. 转换所有预测输出
# y/_pred: (batch/_size, grid, grid, anchors, (x, y, w, h, obj, ...cls))
pred/_box, pred/_obj, pred/_class, pred/_xywh = yolo/_boxes(
y/_pred, anchors, classes)
# predicted (tx, ty, tw, th)
pred/_xy = pred/_xywh/[..., 0:2/] #x,y of last channel
pred/_wh = pred/_xywh/[..., 2:4/] #w,h of last channel
# 2. 转换所有真实输出
# y/_true: (batch/_size, grid, grid, anchors, (x1, y1, x2, y2, obj, cls))
true/_box, true/_obj, true/_class/_idx = tf.split(
y/_true, (4, 1, 1), axis=/-1)
#转换 x1, y1, x2, y2 to x, y, w, h
# x,y = (x2 - x1)/2, (y2-y1)/2
# w, h = (x2- x1), (y2 - y1)
true/_xy = (true/_box/[..., 0:2/] + true/_box/[..., 2:4/]) / 2
true/_wh = true/_box/[..., 2:4/] - true/_box/[..., 0:2/]
# 小的box要更高权重
#shape-> (batch/_size, grid, grid, anchors)
box/_loss/_scale = 2 - true/_wh/[..., 0/] /* true/_wh/[..., 1/]
# 3. 对pred box进行反向
# 把 (bx, by, bw, bh) 变为 (tx, ty, tw, th)
grid/_size = tf.shape(y/_true)/[1/]
grid = tf.meshgrid(tf.range(grid/_size), tf.range(grid/_size))
grid = tf.expand/_dims(tf.stack(grid, axis=/-1), axis=2)
true/_xy = true/_xy /* tf.cast(grid/_size, tf.float32) - tf.cast(grid, tf.float32)
true/_wh = tf.math.log(true/_wh / anchors)
true/_wh = tf.where(tf.logical/_or(tf.math.is/_inf(true/_wh),
tf.math.is/_nan(true/_wh)),
tf.zeros/_like(true/_wh), true/_wh)
# 4. 计算所有掩码
#从张量的形状中去除尺寸为1的维度。
#obj/_mask: (batch/_size, grid, grid, anchors)
obj/_mask = tf.squeeze(true/_obj, /-1)
#当iou超过临界值时,忽略假正例
#best/_iou: (batch/_size, grid, grid, anchors)
best/_iou = tf.map/_fn(
lambda x: tf.reduce/_max(broadcast/_iou(x/[0/], tf.boolean/_mask(
x/[1/], tf.cast(x/[2/], tf.bool))), axis=/-1),
(pred/_box, true/_box, obj/_mask),
tf.float32)
ignore/_mask = tf.cast(best/_iou < ignore/_thresh, tf.float32)
# 5.计算所有损失
xy/_loss = obj/_mask /* box/_loss/_scale /* //
tf.reduce/_sum(tf.square(true/_xy - pred/_xy), axis=/-1)
wh/_loss = obj/_mask /* box/_loss/_scale /* //
tf.reduce/_sum(tf.square(true/_wh - pred/_wh), axis=/-1)
obj/_loss = binary/_crossentropy(true/_obj, pred/_obj)
obj/_loss = obj/_mask /* obj/_loss + //
(1 - obj/_mask) /* ignore/_mask /* obj/_loss
#TODO:使用binary/_crossentropy代替
class/_loss = obj/_mask /* sparse/_categorical/_crossentropy(
true/_class/_idx, pred/_class)
# 6. 在(batch, gridx, gridy, anchors)求和得到 => (batch, 1)
xy/_loss = tf.reduce/_sum(xy/_loss, axis=(1, 2, 3))
wh/_loss = tf.reduce/_sum(wh/_loss, axis=(1, 2, 3))
obj/_loss = tf.reduce/_sum(obj/_loss, axis=(1, 2, 3))
class/_loss = tf.reduce/_sum(class/_loss, axis=(1, 2, 3))
return xy/_loss + wh/_loss + obj/_loss + class/_loss
return yolo/_loss
分数:
为了评估我们的模型,我们使用了AP和mAP来评估训练和测试数据
测试集分数
get/_mAP(model, 'data/test.csv')
训练集分数
get/_mAP(model, 'data/train.csv')
推理:
让我们看看这个模型是如何执行的
体积估算是本案例研究的主要内容。虽然没有评估估计容积的标准,但我们试图找到图像的最佳阈值像素值,以便能够在很大程度上检测阴影区域(通过计算像素数)。
我们将使用卫星拍摄到的4800X4800形状的大图像,并将其分割成100个512x512的子图,两个轴上的子图之间重叠37像素。图像修补程序在id/_row/_column.jpg命名。
每个生成的子图预测都将存储在一个CSV文件中,然后再估计每个浮顶储油罐的体积(代码和解释以Notebook格式在我的GitHub存储库中提供)。
最后,将所有的图像块和边界框与标签合并,输出估计的体积,形成一个大的图像。你可以看看下面的例子:
测试集上浮顶罐的AP分数为0.874,训练集上的AP分数为0.942。
全部0条评论
快来发表一下你的评论吧 !