OpenCV图像处理教程C++(二十二)基于距离变换与分水岭的图像分割

图像分割是图像处理最重要的处理手段之一
图像分割的目标是将图像中像素根据一定的规则分为若干个cluster集合每个集合包括一类像素
根据算法分为监督学习和无监督学习,图像分割的算法多数都是无监督学习-KMenas
距离变换常见算法有两种
- 不断膨胀/ 腐蚀得到
- 基于倒角距离

分水岭变换常见的算法
基于浸泡理论实现,假设颜色数据为一个个山头,在山底不停加水,直到各大山头之间形成了明显的分水线
API:

watershed ( // 分水岭变换
InputArray image,
InputOutputArray  markers
)

distanceTransform ( // 距离变换
InputArray  src, // 输入的图像,一般为二值图像
OutputArray dst, // 输出8位或者32位的浮点数,单一通道,大小与输入图像一致
OutputArray  labels, // 输出 2D 的标签(离散Voronoi(维诺)图),类型为 CV_32SC1 
                                  ,相同距离的算做同一个 label ,算出总共由多少个 labels
int  distanceType, // 所用的求解距离的类型
CV_DIST_L1      distance = |x1-x2| + |y1-y2|
CV_DIST_L2      distance = sqrt((x1-x2)^2 + (y1-y2)^2)  欧几里得距离
CV_DIST_C       distance = max(|x1-x2|, |y1-y2|)
int maskSize, // 最新的支持5x5,推荐3x3
int labelType=DIST_LABEL_CCOMP // Type of the label array to build, see cv::DistanceTransformLabelTypes
)

步骤:

  • 将白色背景变成黑色-目的是为后面的变换做准备
  • 使用filter2D与拉普拉斯算子实现图像对比度提高,sharp锐化
  • 转灰度转为二值图像通过threshold
  • 距离变换
  • 对距离变换结果进行归一化到0-1之间
  • 使用阈值再次二值化得到标记
  • 腐蚀得到每个peak-erode
  • 发现轮廓-findContours
  • 绘制轮廓-drawcontours
  • 分水岭变换watershed
  • 对每个分割区域着色输出结果

代码:

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

