问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

OpenCV实现天空变换:图像分割技术详解

创作时间:
作者:
@小白创作中心

OpenCV实现天空变换:图像分割技术详解

引用
CSDN
1.
https://blog.csdn.net/zhaitianbao/article/details/120158310

天空变换是图像分割的一种应用,通过将图像中的天空与非天空区域分割开来,结合掩膜将天空更改为其他图像。本文将详细介绍天空变换的实现原理、代码实现以及测试效果。

实现原理

天空变换的实现主要分为两个关键步骤:

  1. 天空分割
  • 将图像转换为HSV颜色空间,并对S和V通道进行直方图均衡化。
  • 通过设定的HSV三通道阈值(H为78-124,S为0-255,V为78-255)来选定天空的颜色范围,进而提取天空区域。
  • 识别非天空区域的轮廓,采用外部轮廓方式提取最大的轮廓区域,通常为前景区域。
  • 通过闭运算填充轮廓区内部微小孔洞,并进行均值滤波以平滑边缘。
  1. 两区域边缘融合
  • 将新天空图调整为原图尺寸。
  • 对掩膜区域进行均值滤波,生成介于0-255之间的缓存区。
  • 通过比例分配的方式对缓存区的像素点上色,实现较好地过渡。
  • 最后,根据掩膜图的值决定新天空图和原图的融合。

功能函数代码

