如何将手绘栅格TIFF地图转为平滑矢量中心线Shapefile
解决手绘灰度TIFF转平滑Shapefile的细碎多边形问题
问题根源
手绘地图的灰度TIFF天生存在像素不均、线条边缘毛刺、局部细碎噪点的问题,直接用gdal.Polygonize会把每个灰度波动的小区域都生成独立多边形——电脑绘制的图线条规整,自然不会有这麻烦。必须先做图像预处理把线条捋顺,再换合适的矢量化方法,不能直接硬转。
第一步:图像预处理(用OpenCV+GDAL)
先把手绘TIFF转成清晰的二值化单线条,消掉噪点和平滑毛刺:
- 二值化:把灰度图切成黑白两色,只保留深色的手绘线条,过滤浅灰背景噪点。
- 形态学降噪:先腐蚀掉细碎的小噪点,再膨胀补回线条的正常厚度,让线条更连贯。
- 平滑处理:用高斯模糊弱化线条边缘的锯齿感。
预处理代码示例
import cv2 import numpy as np from osgeo import gdal import os # 读取原TIFF并保留地理信息 ds = gdal.Open('input_image3.tif') raster = ds.ReadAsArray() geo_transform = ds.GetGeoTransform() projection = ds.GetProjection() ds = None # 1. 二值化:阈值根据手绘线条深浅调整,偏暗就调低127 _, binary = cv2.threshold(raster, 127, 255, cv2.THRESH_BINARY_INV) # 2. 形态学操作:先腐蚀消噪,再膨胀补全线条 kernel = np.ones((3, 3), np.uint8) binary = cv2.erode(binary, kernel, iterations=1) binary = cv2.dilate(binary, kernel, iterations=1) # 3. 高斯模糊平滑线条边缘 binary = cv2.GaussianBlur(binary, (3, 3), 0) # 保存预处理后的TIFF,保留原地理坐标 driver = gdal.GetDriverByName('GTiff') out_path = 'preprocessed.tif' if os.path.exists(out_path): driver.DeleteDataSource(out_path) out_ds = driver.Create(out_path, binary.shape[1], binary.shape[0], 1, gdal.GDT_Byte) out_ds.SetGeoTransform(geo_transform) out_ds.SetProjection(projection) out_band = out_ds.GetRasterBand(1) out_band.WriteArray(binary) out_band.FlushCache() out_ds = None
第二步:正确矢量化(生成平滑线条)
原来的gdal.Polygonize是用来生成多边形的,直接放到LineString图层会导致逻辑错误。应该用gdal.TraceContours直接追踪线条轮廓,再做简化处理:
矢量化代码示例
from osgeo import ogr, gdal, osr import os input_tiff = 'preprocessed.tif' output_shapefile = 'outputs/smooth_lines.shp' # 读取预处理后的TIFF ds = gdal.Open(input_tiff) band = ds.GetRasterBand(1) spatial_ref = osr.SpatialReference(wkt=ds.GetProjection()) # 创建输出Shapefile,先删除已有文件 driver = ogr.GetDriverByName('ESRI Shapefile') if os.path.exists(output_shapefile): driver.DeleteDataSource(output_shapefile) shapefile = driver.CreateDataSource(output_shapefile) # 创建LineString类型的图层 layer = shapefile.CreateLayer('lines', srs=spatial_ref, geom_type=ogr.wkbLineString) # 添加ID字段(可选) layer.CreateField(ogr.FieldDefn('ID', ogr.OFTInteger)) # 追踪二值图的黑色线条轮廓(阈值设为0) gdal.TraceContours(band, 0, 0, layer, 0, 0, callback=None) # 用Douglas-Peucker算法简化线条,减少细碎顶点 feature = layer.GetNextFeature() while feature: geom = feature.GetGeometryRef() # 简化阈值:单位与地图坐标一致,值越大线条越平滑,按需调整 simplified_geom = geom.Simplify(1.0) feature.SetGeometry(simplified_geom) layer.SetFeature(feature) feature = layer.GetNextFeature() # 清理资源 ds = None shapefile = None print("平滑线条转换完成")
关键调参提示
- 二值化阈值:手绘线条偏浅就调低
127,背景噪点多就调高,确保只保留目标线条。 - 形态学核大小:线条细用
(2,2)的核,线条粗用(3,3),避免把线条弄断。 - 简化阈值:
Simplify(1.0)的数值是坐标单位(比如米制地图就是1米误差),根据精度需求调整。 - 地理信息保留:预处理时必须复制原TIFF的地理变换和投影,否则Shapefile会出现坐标偏移。
内容的提问来源于stack exchange,提问作者Saddam Hussain
相关产品推荐
相关产品推荐

