4. 水下图像光照不均与后向散射的判读预处理
为什么先处理光照与后向散射
ROV 在浑水或斜向照明条件下拍摄的管体图像,常见两类退化:一是光照不均——补光灯中心亮、边缘暗,形成大尺度亮度梯度;二是后向散射——水中悬浮颗粒把灯光反射回镜头,在画面上叠加一层低对比度的“雾”,使暗部细节被抬亮、边缘被抹平。两者都会让判读员把亮度梯度误认为涂层色差,或把散射雾中的纹理噪声误判为裂纹。因此预处理的目标不是“把图修好看”,而是压平大尺度亮度场、抑制散射雾、保留并锐化真实边缘,同时不引入伪边缘。
处理链与参数选择
本例采用一条可复现的四步链:通道分解 → 大尺度背景估计与光照校正 → 散射雾抑制(暗通道先验去雾)→ 局部对比度增强与边缘保护。关键参数含义如下:
- 背景估计尺度:用大核高斯或形态学闭运算估计低频亮度场,核尺寸应明显大于缺陷尺寸(示例取 61 像素),否则会把缺陷本身当成背景减掉。
- 校正增益上限:除法校正后限制增益(示例 2.5 倍),防止暗角区域噪声被过度放大。
- 去雾强度 ω:暗通道先验中保留多少雾,示例取 0.85;ω 越大去雾越强,但过强会在高亮反光处产生色偏与光晕。
- 引导滤波半径与 ε:用于细化透射率图,半径 41、ε=1e-3,保证边缘处透射率不跳变。
- CLAHE 裁剪限幅与网格:clipLimit=2.0、tile=8×8,提升局部纹理对比而不放大噪声。
判读前先做一次“参数敏感性”检查:同一帧用两组参数各出一版,若疑似缺陷在两组结果中位置与形态一致、仅清晰度不同,则该特征可信;若只在某一组参数下出现,多半是增强伪影。
可执行的预处理实现
下面给出与上图处理链一致的 Python 实现(OpenCV + NumPy),输入为单帧 BGR 图像,输出为校正帧与中间产物,便于逐环节检查。
import cv2
import numpy as np
def estimate_illumination(bgr, ksize=61):
# 大核高斯估计低频亮度场,核尺寸需远大于缺陷尺寸
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY).astype(np.float32)
bg = cv2.GaussianBlur(gray, (ksize, ksize), 0)
return np.maximum(bg, 1.0)
def correct_illumination(bgr, bg, gain_max=2.5):
out = bgr.astype(np.float32)
for c in range(3):
ch = out[:, :, c]
gain = np.clip(bg / np.maximum(ch, 1.0), 1.0, gain_max)
out[:, :, c] = np.clip(ch * gain, 0, 255)
return out.astype(np.uint8)
def dark_channel(img, radius=7):
b, g, r = cv2.split(img.astype(np.float32))
min_ch = np.minimum(np.minimum(b, g), r)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2*radius+1, 2*radius+1))
return cv2.erode(min_ch, kernel)
def dehaze(bgr, omega=0.85, radius=7, t0=0.1, guide_r=41, eps=1e-3):
img = bgr.astype(np.float32) / 255.0
dc = dark_channel(bgr, radius) / 255.0
# 大气光:暗通道最亮的前 0.1% 像素对应原图均值
flat = dc.ravel()
idx = flat.argsort()[-max(1, int(flat.size * 0.001)):]
A = np.mean(img.reshape(-1, 3)[idx], axis=0)
A = np.maximum(A, 1e-3)
norm = img / A
t = 1.0 - omega * dark_channel((norm * 255).astype(np.uint8), radius) / 255.0
t = np.clip(t, t0, 1.0).astype(np.float32)
# 引导滤波细化透射率,避免边缘光晕
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY).astype(np.float32) / 255.0
t = cv2.ximgproc.guidedFilter(gray, t, guide_r, eps)
t = np.clip(t, t0, 1.0)[..., None]
J = (img - A) / t + A
return np.clip(J * 255, 0, 255).astype(np.uint8), t[..., 0]
def enhance(bgr, clip=2.0, tile=8):
lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=(tile, tile))
l2 = clahe.apply(l)
return cv2.cvtColor(cv2.merge([l2, a, b]), cv2.COLOR_LAB2BGR)
def preprocess(frame):
bg = estimate_illumination(frame)
corrected = correct_illumination(frame, bg)
dehazed, tmap = dehaze(corrected)
final = enhance(dehazed)
return final, {"bg": bg, "corrected": corrected, "tmap": tmap}
if __name__ == "__main__":
frame = cv2.imread("rov_frame_0001.png")
final, mid = preprocess(frame)
cv2.imwrite("out_final.png", final)
cv2.imwrite("out_corrected.png", mid["corrected"])
cv2.imwrite("out_tmap.png", (mid["tmap"] * 255).astype(np.uint8))
结果判读与验证
处理完成后按以下顺序核对,避免把增强伪影带入缺陷记录:
- 亮度均匀性:观察校正帧四角与中心,管体同一材质区域灰度差应明显缩小;若暗角处出现块状噪声,说明增益上限过高,回调至 2.0 左右。
- 雾感与色偏:去雾后水体背景应更暗、更透,但管体颜色不应偏蓝或偏黄;出现色偏时降低 ω 至 0.7 附近。
- 边缘真实性:将疑似缺陷边界与原始帧逐点对照,真实缺陷在原始帧中应有可追溯的灰度或颜色突变;仅在增强帧出现的“边缘”判为伪影。
- 参数一致性:同一缺陷用两组参数(如 ω=0.75 与 0.85)各处理一次,位置与形态一致方可进入缺陷圈定。
暗通道先验假设“无雾图像局部存在暗像素”,对高反光金属管体与强补光区域并不总成立,容易在镜面反光处产生光晕。此类区域应结合原始帧人工复核,不可仅凭去雾结果判定缺陷。
判读记录要点
| 记录项 | 示例值 | 作用 |
|---|---|---|
| 帧号 / 里程 | 0001 / K3+120 | 缺陷定位回溯 |
| 背景估计核 | 61 px | 复现光照校正 |
| 增益上限 | 2.5 | 控制暗角噪声放大 |
| 去雾 ω / t0 | 0.85 / 0.1 | 复现去雾强度 |
| CLAHE 参数 | 2.0 / 8×8 | 复现局部增强 |
| 双参数一致性 | 一致 | 排除增强伪影 |
| 疑似缺陷位置 | 管体 10 点钟方向 | 供后续圈定与复拍 |
完成上述步骤后,校正帧即可作为后续缺陷识别与范围圈定的输入;原始帧与中间产物(亮度场、透射率图)应一并归档,以便复核时区分真实特征与处理引入的变化。