int main() {
    Mat src;
    src = imread("C:\\Users\\Administrator\\Desktop\\pic\\10.jpg");
    imshow("input", src);
    //printf("1 depth=%d, type=%d, channels=%d\n", src.depth(), src.type(), src.channels());
    //将白色背景变成黑色
    for (int row = 0; row < src.rows; row++) {
        for (int col = 0; col < src.cols; col++) {
            if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
                src.at<Vec3b>(row, col)[0] = 0;
                src.at<Vec3b>(row, col)[1] = 0;
                src.at<Vec3b>(row, col)[2] = 0;
            }
        }
    }
    imshow("blacksrc", src);
    //锐化
    Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);//拉普拉斯算子
    Mat lapulasimg;
    Mat sharpimg = src;
    //printf("2 depth=%d, type=%d, channels=%d\n", sharpimg.depth(), sharpimg.type(), sharpimg.channels());
    filter2D(src, lapulasimg, CV_32F, kernel);// 这里计算的颜色数据有可能是负值,所以深度传 CV_32F, 不要传 -1,原图的深度是 CV_8U,不能保存负值
    src.convertTo(sharpimg, CV_32F); // mat.type 由 CV_8UC3 转换为 CV_32FC3 ,为了下面的减法计算
    Mat resultimg = sharpimg - lapulasimg;
    lapulasimg.convertTo(lapulasimg, CV_8UC3);
    resultimg.convertTo(resultimg, CV_8UC3);
    imshow("ruihua", resultimg);
    //转为二值
    Mat binimg;
    cvtColor(resultimg, resultimg, CV_RGB2GRAY);
    threshold(resultimg, binimg, 40, 255, THRESH_BINARY);
    imshow("binimg", binimg);
    //距离变化
    Mat disimg;
    distanceTransform(binimg, disimg, DIST_L1, 3,5);// CV_32F表示输出图像的深度,通道数与输入图形一致
    imshow("disimg", disimg);
    //对距离变换结果进行归一化到0-1之间
    normalize(disimg, disimg, 0, 1, NORM_MINMAX);
    imshow("normal", disimg);
    //使用阈值再次二值化得到标记(即颜色值达到0.4的地方,表示轮廓的边界,为发现轮廓做准备)
    threshold(disimg, disimg, 0.4, 1, THRESH_BINARY);
    imshow("erzhiimg", disimg);
    //腐蚀得到每个peak - erode
    Mat k1 = Mat::zeros(13, 13, CV_8UC1);
    erode(disimg, disimg, k1);
    imshow("erodeimg", disimg);

    //发现轮廓
    Mat dist_8u;
    disimg.convertTo(dist_8u, CV_8UC1);
    imshow("dist_8u*100", dist_8u*100 );//元素放大100倍
    vector<vector<Point>>contours;
    findContours(dist_8u, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
    //绘制轮廓
    RNG rng(12345);
    Mat show_contours;
    src.copyTo(show_contours);
    Mat makers = Mat::zeros(src.size(), CV_32SC1);
    for (size_t i = 0; i < contours.size(); i++) {
        if (contours[i].size() <= 2)
            continue;//过滤排除点数不够的轮廓,最终图像分割效果更好
        drawContours(makers, contours, i, Scalar::all(i + 1), -1);//thickness=-1表示填充轮廓
        if (i == 1) {//腐蚀的mat尺寸为3*3时下标1的轮廓两个点,在上面已经排除
            printf("contours[1][0].x=%d, contours[1][0].y=%d, contours[1][1].x=%d,contours[1][1].y=%d\n",
            contours[1][0].x, contours[1][0].y, contours[1][1].x, contours[1][1].y);
            circle(show_contours, contours[1][0], 5, Scalar(0, 0, 255), -1);
            circle(show_contours, contours[1][1], 5, Scalar(0, 0, 0), -1);
        }
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        drawContours(show_contours, contours, i, color, -1);
    }
    //创建标记,标记的位置如果要分割的图像块上会影响分割的效果,若果不创建,分水岭变换会无效
    circle(makers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
    imshow("maker*1000", makers * 1000);
    imshow("show_contours", show_contours);
    //分水岭变换,将绘制的轮廓区域的颜色数据蔓延到各轮廓所在的分水岭,这样图像分割已完成,后续不同着色显示
    watershed(src, makers);
    imshow("waterimg", makers * 1000);
    Mat mark = Mat::zeros(makers.size(), CV_8UC1);
    makers.convertTo(mark, CV_8UC1);
    //bitwise_not(mark, mark, Mat()); // 颜色反差
    imshow("markimg", mark);
    // 为每个轮廓生成随机颜色
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++) {
        int r = theRNG().uniform(0, 255);
        int g = theRNG().uniform(0, 255);
        int b = theRNG().uniform(0, 255);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }

    // fill with color and display final result
    Mat dst = Mat::zeros(makers.size(), CV_8UC3);
    for (int row = 0; row < makers.rows; row++) {
        for (int col = 0; col < makers.cols; col++) {
            int index = makers.at<int>(row, col); // 对应上面传的 Scalar::all(i + 1), -1)
            if (index > 0 && index <= static_cast<int>(contours.size())) { // 给各轮廓上不同色
                dst.at<Vec3b>(row, col) = colors[index - 1]; // 因为上面传的是 Scalar::all(i + 1), -1) 所以要减1
            }
            else {
                dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0); // 轮廓之外全部黑色
            }
        }
    }
    imshow("Final Result", dst);

    waitKey(0);
}

结果:
这里写图片描述
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_26907755/article/details/81740930
今日推荐