基于YoloV3对卫星图像进行储油罐容积占用率的研究

来源:深度学习与计算机视觉
作者:磐怼怼 在1957年以前,地球上只有一颗天然卫星:月球。1957年10月4日,苏联发射了世界上第一颗人造卫星,从那时起,来自40多个国家大约有8900颗卫星发射升空。
这些卫星可以帮助我们进行监视、通信、导航等等。国家可以利用卫星监视另一个国家的土地及其动向,估计其经济和实力,然而所有的国家都互相隐瞒着他们的信息。
同理,全球石油市场也并非完全透明,几乎所有的产油国都在努力隐藏着自己的总产量、消费量和储存量,各国这样做是为了间接地向外界隐瞒其实际经济,并增强其国防系统的能力,这种做法可能会对其他国家造成威胁。
出于这个原因,许多初创公司,如planet和orbital insight,都通过卫星图像来关注各国的此类活动。通过收集储油罐的卫星图像来估算石油储量。
但问题是,如何仅凭卫星图像来估计储油罐的体积呢?首先第一个条件是储油罐为浮顶油罐,因为只有这样,卫星才能检测到。这种特殊类型的油罐是专门为储存大量石油产品而设计的,如原油或凝析油,它由顶盖组成,直接位于油的顶部,随着油箱中油量的增加或下降,并在其周围形成两个阴影。如下图所示,阴影位于北侧
(外部阴影)是指储罐的总高度,而储罐内的阴影(内部阴影)表示浮顶的深度,体积可估计计算为1-(内部阴影区域/外部阴影区域)。
在本文,我们将使用tensorflow2.x框架,在卫星图像的帮助下,使用python从零开始实现一个完整的模型来估计储油罐的占用量。
github仓库
本文的所有内容和整个代码都可以在这个github存储库中找到 https://github.com/mdmub0587/...
以下是本文目录。我们会逐一探索。
目录 问题陈述、数据集和评估指标 现有方法 相关研究工作 有用的博客和研究论文 我们的贡献 探索性数据分析(eda) 数据扩充 数据预处理、扩充和tfrecords 基于yolov3的目标检测 储量估算 结果 结论 参考引用 1.问题陈述、数据集和评估指标 问题陈述: 利用卫星图像进行浮顶油罐的检测和储油量的估算,然后将图像块重新组合成具有储油量估计的全图像。
数据集: 数据集链接:https://www.kaggle.com/toward...
该数据集包含一个带注释的边界框,卫星图像是从谷歌地球(google earth)拍摄的,它包含有世界各地的工业区。数据集中有2个文件夹和3个文件,让我们逐一看看。
large/_images: 这是一个文件夹,包含100个卫星原始图像,每个大小为4800x4800,所有图像都以id/_large.jpg格式命名。 image/_patches: image/_patches目录包含从大图像生成的512x512大小的子图,每个大的图像被分割成100个512x512大小的子图,两个轴上的子图之间有37个像素的重叠,生成图像子图的程序以id/_row/_column.jpg格式命名 labels.json: 它包含所有图像的标签。标签存储为字典列表,每个图像对应一个字典,不包含任何浮顶罐的图像将被标记为“skip”,边界框标签的格式为边界框四个角的(x,y)坐标。 labels/_coco.json:  它包含与前一个文件相同的标签的coco标签格式。在这里,边界框的格式为[x/_min, y/_min, width, height]. large/_image/_data.csv: 它包含大型图像文件的元数据,包括每个图像的中心坐标和海拔高度。 评估指标: 对于储油罐的检测,我们将使用每种储油罐的平均精度(average precision,ap)和各种储油罐的map(mean average precision,平均精度)来作为评估指标。浮顶罐的估计容积没有度量标准。
map 是目标检测模型的标准评估指标。map 的详细说明可以在下面的youtube播放列表中找到
https://www.youtube.com/watch?list=pl1gqavho4f/_je5pnxu/_q4msriqx4wpflm&v=e4g9h18vyma
2.现有方法 karl keyer [1]在他的存储库中使用retinanet来完成储油罐探测任务。他从头开始创建模型,并将生成的锚框应用于该数据集,他的研究使得浮顶罐的平均精度(ap)达到76.3%,然后他应用阴影增强和像素阈值法来计算它的体积。
据我所知,这是互联网上唯一可用的方法。
3.相关研究工作 estimating the volume of oil tanks based on high-resolution remote sensing images [2]: 这篇文章提出了一种基于卫星图像的油罐容量/容积估算方法。为了计算一个储油罐的总容积,他们需要储油罐的高度和半径。为了计算高度,他们使用了与投影阴影长度的几何关系,但是计算阴影的长度并不容易,为了突出阴影使用了hsv(即色调饱和度值)颜色空间,因为通常阴影在hsv颜色空间中具有高饱和度,然后采用基于亚像素细分定位(sub-pixel subdivision positioning)的中值法来计算阴影长度,最后利用hough变换算法得到油罐半径。
在本文的相关工作中,提出了基于卫星图像的建筑物高度计算方法。
4.有用的博客和研究论文 a beginner’s guide to calculating oil storage tank occupancy with help of satellite imagery [3]: 本博客作者为tankertracker.com,其中的一项工作是利用卫星图像跟踪几个感兴趣的地理位置的原油储存情况。
在这篇博客中,他们详细描述了储油罐的外部和内部阴影如何帮助我们估计其中的石油含量,还比较了卫星在特定时间和一个月后拍摄的图像,显示了一个月来储油罐的变化。这个博客给了我们一个直观的知识,即如何估计量。
a gentle introduction to object recognition with deep learning [4] : 本文会介绍对象检测初学者头脑中出现的最令人困惑的概念。首先,描述了目标分类、目标定位、目标识别和目标检测之间的区别,然后讨论了一些最新的深度学习算法来展开目标识别任务。
对象分类是指将标签分配给包含单个对象的图像,而对象定位是指在图像中的一个或多个对象周围绘制一个边界框,目标检测任务结合了目标分类和定位。这意味着这是一个更具挑战性/复杂的任务,首先通过本地化技术在感兴趣对象(oi)周围绘制一个边界框,然后借助分类为每个oi分配一个标签。目标识别是上述所有任务的集合(即分类、定位和检测)。
最后,本文还讨论了两种主要的目标检测算法/模型:region-based convolutional neural networks (r-cnn)和you only look once (yolo)。
selective search for object recognition [5]: 在目标检测任务中,最关键的部分是目标定位,因为目标分类是在此基础上进行的,它依赖于定位所输出的目标区域(简称区域建议)。更完美的定位可以实现更完美的目标检测。选择性搜索是一种新兴的算法,在一些物体识别模型中被用于物体定位,如r-cnn和fast-r-cnn。
该算法首先使用高效的基于图的图像分割方法生成输入图像的子段,然后使用贪婪算法将较小的相似区域合并为较大的相似区域。分段相似性基于颜色、纹理、大小和填充四个属性。
region proposal network — a detailed view[6]: rpn(region-proposition network)由于其比传统选择性搜索算法更快而被广泛地应用于目标定位,它从特征图中学习目标的最佳位置,就像cnn从特征图中学习分类一样。
它负责三个主要任务,首先生成锚定框(每个特征映射点生成9个不同形状的锚定框),然后将每个锚定框分类为前景或背景(即是否包含对象),最后学习锚定框的形状偏移量以使其适合对象。
faster r-cnn: towards real-time object detection with region proposal networks[7]: faster r-cnn模型解决了前两个相关模型(r-cnn和fast r-cnn)的所有问题,并使用rpn作为区域建议生成器。它的架构与fast r-cnn完全相同,只是它使用了rpn而不是选择性搜索,这使得它比fast r-cnn快34倍。
real-time object detection with yolo, yolov2, and now yolov3 [8]: 在介绍yolo系列模型之前,让我们先看一下首席研究员约瑟夫·雷德曼在ted演讲上的演讲。
https://youtu.be/cgxsv1rijhi
这个模型在对象检测模型列表中占据首位的原因有很多,然而,最主要的原因是它的牢固性,它的推理时间非常短,这是为什么它很容易匹配视频的正常速度(即25fps)并应用于实时数据的原因。
与其他对象检测模型不同,yolo模型具有以下特性。
单神经网络模型(即分类和定位任务都将从同一个模型中执行):以一张照片作为输入,直接预测每个边界框的边界框和类标签,这意味着它只看一次图像。 由于它对整个图像而不是图像的一部分执行卷积,因此它产生的背景错误非常少。 yolo学习对象的一般化表示。在对自然图像进行训练和艺术品测试时,yolo的性能远远超过dpm和r-cnn等顶级检测方法。由于yolo具有高度的通用性,所以当应用于新的域或意外的输入时,它不太可能崩溃。 是什么让yolov3比yolov2更好。
如果你仔细看一下yolov2论文的标题,那就是“yolo9000: better, faster, stronger”。yolov3比yolov2更好吗?答案是肯定的,它更好,但不是更快更强,因为体系的复杂性增加了。 yolov2使用了19层darknet架构,没有任何残差块、skip连接和上采样,因此它很难检测到小对象,然而在yolov3中,这些特性被添加了,并且使用了在imagenet上训练的53层darknet网络,除此之外,还堆积了53个卷积层,形成了106个卷积层结构。  
yolov3在三种不同的尺度上进行预测,首先是大对象的13x13网格,其次是中等对象的26x26网格,最后是小对象的52x52网格。 yolov3总共使用9个锚箱,每个标度3个,用k均值聚类法选出最佳锚盒。 yolov3可以对图像中检测到的对象执行多标签分类,通过logistic回归预测对象置信度和类预测。 5.我们的贡献 我们的问题陈述包括两个任务,第一个是浮顶罐的检测,另一个是阴影的提取和已识别罐容积的估计。第一个任务是基于目标检测,第二个任务是基于计算机视觉技术。让我们描述一下解决每个任务的方法。
储罐检测: 我们的目标是估算浮顶罐的容积。我们可以为一个类建立目标检测模型,但是为了减少一个模型与另一种储油罐(即其他类型储油罐)的混淆,并使其具有鲁棒性,我们提出了三个类别的目标检测模型。使用带有转移学习的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开始吧!!
6.探索性数据分析(eda) 探索labels.json文件: 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。
探索labels/_coco.json文件: 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,浮顶罐) 7.数据扩充 在eda中,人们观察到10000幅图像中有8171幅是无用的,因为它们不包含任何对象,此外1595个图像包含至少一个浮顶罐对象。众所周知,所有的深度学习模型都需要大量的数据,没有足够的数据会导致性能的下降。
因此,我们先进行数据扩充,然后将获得的扩充数据拟合到yolov3目标检测模型中。
8.数据预处理、扩充和tfrecords 数据预处理: 对象的注释是以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个新图像:
水平翻转 旋转90度 旋转180度 旋转270度 水平翻转和90度旋转 水平翻转和180度旋转 水平翻转和270度旋转 示例如下所示
tfrecords: 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存储库中找到代码。
9.基于yolov3的目标检测 训练: 为了训练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') 损失函数:
yolo损失函数: yolov3模型训练中所用的损失函数相当复杂。yolo在三个不同的尺度上计算三个不同的损失,并对反向传播进行总结(正如你在上面的代码单元中看到的,最终损失是三个不同损失的列表),每个loss都通过4个子函数来计算检测损失和分类损失。
中心(x,y) 的mse损失. 边界框的宽度和高度的均方误差(mse) 边界框的二元交叉熵得分与无目标得分 边界框多类预测的二元交叉熵或稀疏范畴交叉熵 让我们看看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  (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')  
推理:
让我们看看这个模型是如何执行的
10.储量估算 体积估算是本案例研究的主要内容。虽然没有评估估计容积的标准,但我们试图找到图像的最佳阈值像素值,以便能够在很大程度上检测阴影区域(通过计算像素数)。
我们将使用卫星拍摄到的4800x4800形状的大图像,并将其分割成100个512x512的子图,两个轴上的子图之间重叠37像素。图像修补程序在id/_row/_column.jpg命名。
每个生成的子图预测都将存储在一个csv文件中,然后再估计每个浮顶储油罐的体积(代码和解释以notebook格式在我的github存储库中提供)。
最后,将所有的图像块和边界框与标签合并,输出估计的体积,形成一个大的图像。你可以看看下面的例子:
11.结果 测试集上浮顶罐的ap分数为0.874,训练集上的ap分数为0.942。

CMD327低相位噪声放大器
合肥研发团队成功研制出半导体量子芯片电路载板
DC电源插座都有哪些规格?
tenda ac6路由器为例讲解硬件安全分析流程
One Hyper是摩托罗拉中端One系列中的最新款手机
基于YoloV3对卫星图像进行储油罐容积占用率的研究
安徽大学:在柔性离子型压敏传感器及电子皮肤研究方面取得新进展
海沧集成电路将进一步完善先进产业链布局 并形成超千人规模的集成电路人才高地
高压功率放大器在高校实验室的实际应用领域介绍
最新研发的装置可提高锂金属电池的性能
如何在函数库中调用指令?
存储芯片行业撑过寒冬 行业复苏或将加快来临
A股区块链上市公司概念股盘点整体表现明显低于上市公司
华为全光城市目标网架构,加速构建5G商业正循环
如何鉴别笔记本电脑电池品质优劣?
长江存储32层三维NAND闪存芯片量产提上日程 光谷是希望之地
如何使用脉冲加方向方式对步进电机进行位置的自由控制
恩智浦双目视觉ADAS解决方案
押注向量数据库,为时过早?
超小超薄封装COBP光反射器NJL5901R-2/NJL59