C++ OpenCV利用颜色分割来数红枣

正文索引 [隐藏]

一杯茶一包烟12个红枣数一天

加载红枣图像

string IMG_PATH = "D:/Code/CppCode/OpencvProject/img/";
Mat src = imread(IMG_PATH+"jujube.jpg");
cv::resize(image, image, Size(600, 400));
imshow("原图像", image);

高斯滤波,定义颜色并利用HSV进行颜色分割

高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗地讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。在C++中 GaussianBlur 函数定义如下:

void GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT )

参数1 src是源图像Mat对象,参数2 dst是目标图像Mat对象。 参数3 ksize是高斯核的大小,如Size(3,3),宽和高都必须为正奇数越大越模糊。 参数4 sigmaX是高斯核在 x 方向的标准差。 参数5 sigmaY是高斯核在 y 方向的标准差。当sigmaY=0时,其值自动由sigmaX确定( sigmaY=sigmaX );当sigmaY=sigmaX=0时,它们的值将由ksize.widthksize.height自动确定。 参数6 borderType 是边缘的处理类型,一般使用默认值即可。 颜色空间转换函数cvtColor用来将图像从一个颜色空间转换到另一个颜色空间的转换,比如将RGB图像转换为灰度图像等。在C++中 cvtColor 函数定义如下:

void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 )

参数1 src 是源图像Mat对象,参数2 dst 是目标图像Mat对象, 参数3 code是转换的方式,所取的值以CV_开头后面是转换的方式,比如将RGBA转换为GRAY就是:CV_RGBA2GRAY。 参数4 dstCn是输出的通道数,默认值是0,此时通道数将由输入图像和颜色转换码决定。 inRange函数用来检查数组元素是否位于其他两个数组的元素之间。在C++中 inRange 函数定义如下:

    void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)

参数1 scr 是源图像Mat对象,参数2 lowerb 是包含下边界的数组或标量,参数3 upperb 是包含上边界数组或标量,参数4是输出图像,与输入图像 src 同尺寸且同类型。

选出红色来进行高斯滤波,并利用HSV来进行颜色分割的代码如下:

Mat gauss;
GaussianBlur(image, gauss, Size(5,5), 0.5, 0.5);
cv::cvtColor(image, gauss,CV_BGR2HSV);
cv::inRange(gauss, Scalar(0,43,46), Scalar(10,255,255), gauss);

形态学操作,我做了四次开操作

morphologyEx这个函数可以用来对图像进行一系列的膨胀腐蚀组合,在C++中 morphologyEx 函数定义如下:

void morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar& borderValue = morphologyDefaultBorderValue() )

参数1 src是源图像Mat对象,参数2 dst是目标图像Mat对象,参数3 op是操作的类型,通过源码可以得知总共有以下几种类型:

enum MorphTypes{
    MORPH_ERODE = 0, //腐蚀,跟erode函数的腐蚀效果一样
    MORPH_DILATE = 1, //膨胀,跟dilate函数的膨胀效果一样
    MORPH_OPEN = 2, //开操作,实质是先腐蚀后膨胀的操作
    MORPH_CLOSE = 3, //闭操作,实质上是先膨胀后腐蚀的操作
    MORPH_GRADIENT = 4, //梯度操作,实质是膨胀减去腐蚀
    MORPH_TOPHAT = 5, //顶帽操作
    MORPH_BLACKHAT = 6, //黑帽操作
    MORPH_HITMISS = 7  //命中或未命中
};

参数4 kernel是用于膨胀操作的结构元素,如果取值为Mat()则默认使用一个3 x 3的方形结构元素,可以使用getStructuringElement() 来创建结构元素。在C++中 getStructuringElement() 函数定义如下:

Mat getStructuringElement(int shape, Size esize, Point anchor = Point(-1, -1))

第一个参数表示内核的形状,有三种形状可以选择: • 矩形:MORPH_RECT • 交叉形:MORPH_CROSS; • 椭圆形:MORPH_ELLIPSE; 第二和第三个参数分别是内核的尺寸以及锚点的位置。一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心点。element形状唯一依赖锚点位置,其他情况下,锚点只是影响了形态学运算结果的偏移。

参数5 anchor是参考点,其默认值为(-1,-1)说明位于kernel的中心位置。 参数6 borderType是边缘类型,默认为BORDER_CONSTANT。 参数7 borderValue 是边缘值,使用默认值即可。

下面这段代码对图像做了四次开操作,主要是分开最左边那两个挨在一起的红枣。

Mat kernel = getStructuringElement(MORPH_RECT, Size(3,3));
cv::morphologyEx(gauss, gauss, MORPH_OPEN, kernel, Point(-1,-1), 4);
imshow("高斯滤波形态学处理后的图像", gauss);

寻找轮廓并输出轮廓个数

OpenCV中,轮廓是由STL风格的vector<>模板对象表示的,其中vector中的每个元素都编码了曲线上,下一点的位置信息。 查找图像轮廓的函数是findContours(),在C++中 findContours() 有两种函数定义:

