Skip to content

Commit b661b3c

Browse files
authored
Exclude areas (#3)
* done
1 parent 90e9d63 commit b661b3c

File tree

6 files changed

+95
-60
lines changed

6 files changed

+95
-60
lines changed

lib/helpers.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import cv2
23
import math
34
import random
@@ -31,7 +32,7 @@ def outside(point, lefttop, rightbot):
3132
or point[1] < lefttop[1] \
3233
or point[0] >= rightbot[0] \
3334
or point[1] >= rightbot[1]
34-
35+
3536
class MercatorPainter:
3637
# paints an area with dots and lines representing positive examples.
3738
# everything not painted over is supposed to be negative.
@@ -80,11 +81,17 @@ def add_line_wgs(self, latlng1, latlng2, width):
8081
p2 = self.wgs2px(latlng2)
8182
cv2.line(self.canvas, p1, p2, 255, width)
8283

83-
def add_polyline_wgs(self, latlngs, width):
84-
pixels = [self.wgs2px(l) for l in latlngs]
84+
def add_polyline_wgs(self, latlngs, width=1):
85+
pixels = [self.wgs2px(ll) for ll in latlngs]
86+
pixels = np.array(pixels)
87+
# lineType means 4-connected or 8-connected
88+
cv2.polylines(self.canvas, [pixels], True, 255, width, lineType=4)
89+
90+
def add_fillpoly_wgs(self, latlngs):
91+
pixels = [self.wgs2px(ll) for ll in latlngs]
8592
pixels = np.array(pixels)
8693
# lineType means 4-connected or 8-connected
87-
cv2.polylines(self.canvas, [pixels], False, 255, width, lineType=4)
94+
cv2.fillPoly(self.canvas, [pixels], 255, lineType=4)
8895

8996
def show(self):
9097
# displays the canvas at native resolution
@@ -93,13 +100,14 @@ def show(self):
93100

94101
def show_fixedwindow(self, h, w):
95102
# displays the canvas resized to specified dimensions
103+
# useful for very small or large maps
96104
cv2.namedWindow('canvas-fixed', cv2.WINDOW_NORMAL)
97105
cv2.resizeWindow('canvas-fixed', h, w)
98106
cv2.imshow('canvas-fixed', self.canvas)
99107
cv2.waitKey(0)
100108

101109
def build_index(self):
102-
# creates lookup dict of occupied pixels
110+
# creates dict of occupied pixels for fast lookup
103111
d = {}
104112
for y in range(self.height):
105113
ty = self.tymin + y
@@ -115,7 +123,7 @@ def build_index(self):
115123
self.dict_busy = d
116124

117125
def build_index_free(self):
118-
# creates lookup dict of non-occupied pixels
126+
# creates dict of non-occupied pixels for even faster lookup
119127
d = {}
120128
for y in range(self.height):
121129
ty = self.tymin + y
@@ -205,18 +213,33 @@ def random_free(self):
205213
if self.dict_free[tx] == []:
206214
self.dict_free.pop(tx)
207215
return tile
216+
217+
def latlngs_from_wkt(string):
218+
latlngs = []
219+
polys = string.splitlines()
220+
for poly in polys:
221+
strs = re.findall(r"[-]?\d*\.\d+|\d+", poly)
222+
nums = list(map(float, strs))
223+
lngs = nums[::2]
224+
lats = nums[1::2]
225+
latlngs.append(list(zip(lats,lngs)))
226+
return latlngs
208227

209228
if __name__ == "__main__":
210-
box = (27.4026,53.8306,27.7003,53.9739)
211-
lamps = [(53.85, 27.6), (53.92, 27.5)]
212-
roads = [[(53.85, 27.5), (53.92, 27.6)]]
229+
# box = (27.4026,53.8306,27.7003,53.9739)
230+
# lamps = [(53.85, 27.6), (53.92, 27.5)]
231+
# roads = [[(53.85, 27.5), (53.92, 27.6)]]
232+
#
233+
# mp = MercatorPainter(layers.maxar, *box, z=18)
234+
# mp.add_dots_wgs(lamps)
235+
#
236+
# for nodes in roads:
237+
# mp.add_polyline_wgs(nodes, width=2)
238+
#
239+
# mp.show()
240+
#
241+
# print(mp.contains((302304, 168755)))
213242

214-
mp = MercatorPainter(layers.maxar, *box, z=18)
215-
mp.add_dots_wgs(lamps)
216-
217-
for nodes in roads:
218-
mp.add_polyline_wgs(nodes, width=2)
219-
220-
mp.show()
221-
222-
print(mp.contains((302304, 168755)))
243+
s = """POLYGON ((1.1 .2, 1 2.2, 1 -2.2))
244+
POLYGON ((1 2, 1 2, 1 2))"""
245+
print(latlngs_from_wkt(s))

lib/layers.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import numpy as np
88
from pathlib import Path
99

10+
# every WGS coord here is (LATITUDE, LONGITUDE) - note the order!
11+
# LAT means up-down/north-south, LNG means left-right/west-east
12+
1013
TILESIZE = 256
1114

1215
def get_or_sleep(sess, url, t=0.1):
@@ -115,8 +118,8 @@ def tile_at_wgs(self, latlng, z):
115118
return (tx, ty)
116119

117120
def gettile_wgs(self, latlng, z, skipedge=False, edge=16):
118-
# returns tile at location (as filename string)
119-
# returns None if skipedge is enabled and location is indeed close to edge
121+
# returns tile at WGS point (as filename string)
122+
# returns None if skipedge is enabled and the point is indeed close to edge
120123
scale = 1 << z
121124
wc = project2web(latlng)
122125
# pixel in world
@@ -126,14 +129,14 @@ def gettile_wgs(self, latlng, z, skipedge=False, edge=16):
126129
tx = math.floor(px / TILESIZE)
127130
ty = math.floor(py / TILESIZE)
128131
# pixel in tile
129-
rx = (px - tx) % TILESIZE
130-
ry = (py - ty) % TILESIZE
132+
rx = px - tx * TILESIZE
133+
ry = py - ty * TILESIZE
131134

132135
if skipedge:
133-
edge = (rx < edge) or (rx >= TILESIZE-edge) \
134-
or (ry < edge) or (ry >= TILESIZE-edge)
135-
if edge:
136-
print("edge")
136+
outlier = (rx < edge) or (rx >= TILESIZE-edge) \
137+
or (ry < edge) or (ry >= TILESIZE-edge)
138+
if outlier:
139+
print("outlier")
137140
return None
138141

139142
fname = self.download(tx, ty, z)

make_buildings.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,55 @@ def mil(fp):
1414
return math.floor(fp*1000000)
1515

1616
if __name__ == "__main__":
17-
# box = (27.4631,53.9359,27.4989,53.9566)
17+
# bounding box to work on
1818
box_minsk = (27.4013,53.8157,27.7827,53.9739)
1919
# box_hrodna = (23.7483,53.5909,23.9145,53.7544)
2020

21+
# These areas are where active construction/destruction is
22+
# going on and the satellite imagery is outdated compared to OSM.
23+
# Use http://geojson.io to draw, then export as WKT
24+
with open("make_buildings_except.wkt") as reader:
25+
wkt = reader.read()
26+
# or just initialise with [] if you have nothing to exclude
27+
outdated_polys = helpers.latlngs_from_wkt(wkt)
28+
2129
q = loaders.Querier()
2230
ways = q.query_buildings(*box_minsk)
2331

2432
IMZ = 18
33+
LIMIT = 10000
34+
count = 0
2535
target = helpers.cleandir(f"buildings/yes")
2636
for wayid,nodes in ways:
2737
print(wayid)
28-
2938
# fetch every tile the building has a node at
30-
for i,node in enumerate(nodes):
31-
fname = layers.maxar.gettile_wgs(node, IMZ, skipedge=True, edge=16)
32-
if fname is None:
39+
for node in nodes:
40+
fpath = layers.maxar.gettile_wgs(node, IMZ, skipedge=True, edge=24)
41+
if fpath is None:
3342
continue
34-
dst = str(target)
35-
shutil.copy(fname, dst)
36-
37-
# OR focus on the building and crop
38-
# image = layers.maxar.tiles_way(nodes, IMZ, pad_pct=0.25, pad_px=48)
39-
# dst = str(target / f"m{wayid}.jpg")
40-
# cv2.imwrite(dst, image)
43+
fname = os.path.basename(fpath)
44+
dst = target / fname
45+
if not os.path.isfile(dst):
46+
shutil.copy(fpath, dst)
47+
count += 1
48+
49+
if count >= LIMIT:
50+
break
4151

4252
mp = helpers.MercatorPainter(layers.maxar, *box_minsk, IMZ)
4353
for _, nodes in ways:
44-
mp.add_polyline_wgs(nodes, width=1)
45-
# TODO: add restricted zones where imagery is outdated
46-
# use cv2.polyline(is_closed=True)
47-
54+
mp.add_polyline_wgs(nodes)
55+
for poly in outdated_polys:
56+
mp.add_fillpoly_wgs(poly)
57+
4858
target = helpers.cleandir(f"buildings/no")
4959

5060
train = []
51-
TRAIN = 5755
5261
start = time.time()
53-
for i in range(TRAIN):
62+
for i in range(LIMIT):
5463
tx, ty = mp.random_negative()
5564
print(tx,ty)
5665
fname = layers.maxar.download(tx, ty, IMZ)
57-
# wgs = layers.wgs_at_tile(tx, ty, IMZ)
58-
# print(f"https://www.openstreetmap.org/edit#map=18/{wgs[0]}/{wgs[1]}")
59-
# img = cv2.imread(fname)
60-
# cv2.imshow("sanity check", img)
61-
# cv2.waitKey(0)
6266

6367
dst = str(target / f"m_x{tx}y{ty}.jpg")
6468
if not os.path.exists(dst):

make_buildings_except.wkt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
POLYGON ((27.526330947875977 53.872418900515235, 27.523326873779297 53.869736775642515, 27.538089752197266 53.85994302920735, 27.558259963989258 53.857842263021006, 27.561392784118652 53.86892704376814, 27.552337646484375 53.87206466746561, 27.53937721252441 53.87305145207004, 27.526330947875977 53.872418900515235))
2+
POLYGON ((27.59085416793823 53.84343778491824, 27.588472366333008 53.842361680295205, 27.590253353118907 53.839031910806206, 27.59011387825014 53.83727196370016, 27.594963312149066 53.83596145144472, 27.598900794982928 53.83719599309501, 27.59085416793823 53.84343778491824))
3+
POLYGON ((27.52163171768188 53.906335460705726, 27.520623207092285 53.903908344100664, 27.52152442932129 53.90282115239138, 27.525236606597897 53.90402211834181, 27.523412704467773 53.90631017896792, 27.52163171768188 53.906335460705726))
4+
POLYGON ((27.55069077014923 53.90880983675384, 27.551409602165222 53.90785233697202, 27.552316188812256 53.90823786757667, 27.55150616168976 53.909008918110516, 27.55069077014923 53.90880983675384))
5+
POLYGON ((27.600810527801514 53.837841738845775, 27.60177612304687 53.83675282848474, 27.60791301727295 53.83719599309501, 27.609500885009766 53.83904457202817, 27.61707544326782 53.83989286518068, 27.61662483215332 53.84076646255469, 27.604007720947262 53.83910787808068, 27.600810527801514 53.837841738845775))
6+
POLYGON ((27.542606592178345 53.8908601761207, 27.546662092208862 53.887951598930506, 27.550975084304806 53.88825511209557, 27.54966616630554 53.88931739081768, 27.542606592178345 53.8908601761207))
7+
POLYGON ((27.558270692825317 53.89635430547624, 27.562283277511597 53.89519105182811, 27.56191849708557 53.89751120497534, 27.558270692825317 53.89635430547624))

make_roofshapes.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,11 @@ def mil(fp):
3939
ways[cat] = ways[cat][:LIMIT]
4040
for wayid, nodes in ways[cat]:
4141
image = layers.maxar.tiles_way(nodes, IMZ, pad_pct=0.25, pad_px=48)
42-
# print(image.shape)
4342
if helpers.outside(image.shape[:2], (128,128), (1024,1024)):
4443
continue
45-
# cv2.imshow('crop', image)
46-
# cv2.waitKey(0)
47-
# cv2.destroyAllWindows()
4844
dst = str(target / f"m{wayid}.jpg")
4945
cv2.imwrite(dst, image)
50-
# image = layers.dg.tiles_way(nodes, IMZ)
51-
# dst = str(target / f"d{wayid}.jpg")
52-
# cv2.imwrite(dst, image)
46+
5347

5448
tarball = "./roofshapes.tar"
5549
if os.path.exists(tarball):

readme.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
This is a collection of scripts to generate machine learning training images using OpenStreetMap data and satellite imagery providers. I tend to use [Maxar](https://github.com/osmlab/editor-layer-index/pull/655) layer because it's recent, crispy, and shot at almost vertical angle. At other locations results may vary.
1+
This is a collection of scripts to generate machine learning training images using OpenStreetMap data and satellite imagery providers. The scripts scan through certain objects in a specified area (defined by bounding box) and generate a .tar file with images, labeled by folder names. Upload that to your NVidia farm and train.
2+
3+
I tend to use [Maxar](https://github.com/osmlab/editor-layer-index/pull/655) layer because it's recent, crispy, and shot at almost vertical angle. At other locations results may vary. You can add new imagery providers to `layers.py`.
24

35
The training uses resnet34 architecture with fast.ai library. Every table below lists `error_rate` metric, which is just a percentage of incorrect predictions on the validation set.
46

7+
If you have a high error rate, fast.ai provides a great debugging tool `top_losses()`. It will give you images that confuse the net for some reason. It translates into unmapped areas or outdated imagery (or a bug in data collection script).
8+
59
# Streetlamps
610

711
Task: Classify imagery tiles with [streetlamp](https://wiki.openstreetmap.org/wiki/Tag:highway%3Dstreet_lamp)s vs. no streetlamps.
@@ -135,9 +139,9 @@ For every node of every building, fetch the tile it belongs to. Don't use the ti
135139

136140
| | Frozen, 4 epoch train | Unfreeze, 6 epochs | 8 more epochs |
137141
| -------------------------------- | --------------------- | ------------------ | ------------- |
138-
| size=256 no flip, max_rotate=0 | 3.0% | 3.0% | |
139-
| size=256 no flip, max_rotate=20 | 2.3% | 2.7% | |
142+
| size=256, no flip, max_rotate=0 | 3.0% | 3.0% | |
143+
| size=256, no flip, max_rotate=20 | 2.3% | 2.7% | |
140144
| size=256, no flip, max_rotate=40 | 3.3% | 3.3% | |
141-
| | | | |
145+
| size=256, no flip, max_rotate=20 | 2.5% | | |
142146
| | | | |
143147
| | | | |

0 commit comments

Comments
 (0)