今天带来新的一个原创系列-基于航拍图像的房价分类神经网络。这是我在DFKI做的第一个小项目,虽然比较简单,但是通过这个项目我也大致了解了卫星图像分析的常用工具和一般处理流程,也实践了一下deep learning的一些模型和调参过程。不废话了,los geht‘s~
Intuitive
决定一块区域的房价因素有许多:
从房屋本身的特征:
- 房屋高度
- 房屋朝向
- 建筑之间的密集程度
- 建筑的年份
- 建筑的结构等
到周围环境的影响(surroundings):
- 周围绿植覆盖(积极特征)
- 是否临近马路,工厂(消极特征)
- 是否临近超市,电影院,公园,医院,车站,学校(积极特征)等
传统的估计房价的方法是搜集相关数据,例如周围区域的历史数据,再结合每个具体房产的特征(建筑年份,建筑面积,使用面积),人为的估算。这种方法虽然可以提供一个较为准确的值,但是需要耗费大量的专家知识,效率低下。于是就有如下想法:是否可以利用CNN提取从航拍图像中提取不同的图像特征,来帮助我们预测分类房价呢?
Data preprocessing
数据集总览
DFKI 提供了阿姆斯特丹2015年的整个荷兰的航拍图像,文件格式是.ecw。(吐槽一下这个文件大的一匹,一块区域就有大约20G),这个可以用来当做训练的图像。因为是个分类任务,有了图像,还需要给图像做标注,即哪些图像里的房屋是高房价,哪些是低房价,有的图片里甚至没有房屋,我们需要过滤掉这些图像。City Amsterdam项目提供了许多标注数据,包括阿姆斯特丹的房价分布,城市噪音分布,公园商店分布等。
图片来源:city of Amsterdam
下载下来得到的是一个shapefile。利用GIS软件QGIS可以打开可视化shapefile文件,如下:
选中,一块小的子区域,可以查看具体的标注信息:
可以看到这块子区域的房价区间在2068-2585(因为这块区域包含多出房产)。
裁剪航拍图像
因为DFKI提供的是整个阿姆斯特丹的航拍图像,而我们只有阿姆斯特丹的各类标注信息,所以我们要裁剪航拍图像,使其成为一个一个的小块(patch),并且为每个小块,根据shapefile标注对应的房价标签。
使用gdal工具包可以方便高效的处理这种图像数据。因为提供的图像文件格式是.ecw,而我们要处理的是tiff或者png,所以首先利用gdal_translate完成这种转换。
gdal_translate -srcwin xoff yoff xsize ysize src_dataset dst_dataset
其中,-srcwin 是 “Selects a subwindow from the source image for copying based on pixel/line location.”,也就是选择srcdataset的图像(也就是我们的.ecw文件)的一块子区域,拷贝至dstdataset。需要主义的是,这个坐标的原点定义为左上角,xsize和y_size是以像素为单位的。接着利用gdal_info可以得到.ecw文件的图像信息,包括分辨率啊,坐标格式啊,边界信息等。
gdal_info ****.ecw
得到了边界信息,就可以选定步长,单个图片的大小,来裁剪航拍图像了。因为后面要利用不同分辨率的图像,所以我选择了单个像素分别为0.10m, 0.15m和0.20m的三种不同分辨率的裁剪方式。得到的图像分别如下:
裁剪shapefile文件
现在需要为每个小块航拍图像制作房价标签,为此需要依照每个子块的坐标信息裁剪shapefile。可以使用ogr2ogr:
ogr2ogr -t_srs EPSG 4326 -clipdst xmin ymin xmax ymax input output
注意:这里的坐标信息是 "xmin ymin xmax ymax", 与gdal_translate的坐标输入不同。这样会得到一个个的小shapefile。然后再计算里面的标签信息,为每一个小的航拍图像标记。
label_list=[]
def label(path):
price_list=[]
feature = fiona.open(path)
for i in range(len(feature)):
x = feature[i]['properties']['SELECTIE']
price_list.append(int(x))
if len(price_list) == 0:
label = 0
else:
label = np.mean(np.array(price_list))
return (os.path.split(path)[1], label)
for file in os.listdir(path):
if str(file)!='.DS_Store' and os.path.splitext(file)[-1]=='.shp':
if len(fiona.open(os.path.join(path, file))) != 0:
label_list.append(list(label(os.path.join(path, file))))
for item in label_list:
if item[1]<3200:
item[1]='low'
else:
item[1]='high'
label=open('label_line.txt','w')
label.writelines(str(label_list))
label.close()
在这里,我设置的分类阈值是3200,并且设置成二分类任务。因为图像数量有限(4000张左右)。
所有数据已经处理好了,下一期将带来训练部分。