有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!

opencv小白实现基地学习任务的心路历程!

任务1:  对图中黄色图像的提取

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第1张图片 有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第2张图片

 在网上找了很多资料后,我定下的方案大致如下:

1.对黄色进行二值化处理(inrange()函数),将图像快速分为黑白两色。

其中白色对应的就是最先形成的mask图像(掩膜),用于之后和最初图像的一个叠加。

#include 
#include 
#include 
#include 
#include 
using namespace cv;
using namespace std;



int main(){
    Mat im1,out,labels,stats,centroids,img_bool,final,gray;
    Mat image = imread("C:\\Users\\huli\\Desktop\\test1\\4.png");
    Mat pre=image;
    if (image.empty())
    {
        cout << "Error: Could not load image" << endl;
        return 0;
    }
    int height = image.rows;
    int width = image.cols;
    Mat hsvImage;
    Mat mask;
    cvtColor(image,hsvImage,COLOR_BGR2HSV);
    //基本的颜色转化处理

    Scalar yellow_scalarL = Scalar(11, 70, 110);
    Scalar yellow_scalarH = Scalar(34, 255, 255);
    //你可以上网查找黄色的对应hsv值,此上数据是我再次基础上进行了不断调整后得出的
    inRange(hsvImage, yellow_scalarL, yellow_scalarH, mask);
    imshow("mask",mask);//生成基础mask

至于为什么将RGB转化成HSV,这是因为RGB三个通道共同决定颜色,但HSV却仅仅是H决定了肉眼可见的颜色。S,V分别代表饱和度和明度,这就代表我们将有更多的空间进行参数的调整。

tip1.对inRange浅浅解释一下:

inRange(原图,下限,上限,输出图像)

当原图像素在下限和上限之间的时候,函数会把像素值设置为白色(255),反之则将输出黑色(0)。注意:此时的mask已经是一张灰度图了

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第3张图片

                                                                                   这就是最开始的mask图

从上图我们不难发现仅仅是对黄色的二值化处理,会有一定的残余。为了处理这些面积较小的区域,我们将采用第二个重要函数。

tip2.connectedComponents()

connectedComponents(输入图(二值化),输出的标记图像(labels),connectivity,ltype)

这里的connectivity默认为8,当然你也可以改为较为常见的4。

至于连通域的原理,展开来讲就有一些复杂了。不过基本的方法有两遍扫描法种子填充法,更多的大家可以上csdn上翻翻大佬们的资料。

ltype则代表输出的labels的数据类型。

void ClearSmallConnecttedComponent(Mat srcImage, Mat& dstImage,int S)//去除面积小于等于S的连通域
{
    Mat temp;
    Mat labels;//labels内数据由连通域索引转换到了像素值!!!!!
    srcImage.copyTo(temp);

    //1. 标记连通域
    int n_comps = connectedComponents(temp, labels, 4, CV_16U);
    vector histogram_of_labels;
    for (int i = 0; i < n_comps; i++)//初始化labels的个数为0
    {
        histogram_of_labels.push_back(0);
    }

    int rows = labels.rows;
    int cols = labels.cols;
    for (int row = 0; row < rows; row++) //计算每个labels的面积
    {
        for (int col = 0; col < cols; col++)
        {
            histogram_of_labels.at(labels.at(row, col)) += 1;//面积(像素点数)+1
        }
    }
    histogram_of_labels.at(0) = 0; //将背景的面积设置为0

    //2. 计算连通域labels索引
    int maximum = 0;
    int max_idx = 0;
    int tmp[1024]{};//创建全0tmp数组,1则说明小于等于S
    for (int i = 0; i < n_comps; i++)
    {
        if (histogram_of_labels.at(i) <=S)
        {
            tmp[i]=1;
        }
    }

    //3.
    for (int row = 0; row < rows; row++)
    {
        for (int col = 0; col < cols; col++)
        {
            if (labels.at(row, col) == 0)//背景跳过(保持黑色)
            {
                continue;
            }
            else if (tmp[labels.at(row, col)]==1)//小连通域去除
            {
                continue;
            }
            else
            {
                labels.at(row, col) = 255;//其余连通域转为白色
            }

        }
    }

    //4. 将图像更改为CV_8UC1格式

    labels.convertTo(dstImage, CV_8UC1);
    threshold(dstImage, dstImage, 254, 255, THRESH_BINARY);//非常重要!!!!!!!!!!!!!!!!!!!!
}

这是我定义的一个删除小区域连通区域的一个函数,下面让我们分块来讲解这段函数。

一.

void ClearSmallConnecttedComponent(Mat srcImage, Mat& dstImage,int S)//去除面积小于等于S的连通域
{
    Mat temp;
    Mat labels;//labels内数据由连通域索引转换到了像素值!!!!!
    srcImage.copyTo(temp);

    //1. 标记连通域
    int n_comps = connectedComponents(temp, labels, 4, CV_16U);
    vector histogram_of_labels;
    for (int i = 0; i < n_comps; i++)//初始化labels的个数为0
    {
        histogram_of_labels.push_back(0);
    }

    int rows = labels.rows;
    int cols = labels.cols;
    for (int row = 0; row < rows; row++) //计算每个labels的面积
    {
        for (int col = 0; col < cols; col++)
        {
            histogram_of_labels.at(labels.at(row, col)) += 1;//面积(像素点数)+1
        }
    }
    histogram_of_labels.at(0) = 0; //将背景的面积设置为0

    }

这里的srcImage代表我们输入的要处理的图片,dstImage是我们最后的输出图。

然后我们运用connectedComponents函数得到了初始mask的连通域数量(n_comps),

并由此设定了n_comps个不同连通域的初始面积。

然后我们根据不同像素点对应的label值进行了一个累加,从而得出了不同连通域(label)的面积

(背景面积记得设为0)

二.

 int max_idx = 0;
    int tmp[1024]{};//创建全0tmp数组,1则说明小于等于S
    for (int i = 0; i < n_comps; i++)
    {
        if (histogram_of_labels.at(i) <=S)
        {
            tmp[i]=1;
        }
    }

这个部分的代码比较简单,对面积的比较的判断。当面积小于S时,我们将tmp这个标志变量设为1,代表我们会删除这个部分。

三.

//3.
    for (int row = 0; row < rows; row++)
    {
        for (int col = 0; col < cols; col++)
        {
            if (labels.at(row, col) == 0)//背景跳过(保持黑色)
            {
                continue;
            }
            else if (tmp[labels.at(row, col)]==1)//小连通域去除
            {
                continue;
            }
            else
            {
                labels.at(row, col) = 255;//其余连通域转为白色
            }

        }
    }

    //4. 将图像更改为CV_8UC1格式

    labels.convertTo(dstImage, CV_8UC1);
    threshold(dstImage, dstImage, 254, 255, THRESH_BINARY);//非常重要!!!!!!!!!!!!!!!!!!!!

 在以上代码中我们遍历了所有的像素点并对其进行了判断,只有当对应像素的label不代表背景同时它的面积大于S,我们才会将其转化成白色的像素点。

然后看图!!!

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第4张图片

 经过上述操作,我们就成功实现了对多余区域的去除!!!

tip3.bitwise_and()

bitwise_and(输入图,输入图,输出图,掩膜(mask))

Mat mix1,mix2;
    bitwise_and(image,image,mix1,final);
    imshow("mix1",mix1);

 通过该程序,我们就成功将两张图片的像素相加。

效果如下:

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第5张图片

那么第一题的大体流程就结束了!!!!!!!

任务2:对物体轮廓的提取 

先放效果图:

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第6张图片有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第7张图片

在得到了第一题的图像后,我们对第二题的处理将会变得简单的多。

下面让我们由图像“final”入手,实现对图像的轮廓提取。

tip4.findContours()

findContours

(输入(多为边缘图),输出(contours),对应关系(hierarchy),mode,method,offset)

 blur(final,im1,Size(3,3));
    Canny(im1,canny,100,120,3);
    imshow("canny",canny);
    vector> contours;
    vector hierarchy;
    findContours(canny, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
    Mat imageContour= Mat::zeros(im1.size(),CV_8UC1);
    Mat Contours = Mat::zeros(image.size(), CV_8UC1);

输入是经由Canny函数处理后得到的边界函数。

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第8张图片

 

contours,则储存了每一层轮廓的坐标。

(大体就是vector向量中构建了多个由point类构建的vector向量,所以它可以同时储存不同轮廓的多个坐标)

hierarchy,输出的轮廓关系的存储。

(生成hierarchy[i][0] ~hierarchy[i][3],分别表示i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号)

 

 cvtColor(imageContour,im1,COLOR_GRAY2BGR);
    //drawContours(im1, contours, 1, Scalar(0,0,255), 4, 7, hierarchy);
    drawContours(im1, contours, 2, Scalar(0,0,255), 4, 7, hierarchy);
    im1.copyTo(pre,im1);
    imshow("final2",pre);
    waitKey(0);
    destroyAllWindows();

在得到函数相对轮廓的坐标后,我们就可以直接使用drawContours将我们想要的图像画出来。

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第9张图片

 

有关opencv的轮廓识别,图像提取,阈值分割的笔记!!!_第10张图片 

 那么到这,基地的基础任务1大体就完成了!!!!

撒花✿✿ヽ(°▽°)ノ✿!

你可能感兴趣的:(opencv,计算机视觉)