refactor: 后处理阈值完全自动化,零人工参数

- keep_largest_object: 每格仅留最大块,不设最低门槛
- remove_small_objects: 统计全局面积中位数,<25%自动判定为噪声
This commit is contained in:
2026-05-08 08:57:48 +08:00
parent efc6704b14
commit 085c27c050
+24 -20
View File
@@ -203,27 +203,36 @@ def gridding(gray: np.ndarray) -> tuple:
# 第四部分:后处理(参考choice.m, choosemaxobj.m # 第四部分:后处理(参考choice.m, choosemaxobj.m
# ============================================================ # ============================================================
def remove_small_objects(binary: np.ndarray, cell_area: int = 1225, pct: float = 0.02) -> np.ndarray: def remove_small_objects(binary: np.ndarray) -> np.ndarray:
"""去除面积小于 cell_area*pct 的连通域(pct默认2%""" """
min_size = int(cell_area * pct) 自动去除小连通域。
统计所有连通域的面积中位数,小于中位数 25% 的视为噪声,
自动剔除,不需要人设定阈值。
"""
labeled, num = ndimage.label(binary) labeled, num = ndimage.label(binary)
if num == 0:
return binary
# 统计每个连通域的面积
areas = [int(np.sum(labeled == i)) for i in range(1, num + 1)]
median_area = np.median(areas)
min_size = max(1, int(median_area * 0.25)) # 中位数的25%,最少1像素
result = binary.copy() result = binary.copy()
for i in range(1, num + 1): for i in range(1, num + 1):
if np.sum(labeled == i) < min_size: if areas[i - 1] < min_size:
result[labeled == i] = 0 result[labeled == i] = 0
return result return result
def keep_largest_object(binary: np.ndarray, cell_area: int = 1225, pct: float = 0.01) -> np.ndarray: def keep_largest_object(binary: np.ndarray) -> np.ndarray:
"""只保留最大连通域,若最大块面积 < cell_area*pct 则整块抹黑(pct默认1%""" """每个格子里只保留面积最大连通域(无需设定阈值"""
min_size = int(cell_area * pct)
labeled, num = ndimage.label(binary) labeled, num = ndimage.label(binary)
if num == 0: if num == 0:
return binary
areas = [int(np.sum(labeled == i)) for i in range(1, num + 1)]
max_area = max(areas)
if max_area < min_size:
return np.zeros_like(binary) return np.zeros_like(binary)
areas = [int(np.sum(labeled == i)) for i in range(1, num + 1)]
max_idx = int(np.argmax(areas)) + 1 max_idx = int(np.argmax(areas)) + 1
return (labeled == max_idx).astype(np.uint8) return (labeled == max_idx).astype(np.uint8)
@@ -375,11 +384,6 @@ def main() -> None:
# ---- 步骤3: 全图逐块分割(Otsu + TV去噪) ---- # ---- 步骤3: 全图逐块分割(Otsu + TV去噪) ----
print("\n[步骤3] 全图逐块分割...") print("\n[步骤3] 全图逐块分割...")
# 计算每个格子的面积,用于自适应后处理阈值
cell_h = int(np.median(np.diff(y_grid))) if len(y_grid) > 1 else 35
cell_w = int(np.median(np.diff(x_grid))) if len(x_grid) > 1 else 35
cell_area = cell_h * cell_w
bw_full = np.zeros_like(gray) bw_full = np.zeros_like(gray)
if len(x_grid) >= 2 and len(y_grid) >= 2: if len(x_grid) >= 2 and len(y_grid) >= 2:
for i in range(len(y_grid) - 1): for i in range(len(y_grid) - 1):
@@ -403,12 +407,12 @@ def main() -> None:
bw_blk = (blk_denoised > T).astype(np.uint8) bw_blk = (blk_denoised > T).astype(np.uint8)
except ValueError: except ValueError:
bw_blk = np.zeros(blk.shape, dtype=np.uint8) bw_blk = np.zeros(blk.shape, dtype=np.uint8)
# 后处理:保留最大连通域(最小面积 = 格子面积的1% # 后处理:保留最大连通域(无需阈值
bw_blk = keep_largest_object(bw_blk, cell_area=cell_area) bw_blk = keep_largest_object(bw_blk)
bw_full[r1:r2, c1:c2] = bw_blk bw_full[r1:r2, c1:c2] = bw_blk
# 全局去小连通域(最小面积 = 格子面积的2% # 全局去小连通域(中位数的25%以下自动判为噪声
bw_full = remove_small_objects(bw_full, cell_area=cell_area) bw_full = remove_small_objects(bw_full)
fig_full = plot_full_segmentation(gray, bw_full, "全图逐块Otsu分割结果") fig_full = plot_full_segmentation(gray, bw_full, "全图逐块Otsu分割结果")
fig_full.savefig(os.path.join(OUTPUT_DIR, 'result_full_segmentation.png'), dpi=150, bbox_inches='tight') fig_full.savefig(os.path.join(OUTPUT_DIR, 'result_full_segmentation.png'), dpi=150, bbox_inches='tight')