void findContours(
    cv::InputOutputArray image, // 输入的8位单通道“二值”图像
    cv::OutputArrayOfArrays contours, // 包含points的vectors的vector
    cv::OutputArray hierarchy, // (可选) 拓扑信息
    int mode, // 轮廓检索模式
    int method, // 轮廓近似方法
    cv::Point offset = cv::Point() // (可选) 所有点的偏移
);
void findContours(
   cv::InputOutputArray image, // 输入的8位单通道“二值”图像
   cv::OutputArrayOfArrays contours, // 包含points的vectors的vector
   int mode, // 轮廓检索模式
   int method, // 轮廓近似方法
   cv::Point offset = cv::Point() //  (可选) 所有点的偏移
);

参数1 image 是输入图像,图像的格式是8位单通道的图像,并且被解析为二值图像(即图中的所有非零像素之间都是相等的)。 参数2 contours 是二维vector对象,这里将使用找到的轮廓的列表进行填充。 参数3 hierarchy是可选的拓扑信息,输出的hierarchy将会描述输出轮廓树的结构信息。 参数4 mode 是轮廓检索模式,表明用何种方式来对轮廓进行提取,四个可选的值如下:

enum RetrievalModes {
    RETR_EXTERNAL = 0,  //只提取最外面的轮廓
    RETR_LIST = 1,    //提取所有轮廓并将其放入列表
    RETR_CCOMP = 2,  //提取所有轮廓并将组织成一个两层结构,其中顶层轮廓是外部轮廓,第二层轮廓是“洞”的轮廓
    RETR_TREE = 3,  //提取所有轮廓并重建嵌套轮廓的完整层级结构
    RETR_FLOODFILL = 4  //填色
}

参数5 method 是轮廓近似方法,即轮廓如何呈现的方法,有三种可选的方法:

enum ContourApproximationModes {
    CHAIN_APPROX_NONE = 1,  //绝对存储所有轮廓点,将轮廓中的所有点的编码转换成点
    CHAIN_APPROX_SIMPLE = 2,  //压缩水平,垂直和对角线段,仅保留其端点
    CHAIN_APPROX_TC89_L1 = 3,  //应用Teh-Chin链近似算法中的一种风格
    CHAIN_APPROX_TC89_KCOS = 4  //应用Teh-Chin链逼近算法的一种风格
}

参数6 offset是每个轮廓点移动的可选偏移量。

寻找出红枣轮廓的代码如下:

vector<vector<Point>> contours;
findContours(gauss, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
cout << "一共有" << contours.size() << "个红枣。" << endl;

画出轮廓并显示图像

drawContours函数主要用于将查找到的轮廓绘制到图像上,即画出图像的轮廓,在C++中 drawContours 函数定义如下:

void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )

参数1 image 表示目标图像,参数2 contours 表示输入的轮廓组,每一组轮廓由点vector构成, 参数3 contourIdx 指明画第几个轮廓,如果该参数为负值则画全部轮廓,参数4 color 为轮廓的颜色, 参数5 thickness为轮廓的线宽,如果是负值CV_FILLED则表示填充轮廓内部, 参数6 lineType 为线型,参数7 hierarchy 为轮廓结构信息,参数8 maxLevel,参数9 offset是每个轮廓点移动的可选偏移量。一般用不着线型后的几个参数,默认值即可。 写出的下面这段代码可以画出红枣的轮廓。

Mat markers = Mat::zeros(image.size(), CV_8UC3);    
RNG rng;   //Royal Never Give up???
for(size_t i = 0; i < contours.size(); i++) {
    drawContours(markers, contours, i, Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)), -1, 8);
}
imshow("红枣轮廓", markers);

最后把数红枣的完整代码写在类函数里面。

int TydCV::count(Mat &image) {
    cv::resize(image, image, Size(600, 400));
    imshow("原图像", image);
    Mat gauss;
    GaussianBlur(image, gauss, Size(5,5), 0.5, 0.5);
    cv::cvtColor(image, gauss,CV_BGR2HSV);
    cv::inRange(gauss, Scalar(0,43,46), Scalar(10,255,255), gauss); 
    Mat kernel = getStructuringElement(MORPH_RECT, Size(3,3));
    cv::morphologyEx(gauss, gauss, MORPH_OPEN, kernel, Point(-1,-1), 4);
    imshow("高斯滤波形态学处理后的图像", gauss);
    vector<vector<Point>> contours;
    findContours(gauss, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
    cout << "一共有" << contours.size() << "个红枣。" << endl;
    Mat markers = Mat::zeros(image.size(), CV_8UC3);
    RNG rng;
    for(size_t i = 0; i < contours.size(); i++) {
        drawContours(markers, contours, i, Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)), -1, 8);
    }
    imshow("红枣轮廓", markers);
    return contours.size();
}