水下照片又糊又偏色?用OpenCV三步就能救回来
【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv
刚把AQUA相机拉出来的水下照片往检测流水线里一扔,目标识别的召回率直接腰斩——画面整体发灰偏蓝绿,目标边缘被一层"雾"糊住。这种片子,人眼都费劲,别指望算法能扛住。用 OpenCV 做水下图像颜色校正与去雾增强,不用上复杂模型,几条基础函数串起来就能把片子拉回可用状态。
先把原理说清楚:水就是一块磨砂玻璃
光穿过水,红光先被吸收得最狠,所以你看到的照片只剩蓝绿;悬浮的泥沙、藻类把光线往四面八方散,画面像隔了一层毛玻璃——对比度被整体压下去。处理思路就是顺着光路反着来:先把压扁的对比度撑开,再把偏掉的色调掰正,最后处理残留的脏点。
第一步:把被压扁的对比度撑开
这一步解决的是"整幅图灰蒙蒙、明暗都挤在中间"的问题。
全局的equalizeHist()对水下图基本不友好,换成createCLAHE()的局部自适应均衡,而且只作用在亮度通道上——这样局部最亮的区域不会被拉成过曝,暗部细节又能被抬出来。
Mat lab, bgr; cvtColor(img, lab, COLOR_BGR2LAB); // LAB 的 L 通道才是亮度 vector<Mat> ch; split(lab, ch); createCLAHE(2.0, Size(8, 8)).apply(ch[0], ch[0]); // 局部均衡:clipLimit 2.0 封顶防过曝 merge(ch, lab); cvtColor(lab, bgr, COLOR_LAB2BGR);跑完应该看到背景的水雾和主体之间拉开明显反差,主体轮廓清晰了。
clipLimit 别一上来就拉满,2.0~3.0 起步,调太大会把气泡和噪点一起放大,整幅图发"颗粒感"。
撑开的对比度只是"底子",接下来把偏掉的色调掰正。
第二步:给蓝通道单独"退烧"
这一步解决的是"偏蓝绿"的问题,用pow()做伽马校正(源码在modules/core/src/arithm.cpp)。
水下片子的偏色有个规律:蓝通道整体偏亮、红通道整体偏暗。与其全局调,不如挑偏得最狠的通道单独降曝光——伽马值小于 1 就是提亮,大于 1 就是压暗。
void gamma(Mat& src, double g) { // 按通道套伽马曲线 Mat LUT(1, 256, CV_8U); for (int i = 0; i < 256; i++) LUT.at<uchar>(i) = saturate_cast<uchar>(255 * pow(i / 255.0, g)); LUT.reshape(1, 3).convertTo(src, -1, 1, 0); // 每通道各查各的 LUT } vector<double> gain = {1.0, 1.0, 1.4}; // BGR:只压蓝通道 Mat t; convertScaleAbs(img, t, 1); // 拷贝 gamma(t, gain);对一张偏蓝的片子跑完,蓝绿色罩明显退掉,暖色细节回来,整体不再"发青"。
伽马参数按通道看直方图定:哪个通道直方图整体右移(偏亮),就给哪个通道设大于 1 的值。
第三步:把残留脏点圈出来补掉
这一步解决的是"去雾后镜头脏点、气泡、划痕特别显眼"的问题,核心是inpaint()(实现在modules/photo/src/inpaint.cpp)。
前面两步做完,小的脏斑就成了画面里最扎眼的东西。用 Canny 找轮廓、筛面积,把脏点标进 mask,交给修复算法用周围像素填充。
Mat blur, edges; GaussianBlur(t, blur, Size(5, 5), 1.2); // 先去噪,Canny 才不会满屏碎边 Canny(blur, edges, 60, 140); vector<vector<Point>> cs; findContours(edges, cs, RETR_LIST, CHAIN_APPROX_SIMPLE); Mat mask = Mat::zeros(t.size(), CV_8U); for (auto& c : cs) // 面积过滤:只圈"脏点"大小的块 if (contourArea(c) > 20) drawContours(mask, c, 0, Scalar(255), -1); inpaint(t, mask, t, 4, INPAINT_TELEA);跑完预期是 mask 圈住的脏斑被周围纹理自然填平,肉眼看不到修补痕迹。
把三步串成流水线
- 原始水下照片 →
GaussianBlur+createCLAHE均衡亮度 → 对比度拉开的干净底子 - 均衡后的图 → 分通道
pow()伽马校正 → 偏蓝绿色调退掉、颜色接近真实 - 校正后的图 → Canny 圈脏点 +
inpaint()修复 → 可交付检测/标注环节的最终图
调参与避坑
- 现象:饱和度一加就出现纯红纯蓝的色块。→原因:HSV 的 S 通道是 8 位,直接
+= 50会溢出截断。→解法:加法前先saturate_cast,或用convertScaleAbs限幅。 - 现象:全局
equalizeHist一上,整张图噪点炸开、局部过曝。→原因:全局均衡把整幅图的分布强行拉平,水下图的局部亮斑被放大。→解法:改走 LAB 空间的 L 通道 + CLAHE。 - 现象:去噪后气泡和细小纹理糊成一团。→原因:高斯模糊是"无差别"平滑,不区分边缘。→解法:换
bilateralFilter(),它保边去噪,边缘和噪声各论各的。 - 现象:
inpaint半径一调大,修复区像被涂了层腻子。→原因:半径大意味着采样范围大,周围纹理被平均化。→解法:半径保持 2~5,脏点大就先分割再逐个修。
还能往哪儿走
- 再深一点、水更浑的场景,基础函数栈就到头了,可以考虑 Retinex 类物理退散射模型(OpenCV 里有
createRetinex()的近似)。 - 目标尺度跨度大的检测任务,把 CLAHE 的 tile 网格按目标尺寸缩放试试。
- 批量数据想统一风格,参考仓库里
modules/photo/src/inpaint.cpp和modules/imgproc/src/histogram.cpp的写法,自己封装一条流水线。
现在挑一张自己的水下片子,把这三段代码贴进项目跑一遍,参数对着上面的坑位调,半小时内你能拿到第一版可用的增强结果。
【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考