http://ataspinar.com/2017/12/04/using-convolutional-neural-networks-to-detect-features-in-sattelite-images/
在之前的博客文章中,我们已经看到了如何在Tensorflow中构建卷积神经网络(CNN),从零开始构建各种CNN架构(如LeNet5,AlexNet,VGGNet-16),并在MNIST,CIFAR-10,Oxflower17数据集上进行训练。
如果您一直关注最新的技术发展,您可能已经知道CNN用于人脸识别,物体检测,医学图像分析,制造过程中的自动检测,自然语言处理任务以及其他许多应用。可以说,想找到CNN的实际应用,你仅仅受想象力和创造力(当然还有动机,精力和时间)的限制。
受到Kaggle的卫星图像特征检测挑战的启发,我想知道在卫星和航拍图像中检测特征是否容易。
如果这是可能的话,它的实际应用将是巨大的。 在全球城市化进程中,城市正在不断扩大,发展和变化。 这伴随着新的基础设施,新的建筑和社区,以及不断变化的景观。 监控和跟踪所有这些变化一直是一个劳动密集型工作。 如果我们每天都可以得到全新的卫星图像,并使用深度学习来立即更新我们所有的地图,那么对于在这个领域工作的每个人来说都是一个很大的帮助!
深度学习领域的发展如此之快,以至于几年前的一个大炒作——“简单”的图片分类已经显得过时了。 目前,对象检测已经成为主流,在接下来的几年里我们可能会看到越来越多的应用使用图像分割(见图1)。
图1:计算机视觉中的任务可以分为图像分类,对象检测或分割任务
在这个博客中,我们将使用图像分类来检测航拍图像中的道路。
为此,我们首先需要获取这些航拍图像,并获取包含道路位置信息的数据(第2.1节)。
之后,我们需要将这两个图层放在一起(第3.1节)。
将准备好的数据集(第4.1节)以正确的格式保存后,我们可以将其送入建立卷积神经网络(4.3节)。
最后查看这个方法的准确性,并讨论可以使用哪些方法来改进它。
任何数据科学项目中的第一个(也是最困难的)步骤总是获取数据。 幸运的是,有许多包含各种形式卫星图像的开放数据集。 有Landsat数据集,ESA的Sentinel数据集,MODIS数据集,NAIP数据集等。
每个数据集都有不同的优点和缺点。 像NAIP数据集提供高分辨率(1米分辨率),但只覆盖美国。 像Landsat覆盖整个地球,但具有较低(30米)的分辨率。 还有其中一些数据集显示哪种类型的土地覆盖(森林,水,草地),其他数据集包含大气和气候数据。
由于我来自荷兰,所以我想使用覆盖荷兰的航空/卫星图像,因此我将使用PDOK提供的航拍图像。 这不仅是相当新的,并且他们也有一个令人难以置信的25厘米的精度。
荷兰政府组织有很多可用的开放数据。 使用Pdokviewer,您可以在线查看大量这些打开的数据集;
- 包含基础设施的层(荷兰的各种公路,铁路,水路(NWB wegenbestand),
- 包含市和地区的边界的层,
- 物理地理区域,
- 每个政府组织的地点,
- 农业区域,
- 每块用电量,
- 土壤的类型,地形图,
- 每块居民的人数,
- 表面使用等等(甚至棕色长耳蝙蝠的生活栖息地)。
图2:PDOK中可用的不同类型的图层
因此,有许多可能的数据集可以用作第二层(即作为包含道路位置信息的数据,之后需要与航拍图像数据进行合并),并使用它来自动检测卫星图像中的这些类型的特征。
PS:另一个包含很多地图的网站是Atlas Natuurlijk Kapitaal。
我感兴趣的图层是包含道路类型的图层。 可以从荷兰政府的开放数据门户下载具有道路类型(NWB wegenbestand)的地图。 航拍图像可以使用Web地图服务(WMS),可以通过Python包owslib下载。
URL = "https://geodata.nationaalgeoregister.nl/luchtfoto/rgb/wms?request=GetCapabilities"
wms = WebMapService(URL, version='1.1.1')
OUTPUT_DIRECTORY = './data/image_tiles/'
x_min = 90000
y_min = 427000
dx, dy = 200, 200
no_tiles_x = 100
no_tiles_y = 100
total_no_tiles = no_tiles_x * no_tiles_y
x_max = x_min + no_tiles_x * dx
y_max = y_min + no_tiles_y * dy
BOUNDING_BOX = [x_min, y_min, x_max, y_max]
for ii in range(0,no_tiles_x):
print(ii)
for jj in range(0,no_tiles_y):
ll_x_ = x_min + ii*dx
ll_y_ = y_min + jj*dy
bbox = (ll_x_, ll_y_, ll_x_ + dx, ll_y_ + dy)
img = wms.getmap(layers=['Actueel_ortho25'], srs='EPSG:28992', bbox=bbox, size=(256, 256), format='image/jpeg', transparent=True)
filename = "{}_{}_{}_{}.jpg".format(bbox[0], bbox[1], bbox[2], bbox[3])
out = open(OUTPUT_DIRECTORY + filename, 'wb')
out.write(img.read())
out.close()
用“dx”和“dy”我们可以调整缩放级别。200的值大致对应于12的缩放级别,并且100的值大致对应于13的缩放级别。
此过程会在边界框((90000,427000),(110000,447000))内生成10.000个图块。 这些坐标在rijksdriehoekscoordinate参考系统中给出,并且与WGS 84参考系统中的坐标((51.82781,4.44428),(52.00954,4.73177))相对应。
它覆盖了鹿特丹南部几平方公里(见图3),既包括城市也包括非城市地区,即我们的卷积神经网络有足够的道路进行训练。
图3:我们用来训练ConvNet的区域的边界框。
接下来,我们将使用来自NWB Wegvakken(2017年9月版)的数据确定每个图像的内容。 这是一个包含荷兰所有道路的文件,经常更新。 可以从这个位置以shapefile的形式下载它。
https://www.rijkswaterstaat.nl/apps/geoservices/geodata/dmc/nwb-wegen/geogegevens/shapefile/Nederland_totaal/
Shapefile包含具有地理空间数据的形状,通常使用ArcGIS或QGIS等GIS软件打开。 也可以使用pyshp库在Python中打开它。
import shapefile
import json
input_filename = './nwb_wegvakken/Wegvakken.shp'
output_filename = './nwb_wegvakken/Wegvakken.json'
reader = shapefile.Reader(shp_filename)
fields = reader.fields[1:]
field_names = [field[0] for field in fields]
buffer = []
for sr in reader.shapeRecords():
atr = dict(zip(field_names, sr.record))
geom = sr.shape.__geo_interface__
buffer.append(dict(type="Feature", geometry=geom, properties=atr))
output_filename = './data/nwb_wegvakken/2017_09_wegvakken.json'
json_file = open(output_filename , "w")
json_file.write(json.dumps({"type": "FeatureCollection", "features": buffer}, indent=2, default=JSONencoder) + "\n")
json_file.close()
在这段代码中,列表’buffer’包含shapefile的内容。 由于我们不想每次重复相同的shapefile读取过程,所以我们现在使用json.dumps()将其保存为json格式。
如果我们试图保存shapefile的内容,它会报错。 这是因为shapefile包含JSON本身不支持的数据类型(字节和日期时间对象)。 因此,我们需要为标准的JSON序列化器编写一个扩展,它可以获取JSON不支持的数据类型,并将它们转换为可序列化的数据类型。 这是JSONencoder的方法。 (更多关于这个请看这里)。
http://www.diveintopython3.net/serializing.html#json-unknown-types
def JSONencoder(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
serial = obj.isoformat()
return serial
if isinstance(obj, bytes):
return {'__class__': 'bytes',
'__value__': list(obj)}
raise TypeError ("Type %s not serializable" % type(obj))
如果我们查看这个shapefile的内容,我们将会看到它包含以下类型的对象列表:
{'properties': {'E_HNR_LNKS': 1, 'WEGBEHSRT': 'G', 'EINDKM': None, 'RIJRICHTNG': '', 'ROUTELTR4': '',
'ROUTENR3': None, 'WEGTYPE': '', 'ROUTENR': None, 'GME_ID': 717, 'ADMRICHTNG': '', 'WPSNAAMNEN': 'DOMBURG', 'DISTRCODE': 0,
'WVK_BEGDAT': '1998-01-21', 'WGTYPE_OMS': '', 'WEGDEELLTR': '#', 'HNRSTRRHTS': '', 'WEGBEHNAAM': 'Veere', 'JTE_ID_BEG': 47197012,
'DIENSTNAAM': '', 'WVK_ID': 47197071, 'HECTO_LTTR': '#', 'RPE_CODE': '#', 'L_HNR_RHTS': None, 'ROUTELTR3': '',
'WEGBEHCODE': '717', 'ROUTELTR': '', 'GME_NAAM': 'Veere', 'L_HNR_LNKS': 11, 'POS_TV_WOL': '', 'BST_CODE': '',
'BEGINKM': None, 'ROUTENR2': None, 'DISTRNAAM': '', 'ROUTELTR2': '', 'WEGNUMMER': '', 'ENDAFSTAND': None, 'E_HNR_RHTS': None,
'ROUTENR4': None, 'BEGAFSTAND': None, 'DIENSTCODE': '', 'STT_NAAM': 'Van Voorthuijsenstraat', 'WEGNR_AW': '',
'HNRSTRLNKS': 'O', 'JTE_ID_END': 47197131}, 'type': 'Feature',
'geometry': {'coordinates': [[23615.0, 398753.0], [23619.0, 398746.0], [23622.0, 398738.0], [23634.0, 398692.0]],
'type': 'LineString'}}
它包含了大量的信息(手册中规定了每个信息的意义),但是对我们来说最重要的是
在NWB Wegvakken中存在的不同的道路类型是:
现在是时候确定哪些图像包含道路,哪些不包含道路。 我们将NWB-Wegvakken的内容映射到下载的航拍照片的顶部来实现这一点。
我们可以使用Python字典来跟踪映射。 我们还将使用字典来跟踪每个图像中存在的道路类型。
#First we define some variables, and dictionary keys which are going to be used throughout the rest.
dict_roadtype = {
"G": 'Gemeente',
"R": 'Rijk',
"P": 'Provincie',
"W": 'Waterschap',
'T': 'Andere wegbeheerder',
'' : 'leeg'
}
dict_roadtype_to_color = {
"G": 'red',
"R": 'blue',
"P": 'green',
"W": 'magenta',
'T': 'yellow',
'' : 'leeg'
}
FEATURES_KEY = 'features'
PROPERTIES_KEY = 'properties'
GEOMETRY_KEY = 'geometry'
COORDINATES_KEY = 'coordinates'
WEGSOORT_KEY = 'WEGBEHSRT'
MINIMUM_NO_POINTS_PER_TILE = 4
POINTS_PER_METER = 0.1
INPUT_FOLDER_TILES = './data/image_tiles/'
filename_wegvakken = './data/nwb_wegvakken/2017_09_wegvakken.json'
dict_nwb_wegvakken = json.load(open(filename_wegvakken))[FEATURES_KEY]
d_tile_contents = defaultdict(list)
d_roadtype_tiles = defaultdict(set)
在上面的代码中,我们先前从Shapefile转换为.JSON格式的NWB wegvakken的内容被加载到了dict_nwb_wegvakken中。
此外,我们初始化两个字典。 第一个将填图像作为key,并将其内容列表作为值。 第二个字典将填路类型作为key,将包含这些道路类型的所有图块作为值。 如下所示:
for elem in dict_nwb_wegvakken:
coordinates = retrieve_coordinates(elem)
rtype = retrieve_roadtype(elem)
coordinates_in_bb = [coord for coord in coordinates if coord_is_in_bb(coord, BOUNDING_BOX)]
if len(coordinates_in_bb)==1:
coord = coordinates_in_bb[0]
add_to_dict(d_tile_contents, d_roadtype_tiles, coord, rtype)
if len(coordinates_in_bb)>1:
add_to_dict(d_tile_contents, d_roadtype_tiles, coordinates_in_bb[0], rtype)
for ii in range(1,len(coordinates_in_bb)):
previous_coord = coordinates_in_bb[ii-1]
coord = coordinates_in_bb[ii]
add_to_dict(d_tile_contents, d_roadtype_tiles, coord, rtype)
dist = eucledian_distance(previous_coord, coord)
no_intermediate_points = int(dist/10)
intermediate_coordinates = calculate_intermediate_points(previous_coord, coord, no_intermediate_points)
for intermediate_coord in intermediate_coordinates:
add_to_dict(d_tile_contents, d_roadtype_tiles, intermediate_coord, rtype)
我们迭代dict_nwb_wegvakken的内容,对于每个元素,我们查找坐标和道路类型,并检查这些坐标是在我们的边界框内。这是通过以下方法完成的:
def coord_is_in_bb(coord, bb):
x_min = bb[0]
y_min = bb[1]
x_max = bb[2]
y_max = bb[3]
return coord[0] > x_min and coord[0] < x_max and coord[1] > y_min and coord[1] < y_max
def retrieve_roadtype(elem):
return elem[PROPERTIES_KEY][WEGSOORT_KEY]
def retrieve_coordinates(elem):
return elem[GEOMETRY_KEY][COORDINATES_KEY]
def add_to_dict(d1, d2, coordinates, rtype):
coordinate_ll_x = int((coordinates[0] // dx)*dx)
coordinate_ll_y = int((coordinates[1] // dy)*dy)
coordinate_ur_x = int((coordinates[0] // dx)*dx + dx)
coordinate_ur_y = int((coordinates[1] // dy)*dy + dy)
tile = "{}_{}_{}_{}.jpg".format(coordinate_ll_x, coordinate_ll_y, coordinate_ur_x, coordinate_ur_y)
rel_coord_x = (coordinates[0] - coordinate_ll_x) / dx
rel_coord_y = (coordinates[1] - coordinate_ll_y) / dy
value = (rtype, rel_coord_x, rel_coord_y)
d1[tile].append(value)
d2[rtype].add(tile)
add_to_dict方法首先通过确定每个tile命名的四个坐标(lowerleft x,y和upperright x,y)来确定一个坐标属于哪个tile。
我们还确定了每个坐标在其瓦片中的相对位置。 例如,“99800_445000_100000_445200.jpg”中的坐标(99880,445120)将具有相对坐标(0.4,0.6)。 当你想绘制一个图块的内容时,这个方法很方便。
道路类型与相对坐标一起添加到图像的内容列表。
同时,我们将tilename添加到第二个包含每个道路类型的tilename列表的字典中。
利用可视化,查看映射是否正确,是否遗漏了道路,以及是否在两个坐标之间选择了足够的中间点以完全覆盖道路的所有部分。
fig, axarr = plt.subplots(nrows=11,ncols=11, figsize=(16,16))
for ii in range(0,11):
for jj in range(0,11):
ll_x = x0 + ii*dx
ll_y = y0 + jj*dy
ur_x = ll_x + dx
ur_y = ll_y + dy
tile = "{}_{}_{}_{}.jpg".format(ll_x, ll_y, ur_x, ur_y)
filename = INPUT_FOLDER_TILES + tile
tile_contents = d_tile_contents[tile]
ax = axarr[10-jj, ii]
image = ndimage.imread(filename)
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
ax.imshow(rgb_image)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
for elem in tile_contents:
color = dict_roadtype_to_color[elem[0]]
x = elem[1]*256
y = (1-elem[2])*256
ax.scatter(x,y,c=color,s=10)
plt.subplots_adjust(wspace=0, hspace=0)
plt.show()
在图5中,我们可以看到两个数字,左边是(x0 = 94400,y0 = 432000),右边是(x0 = 93000,y0 = 430000)。
接下来,我们将把所有图像及其正确标签(道路是否存在和道路类型)加载到数据集中。数据集是随机的,然后分成训练,测试和验证部分。
image_width = 256
image_height = 256
image_depth = 3
total_no_images = 10000
image_files = os.listdir(INPUT_FOLDER_TILES)
dataset = np.ndarray(shape=(total_no_images, image_width, image_height, image_depth), dtype=np.float32)
labels_roadtype = []
labels_roadpresence = np.ndarray(total_no_images, dtype=np.float32)
for counter, image in enumerate(image_files):
filename = INPUT_FOLDER_TILES + image
if image in list(d_tile_contents.keys()):
tile_contents = d_tile_contents[image]
roadtypes = sorted(list(set([elem[0] for elem in tile_contents])))
roadtype = "_".join(roadtypes)
labels_roadpresence[counter] = 1
else:
roadtype = ''
labels_roadpresence[counter] = 0
labels_roadtype.append(roadtype)
image_data = ndimage.imread(filename).astype(np.float32)
dataset[counter, :, :] = image_data
labels_roadtype_ohe = np.array(list(onehot_encode_labels(labels_roadtype)))
dataset, labels_roadpresence, labels_roadtype_ohe = reformat_data(dataset, labels_roadpresence, labels_roadtype_ohe)
我们可以使用以下函数对标签进行独热编码,并随机化数据集:
def onehot_encode_labels(labels):
list_possible_labels = list(np.unique(labels))
encoded_labels = map(lambda x: list_possible_labels.index(x), labels)
return encoded_labels
def randomize(dataset, labels1, labels2):
permutation = np.random.permutation(dataset.shape[0])
randomized_dataset = dataset[permutation, :, :, :]
randomized_labels1 = labels1[permutation]
randomized_labels2 = labels2[permutation]
return randomized_dataset, randomized_labels1, randomized_labels2
def one_hot_encode(np_array, num_unique_labels):
return (np.arange(num_unique_labels) == np_array[:,None]).astype(np.float32)
def reformat_data(dataset, labels1, labels2):
dataset, labels1, labels2 = randomize(dataset, labels1, labels2)
num_unique_labels1 = len(np.unique(labels1))
num_unique_labels2 = len(np.unique(labels2))
labels1 = one_hot_encode(labels1, num_unique_labels1)
labels2 = one_hot_encode(labels2, num_unique_labels2)
return dataset, labels1, labels2
将数据集加载到内存中的整个过程,尤其是随机化图像的顺序通常需要很长时间。所以在完成一次之后,最好把结果保存为一个pickle文件。
start_train_dataset = 0
start_valid_dataset = 1200
start_test_dataset = 1600
total_no_images = 2000
output_pickle_file = './data/sattelite_dataset.pickle'
f = open(output_pickle_file, 'wb')
save = {
'train_dataset': dataset[start_train_dataset:start_valid_dataset,:,:,:],
'train_labels_roadtype': labels_roadtype[start_train_dataset:start_valid_dataset],
'train_labels_roadpresence': labels_roadpresence[start_train_dataset:start_valid_dataset],
'valid_dataset': dataset[start_valid_dataset:start_test_dataset,:,:,:],
'valid_labels_roadtype': labels_roadtype[start_valid_dataset:start_test_dataset],
'valid_labels_roadpresence': labels_roadpresence[start_valid_dataset:start_test_dataset],
'test_dataset': dataset[start_test_dataset:total_no_images,:,:,:],
'test_labels_roadtype': labels_roadtype[start_test_dataset:total_no_images],
'test_labels_roadpresence': labels_roadpresence[start_test_dataset:total_no_images],
}
pickle.dump(save, f, pickle.HIGHEST_PROTOCOL)
f.close()
print("\nsaved dataset to {}".format(output_pickle_file))
现在可以把这个pickle文件加载到卷积神经网络中并训练它来识别道路。
正如你所看到的,在我们甚至可以开始使用CNN之前,我们不得不做很多工作来准备数据集。 这也反映了数据科学在现实生活中的作用; 70%到80%的时间用于获取,理解和清理数据。 数据的实际建模/训练只是工作的一小部分。
首先,我们从保存的pickle文件中加载数据集,从cnn_models模块中导入VGGNet并设置学习速率,批量大小等的值。
import pickle
import tensorflow as tf
from cnn_models.vggnet import *
from utils import *
pickle_file = './data/sattelite_dataset.pickle'
f = open(pickle_file, 'rb')
save = pickle.load(f)
train_dataset = save['train_dataset'].astype(dtype = np.float32)
train_labels = save['train_labels_roadpresence'].astype(dtype = np.float32)
test_dataset = save['test_dataset'].astype(dtype = np.float32)
test_labels = save['test_labels_roadpresence'].astype(dtype = np.float32)
valid_dataset = save['valid_dataset'].astype(dtype = np.float32)
valid_labels = save['valid_labels_roadpresence'].astype(dtype = np.float32)
f.close()
num_labels = len(np.unique(train_labels))
num_steps = 501
display_step = 10
batch_size = 16
learning_rate = 0.0001
lambda_loss_amount = 0.0015
之后,可以构建包含卷积神经网络所有计算步骤的图并开始训练它。 我们使用VGGNet-16卷积神经网络,l2正则化来最小化误差,学习率为0.0001。
在每一步中,训练准确率被附加到train_accuracies,并且在每第10步,测试和验证准确率被附加到类似的列表。 稍后我们将使用这些来形象化我们的准确率。
train_accuracies, test_accuracies, valid_accuracies = [], [], []
print("STARTING WITH SATTELITE")
graph = tf.Graph()
with graph.as_default():
#1) First we put the input data in a tensorflow friendly form.
tf_train_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))
tf_train_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))
tf_test_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))
tf_test_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))
tf_valid_dataset = tf.placeholder(tf.float32, shape=(batch_size, image_width, image_height, image_depth))
tf_valid_labels = tf.placeholder(tf.float32, shape = (batch_size, num_labels))
#2) Then, the weight matrices and bias vectors are initialized
variables = variables_vggnet16()
#3. The model used to calculate the logits (predicted labels)
model = model_vggnet16
logits = model(tf_train_dataset, variables)
#4. then we compute the softmax cross entropy between the logits and the (actual) labels
l2 = lambda_loss_amount * sum(tf.nn.l2_loss(tf_var) for tf_var in tf.trainable_variables())
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=tf_train_labels)) + l2
#learning_rate = tf.train.exponential_decay(0.05, global_step, 1000, 0.85, staircase=True)
#5. The optimizer is used to calculate the gradients of the loss function
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)
# Predictions for the training, validation, and test data.
train_prediction = tf.nn.softmax(logits)
test_prediction = tf.nn.softmax(model(tf_test_dataset, variables))
valid_prediction = tf.nn.softmax(model(tf_valid_dataset, variables))
with tf.Session(graph=graph) as session:
test_counter = 0
tf.global_variables_initializer().run()
print('Initialized with learning_rate', learning_rate, " model ", ii)
for step in range(num_steps):
#Since we are using stochastic gradient descent, we are selecting small batches from the training dataset,
#and training the convolutional neural network each time with a batch.
offset = (step * batch_size) % (train_labels.shape[0] - batch_size)
batch_data = train_dataset[offset:(offset + batch_size), :, :]
batch_labels = train_labels[offset:(offset + batch_size), :]
feed_dict = {tf_train_dataset : batch_data, tf_train_labels : batch_labels}
_, l, predictions = session.run([optimizer, loss, train_prediction], feed_dict=feed_dict)
train_accuracy = accuracy(predictions, batch_labels)
train_accuracies.append(train_accuracy)
if step % display_step == 0:
offset2 = (test_counter * batch_size) % (test_labels.shape[0] - batch_size)
test_dataset_batch = test_dataset[offset2:(offset2 + batch_size), :, :]
test_labels_batch = test_labels[offset2:(offset2 + batch_size), :]
feed_dict2 = {tf_test_dataset : test_dataset_batch, tf_test_labels : test_labels_batch}
test_prediction_ = session.run(test_prediction, feed_dict=feed_dict2)
test_accuracy = accuracy(test_prediction_, test_labels_batch)
test_accuracies.append(test_accuracy)
valid_dataset_batch = valid_dataset[offset2:(offset2 + batch_size), :, :]
valid_labels_batch = valid_labels[offset2:(offset2 + batch_size), :]
feed_dict3 = {tf_valid_dataset : valid_dataset_batch, tf_valid_labels : valid_labels_batch}
valid_prediction_ = session.run(valid_prediction, feed_dict=feed_dict3)
valid_accuracy = accuracy(valid_prediction_, valid_labels_batch)
valid_accuracies.append(valid_accuracy)
message = "step {:04d} : loss is {:06.2f}, accuracy on training set {:02.2f} %, accuracy on test set {:02.2f} accuracy on valid set {:02.2f} %".format(step, l, train_accuracy, test_accuracy, valid_accuracy)
print(message)
下面我们可以看到卷积神经网络的准确率结果。 正如你所看到的,测试的准确率和验证集合在80%左右。
图6:训练,测试和验证集的准确率
可以看看被错误分类的图片,比如下面这些, 道路确实很难被检测到
我们已经看到了如何使用CNN在卫星或航空图像中检测道路。 A.I.的技术发展和深度学习的世界是如此之快,使用“只有CNN”已经过时了。
几年以来,也有神经网络被称为R-CNN,Fast R-CNN和R-CNN(如SSD,YOLO和YOLO9000)。这些神经网络不仅可以检测图像中物体的存在,还可以返回物体的边界框。
现在还有可以执行分割任务的神经网络(如DeepMask,SharpMask,MultiPath),即它们可以确定图像中的每个像素属于哪个对象。
我认为这些可以执行图像分割的神经网络将是确定卫星图像内道路和其他物体位置的理想选择。在未来的博客中,我想看看如何使用这些类型的神经网络来检测卫星和航空图像中的道路(或其他特征)。