水下照片又糊又偏色?用OpenCV三步就能救回来
2026/8/29 8:34:42 网站建设 项目流程

水下照片又糊又偏色?用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 圈住的脏斑被周围纹理自然填平,肉眼看不到修补痕迹。

把三步串成流水线

  1. 原始水下照片 →GaussianBlur+createCLAHE均衡亮度 → 对比度拉开的干净底子
  2. 均衡后的图 → 分通道pow()伽马校正 → 偏蓝绿色调退掉、颜色接近真实
  3. 校正后的图 → 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.cppmodules/imgproc/src/histogram.cpp的写法,自己封装一条流水线。

现在挑一张自己的水下片子,把这三段代码贴进项目跑一遍,参数对着上面的坑位调,半小时内你能拿到第一版可用的增强结果。

【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询