OpenCV联合C++/Qt 学习笔记(十九)----图像分割

一、图像分割之漫水填充

1、漫水填充方法介绍

漫水填充算法步骤:

  1. 选择种子点
  2. 以种子为中心,判断4-邻域或者8-邻域的像素值与种子点像素值的差值,将差值小于阈值的像素点添加进区域内
  3. 将新加入的像素点作为新的种子点,反复执行Step2,直到没有新的像素点被添加进该区域

2、相关函数

cpp 复制代码
/* 用途:用于从指定种子点开始,
       将与该点颜色相近且连通的区域
       替换为新的颜色 */
int cvfloodFill( InputOutputArray image, InputOutputArray mask,
                            Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
                            Scalar loDiff = Scalar(), Scalar upDiff = Scalar(),
                            int flags = 4 );
/*
image:输入/输出图像,图像数据类型可以为CV_8U或者CV_32F的单通道或者三通道图像
mask:掩码矩阵,尺寸必须比image宽和高各大2个像素,用于标记漫水填充的区域
seedPoint:种子点坐标,从该点开始进行区域生长与填充
newVal:归入种子点区域内像素点的新像素值
rect:种子点漫水填充区域的最小矩形边界,默认值为0,表示不输出边界
loDiff:添加进种子点区域条件的下界差值
upDiff:添加进种子点区域条件的上界差值
flags:填充方式控制标志,由多个二进制位共同组成
        低8位(0~7位):用于控制区域连通方式
        4:表示4邻域填充,只考虑当前像素上下左右的相邻像素
        8:表示8邻域填充,除了上下左右外,还考虑左上、右上、左下、右下四个对角方向像素
        中间8位(8~15位):用于指定mask掩码图像中填充值
        如果该部分值为0,
        mask默认使用1填充
        高8位(16~23位):用于控制填充策略
        FLOODFILL_FIXED_RANGE:固定范围模式,当前像素与"种子点像素"比较颜色差值
        默认模式:浮动范围模式,当前像素与"邻域像素"比较颜色差值,
        因此区域可能不断扩张
        FLOODFILL_MASK_ONLY:只填充mask掩码图像,不修改原始图像,此时newVal参数会被忽略
*/

3、示例代码

cpp 复制代码
    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    Mat img = imread(s_imgPath + "/lena.jpg");
    if (img.empty())
    {
        qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
        return;
    }
    RNG rng(10086);/*随机数,用于随机生成像素*/
    /*设置操作标志flag*/
    int connectivity = 4;/*连通邻域方式*/
    int maskVal = 255;/*掩码图像的数值*/
    int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE;/*漫水填充操作方式标志*/
    /*设置与选中像素点的差值*/
    Scalar loDiff = Scalar(20, 20, 20);
    Scalar upDiff = Scalar(20, 20, 20);
    /*声明掩膜矩阵变量*/
    Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);
    while (true)
    {
        /*随机产生图像中某一像素点*/
        int py = rng.uniform(0, img.rows - 1);
        int px = rng.uniform(0, img.cols - 1);
        Point point = Point(px, py);
        /*彩色图像中填充的像素值*/
        Scalar newVal = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        Rect rect;
        /*漫水填充函数*/
        int area = floodFill(img, mask, point, newVal, &rect, loDiff, upDiff, flags);
        /*输出像素点和填充的像素数目*/
        cout << "Pixel point x: " << point.x << " y: " << point.y 
            << " Number of filled pixels: " << area << endl;
        /*输出填充的图像结果*/
        imshow("img", img);
        imshow("mask", mask);

        int c = waitKey(0);
        if ((c & 255) == 27)
        {
            break;
        }
    }
    waitKey(0);
    destroyAllWindows();

一、图像分割之分水岭法

1、分水岭方法介绍

分水岭算法步骤:

  1. 排序过程,首先对图像像素的灰度级进行排序,确定灰度值较小的像素点,该像素点即为开始注水点
  2. 淹没过程,对每个最低点开始不断注水,不断掩膜周围的像素点,不同注水点的水汇集在一起,形成分割线

2、相关函数

cpp 复制代码
/* 用途:用于基于"分水岭算法"
       对图像进行区域分割 */