// 天空分离
cv::Mat SkySeparation(cv::Mat src, Inputparama input)
{
    // 异常数值修正
    input.low_h = max(uchar(0), min(uchar(255), input.low_h));
    input.high_h = max(uchar(0), min(uchar(255), input.high_h));
    input.low_s = max(uchar(0), min(uchar(255), input.low_s));
    input.high_s = max(uchar(0), min(uchar(255), input.high_s));
    input.low_v = max(uchar(0), min(uchar(255), input.low_v));
    input.high_v = max(uchar(0), min(uchar(255), input.high_v));
    input.close_size= max(0, min(10, input.close_size));
    input.blur_size = max(0, min(10, input.blur_size));

    // 转为hsv通道
    cv::Mat hsv,nhsv,thresh;
    cvtColor(src, hsv, COLOR_BGR2HSV);
    vector<cv::Mat> hsvs;
    split(hsv, hsvs);
    cv::Mat h,s,v;

    // 直方图均衡化
    equalizeHist(hsvs[1], s);
    equalizeHist(hsvs[2], v);
    hsvs[1] = s.clone();
    hsvs[2] = v.clone();
    merge(hsvs, nhsv);

    // 按天空色选出mask并反相
    cv::Mat low=(cv::Mat_<uchar>{ input.low_h, input.low_s, input.low_v });
    cv::Mat high = (cv::Mat_<uchar>{ input.high_h, input.high_s, input.high_v  });
    inRange(nhsv, low, high, thresh);
    cv::Mat thresh_ = 255 - thresh;

    // 寻找轮廓,找出最大轮廓作为前景图
    vector<vector<Point>> contour;
    vector<Vec4i> hierarchy;
    findContours(thresh_, contour, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    cv::Mat Foreground=thresh_.clone();
    if (!contour.empty() && !hierarchy.empty())
    {
        int max = 0;
        std::vector<std::vector<cv::Point> >::const_iterator itc = contour.begin();
        std::vector<std::vector<cv::Point> >::const_iterator itmax;
        while (itc != contour.end())
        {
            double area = cv::contourArea(*itc);
            if (area > max)
            {
                itmax = itc;
                max = area;
            }
            itc++;
        }
        for (auto it = contour.begin(); it != contour.end(); it++)
        {
            if (it!=itmax)
            {
                cv::Rect rect = cv::boundingRect(cv::Mat(*it));
                for (int i = rect.y; i < rect.y + rect.height; i++)
                {
                    uchar *output_data = Foreground.ptr<uchar>(i);
                    for (int j = rect.x; j < rect.x + rect.width; j++)
                    {
                        if (output_data[j] == 255)
                        {
                            output_data[j] = 0;
                        }
                    }
                }
            }
        }
    }

    // 闭运算
    cv::Mat element = getStructuringElement(MORPH_ELLIPSE, Size(2*input.close_size+1, 2 * input.close_size + 1));
    cv::morphologyEx(Foreground, Foreground, MORPH_CLOSE, element);

    // 滤波
    cv::blur(Foreground, Foreground, Size(2 * input.blur_size + 1, 2 * input.blur_size + 1));
    return Foreground;
}

C++测试代码

#include <iostream>
#include <opencv2/opencv.hpp>
#include <time.h>
using namespace std;
using namespace cv;

struct Inputparama { 
    uchar low_h = 78;                     // 识别天空区域hsv颜色的最底H值
    uchar high_h = 124;                   // 识别天空区域hsv颜色的最高H值
    uchar low_s = 0;                      // 识别天空区域hsv颜色的最底S值
    uchar high_s = 255;                   // 识别天空区域hsv颜色的最高S值
    uchar low_v = 78;                     // 识别天空区域hsv颜色的最底V值
    uchar high_v = 255;                   // 识别天空区域hsv颜色的最高V值
    int close_size = 4;                 // 非天空区域闭运算尺寸
    int blur_size = 2;                  // 非天空区域滤波窗口尺寸
};

cv::Mat SkySeparation(cv::Mat src, Inputparama input);
cv::Mat ImageFusion(cv::Mat src1, cv::Mat src2, cv::Mat mask);

int main()
{
    cv::Mat src = imread("test3.jpg");
    cv::Mat sky = imread("sky5.jpg");
    Inputparama input;
    input.low_h = 78;
    input.high_h = 124;
    input.low_s = 0;
    input.high_s = 255;
    input.low_v = 78;
    input.high_v = 255;
    input.close_size = 4;
    input.blur_size = 2;

    clock_t s, e;
    s = clock();
    cv::Mat thresh = SkySeparation(src,input);
    cv::Mat result = ImageFusion(src, sky, thresh);
    e = clock();
    double dif = (e - s) / CLOCKS_PER_SEC;
    cout << "time:" << dif << endl;

    imshow("original", src);
    imshow("result", result);
    waitKey(0);
    return 0;
}

// 前景背景融合
cv::Mat ImageFusion(cv::Mat src1, cv::Mat src2, cv::Mat mask)
{
    cv::Mat sky;
    resize(src2, sky, Size(src1.cols, src1.rows));
    cv::Mat result = src1.clone();
    int row = src1.rows;
    int col = src1.cols;

    for (int i = 0; i < row; ++i)
    {
        uchar *s1 = result.ptr<uchar>(i);
        uchar *s2 = sky.ptr<uchar>(i);
        uchar *m = mask.ptr<uchar>(i);
        for (int j = 0; j < col; ++j)
        {
            if (m[j] == 0)
            {
                s1[3 * j] = s2[3 * j];
                s1[3 * j + 1] = s2[3 * j + 1];
                s1[3 * j + 2] = s2[3 * j + 2];
            }
            else if (m[j] != 255)
            {
                int newb = (s1[3 * j] * m[j] * 0.3 + s2[3 * j] * (255 - m[j])*0.7) / ((255 - m[j])*0.7 + m[j] * 0.3);
                int newg = (s1[3 * j + 1] * m[j] * 0.3 + s2[3 * j + 1] * (255 - m[j])*0.7) / ((255 - m[j])*0.7 + m[j] * 0.3);
                int newr = (s1[3 * j + 2] * m[j] * 0.3 + s2[3 * j + 2] * (255 - m[j])*0.7) / ((255 - m[j])*0.7 + m[j] * 0.3);
                newb = max(0, min(255, newb));
                newg = max(0, min(255, newg));
                newr = max(0, min(255, newr));
                s1[3 * j] = newb;
                s1[3 * j + 1] = newg;
                s1[3 * j + 2] = newr;
            }
        }
    }
    return result;
}

测试效果

以下是天空变换的测试效果对比图:




参数说明

函数输入参数共有5项:

  1. 前6个参数分别为hsv三通道的最大最小值。
  2. close_size为闭运算尺寸,如果处理的图像中有小树林,建议尺寸调小,不然小树林间的缝隙就是原图,有点不协调。
  3. blur_size为滤波窗口尺寸,平滑天空与非天空区衔接处。

总的来说,图像如果有明显天空背景,基本都能成功;天空白色区域过多可能识别不准,因为白色的hsv值和蓝色差太多;天空下面有大片海水,也不太行,就识别出来不符合现实逻辑。

源码只有100多行,看懂原理最重要,比直接调用api更能学到知识。永远记住,“代码是死的,场景是多变的,而人是活的。”,针对不同场景,合理改写代码,才能产出最适合你的代码。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号