Spaces:
Sleeping
Sleeping
Commit ·
ce994d8
1
Parent(s): b7a71f3
Improve building detection: fix tile size to 256, lower thresholds, add SSIM, smarter scoring
Browse files- Dockerfile +1 -1
- app/detection_engine.py +140 -32
- app/model_inference.py +15 -8
- templates/index.html +2 -2
Dockerfile
CHANGED
|
@@ -19,7 +19,7 @@ WORKDIR /app
|
|
| 19 |
|
| 20 |
# Build-time info + cache-bust:
|
| 21 |
# Changing APP_BUILD forces Docker to re-run subsequent layers (including pip install).
|
| 22 |
-
ARG APP_BUILD=
|
| 23 |
ENV APP_BUILD=${APP_BUILD}
|
| 24 |
RUN echo "Docker build start: APP_BUILD=${APP_BUILD}" && python -V
|
| 25 |
|
|
|
|
| 19 |
|
| 20 |
# Build-time info + cache-bust:
|
| 21 |
# Changing APP_BUILD forces Docker to re-run subsequent layers (including pip install).
|
| 22 |
+
ARG APP_BUILD=17
|
| 23 |
ENV APP_BUILD=${APP_BUILD}
|
| 24 |
RUN echo "Docker build start: APP_BUILD=${APP_BUILD}" && python -V
|
| 25 |
|
app/detection_engine.py
CHANGED
|
@@ -593,7 +593,7 @@ def ai_deep_learning_method(img1, img2, sensitivity=0.5):
|
|
| 593 |
from .model_inference import is_model_available, predict_change_mask
|
| 594 |
|
| 595 |
if is_model_available():
|
| 596 |
-
threshold = 0.
|
| 597 |
try:
|
| 598 |
change_mask, score_map = predict_change_mask(
|
| 599 |
img1, img2, threshold=threshold)
|
|
@@ -705,7 +705,7 @@ def _clean_mask(mask, sensitivity=0.5, border_margin=12):
|
|
| 705 |
filled = cv2.dilate(filled, k_break, iterations=1)
|
| 706 |
|
| 707 |
# 7. Component-level filtering: remove tiny survivors and elongated noise
|
| 708 |
-
min_component_px = max(
|
| 709 |
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(filled, connectivity=8)
|
| 710 |
clean = np.zeros_like(filled)
|
| 711 |
for i in range(1, num_labels):
|
|
@@ -980,12 +980,33 @@ def _extract_differential_features(before_crop, after_crop):
|
|
| 980 |
lines_a, linelen_a = _count_line_segments(gray_a)
|
| 981 |
corners_b = _count_corners(gray_b)
|
| 982 |
corners_a = _count_corners(gray_a)
|
|
|
|
| 983 |
hull_a = _rectangular_hull_ratio(gray_a)
|
| 984 |
|
| 985 |
lab_b = cv2.cvtColor(before_crop, cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 986 |
lab_a = cv2.cvtColor(after_crop, cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 987 |
lab_dist = float(np.mean(np.sqrt(np.sum((lab_a - lab_b) ** 2, axis=2))))
|
| 988 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
return {
|
| 990 |
"before": feat_b, "after": feat_a,
|
| 991 |
"delta_ndvi": feat_a["ndvi"] - feat_b["ndvi"],
|
|
@@ -1000,8 +1021,10 @@ def _extract_differential_features(before_crop, after_crop):
|
|
| 1000 |
"delta_corners": corners_a - corners_b,
|
| 1001 |
"lines_after": lines_a, "corners_after": corners_a,
|
| 1002 |
"lines_before": lines_b, "corners_before": corners_b,
|
|
|
|
| 1003 |
"hull_ratio_after": hull_a,
|
| 1004 |
"lab_color_distance": lab_dist,
|
|
|
|
| 1005 |
}
|
| 1006 |
|
| 1007 |
|
|
@@ -1102,60 +1125,145 @@ def classify_object_type(image_region, bbox, before_region=None):
|
|
| 1102 |
# ---- New Construction/Building ----
|
| 1103 |
bld = 0.0
|
| 1104 |
if diff:
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
if
|
| 1108 |
-
bld += 0.
|
| 1109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1110 |
bld += 0.15
|
| 1111 |
-
|
| 1112 |
-
bld += 0.
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1116 |
bld += 0.10
|
| 1117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1118 |
bld += 0.08
|
| 1119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1120 |
bld += 0.05
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1121 |
else:
|
| 1122 |
if feat_a["orientation_entropy"] < 2.5:
|
| 1123 |
bld += 0.18
|
| 1124 |
if feat_a["color_homogeneity"] < 28:
|
| 1125 |
bld += 0.15
|
| 1126 |
-
if 1.0 <= aspect_ratio <=
|
| 1127 |
-
bld += 0.12
|
| 1128 |
-
if 0.3 <= compactness <= 0.9:
|
| 1129 |
bld += 0.10
|
| 1130 |
-
if
|
| 1131 |
-
bld += 0.12
|
| 1132 |
-
if feat_a["glcm_contrast"] > 400:
|
| 1133 |
bld += 0.10
|
| 1134 |
-
if feat_a["
|
|
|
|
|
|
|
| 1135 |
bld += 0.10
|
| 1136 |
-
if
|
|
|
|
|
|
|
| 1137 |
bld += 0.08
|
| 1138 |
-
if area >
|
| 1139 |
bld += 0.05
|
| 1140 |
scores["New Construction/Building"] = bld
|
| 1141 |
|
| 1142 |
# ---- Demolition/Clearing ----
|
| 1143 |
demo = 0.0
|
| 1144 |
if diff:
|
| 1145 |
-
|
|
|
|
| 1146 |
demo += 0.22
|
| 1147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1148 |
demo += 0.18
|
| 1149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1150 |
demo += 0.15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1151 |
if diff["delta_texture_std"] > 8:
|
| 1152 |
-
demo += 0.
|
| 1153 |
if diff["delta_brightness"] > 10:
|
| 1154 |
-
demo += 0.
|
| 1155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1156 |
demo += 0.08
|
| 1157 |
-
|
| 1158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1159 |
else:
|
| 1160 |
if feat_a["texture_std"] > 30:
|
| 1161 |
demo += 0.18
|
|
@@ -1904,7 +2012,7 @@ def analyze_change_regions(change_mask, image, min_area=400, use_ensemble=True,
|
|
| 1904 |
# - keeps sensitivity on smaller images
|
| 1905 |
# - suppresses speckle noise on larger images
|
| 1906 |
if min_area is None:
|
| 1907 |
-
min_area = int(max(
|
| 1908 |
|
| 1909 |
for i in range(1, num_labels):
|
| 1910 |
raw_area = stats[i, cv2.CC_STAT_AREA]
|
|
|
|
| 593 |
from .model_inference import is_model_available, predict_change_mask
|
| 594 |
|
| 595 |
if is_model_available():
|
| 596 |
+
threshold = 0.25 + (1.0 - sensitivity) * 0.25
|
| 597 |
try:
|
| 598 |
change_mask, score_map = predict_change_mask(
|
| 599 |
img1, img2, threshold=threshold)
|
|
|
|
| 705 |
filled = cv2.dilate(filled, k_break, iterations=1)
|
| 706 |
|
| 707 |
# 7. Component-level filtering: remove tiny survivors and elongated noise
|
| 708 |
+
min_component_px = max(50, int(h * w * 0.00003))
|
| 709 |
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(filled, connectivity=8)
|
| 710 |
clean = np.zeros_like(filled)
|
| 711 |
for i in range(1, num_labels):
|
|
|
|
| 980 |
lines_a, linelen_a = _count_line_segments(gray_a)
|
| 981 |
corners_b = _count_corners(gray_b)
|
| 982 |
corners_a = _count_corners(gray_a)
|
| 983 |
+
hull_b = _rectangular_hull_ratio(gray_b)
|
| 984 |
hull_a = _rectangular_hull_ratio(gray_a)
|
| 985 |
|
| 986 |
lab_b = cv2.cvtColor(before_crop, cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 987 |
lab_a = cv2.cvtColor(after_crop, cv2.COLOR_RGB2LAB).astype(np.float32)
|
| 988 |
lab_dist = float(np.mean(np.sqrt(np.sum((lab_a - lab_b) ** 2, axis=2))))
|
| 989 |
|
| 990 |
+
# Fast SSIM approximation using cv2 (avoids scikit-image dependency)
|
| 991 |
+
ssim_val = 1.0
|
| 992 |
+
try:
|
| 993 |
+
if gray_b.shape == gray_a.shape and gray_b.shape[0] >= 7 and gray_b.shape[1] >= 7:
|
| 994 |
+
C1 = (0.01 * 255) ** 2
|
| 995 |
+
C2 = (0.03 * 255) ** 2
|
| 996 |
+
fb = gray_b.astype(np.float64)
|
| 997 |
+
fa = gray_a.astype(np.float64)
|
| 998 |
+
mu_b = cv2.GaussianBlur(fb, (11, 11), 1.5)
|
| 999 |
+
mu_a = cv2.GaussianBlur(fa, (11, 11), 1.5)
|
| 1000 |
+
sig_b2 = cv2.GaussianBlur(fb * fb, (11, 11), 1.5) - mu_b * mu_b
|
| 1001 |
+
sig_a2 = cv2.GaussianBlur(fa * fa, (11, 11), 1.5) - mu_a * mu_a
|
| 1002 |
+
sig_ba = cv2.GaussianBlur(fb * fa, (11, 11), 1.5) - mu_b * mu_a
|
| 1003 |
+
numer = (2 * mu_b * mu_a + C1) * (2 * sig_ba + C2)
|
| 1004 |
+
denom = (mu_b ** 2 + mu_a ** 2 + C1) * (sig_b2 + sig_a2 + C2)
|
| 1005 |
+
ssim_map = numer / (denom + 1e-12)
|
| 1006 |
+
ssim_val = float(np.mean(ssim_map))
|
| 1007 |
+
except Exception:
|
| 1008 |
+
pass
|
| 1009 |
+
|
| 1010 |
return {
|
| 1011 |
"before": feat_b, "after": feat_a,
|
| 1012 |
"delta_ndvi": feat_a["ndvi"] - feat_b["ndvi"],
|
|
|
|
| 1021 |
"delta_corners": corners_a - corners_b,
|
| 1022 |
"lines_after": lines_a, "corners_after": corners_a,
|
| 1023 |
"lines_before": lines_b, "corners_before": corners_b,
|
| 1024 |
+
"hull_ratio_before": hull_b,
|
| 1025 |
"hull_ratio_after": hull_a,
|
| 1026 |
"lab_color_distance": lab_dist,
|
| 1027 |
+
"ssim": ssim_val,
|
| 1028 |
}
|
| 1029 |
|
| 1030 |
|
|
|
|
| 1125 |
# ---- New Construction/Building ----
|
| 1126 |
bld = 0.0
|
| 1127 |
if diff:
|
| 1128 |
+
# Edge density increase: strong for buildings, lower threshold to catch smaller ones
|
| 1129 |
+
ded = diff["delta_edge_density"]
|
| 1130 |
+
if ded > 20:
|
| 1131 |
+
bld += 0.22
|
| 1132 |
+
elif ded > 10:
|
| 1133 |
+
bld += 0.16
|
| 1134 |
+
elif ded > 5:
|
| 1135 |
+
bld += 0.08
|
| 1136 |
+
|
| 1137 |
+
# More ordered structure (lower entropy = more regular geometry)
|
| 1138 |
+
doe = diff["delta_orientation_entropy"]
|
| 1139 |
+
if doe < -0.6:
|
| 1140 |
bld += 0.15
|
| 1141 |
+
elif doe < -0.2:
|
| 1142 |
+
bld += 0.10
|
| 1143 |
+
|
| 1144 |
+
# New straight lines appearing
|
| 1145 |
+
dl = diff["delta_lines"]
|
| 1146 |
+
if dl > 8:
|
| 1147 |
+
bld += 0.16
|
| 1148 |
+
elif dl > 3:
|
| 1149 |
+
bld += 0.10
|
| 1150 |
+
elif dl > 1:
|
| 1151 |
+
bld += 0.05
|
| 1152 |
+
|
| 1153 |
+
# New corners appearing
|
| 1154 |
+
dc = diff["delta_corners"]
|
| 1155 |
+
if dc > 10:
|
| 1156 |
+
bld += 0.14
|
| 1157 |
+
elif dc > 4:
|
| 1158 |
+
bld += 0.10
|
| 1159 |
+
elif dc > 1:
|
| 1160 |
+
bld += 0.05
|
| 1161 |
+
|
| 1162 |
+
# Vegetation replaced by non-vegetation
|
| 1163 |
+
if diff["after"]["ndvi"] < 0.08 and diff["before"]["ndvi"] > 0.02:
|
| 1164 |
+
bld += 0.10
|
| 1165 |
+
# Brightness increase (concrete/roofing vs bare ground)
|
| 1166 |
+
if diff["delta_brightness"] > 8:
|
| 1167 |
+
bld += 0.06
|
| 1168 |
+
# Rectangular shape in after image
|
| 1169 |
+
if diff["hull_ratio_after"] > 0.50:
|
| 1170 |
bld += 0.10
|
| 1171 |
+
elif diff["hull_ratio_after"] > 0.35:
|
| 1172 |
+
bld += 0.05
|
| 1173 |
+
# After image has structural features even if delta is modest
|
| 1174 |
+
if diff["lines_after"] > 4 and diff["corners_after"] > 6:
|
| 1175 |
+
bld += 0.08
|
| 1176 |
+
# LAB color distance (significant visual change)
|
| 1177 |
+
if diff["lab_color_distance"] > 25:
|
| 1178 |
bld += 0.08
|
| 1179 |
+
elif diff["lab_color_distance"] > 15:
|
| 1180 |
+
bld += 0.04
|
| 1181 |
+
# SSIM: low = big structural change; very important for building detection
|
| 1182 |
+
ssim = diff.get("ssim", 1.0)
|
| 1183 |
+
if ssim < 0.4:
|
| 1184 |
+
bld += 0.14
|
| 1185 |
+
elif ssim < 0.6:
|
| 1186 |
+
bld += 0.10
|
| 1187 |
+
elif ssim < 0.75:
|
| 1188 |
bld += 0.05
|
| 1189 |
+
# Low NDVI + high edge density in after = likely built structure
|
| 1190 |
+
if diff["after"]["ndvi"] < 0.05 and diff["after"]["edge_density"] > 25:
|
| 1191 |
+
bld += 0.08
|
| 1192 |
+
# New rectangular shape appearing (hull increased)
|
| 1193 |
+
hull_delta = diff["hull_ratio_after"] - diff.get("hull_ratio_before", 0)
|
| 1194 |
+
if hull_delta > 0.2:
|
| 1195 |
+
bld += 0.06
|
| 1196 |
+
if 1.0 <= aspect_ratio <= 5.0:
|
| 1197 |
+
bld += 0.06
|
| 1198 |
+
if area > 600:
|
| 1199 |
+
bld += 0.04
|
| 1200 |
else:
|
| 1201 |
if feat_a["orientation_entropy"] < 2.5:
|
| 1202 |
bld += 0.18
|
| 1203 |
if feat_a["color_homogeneity"] < 28:
|
| 1204 |
bld += 0.15
|
| 1205 |
+
if 1.0 <= aspect_ratio <= 5.0:
|
|
|
|
|
|
|
| 1206 |
bld += 0.10
|
| 1207 |
+
if 0.2 <= compactness <= 0.95:
|
|
|
|
|
|
|
| 1208 |
bld += 0.10
|
| 1209 |
+
if feat_a["edge_density"] > 25:
|
| 1210 |
+
bld += 0.14
|
| 1211 |
+
if feat_a["glcm_contrast"] > 300:
|
| 1212 |
bld += 0.10
|
| 1213 |
+
if feat_a["saturation"] < 100:
|
| 1214 |
+
bld += 0.08
|
| 1215 |
+
if 30 <= feat_a["brightness"] <= 95:
|
| 1216 |
bld += 0.08
|
| 1217 |
+
if area > 600:
|
| 1218 |
bld += 0.05
|
| 1219 |
scores["New Construction/Building"] = bld
|
| 1220 |
|
| 1221 |
# ---- Demolition/Clearing ----
|
| 1222 |
demo = 0.0
|
| 1223 |
if diff:
|
| 1224 |
+
ded_neg = diff["delta_edge_density"]
|
| 1225 |
+
if ded_neg < -20:
|
| 1226 |
demo += 0.22
|
| 1227 |
+
elif ded_neg < -10:
|
| 1228 |
+
demo += 0.16
|
| 1229 |
+
elif ded_neg < -5:
|
| 1230 |
+
demo += 0.08
|
| 1231 |
+
|
| 1232 |
+
dl_neg = diff["delta_lines"]
|
| 1233 |
+
if dl_neg < -8:
|
| 1234 |
demo += 0.18
|
| 1235 |
+
elif dl_neg < -3:
|
| 1236 |
+
demo += 0.12
|
| 1237 |
+
elif dl_neg < -1:
|
| 1238 |
+
demo += 0.05
|
| 1239 |
+
|
| 1240 |
+
dc_neg = diff["delta_corners"]
|
| 1241 |
+
if dc_neg < -10:
|
| 1242 |
demo += 0.15
|
| 1243 |
+
elif dc_neg < -4:
|
| 1244 |
+
demo += 0.10
|
| 1245 |
+
elif dc_neg < -1:
|
| 1246 |
+
demo += 0.05
|
| 1247 |
+
|
| 1248 |
if diff["delta_texture_std"] > 8:
|
| 1249 |
+
demo += 0.10
|
| 1250 |
if diff["delta_brightness"] > 10:
|
| 1251 |
+
demo += 0.10
|
| 1252 |
+
# Structural features disappeared
|
| 1253 |
+
if diff["lines_before"] > 4 and diff["lines_after"] <= 1:
|
| 1254 |
+
demo += 0.10
|
| 1255 |
+
# Hull ratio dropped (rectangular structure removed)
|
| 1256 |
+
hull_drop = diff.get("hull_ratio_before", 0) - diff["hull_ratio_after"]
|
| 1257 |
+
if hull_drop > 0.2:
|
| 1258 |
demo += 0.08
|
| 1259 |
+
# SSIM confirms big structural change
|
| 1260 |
+
ssim = diff.get("ssim", 1.0)
|
| 1261 |
+
if ssim < 0.5:
|
| 1262 |
+
demo += 0.08
|
| 1263 |
+
if diff["after"]["ndvi"] > 0.03 and diff["before"]["ndvi"] < 0.02:
|
| 1264 |
+
demo += 0.06
|
| 1265 |
+
if area > 500:
|
| 1266 |
+
demo += 0.04
|
| 1267 |
else:
|
| 1268 |
if feat_a["texture_std"] > 30:
|
| 1269 |
demo += 0.18
|
|
|
|
| 2012 |
# - keeps sensitivity on smaller images
|
| 2013 |
# - suppresses speckle noise on larger images
|
| 2014 |
if min_area is None:
|
| 2015 |
+
min_area = int(max(200, min(1000, img_area * 0.00008)))
|
| 2016 |
|
| 2017 |
for i in range(1, num_labels):
|
| 2018 |
raw_area = stats[i, cv2.CC_STAT_AREA]
|
app/model_inference.py
CHANGED
|
@@ -19,7 +19,7 @@ _MODEL = None
|
|
| 19 |
_PROCESSOR = None
|
| 20 |
_DEVICE = None
|
| 21 |
_MODEL_ID = "deepang/adaptformer-LEVIR-CD"
|
| 22 |
-
_TILE_SIZE =
|
| 23 |
_AVAILABLE = None
|
| 24 |
|
| 25 |
|
|
@@ -68,8 +68,9 @@ def _load_model():
|
|
| 68 |
def predict_change_mask(img1, img2, threshold=0.5):
|
| 69 |
"""
|
| 70 |
Run AdaptFormer inference on two RGB numpy arrays (H, W, 3).
|
| 71 |
-
Images are split into overlapping tiles
|
| 72 |
-
|
|
|
|
| 73 |
|
| 74 |
Returns (uint8 mask [0 or 255], float32 score map [0-1]).
|
| 75 |
"""
|
|
@@ -82,7 +83,8 @@ def predict_change_mask(img1, img2, threshold=0.5):
|
|
| 82 |
|
| 83 |
h, w = img1.shape[:2]
|
| 84 |
tile = _TILE_SIZE
|
| 85 |
-
|
|
|
|
| 86 |
|
| 87 |
pad_h = (tile - h % tile) % tile
|
| 88 |
pad_w = (tile - w % tile) % tile
|
|
@@ -94,6 +96,12 @@ def predict_change_mask(img1, img2, threshold=0.5):
|
|
| 94 |
score_sum = np.zeros((ph, pw), dtype=np.float32)
|
| 95 |
count = np.zeros((ph, pw), dtype=np.float32)
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
with torch.no_grad():
|
| 98 |
for y0 in range(0, ph - tile + 1, stride):
|
| 99 |
for x0 in range(0, pw - tile + 1, stride):
|
|
@@ -110,7 +118,6 @@ def predict_change_mask(img1, img2, threshold=0.5):
|
|
| 110 |
logits = outputs.logits
|
| 111 |
probs = torch.softmax(logits, dim=1)
|
| 112 |
|
| 113 |
-
# Class 1 = change
|
| 114 |
prob_map = probs[0, 1].cpu().numpy()
|
| 115 |
|
| 116 |
out_h, out_w = prob_map.shape
|
|
@@ -118,10 +125,10 @@ def predict_change_mask(img1, img2, threshold=0.5):
|
|
| 118 |
prob_map = cv2.resize(prob_map, (tile, tile),
|
| 119 |
interpolation=cv2.INTER_LINEAR)
|
| 120 |
|
| 121 |
-
score_sum[y0:y0+tile, x0:x0+tile] += prob_map
|
| 122 |
-
count[y0:y0+tile, x0:x0+tile] +=
|
| 123 |
|
| 124 |
-
count = np.maximum(count,
|
| 125 |
avg_score = score_sum / count
|
| 126 |
avg_score = avg_score[:h, :w]
|
| 127 |
|
|
|
|
| 19 |
_PROCESSOR = None
|
| 20 |
_DEVICE = None
|
| 21 |
_MODEL_ID = "deepang/adaptformer-LEVIR-CD"
|
| 22 |
+
_TILE_SIZE = 256 # LEVIR-CD native patch size
|
| 23 |
_AVAILABLE = None
|
| 24 |
|
| 25 |
|
|
|
|
| 68 |
def predict_change_mask(img1, img2, threshold=0.5):
|
| 69 |
"""
|
| 70 |
Run AdaptFormer inference on two RGB numpy arrays (H, W, 3).
|
| 71 |
+
Images are split into overlapping 256x256 tiles (matching LEVIR-CD
|
| 72 |
+
training resolution), predicted individually, and stitched back into
|
| 73 |
+
a full-resolution binary mask.
|
| 74 |
|
| 75 |
Returns (uint8 mask [0 or 255], float32 score map [0-1]).
|
| 76 |
"""
|
|
|
|
| 83 |
|
| 84 |
h, w = img1.shape[:2]
|
| 85 |
tile = _TILE_SIZE
|
| 86 |
+
overlap = tile // 4 # 64px overlap
|
| 87 |
+
stride = tile - overlap # 192
|
| 88 |
|
| 89 |
pad_h = (tile - h % tile) % tile
|
| 90 |
pad_w = (tile - w % tile) % tile
|
|
|
|
| 96 |
score_sum = np.zeros((ph, pw), dtype=np.float32)
|
| 97 |
count = np.zeros((ph, pw), dtype=np.float32)
|
| 98 |
|
| 99 |
+
# Blending weight: raised-cosine window avoids hard tile boundary seams
|
| 100 |
+
ramp = np.linspace(0, 1, overlap)
|
| 101 |
+
flat = np.ones(tile - 2 * overlap)
|
| 102 |
+
profile = np.concatenate([ramp, flat, ramp[::-1]])
|
| 103 |
+
weight_2d = np.outer(profile, profile).astype(np.float32)
|
| 104 |
+
|
| 105 |
with torch.no_grad():
|
| 106 |
for y0 in range(0, ph - tile + 1, stride):
|
| 107 |
for x0 in range(0, pw - tile + 1, stride):
|
|
|
|
| 118 |
logits = outputs.logits
|
| 119 |
probs = torch.softmax(logits, dim=1)
|
| 120 |
|
|
|
|
| 121 |
prob_map = probs[0, 1].cpu().numpy()
|
| 122 |
|
| 123 |
out_h, out_w = prob_map.shape
|
|
|
|
| 125 |
prob_map = cv2.resize(prob_map, (tile, tile),
|
| 126 |
interpolation=cv2.INTER_LINEAR)
|
| 127 |
|
| 128 |
+
score_sum[y0:y0+tile, x0:x0+tile] += prob_map * weight_2d
|
| 129 |
+
count[y0:y0+tile, x0:x0+tile] += weight_2d
|
| 130 |
|
| 131 |
+
count = np.maximum(count, 1e-6)
|
| 132 |
avg_score = score_sum / count
|
| 133 |
avg_score = avg_score[:h, :w]
|
| 134 |
|
templates/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>AI Change Detection</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/css/style.css?v=
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
<div class="app">
|
|
@@ -360,6 +360,6 @@
|
|
| 360 |
</div>
|
| 361 |
</div>
|
| 362 |
|
| 363 |
-
<script src="/static/js/app.js?v=
|
| 364 |
</body>
|
| 365 |
</html>
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>AI Change Detection</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/css/style.css?v=23" />
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
<div class="app">
|
|
|
|
| 360 |
</div>
|
| 361 |
</div>
|
| 362 |
|
| 363 |
+
<script src="/static/js/app.js?v=38"></script>
|
| 364 |
</body>
|
| 365 |
</html>
|