OpenCV使用分水岭变换来实现图像中对象计数

一、概述

  案例:使用分水岭变换来实现图像中对象计数,达到统计图像中对象数目的目的

  重要API介绍:

watershed(src,markers);
src:原图像
markers:目标markers,生成markers是通过findContours边沿查找+drawContours来实现的。ps:这一步非常重要,有了marker就可以使用分水岭算法了。

  使用分水岭算法实现图像中对象计数的步骤:

    1.输入图像(如果有需要可以对图像进行滤波模糊去除图像中的噪声)

    2.图像灰度化

    3.图像二值化(由于要使用距离变换,此处必须是二值图像)

    4.执行距离变换

    5.图像归一化

    6.图像二值化(只1取值需要的部分)

    7.执行轮廓发现,先找到marker

    8.绘制找到的marker

    9.执行分水岭变换

    10.输出最终的变换后的marker(其实此时已经可以了,只是为了好看,所以需要下面11、12步给每一个对象着色)

    11.给找到的对象进行着色(此处有技巧,可查看代码中的注释)

    12.输出着色后的图像

 

二、代码示例

 //1.载入图像
    Mat src = imread(filePath);
    if(src.empty()){
        qDebug()<<"输入图片为空";
        return;
    }
    //2.使用金字塔边缘保留滤波平滑图像平坦区域,为了去除图像中的噪声
    Mat meanShiftMat;
    pyrMeanShiftFiltering(src,meanShiftMat,21,51);
    imshow("meanShiftMat",meanShiftMat);
    //3.图像灰度化(因为后面要做二值化)
    Mat gray;
    cvtColor(meanShiftMat,gray,COLOR_BGR2GRAY);
    imshow("gray",gray);
    //4.图像二值化
    Mat thresholdMat;
    threshold(gray,thresholdMat,0,255,THRESH_BINARY|THRESH_OTSU);
    imshow("threshold",thresholdMat);
    //5.执行距离变换
    Mat dist;
    distanceTransform(thresholdMat,dist,DistanceTypes::DIST_L2,3,CV_32F);
    normalize(dist,dist,0,1,NORM_MINMAX);//将数据归一化0~1
    threshold(dist,dist,0.4,1,THRESH_BINARY);//再次执行二值化是去掉小于0.4的像素值,只保留较大的
    imshow("dst",dist);
    //6.寻找种子
    Mat dist_mark;
    dist.convertTo(dist_mark,CV_8U);
    vector<vector<Point>> contours;
    findContours(dist_mark,contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE,Point(0,0));

    //7.寻找mark
    Mat markers = Mat::zeros(src.size(),CV_32SC1);
    for(size_t i =0;i<contours.size();i++){
        drawContours(markers,contours,static_cast<int>(i),Scalar::all(static_cast<int>(i) + 1), -1);//此处是为下面着色做准备的
    }
    circle(markers, Point(5, 5), 3, Scalar(255), -1);
    //    imshow("markers",markers*10000);
    //执行形态学变换
    Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
    morphologyEx(src, src, MORPH_ERODE, k);
    //8.执行分水岭变换
    watershed(src,markers);
    Mat mark = Mat::zeros(markers.size(),CV_8UC1);
    markers.convertTo(mark,CV_8UC1);
    bitwise_not(mark,mark,Mat());
    imshow("mark",mark);
    //9.给找到的对象进行着色
    vector<Vec3b> colors;//将随机生成的RGB颜色放入集合中,为下面markers对象着色做铺垫
    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));
    }

    Mat dst = Mat::zeros(markers.size(),CV_8UC3);//因为要绘制彩色图像,所以这里要创建三通道的
    int index = 0;
    for(int row=0;row<markers.rows;row++){
        for(int col=0;col<markers.cols;col++){
            index = markers.at<int>(row,col);//获取指定位置的像素值
            //此处要好好解释一下:
            //index>0过滤掉黑色的,index<=contours.size()的意思和上面markers的颜色对应,上面markers的颜色值是1~contours.size()之间
            //每个markers中的对象的颜色值都是1~contours.size()之间的值,只要判断这个值就说明找到了这个对象,而找到了这个对象自然可以给这个对象重新安排值
            //这个技巧一定要知道不然下面的内容是看不懂的。
            if (index > 0 && index <= contours.size()) {
                dst.at<Vec3b>(row, col) = colors[index - 1];
            } else {
                dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
            }
        }
    }
    cout<<"对象个数:"<<contours.size()<<endl;

    //10.显示最终图像
    imshow("Final Result", dst);

 

三、演示图像(每一个步骤都有呈现的展示效果)

 

posted on 2022-04-12 20:21  飘杨......  阅读(332)  评论(0编辑  收藏  举报