void cv::watershed( InputArray image, InputOutputArray markers );
/*
image:输入图像,数据类型为CV_8U的三通道图像
markers:输入/输出CV_32S的单通道图像的标记结果,与原图像具有相同的尺寸
*/

3、示例代码

cpp 复制代码
    QString imgPath = QApplication::applicationDirPath() + "/Images";
    cv::String s_imgPath = imgPath.toLocal8Bit().data();
    
    Mat img, imgGray, imgMask, img_;
    Mat maskWaterShed;/*watershed()函数的参数*/
    img = imread(s_imgPath + "/lenaw.jpg");/*含有标记的图像*/
    img_ = imread(s_imgPath + "/lena.jpg");/*原图像*/
    cvtColor(img, imgGray, COLOR_BGR2GRAY);
    /*二值化并开运算*/
    threshold(imgGray, imgMask, 254, 255, THRESH_BINARY);
    Mat k = getStructuringElement(0, Size(3, 3));
    morphologyEx(imgMask, imgMask, MORPH_OPEN, k);
    imshow("mark img", img);
    imshow("img", img_);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(imgMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);

    /*在maskWaterShed上绘制轮廓,用于输入分水岭算法*/
    maskWaterShed = Mat::zeros(imgMask.size(), CV_32S);
    for (int index = 0; index < contours.size(); index++)
    {
        drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);
    }
    /*分水岭算法 需要对原图像进行处理*/
    watershed(img_, maskWaterShed);
    vector<Vec3b> colors;/*随机生成几种颜色*/
    for (int i = 0; i < contours.size(); i++)
    {
        int b = theRNG().uniform(0, 255);
        int g = theRNG().uniform(0, 255);
        int r = theRNG().uniform(0, 255);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }
    Mat resultImg = Mat(img.size(), CV_8UC3);/*显示图像*/
    for (int i = 0; i < imgMask.rows; i++)
    {
        for (int j = 0; j < imgMask.cols; j++)
        {
            /*绘制每个区域的颜色*/
            int index = maskWaterShed.at<int>(i, j);
            if (index == -1)/*区域间的值被置为-1(边界)*/
            {
                resultImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
            }
            else if (index <= 0 || index > contours.size())/*没有标记清除的区域被置为0*/
            {
                resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
            }
            else/*其他每个区域的值保持不变*/
            {
                resultImg.at<Vec3b>(i, j) = colors[index - 1];/*把这些区域绘制成不同颜色*/
            }
        }
    }
    imshow("resultImg", resultImg);
    resultImg = resultImg * 0.8 + img_ * 0.2;
    waitKey(0);
    imshow("watershed resultImg", resultImg);

    /*绘制每个区域的图像*/
    for (int n = 1; n <= contours.size(); n++)
    {
        Mat resImage1 = Mat(img.size(), CV_8UC3);/*声明一个最后要显示的图像*/
        for (int i = 0; i < imgMask.cols; i++)
        {
            for (int j = 0; j < imgMask.rows; j++)
            {
                int index = maskWaterShed.at<int>(i, j);
                if (index == n)
                {
                    resImage1.at<Vec3b>(i, j) = img_.at<Vec3b>(i, j);
                }
                else
                {
                    resImage1.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
                }
            }
        }
        /*显示图像*/
        imshow(to_string(n), resImage1);

    }
    waitKey(0);
    destroyAllWindows();
相关推荐
kyle~1 小时前
调试器---GDB(Linux/Unix平台下编译型语言,C++、Go、Rust)
linux·c++·unix
七爷不在我这里1 小时前
dockerB站笔记
笔记·docker
宏笋1 小时前
C++ string 和string_view的区别和用法
c++
宏笋1 小时前
C++ 回调函数详解和常用场景
开发语言·c++
WBluuue1 小时前
Codeforces 1095 Div2(ABCDE)
c++·算法
奋斗的小乌龟1 小时前
langchain4j笔记-07-tool
笔记
東隅已逝,桑榆非晚1 小时前
深⼊理解指针(4)
c语言·笔记
咩咦1 小时前
C++学习笔记07:引用做返回值
c++·学习笔记·引用·static·引用返回
郭涤生1 小时前
C++ 20联合体(Union)
开发语言·c++