2. 钢轨探伤超声回波判读:B扫图像中缺陷轮廓的提取与标注

B扫成像与轮廓提取的任务拆解

B扫是把探头沿钢轨纵向移动时,每个位置上的A扫回波按声程展开成一条竖直亮度线,横向拼接后形成“纵向位置—深度”的二维图像。缺陷在B扫上表现为一片高亮回波区,而轨头踏面、轨腰过渡、轨底等结构边界也会产生连续的结构回波。本案例的任务是:在含人工缺陷的钢轨试块B扫图上,把缺陷回波区从结构回波背景中分离出来,提取其外轮廓,并标注缺陷的纵向位置区间与深度延伸范围。

轮廓提取的关键不是“找最亮的点”,而是先建立判据再分割。缺陷回波与结构回波在B扫上的差异主要体现在三处:形态(缺陷回波多为局部团块或弧形包络,结构回波多为沿某一深度连续延伸的条带)、连续性(结构回波在纵向上贯穿整幅图,缺陷回波只在有限区间出现)、幅度分布(缺陷回波峰值集中,结构回波幅度沿纵向较均匀)。因此采用“阈值分割 + 连通域分析 + 形态学闭运算”的组合,比单一阈值更稳健。

① 原始B扫(纵向位置 × 深度) 踏面结构回波 轨腰过渡回波 人工缺陷回波 小缺陷 ← 纵向位置(mm)→ ↑ 深度(声程,mm) ② 分割与轮廓提取 阈值分割 幅度 ≥ 阈值 T 判为回波 形态学闭运算 填补回波内部空洞 连通域标记 按面积/长宽比筛选 边界追踪 提取外轮廓坐标序列 ③ 轮廓标注结果 ΔL=纵向延伸 ΔD=深度范围 红框:缺陷轮廓;蓝带:结构回波(已排除)

判据参数与分割实现

设B扫图像为二维矩阵 I[y][x],其中 x 为纵向采样位置(步距 1 mm),y 为深度采样点(声程步距 0.5 mm)。回波幅度已归一化到 0–255。分割阈值不能取固定值,因为轨头踏面回波往往比缺陷回波更强。工程上采用相对阈值:以整幅图幅度分布的 90 分位值 P90 为参考,取 T = 0.45 × P90,既保留弱缺陷回波,又抑制噪声底。

分割后得到二值图,缺陷回波与结构回波都会成为连通域。区分二者的判据是:结构回波连通域在纵向 x 方向上的跨度接近整幅图宽度(≥ 85%),且其深度方向厚度较薄(≤ 6 个采样点);缺陷回波连通域纵向跨度有限,且面积与长宽比落在合理区间。满足“纵向跨度 < 85% 图宽”且“面积 ≥ 30 像素”的连通域保留为候选缺陷。

import numpy as np
from scipy import ndimage

def extract_defect_contours(img, x_step=1.0, y_step=0.5):
    # 1) 相对阈值分割
    p90 = np.percentile(img, 90)
    T = 0.45 * p90
    binary = img >= T

    # 2) 形态学闭运算,填补回波内部空洞
    binary = ndimage.binary_closing(binary, structure=np.ones((3, 3)))

    # 3) 连通域标记
    labels, n = ndimage.label(binary)
    H, W = img.shape
    defects = []

    for lab in range(1, n + 1):
        ys, xs = np.where(labels == lab)
        if len(xs) < 30:
            continue
        x_span = xs.max() - xs.min() + 1
        y_span = ys.max() - ys.min() + 1
        # 结构回波:纵向贯穿整幅图且厚度薄
        if x_span >= 0.85 * W and y_span <= 6:
            continue
        # 4) 边界追踪(外轮廓)
        mask = (labels == lab)
        contour = trace_outer_boundary(mask)
        defects.append({
            "x_min_mm": xs.min() * x_step,
            "x_max_mm": xs.max() * x_step,
            "depth_min_mm": ys.min() * y_step,
            "depth_max_mm": ys.max() * y_step,
            "area_px": len(xs),
            "contour": contour,
        })
    return defects

def trace_outer_boundary(mask):
    # 简化版:取每列最上/最下非零点构成上下包络
    ys, xs = np.where(mask)
    cols = np.unique(xs)
    top = [(c, ys[xs == c].min()) for c in cols]
    bot = [(c, ys[xs == c].max()) for c in cols]
    return top + bot[::-1]

上述代码中 trace_outer_boundary 用上下包络近似外轮廓,适合B扫中缺陷回波呈团块状的场景;若缺陷回波呈弧形包络,可改用 skimage.measure.find_contours 获得亚像素轮廓。两种方式得到的轮廓都用于后续标注,不影响位置与延伸范围的计算。

标注内容与结果判读

对每个保留的缺陷连通域,标注四项信息:纵向起点 x_min、纵向终点 x_max、深度上限 depth_min、深度下限 depth_max。这四项直接对应缺陷在钢轨中的位置与延伸范围。以示例试块为例,若某缺陷连通域 x_min=88 mm、x_max=122 mm、depth_min=42 mm、depth_max=55 mm,则判读为:缺陷纵向延伸约 34 mm,位于深度 42–55 mm 区间,深度方向延伸约 13 mm。

判读时需注意两点。第一,结构回波排除的边界情况:轨腰过渡回波若因耦合不良出现断续,其连通域纵向跨度可能不足 85% 图宽,会被误判为缺陷。此时应检查该连通域的深度是否与已知结构边界深度一致,一致则人工剔除。第二,相邻缺陷的合并:两个缺陷回波若在闭运算后连成一片,会被标记为同一连通域,导致延伸范围偏大。可将闭运算结构元缩小到 2×2,或在连通域分析前先做一次开运算断开细连接。

标注完成后,把轮廓坐标叠加回原始B扫图,用红色描边显示缺陷外轮廓、蓝色虚线标出已排除的结构回波条带,即可直观核对提取结果是否与图像中的高亮区吻合。若轮廓明显偏离高亮区,优先复查阈值 T 与闭运算结构元尺寸。

验证提取是否正确,可用两个可量化指标:一是轮廓内像素的平均幅度应显著高于轮廓外邻域(示例中可要求比值 ≥ 1.8);二是同一试块上已知人工缺陷的标称位置与提取出的 x_min/x_max 偏差应控制在 ±3 mm 内。两项都满足,才认为轮廓提取与标注有效。